#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Mark string for translation (alias for leatherman::locale::format) using leatherman::locale::_; #ifdef USE_CURL #include #include #include namespace lth_curl = leatherman::curl; #endif using namespace std; using namespace rapidjson; namespace facter { namespace facts { namespace resolvers { #ifdef USE_CURL static const unsigned int GCE_CONNECTION_TIMEOUT = 1000; static const unsigned int GCE_SESSION_TIMEOUT = 5000; #endif // Helper event handler for parsing JSON data struct gce_event_handler { explicit gce_event_handler(map_value& root) : _initialized(false), _root(root) { } bool Null() { check_initialized(); _key.clear(); return true; } bool Bool(bool b) { add_value(make_value(b)); return true; } bool Int(int i) { Uint64(static_cast(i)); return true; } bool Uint(unsigned int i) { Uint64(static_cast(i)); return true; } bool Int64(int64_t i) { Uint64(static_cast(i)); return true; } bool Uint64(uint64_t i) { add_value(make_value(i)); return true; } bool Double(double d) { add_value(make_value(d)); return true; } bool String(char const* s, SizeType len, bool copy) { string value(s, len); // See https://cloud.google.com/compute/docs/metadata for information about these values if (_key == "sshKeys") { // The sshKeys attribute is a list of SSH keys delimited by newline characters // Turn this value into an array for the fact // Trim any whitespace off the string before splitting boost::trim(value); // Split at newlines and transform into an array value vector keys; boost::split(keys, value, boost::is_any_of("\n"), boost::token_compress_on); auto array = make_value(); for (auto& key : keys) { array->add(make_value(move(key))); } add_value(move(array)); return true; } if (_key == "image" || _key == "machineType" || _key == "zone" || _key == "network") { // These values are fully qualified, but we only want to display the last name // Therefore, use only what comes after the last / character auto pos = value.find_last_of('/'); if (pos != string::npos) { value = value.substr(pos + 1); } } add_value(make_value(move(value))); return true; } bool Key(const char* str, SizeType length, bool copy) { check_initialized(); _key = string(str, length); return true; } bool StartObject() { if (!_initialized) { _initialized = true; return true; } // Push a map onto the stack _stack.emplace(make_tuple(move(_key), make_value())); return true; } bool EndObject(SizeType count) { // Check to see if the stack is empty since we don't push for the top-level object if (_stack.empty()) { return true; } // Pop the data off the stack auto top = move(_stack.top()); _stack.pop(); // Restore the key and add the value _key = move(get<0>(top)); add_value(move(get<1>(top))); return true; } bool StartArray() { check_initialized(); // Push an array onto the stack _stack.emplace(make_tuple(move(_key), make_value())); return true; } bool EndArray(SizeType count) { // Pop the data off the stack auto top = move(_stack.top()); _stack.pop(); // Restore the key and add the value _key = move(get<0>(top)); add_value(move(get<1>(top))); return true; } private: template void add_value(unique_ptr&& val) { check_initialized(); value* current = nullptr; if (_stack.empty()) { current = &_root; } else { current = get<1>(_stack.top()).get(); } auto map = dynamic_cast(current); if (map) { if (_key.empty()) { throw external::external_fact_exception(_("expected non-empty key in object.")); } map->add(move(_key), move(val)); return; } auto array = dynamic_cast(current); if (array) { array->add(move(val)); return; } } void check_initialized() const { if (!_initialized) { throw external::external_fact_exception(_("expected document to contain an object.")); } } bool _initialized; map_value& _root; string _key; stack>> _stack; }; gce_resolver::gce_resolver() : resolver("GCE", { fact::gce }) { } void gce_resolver::resolve(collection& facts) { auto virtualization = facts.get(fact::virtualization); if (!virtualization || virtualization->value() != vm::gce) { LOG_DEBUG("not running under a GCE instance."); return; } #ifndef USE_CURL LOG_INFO("GCE facts are unavailable: facter was built without libcurl support."); return; #else LOG_DEBUG("querying GCE metadata."); try { lth_curl::request req("http://metadata.google.internal/computeMetadata/v1beta1/?recursive=true&alt=json"); req.connection_timeout(GCE_CONNECTION_TIMEOUT); req.timeout(GCE_SESSION_TIMEOUT); if (!http_langs().empty()) req.add_header("Accept-Language", http_langs()); lth_curl::client cli; auto response = cli.get(req); if (response.status_code() != 200) { LOG_DEBUG("request for {1} returned a status code of {2}.", req.url(), response.status_code()); return; } auto data = make_value(); Reader reader; StringStream ss(response.body().c_str()); gce_event_handler handler(*data); auto result = reader.Parse(ss, handler); if (!result) { LOG_ERROR("failed to parse GCE metadata: {1}.", GetParseError_En(result.Code())); return; } if (!data->empty()) { facts.add(fact::gce, move(data)); } } catch (runtime_error& ex) { LOG_ERROR("GCE metadata request failed: {1}", ex.what()); } #endif } }}} // namespace facter::facts::resolvers