Skip to content

Commit

Permalink
Merge #628
Browse files Browse the repository at this point in the history
#628 Enable passing cloud-init config via stdin a=gerboland r=townsend2010
  • Loading branch information
Saviq authored Feb 28, 2019
2 parents 08fc9b5 + 5b29dae commit 90e5783
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 2 deletions.
1 change: 1 addition & 0 deletions include/multipass/cli/client_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ void parse_copy_files_entry(const QString& entry, QString& path, QString& instan
bool is_tty();
int getuid();
int getgid();
void prepare_stdin_for_read();
}
}
}
Expand Down
27 changes: 25 additions & 2 deletions src/client/cmd/launch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ mp::ParseCode cmd::Launch::parse_args(mp::ArgParser* parser)
QCommandLineOption memOption({"m", "mem"}, "Amount of memory to allocate in bytes, or with K, M, G suffix", "mem",
"1024"); // In MB's
QCommandLineOption nameOption({"n", "name"}, "Name for the instance", "name");
QCommandLineOption cloudInitOption("cloud-init", "Path to a user-data cloud-init configuration", "file");
QCommandLineOption cloudInitOption("cloud-init", "Path to a user-data cloud-init configuration, or '-' for stdin",
"file");
parser->addOptions({cpusOption, diskOption, memOption, nameOption, cloudInitOption});

auto status = parser->commandParse(this);
Expand Down Expand Up @@ -160,7 +161,29 @@ mp::ParseCode cmd::Launch::parse_args(mp::ArgParser* parser)
{
try
{
auto node = YAML::LoadFile(parser->value(cloudInitOption).toStdString());
YAML::Node node;
const QString& cloudInitFile = parser->value(cloudInitOption);
if (cloudInitFile == "-")
{
if (!mcp::is_tty())
{
throw std::runtime_error("cannot read from stdin without a TTY");
}
mcp::prepare_stdin_for_read();
QByteArray content;
char arr[1024];
while (!std::cin.eof())
{
std::cin.read(arr, sizeof(arr));
int s = std::cin.gcount();
content.append(arr, s);
}
node = YAML::Load(content.toStdString());
}
else
{
node = YAML::LoadFile(cloudInitFile.toStdString());
}
request.set_cloud_init_user_data(YAML::Dump(node));
}
catch (const std::exception& e)
Expand Down
5 changes: 5 additions & 0 deletions src/platform/client/client_platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ int mcp::getgid()
{
return ::getgid();
}

void mcp::prepare_stdin_for_read()
{
// NO-OP
}
48 changes: 48 additions & 0 deletions tests/mock_stdcin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2019 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#ifndef MULTIPASS_MOCK_STDCIN_H
#define MULTIPASS_MOCK_STDCIN_H

#include <iostream>
#include <sstream>

class MockStdCin
{
public:
MockStdCin(const std::string& s)
{
fake_cin << s;

// Backup and replace std::cin's streambuffer
cin_backup = std::cin.rdbuf();
std::streambuf* psbuf = fake_cin.rdbuf();
std::cin.rdbuf(psbuf); // assign streambuf to cin
}

~MockStdCin()
{
// Restore cin's original streanbuffer
std::cin.rdbuf(cin_backup);
}

private:
std::streambuf* cin_backup;
std::stringstream fake_cin;
};

#endif // MULTIPASS_MOCK_STDCIN_H
29 changes: 29 additions & 0 deletions tests/test_cli_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*
*/

#include "mock_stdcin.h"
#include "path.h"
#include "stub_cert_store.h"
#include "stub_certprovider.h"
Expand All @@ -25,6 +26,7 @@

#include <QEventLoop>
#include <QStringList>
#include <QTemporaryFile>

#include <gmock/gmock.h>

Expand Down Expand Up @@ -204,6 +206,33 @@ TEST_F(Client, launch_cmd_custom_image_http_ok)
EXPECT_THAT(send_command({"launch", "http://foo"}), Eq(mp::ReturnCode::Ok));
}

TEST_F(Client, launch_cmd_cloudinit_option_with_valid_file_is_ok)
{
QTemporaryFile tmpfile; // file is auto-deleted when this goes out of scope
tmpfile.open();
tmpfile.write("password: passw0rd"); // need some YAML
tmpfile.close();
EXPECT_THAT(send_command({"launch", "--cloud-init", qPrintable(tmpfile.fileName())}), Eq(mp::ReturnCode::Ok));
}

TEST_F(Client, launch_cmd_cloudinit_option_with_missing_file)
{
EXPECT_THAT(send_command({"launch", "--cloud-init", "/definitely/missing-file"}),
Eq(mp::ReturnCode::CommandLineError));
}

TEST_F(Client, launch_cmd_cloudinit_option_fails_no_value)
{
EXPECT_THAT(send_command({"launch", "--cloud-init"}), Eq(mp::ReturnCode::CommandLineError));
}

TEST_F(Client, launch_cmd_cloudinit_option_reads_stdin_ok)
{
MockStdCin cin("password: passw0rd");

EXPECT_THAT(send_command({"launch", "--cloud-init", "-"}), Eq(mp::ReturnCode::Ok));
}

// purge cli tests
TEST_F(Client, empty_trash_cmd_ok_no_args)
{
Expand Down

0 comments on commit 90e5783

Please sign in to comment.