blob: 6d320a18e8428682658021810ae097417c86dde2 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "vm_tools/garcon/ansible_playbook_application.h"
#include <errno.h>
#include <fcntl.h>
#include <sstream>
#include <unistd.h>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/posix/safe_strerror.h>
#include <base/synchronization/waitable_event.h>
#include "vm_tools/common/spawn_util.h"
namespace vm_tools {
namespace garcon {
namespace {
// Return true on successful ansible-playbook result and false otherwise.
bool GetPlaybookApplicationResult(const std::string& stdout,
const std::string& stderr,
std::string* failure_reason) {
LOG(INFO) << "Ansible playbook application result: " << stdout;
LOG(INFO) << "Ansible playbook application error: " << stderr;
// TODO(okalitova): Process ansible-playbook stdout and stderr.
if (!stderr.empty()) {
*failure_reason = stderr;
return false;
}
return true;
}
bool CreatePipe(base::ScopedFD* read_fd,
base::ScopedFD* write_fd,
std::string* error_msg) {
int fds[2];
if (pipe2(fds, O_CLOEXEC) < 0) {
*error_msg =
"Failed to open target process pipe: " + base::safe_strerror(errno);
return false;
}
read_fd->reset(fds[0]);
write_fd->reset(fds[1]);
return true;
}
} // namespace
void ExecuteAnsiblePlaybook(AnsiblePlaybookApplicationObserver* observer,
base::WaitableEvent* event,
const base::FilePath& ansible_playbook_file_path,
std::string* error_msg) {
std::vector<std::string> argv{
"ansible-playbook", "--become", "--connection=local",
"--inventory", "127.0.0.1,", ansible_playbook_file_path.value()};
// Set child's process stdout and stderr to write end of pipes.
int stdio_fd[] = {-1, -1, -1};
base::ScopedFD read_stdout;
base::ScopedFD write_stdout;
if (!CreatePipe(&read_stdout, &write_stdout, error_msg)) {
return;
}
base::ScopedFD read_stderr;
base::ScopedFD write_stderr;
if (!CreatePipe(&read_stderr, &write_stderr, error_msg)) {
return;
}
stdio_fd[STDOUT_FILENO] = write_stdout.get();
stdio_fd[STDERR_FILENO] = write_stderr.get();
if (!Spawn(std::move(argv), {}, "", stdio_fd)) {
*error_msg = "Failed to spawn ansible-playbook process";
return;
}
write_stdout.reset();
write_stderr.reset();
event->Signal();
// TODO(okalitova): Add file watchers.
char buffer[100];
std::stringstream stdout;
ssize_t count = read(read_stdout.get(), buffer, sizeof(buffer));
while (count > 0) {
stdout.write(buffer, count);
count = read(read_stdout.get(), buffer, sizeof(buffer));
}
std::stringstream stderr;
count = read(read_stderr.get(), buffer, sizeof(buffer));
while (count > 0) {
stderr.write(buffer, count);
count = read(read_stderr.get(), buffer, sizeof(buffer));
}
std::string failure_reason;
bool success =
GetPlaybookApplicationResult(stdout.str(), stderr.str(), &failure_reason);
observer->OnApplyAnsiblePlaybookCompletion(success, failure_reason);
return;
}
base::FilePath CreateAnsiblePlaybookFile(const std::string& playbook,
std::string* error_msg) {
base::FilePath ansible_dir;
bool success = base::CreateNewTempDirectory("", &ansible_dir);
if (!success) {
LOG(ERROR) << "Failed to create directory for ansible playbook file";
*error_msg = "Failed to create directory for ansible playbook file";
return base::FilePath();
}
const base::FilePath ansible_playbook_file_path =
ansible_dir.Append("playbook.yaml");
base::File ansible_playbook_file(
ansible_playbook_file_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!ansible_playbook_file.created()) {
*error_msg = "Failed to create file for Ansible playbook";
return base::FilePath();
}
if (!ansible_playbook_file.IsValid()) {
*error_msg = "Failed to create valid file for Ansible playbook";
return base::FilePath();
}
int bytes = ansible_playbook_file.WriteAtCurrentPos(playbook.c_str(),
playbook.length());
if (bytes != playbook.length()) {
*error_msg = "Failed to write Ansible playbook content to file";
return base::FilePath();
}
return ansible_playbook_file_path;
}
} // namespace garcon
} // namespace vm_tools