Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
garage_push.cc
1 #include <string>
2 
3 #include <boost/filesystem.hpp>
4 #include <boost/program_options.hpp>
5 
6 #include "authenticate.h"
7 #include "deploy.h"
8 #include "garage_common.h"
9 #include "garage_tools_version.h"
10 #include "logging/logging.h"
11 #include "ostree_dir_repo.h"
12 #include "ostree_repo.h"
13 #include "utilities/xml2json.h"
14 
15 namespace po = boost::program_options;
16 
17 int main(int argc, char **argv) {
18  logger_init();
19 
20  boost::filesystem::path repo_path;
21  std::string ref;
22  boost::filesystem::path credentials_path;
23  std::string cacerts;
24  boost::filesystem::path manifest_path;
25  int max_curl_requests;
27  po::options_description desc("garage-push command line options");
28  // clang-format off
29  desc.add_options()
30  ("help", "print usage")
31  ("version", "Current garage-push version")
32  ("verbose,v", "Verbose logging (loglevel 1)")
33  ("quiet,q", "Quiet mode (loglevel 3)")
34  ("loglevel", po::value<int>(), "set log level 0-5 (trace, debug, info, warning, error, fatal)")
35  ("repo,C", po::value<boost::filesystem::path>(&repo_path)->required(), "location of OSTree repo")
36  ("ref,r", po::value<std::string>(&ref)->required(), "OSTree ref to push (or commit refhash)")
37  ("credentials,j", po::value<boost::filesystem::path>(&credentials_path)->required(), "credentials (json or zip containing json)")
38  ("cacert", po::value<std::string>(&cacerts), "override path to CA root certificates, in the same format as curl --cacert")
39  ("repo-manifest", po::value<boost::filesystem::path>(&manifest_path), "manifest describing repository branches used in the image, to be sent as attached metadata")
40  ("jobs", po::value<int>(&max_curl_requests)->default_value(30), "maximum number of parallel requests")
41  ("dry-run,n", "check arguments and authenticate but don't upload")
42  ("walk-tree,w", "walk entire tree and upload all missing objects");
43  // clang-format on
44 
45  po::variables_map vm;
46 
47  try {
48  po::store(po::parse_command_line(argc, reinterpret_cast<const char *const *>(argv), desc), vm);
49 
50  if (vm.count("help") != 0U) {
51  LOG_INFO << desc;
52  return EXIT_SUCCESS;
53  }
54  if (vm.count("version") != 0) {
55  LOG_INFO << "Current garage-push version is: " << garage_tools_version();
56  exit(EXIT_SUCCESS);
57  }
58  po::notify(vm);
59  } catch (const po::error &o) {
60  LOG_ERROR << o.what();
61  LOG_ERROR << desc;
62  return EXIT_FAILURE;
63  }
64 
65  // Configure logging. Try loglevel first, then verbose, then quiet.
66  try {
67  if (vm.count("loglevel") != 0) {
68  const int loglevel = vm["loglevel"].as<int>();
69  logger_set_threshold(static_cast<boost::log::trivial::severity_level>(loglevel));
70  LOG_INFO << "Loglevel set to " << loglevel;
71  } else if (static_cast<int>(vm.count("verbose")) != 0) {
72  logger_set_threshold(boost::log::trivial::debug);
73  LOG_DEBUG << "Debug level debugging enabled";
74  } else if (static_cast<int>(vm.count("quiet")) != 0) {
75  logger_set_threshold(boost::log::trivial::warning);
76  } else {
77  logger_set_threshold(boost::log::trivial::info);
78  }
79  } catch (std::exception &e) {
80  LOG_FATAL << e.what();
81  return EXIT_FAILURE;
82  }
83 
84  Utils::setUserAgent(std::string("garage-push/") + garage_tools_version());
85 
86  if (!cacerts.empty()) {
87  if (!boost::filesystem::exists(cacerts)) {
88  LOG_FATAL << "--cacert path " << cacerts << " does not exist";
89  return EXIT_FAILURE;
90  }
91  }
92 
93  if (vm.count("dry-run") != 0U) {
94  mode = RunMode::kDryRun;
95  }
96  if (vm.count("walk-tree") != 0U) {
97  // If --walk-tree and --dry-run were provided, walk but do not push.
98  if (mode == RunMode::kDryRun) {
99  mode = RunMode::kWalkTree;
100  } else {
101  mode = RunMode::kPushTree;
102  }
103  }
104 
105  if (max_curl_requests < 1) {
106  LOG_FATAL << "--jobs must be greater than 0";
107  return EXIT_FAILURE;
108  }
109 
110  OSTreeRepo::ptr src_repo = std::make_shared<OSTreeDirRepo>(repo_path);
111  if (!src_repo->LooksValid()) {
112  LOG_FATAL << "The OSTree src repository does not appear to contain a valid OSTree repository";
113  return EXIT_FAILURE;
114  }
115 
116  try {
117  std::unique_ptr<OSTreeHash> commit;
118  bool is_ref = true;
119  OSTreeRef ostree_ref = src_repo->GetRef(ref);
120  if (ostree_ref.IsValid()) {
121  commit = std_::make_unique<OSTreeHash>(ostree_ref.GetHash());
122  } else {
123  try {
124  commit = std_::make_unique<OSTreeHash>(OSTreeHash::Parse(ref));
125  } catch (const OSTreeCommitParseError &e) {
126  LOG_FATAL << "Ref or commit refhash " << ref << " was not found in repository " << repo_path.string();
127  return EXIT_FAILURE;
128  }
129  is_ref = false;
130  }
131 
132  ServerCredentials push_credentials(credentials_path);
133  TreehubServer push_server;
134  if (authenticate(cacerts, push_credentials, push_server) != EXIT_SUCCESS) {
135  LOG_FATAL << "Authentication with push server failed";
136  return EXIT_FAILURE;
137  }
138  if (!UploadToTreehub(src_repo, push_server, *commit, mode, max_curl_requests)) {
139  LOG_FATAL << "Upload to treehub failed";
140  return EXIT_FAILURE;
141  }
142 
143  if (mode != RunMode::kDryRun) {
144  if (is_ref) {
145  if (!PushRootRef(push_server, ostree_ref)) {
146  LOG_FATAL << "Error pushing root ref to treehub";
147  return EXIT_FAILURE;
148  }
149  } else {
150  LOG_INFO << "Provided ref " << ref << " is a commit refhash. Cannot push root ref";
151  }
152  }
153 
154  if (!manifest_path.empty()) {
155  try {
156  std::string manifest_json_str;
157  std::ifstream ifs(manifest_path.string());
158  std::stringstream ss;
159  auto manifest_json = xml2json::xml2json(ifs);
160  ss << manifest_json;
161  manifest_json_str = ss.str();
162 
163  LOG_INFO << "Sending manifest:\n" << manifest_json_str;
164  if (mode != RunMode::kDryRun) {
165  CURL *curl = curl_easy_init();
166  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
167  push_server.SetContentType("Content-Type: application/json");
168  push_server.InjectIntoCurl("manifests/" + commit->string(), curl);
169  curlEasySetoptWrapper(curl, CURLOPT_CUSTOMREQUEST, "PUT");
170  curlEasySetoptWrapper(curl, CURLOPT_POSTFIELDS, manifest_json_str.c_str());
171  CURLcode rc = curl_easy_perform(curl);
172 
173  if (rc != CURLE_OK) {
174  LOG_ERROR << "Error pushing repo manifest to Treehub";
175  }
176  curl_easy_cleanup(curl);
177  }
178  } catch (std::exception &e) {
179  LOG_ERROR << "Could not send repo manifest to Treehub";
180  }
181  }
182  } catch (const BadCredentialsArchive &e) {
183  LOG_FATAL << e.what();
184  return EXIT_FAILURE;
185  } catch (const BadCredentialsContent &e) {
186  LOG_FATAL << e.what();
187  return EXIT_FAILURE;
188  } catch (const BadCredentialsJson &e) {
189  LOG_FATAL << e.what();
190  return EXIT_FAILURE;
191  }
192  return EXIT_SUCCESS;
193 }
194 // vim: set tabstop=2 shiftwidth=2 expandtab:
RunMode::kPushTree
@ kPushTree
Walk the entire tree and upload any missing objects.
ServerCredentials
Definition: server_credentials.h:25
OSTreeCommitParseError
Definition: ostree_hash.h:30
RunMode::kWalkTree
@ kWalkTree
Walk the entire tree (without uploading).
BadCredentialsJson
Definition: server_credentials.h:15
TreehubServer
Definition: treehub_server.h:11
OSTreeHash::Parse
static OSTreeHash Parse(const std::string &hash)
Parse an OSTree hash from a string.
Definition: ostree_hash.cc:7
garage_common.h
BadCredentialsContent
Definition: server_credentials.h:10
RunMode::kDefault
@ kDefault
Default operation.
RunMode::kDryRun
@ kDryRun
Dry run.
OSTreeRef
Definition: ostree_ref.h:13
BadCredentialsArchive
Definition: server_credentials.h:20
RunMode
RunMode
Execution mode to run garage tools in.
Definition: garage_common.h:6