{"action":"created","comment":{"url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/comments/91647784","pull_request_review_id":12158476,"id":91647784,"diff_hunk":"@@ -0,0 +1,639 @@\n+require 'optparse'\n+\n+module MaintenanceModule\n+\n+  class Maintenance\n+    require 'openssl'\n+    require 'fileutils'\n+    require 'net/http'\n+    require 'uri'\n+    require 'gyoku'\n+    require 'syslog/logger'\n+    require 'etc'\n+\n+    require_relative 'oms_common'\n+    require_relative 'agent_topology_request_script'\n+    require_relative 'oms_configuration'\n+\n+    attr_reader :AGENT_USER, :load_config_success\n+    attr_accessor :suppress_output\n+  \n+    def initialize(omsadmin_conf_path, cert_path, key_path, proxy_path, os_info, install_info, verbose = false)\n+      @suppress_output = true\n+\n+      @AGENT_USER = \"omsagent\"\n+      @AGENT_GROUP = \"omiusers\"\n+      @omsadmin_conf_path = omsadmin_conf_path\n+      @cert_path = cert_path\n+      @key_path = key_path\n+      @proxy_path = proxy_path\n+      @os_info = os_info\n+      @install_info = install_info\n+      @verbose = verbose\n+      # Config to be read/written from omsadmin.conf\n+      @WORKSPACE_ID = nil\n+      @AGENT_GUID = nil\n+      @URL_TLD = nil\n+      @FQDN = nil\n+      @LOG_FACILITY = nil\n+      @CERTIFICATE_UPDATE_ENDPOINT = nil\n+\n+      @load_config_success = load_config\n+      init_logger\n+\n+      @suppress_output = false\n+    end\n+\n+    # Assign logger from log facility in omsadmin.conf\n+    def init_logger\n+      facility = case @LOG_FACILITY\n+          # Custom log facilities supported by both Ruby and bash logger\n+          when \"auth\"     then Syslog::LOG_AUTHPRIV  # LOG_AUTH is deprecated\n+          when \"authpriv\" then Syslog::LOG_AUTHPRIV\n+          when \"cron\"     then Syslog::LOG_CRON\n+          when \"daemon\"   then Syslog::LOG_DAEMON\n+          when \"ftp\"      then Syslog::LOG_FTP\n+          when \"kern\"     then Syslog::LOG_KERN\n+          when \"lpr\"      then Syslog::LOG_LRP\n+          when \"mail\"     then Syslog::LOG_MAIL\n+          when \"news\"     then Syslog::LOG_NEWS\n+          when \"security\" then Syslog::LOG_SECURITY\n+          when \"syslog\"   then Syslog::LOG_SYSLOG\n+          when \"user\"     then Syslog::LOG_USER\n+          when \"uucp\"     then Syslog::LOG_UUCP\n+\n+          when \"local0\"   then Syslog::LOG_LOCAL0\n+          when \"local1\"   then Syslog::LOG_LOCAL1\n+          when \"local2\"   then Syslog::LOG_LOCAL2\n+          when \"local3\"   then Syslog::LOG_LOCAL3\n+          when \"local4\"   then Syslog::LOG_LOCAL4\n+          when \"local5\"   then Syslog::LOG_LOCAL5\n+          when \"local6\"   then Syslog::LOG_LOCAL6\n+          when \"local7\"   then Syslog::LOG_LOCAL7\n+\n+          # default logger will be local0\n+          else Syslog::LOG_LOCAL0\n+      end\n+\n+      if !Syslog.opened?\n+        Syslog::Logger.syslog = Syslog.open(\"omsagent\", Syslog::LOG_PID, facility)\n+      end\n+      @logger = Syslog::Logger.new\n+    end\n+\n+    # Return true if the current executing user is root\n+    def is_current_user_root\n+      return true if (Process.euid == 0)\n+    end\n+\n+    # Return true if the user should be running this script (root or omsagent or testing)\n+    def check_user\n+      if (!ENV[\"TEST_WORKSPACE_ID\"].nil? or !ENV[\"TEST_SHARED_KEY\"].nil?) or\n+          (is_current_user_root or Etc.getpwuid(Process.euid).name == @AGENT_USER)\n+        return true\n+      else\n+        log_error(\"This script must be run as root or as the #{@AGENT_USER} user.\")\n+        return false\n+      end\n+    end\n+\n+    # Return variable derived from install_info.txt (like \"LinuxMonitoringAgent/1.2.0-148\")\n+    def get_user_agent\n+      user_agent = \"LinuxMonitoringAgent/\"\n+      if file_exists_nonempty(@install_info)\n+        user_agent.concat(File.readlines(@install_info)[0].split.first)\n+      end\n+      return user_agent\n+    end\n+\n+    # Ensure files generated by this script are owned by omsagent\n+    def chown_omsagent(file_list)\n+      if is_current_user_root\n+        FileUtils.chown(@AGENT_USER, @AGENT_GROUP, file_list)\n+      end\n+    end\n+\n+    # Logging methods\n+    def log_info(message)\n+      if !@suppress_output\n+        print(\"info\\t#{message}\\n\")\n+        @logger.info(message)\n+      end\n+    end\n+\n+    def log_error(message)\n+      if !@suppress_output\n+        print(\"error\\t#{message}\\n\")\n+        @logger.error(message)\n+      end\n+    end\n+\n+    # Helper method that returns true if a file exists and is non-empty\n+    def file_exists_nonempty(file_path)\n+      return true if !file_path.nil? and File.exist?(file_path) and !File.zero?(file_path)\n+    end\n+\n+    # Load necessary configuration values from omsadmin.conf\n+    def load_config\n+      if !File.exist?(@omsadmin_conf_path)\n+        log_error(\"Missing configuration file: #{@omsadmin_conf_path}\")\n+        return 1\n+      end\n+\n+      File.open(@omsadmin_conf_path, \"r\").each_line do |line|\n+        if line =~ /^WORKSPACE_ID/\n+          @WORKSPACE_ID = line.sub(\"WORKSPACE_ID=\",\"\").strip\n+        elsif line =~ /^AGENT_GUID/\n+          @AGENT_GUID = line.sub(\"AGENT_GUID=\",\"\").strip\n+        elsif line =~ /^URL_TLD/\n+          @URL_TLD = line.sub(\"URL_TLD=\",\"\").strip\n+          if !@URL_TLD.end_with?(\".com\")\n+            @URL_TLD.concat(\".com\")  # Ensure URL_TLD is backwards-compatible\n+          end\n+        elsif line =~ /^FQDN/\n+          @FQDN = line.sub(\"FQDN=\",\"\").strip\n+        elsif line =~ /^LOG_FACILITY/\n+          @LOG_FACILITY = line.sub(\"LOG_FACILITY=\",\"\").strip\n+        elsif line =~ /^CERTIFICATE_UPDATE_ENDPOINT/\n+          @CERTIFICATE_UPDATE_ENDPOINT = line.sub(\"CERTIFICATE_UPDATE_ENDPOINT=\",\"\").strip\n+        end\n+      end\n+\n+      return 0\n+    end\n+\n+    # Update omsadmin.conf with the specified variable's value\n+    def update_config(var, val)\n+      if !File.exist?(@omsadmin_conf_path)\n+        return 1\n+      end\n+\n+      old_text = File.read(@omsadmin_conf_path)\n+      new_text = old_text.sub(/^#{var}=.*\\n/,\"#{var}=#{val}\\n\")\n+\n+      File.open(@omsadmin_conf_path, \"w\") { |file|\n+        file.puts(new_text)\n+      }\n+    end\n+\n+    # Extract proxy setting information from proxy configuration file\n+    def get_proxy_info\n+      proxy = {}\n+\n+      if file_exists_nonempty(@proxy_path)\n+        proxy = OMS::Configuration.get_proxy_config(@proxy_path)\n+        log_info(\"Using proxy settings from #{@proxy_path}\")\n+      end\n+\n+      return proxy\n+    end\n+\n+    # Updates the CERTIFICATE_UPDATE_ENDPOINT variable and renews certificate if requested\n+    def apply_certificate_update_endpoint(server_resp, check_for_renew_request = true)\n+      update_attr = \"\"\n+      cert_update_endpoint = \"\"\n+\n+      # Extract the certificate update endpoint from the server response\n+      endpoint_tag_regex = /\\<CertificateUpdateEndpoint.*updateCertificate=\\\"(?<update_cert>(true|false))\\\".*(?<cert_update_endpoint>https.*RenewCertificate).*CertificateUpdateEndpoint\\>/\n+      endpoint_tag_regex.match(server_resp) { |match|\n+        cert_update_endpoint = match[\"cert_update_endpoint\"]\n+        update_attr = match[\"update_cert\"]\n+      }\n+ \n+      if cert_update_endpoint.empty?\n+        log_error(\"Could not extract the update certificate endpoint.\")\n+        return 1\n+      elsif update_attr.empty?\n+        log_error(\"Could not find the updateCertificate tag in the heartbeat response\")\n+        return 1\n+      end\n+\n+      # Update omsadmin.conf with cert_update_endpoint variable\n+      @CERTIFICATE_UPDATE_ENDPOINT = cert_update_endpoint\n+      # When apply_dsc_endpoint is called from onboarding, dsc_endpoint will be returned in file\n+      update_config(\"CERTIFICATE_UPDATE_ENDPOINT\", cert_update_endpoint)\n+ \n+      # Check in the response if the certs should be renewed\n+      if update_attr == \"true\" and check_for_renew_request\n+        renew_certs_ret = renew_certs\n+        if renew_certs_ret != 0\n+          return 1\n+        end\n+      end\n+\n+      return cert_update_endpoint\n+    end\n+\n+    # Update the DSC_ENDPOINT variable in omsadmin.conf from the server XML\n+    def apply_dsc_endpoint(server_resp)\n+      dsc_endpoint = \"\"\n+\n+      # Extract the DSC endpoint from the server response\n+      dsc_conf_regex = /<DscConfiguration.*<Endpoint>(?<endpoint>.*)<\\/Endpoint>.*DscConfiguration>/\n+      dsc_conf_regex.match(server_resp) { |match|\n+        dsc_endpoint = match[\"endpoint\"]\n+        # Insert escape characters before open and closed parentheses\n+        dsc_endpoint = dsc_endpoint.gsub(\"(\", \"\\\\\\\\(\").gsub(\")\", \"\\\\\\\\)\")\n+      }\n+\n+      if dsc_endpoint.empty?\n+        log_error(\"Could not extract the DSC endpoint.\")\n+        return 1\n+      end\n+\n+      # Update omsadmin.conf with dsc_endpoint variable\n+      # When apply_dsc_endpoint is called from onboarding, dsc_endpoint will be returned in file\n+      update_config(\"DSC_ENDPOINT\", dsc_endpoint)\n+\n+      return dsc_endpoint\n+    end\n+\n+    # Pass the server response from an XML file to apply_dsc_endpoint and apply_certificate_update_endpoint\n+    # Save DSC_ENDPOINT and CERTIFICATE_UPDATE_ENDPOINT variables in file to be read outside of this script\n+    def apply_endpoints_file(xml_file, output_file)\n+      if !file_exists_nonempty(xml_file)\n+        return 1\n+      end\n+\n+      server_resp = File.read(xml_file)\n+      cert_update_applied = apply_certificate_update_endpoint(server_resp, check_for_renew_request = false)\n+      dsc_applied = apply_dsc_endpoint(server_resp)\n+\n+      if cert_update_applied.class != String\n+        return cert_update_applied\n+      elsif dsc_applied.class != String\n+        return dsc_applied\n+      else\n+        output_handle = nil\n+        begin\n+          # To return endpoint strings to onboarding script, save to file\n+          output_handle = File.new(output_file, \"w\")\n+          chown_omsagent(output_file)\n+          output_handle.write(\"#{cert_update_applied}\\n\"\\\n+                              \"#{dsc_applied}\\n\")\n+        rescue => e\n+          log_error(\"Error saving endpoints to file: #{e.message}\")\n+          return 1\n+        ensure\n+          if !output_handle.nil?\n+            output_handle.close\n+          end\n+        end\n+      end\n+\n+      return 0\n+    end\n+\n+    # Return the certificate text as a single formatted string\n+    def get_cert_server(cert_path)\n+      cert_server = \"\"\n+\n+      cert_file_contents = File.readlines(cert_path)\n+      for i in 1..(cert_file_contents.length-2) #skip first and last line in file\n+        line = cert_file_contents[i]\n+        cert_server.concat(line[0..-2])\n+        if i < (cert_file_contents.length-2)\n+          cert_server.concat(\" \")\n+        end\n+      end\n+\n+      return cert_server\n+    end\n+\n+    # Perform a heartbeat against the OMS endpoint\n+    def heartbeat\n+      # Check necessary inputs\n+      if @load_config_success != 0\n+        log_error(\"Error loading configuration from #{@omsadmin_conf_path}\")\n+        return 1\n+      elsif @WORKSPACE_ID.nil? or @AGENT_GUID.nil? or @URL_TLD.nil? or @FQDN.nil? or\n+          @WORKSPACE_ID.empty? or @AGENT_GUID.empty? or @URL_TLD.empty? or @FQDN.empty?\n+        log_error(\"Missing required field from configuration file: #{@omsadmin_conf_path}\")\n+        return 1\n+      elsif !file_exists_nonempty(@cert_path) or !file_exists_nonempty(@key_path)\n+        log_error(\"Certificates for heartbeat request do not exist\")\n+        return 1\n+      end\n+\n+      # Generate the request body\n+      begin\n+        body_hb_xml = AgentTopologyRequestHandler.new.handle_request(@os_info, @omsadmin_conf_path,\n+            @FQDN, @AGENT_GUID, get_cert_server(@cert_path), telemetry=true)\n+      rescue => e\n+        log_error(\"Error when appending Telemetry to Heartbeat\")\n+      end\n+\n+      # Form POST request with headers\n+      headers = {}\n+      req_date = Time.now.utc.strftime(\"%Y-%m-%dT%T.%N%:z\")\n+      headers[OMS::CaseSensitiveString.new(\"x-ms-Date\")] = req_date\n+      headers[\"User-Agent\"] = get_user_agent\n+      headers[OMS::CaseSensitiveString.new(\"Accept-Language\")] = \"en-US\"\n+      uri = URI.parse(\"https://#{@WORKSPACE_ID}.oms.#{@URL_TLD}/AgentService.svc/LinuxAgentTopologyRequest\")\n+      req = Net::HTTP::Post.new(uri.request_uri, headers)\n+      req.body = body_hb_xml\n+\n+      # Form HTTP\n+      http = OMS::Common.create_secure_http(uri, get_proxy_info)\n+      http.cert = OpenSSL::X509::Certificate.new(File.open(@cert_path))\n+      http.key = OpenSSL::PKey::RSA.new(File.open(@key_path))\n+\n+      print(\"Generated heartbeat request:\\n#{req.body}\\n\") if @verbose\n+\n+      # Submit request\n+      begin\n+        res = nil\n+        res = http.start { |http_each| http.request(req) }\n+      rescue\n+        log_error(\"Error sending the heartbeat.\")\n+      end\n+\n+      if !res.nil?\n+        print(\"Heartbeat response code: #{res.code}\\n\") if @verbose\n+\n+        if res.code == \"200\"\n+          results = 0\n+          results = 1 if apply_certificate_update_endpoint(res.body) == 1\n+          results = 1 if apply_dsc_endpoint(res.body) == 1\n+\n+          log_info(\"Heartbeat success\") if results == 0\n+          return results\n+        else\n+          log_error(\"Error sending the heartbeat. HTTP code #{res.code}\")\n+          return 1\n+        end\n+      else\n+        log_error(\"Error sending the heartbeat. No HTTP code\")\n+        return 1\n+      end\n+    end\n+\n+    # Create the public/private key pair for the agent/workspace\n+    def generate_certs(workspace_id, agent_guid)\n+      if workspace_id.nil? or agent_guid.nil? or workspace_id.empty? or agent_guid.empty?\n+        log_error(\"Both WORKSPACE_ID and AGENT_GUID must be defined to generate certificates\")\n+        return 1\n+      end\n+\n+      log_info(\"Generating certificate ...\")\n+      error=nil\n+\n+      # Set safe certificate permissions before to prevent timing attacks\n+      key_file = File.new(@key_path, \"w\")\n+      cert_file = File.new(@cert_path, \"w\")\n+      File.chmod(0600, @key_path)\n+      File.chmod(0600, @cert_path)\n+      chown_omsagent([@key_path, @cert_path])\n+\n+      begin\n+        # Create new private key of 2048 bits\n+        key = OpenSSL::PKey::RSA.new(2048)\n+\n+        x509_version = 2  # enable X509 V3 extensions\n+        two_byte_range = 2**16 - 2  # 2 digit byte range for serial number\n+        year = 1 * 365 * 24 * 60 * 60  # 365 days validity for certificate\n+  \n+        # Generate CSR from new private key\n+        csr = OpenSSL::X509::Request.new\n+        csr.version = x509_version\n+        csr.subject = OpenSSL::X509::Name.new([\n+            [\"CN\", workspace_id],\n+            [\"CN\", agent_guid],\n+            [\"OU\", \"Linux Monitoring Agent\"],\n+            [\"O\", \"Microsoft\"]])\n+        csr.public_key = key.public_key\n+        csr.sign(key, OpenSSL::Digest::SHA256.new)\n+  \n+        # Self-sign CSR\n+        csr_cert = OpenSSL::X509::Certificate.new\n+        csr_cert.serial = SecureRandom.random_number(two_byte_range) + 1\n+        csr_cert.version = x509_version\n+        csr_cert.not_before = Time.now\n+        csr_cert.not_after = Time.now + year\n+        csr_cert.subject = csr.subject\n+        csr_cert.public_key = csr.public_key\n+        csr_cert.issuer = csr_cert.subject  # self-signed\n+        ef = OpenSSL::X509::ExtensionFactory.new\n+        ef.subject_certificate = csr_cert\n+        ef.issuer_certificate = csr_cert\n+        csr_cert.add_extension(ef.create_extension(\"subjectKeyIdentifier\",\"hash\",false))\n+        csr_cert.add_extension(ef.create_extension(\"authorityKeyIdentifier\",\"keyid:always\",false))\n+        csr_cert.add_extension(ef.create_extension(\"basicConstraints\",\"CA:TRUE\",false))\n+        csr_cert.sign(key, OpenSSL::Digest::SHA256.new)\n+\n+        # Write key and cert to files\n+        key_file.write(key)\n+        cert_file.write(csr_cert)\n+      rescue => e\n+        error = e\n+      ensure\n+        key_file.close\n+        cert_file.close\n+      end\n+\n+      # Check for any error or non-existent or empty files\n+      if !error.nil? or !file_exists_nonempty(@cert_path) or !file_exists_nonempty(@key_path)\n+        log_error(\"Error generating certs\")\n+        return 1\n+      end\n+\n+      return 0\n+    end\n+\n+    # Simple class to support interaction with topology script helper method (obj_to_hash)\n+    class AgentRenewCertificateRequest < StrongTypedClass\n+      strongtyped_accessor :NewCertificate, String\n+    end\n+\n+    # Restore the provided public/private key to the certs files\n+    def restore_old_certs(cert_old, key_old)\n+      cert_file = File.open(@cert_path, \"w\")\n+      cert_file.write(cert_old)\n+      cert_file.close\n+\n+      key_file = File.open(@key_path, \"w\")\n+      key_file.write(key_old)\n+      key_file.close\n+    end\n+ \n+    # Renew certificates for agent/workspace connection\n+    def renew_certs\n+      # Check necessary inputs\n+      if @load_config_success != 0\n+        log_error(\"Error loading configuration from #{@omsadmin_conf_path}\")\n+        return 1\n+      elsif @WORKSPACE_ID.nil? or @AGENT_GUID.nil? or @WORKSPACE_ID.empty? or @AGENT_GUID.empty?\n+        log_error(\"Missing required field from configuration file: #{@omsadmin_conf_path}\")\n+        return 1\n+      elsif @CERTIFICATE_UPDATE_ENDPOINT.nil? or @CERTIFICATE_UPDATE_ENDPOINT.empty?\n+        log_error(\"Missing CERTIFICATE_UPDATE_ENDPOINT from configuration\")\n+        return 1\n+      elsif !file_exists_nonempty(@cert_path) or !file_exists_nonempty(@key_path)\n+        log_error(\"No certificates exist; cannot renew certificates\")\n+        return 1\n+      end\n+\n+      log_info(\"Renewing the certificates\")\n+\n+      # Save old certs\n+      cert_old = OpenSSL::X509::Certificate.new(File.open(@cert_path))\n+      key_old = OpenSSL::PKey::RSA.new(File.open(@key_path))\n+\n+      generated = generate_certs(@WORKSPACE_ID, @AGENT_GUID)\n+      if generated != 0\n+        return 1\n+      end\n+\n+      # Form POST request\n+      renew_certs_req = AgentRenewCertificateRequest.new\n+      renew_certs_req.NewCertificate = get_cert_server(@cert_path)\n+\n+      renew_certs_xml = \"<?xml version=\\\"1.0\\\"?>\\n\"\n+      renew_certs_xml.concat(Gyoku.xml({ \"CertificateUpdateRequest\" => {:content! => obj_to_hash(renew_certs_req), \\\n+:'@xmlns:xsi' => \"http://www.w3.org/2001/XMLSchema-instance\", :'@xmlns:xsd' => \"http://www.w3.org/2001/XMLSchema\", \\\n+:@xmlns => \"http://schemas.microsoft.com/WorkloadMonitoring/HealthServiceProtocol/2014/09/\"}}))\n+\n+      uri = URI.parse(@CERTIFICATE_UPDATE_ENDPOINT)\n+      req = Net::HTTP::Post.new(uri.request_uri, headers = {})\n+      req.body = renew_certs_xml\n+\n+      # Form HTTP\n+      http = OMS::Common.create_secure_http(uri, get_proxy_info)\n+      http.cert = cert_old\n+      http.key = key_old\n+\n+      print(\"Generated renew certificates request:\\n#{req.body}\\n\") if @verbose\n+\n+      # Submit request\n+      begin\n+        res = nil\n+        res = http.start { |http_each| http.request(req) }\n+      rescue\n+        log_error(\"Error renewing certificate.\")","path":"source/code/plugins/agent_maintenance_script.rb","position":null,"original_position":512,"commit_id":"c1fd48628a837dae9cf25092843d74a8eae93f33","original_commit_id":"11330f5d8930eadca57ff4e819ef5e6dfb7974af","user":{"login":"lagalbra","id":21086952,"avatar_url":"https://avatars.githubusercontent.com/u/21086952?v=3","gravatar_id":"","url":"https://api.github.com/users/lagalbra","html_url":"https://github.com/lagalbra","followers_url":"https://api.github.com/users/lagalbra/followers","following_url":"https://api.github.com/users/lagalbra/following{/other_user}","gists_url":"https://api.github.com/users/lagalbra/gists{/gist_id}","starred_url":"https://api.github.com/users/lagalbra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lagalbra/subscriptions","organizations_url":"https://api.github.com/users/lagalbra/orgs","repos_url":"https://api.github.com/users/lagalbra/repos","events_url":"https://api.github.com/users/lagalbra/events{/privacy}","received_events_url":"https://api.github.com/users/lagalbra/received_events","type":"User","site_admin":false},"body":"Done.","created_at":"2016-12-09T02:06:49Z","updated_at":"2016-12-09T02:06:49Z","html_url":"https://github.com/Microsoft/OMS-Agent-for-Linux/pull/296#discussion_r91647784","pull_request_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/296","_links":{"self":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/comments/91647784"},"html":{"href":"https://github.com/Microsoft/OMS-Agent-for-Linux/pull/296#discussion_r91647784"},"pull_request":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/296"}}},"pull_request":{"url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/296","id":97031853,"html_url":"https://github.com/Microsoft/OMS-Agent-for-Linux/pull/296","diff_url":"https://github.com/Microsoft/OMS-Agent-for-Linux/pull/296.diff","patch_url":"https://github.com/Microsoft/OMS-Agent-for-Linux/pull/296.patch","issue_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues/296","number":296,"state":"open","locked":false,"title":"Refactor maintenance tasks and heartbeat for multihoming and tests","user":{"login":"lagalbra","id":21086952,"avatar_url":"https://avatars.githubusercontent.com/u/21086952?v=3","gravatar_id":"","url":"https://api.github.com/users/lagalbra","html_url":"https://github.com/lagalbra","followers_url":"https://api.github.com/users/lagalbra/followers","following_url":"https://api.github.com/users/lagalbra/following{/other_user}","gists_url":"https://api.github.com/users/lagalbra/gists{/gist_id}","starred_url":"https://api.github.com/users/lagalbra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lagalbra/subscriptions","organizations_url":"https://api.github.com/users/lagalbra/orgs","repos_url":"https://api.github.com/users/lagalbra/repos","events_url":"https://api.github.com/users/lagalbra/events{/privacy}","received_events_url":"https://api.github.com/users/lagalbra/received_events","type":"User","site_admin":false},"body":"Refactored maintenance tasks to be workspace-specific for multihoming\r\nMoved heartbeat to input FluentD plugin that produces no output\r\nMoved renew_certs, generate_certs to Ruby script implementation\r\nParity info/error logging between Ruby implementation and omsadmin.sh\r\nAdapted existing tests for current multihoming support\r\nAdded unit and system tests for refactored implementations\r\n\r\nThis change is intended to be merged into the FB-multihoming feature branch.\r\n@Microsoft/omsagent-devs @robbiezhang ","created_at":"2016-12-07T23:45:28Z","updated_at":"2016-12-09T02:06:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"788ee62ea54162a080cb58095ae85c19ebabdff9","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/296/commits","review_comments_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/296/comments","review_comment_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues/296/comments","statuses_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/statuses/c1fd48628a837dae9cf25092843d74a8eae93f33","head":{"label":"Microsoft:lagalbra-mh-heartbeat","ref":"lagalbra-mh-heartbeat","sha":"c1fd48628a837dae9cf25092843d74a8eae93f33","user":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars.githubusercontent.com/u/6154722?v=3","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"repo":{"id":43709699,"name":"OMS-Agent-for-Linux","full_name":"Microsoft/OMS-Agent-for-Linux","owner":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars.githubusercontent.com/u/6154722?v=3","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Microsoft/OMS-Agent-for-Linux","description":null,"fork":false,"url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux","forks_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/forks","keys_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/teams","hooks_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/hooks","issue_events_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues/events{/number}","events_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/events","assignees_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/assignees{/user}","branches_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/branches{/branch}","tags_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/tags","blobs_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/refs{/sha}","trees_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/statuses/{sha}","languages_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/languages","stargazers_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/stargazers","contributors_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/contributors","subscribers_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/subscribers","subscription_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/subscription","commits_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/commits{/sha}","git_commits_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/commits{/sha}","comments_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/comments{/number}","issue_comment_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues/comments{/number}","contents_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/contents/{+path}","compare_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/merges","archive_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/downloads","issues_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues{/number}","pulls_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls{/number}","milestones_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/milestones{/number}","notifications_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/labels{/name}","releases_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/releases{/id}","deployments_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/deployments","created_at":"2015-10-05T19:50:18Z","updated_at":"2016-12-08T01:28:17Z","pushed_at":"2016-12-09T02:05:20Z","git_url":"git://github.com/Microsoft/OMS-Agent-for-Linux.git","ssh_url":"git@github.com:Microsoft/OMS-Agent-for-Linux.git","clone_url":"https://github.com/Microsoft/OMS-Agent-for-Linux.git","svn_url":"https://github.com/Microsoft/OMS-Agent-for-Linux","homepage":"http://www.microsoft.com/oms","size":18630,"stargazers_count":71,"watchers_count":71,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":33,"mirror_url":null,"open_issues_count":19,"forks":33,"open_issues":19,"watchers":71,"default_branch":"master"}},"base":{"label":"Microsoft:FB-multihoming","ref":"FB-multihoming","sha":"ab7e72d88882323b146ca94efb532f769cb04af1","user":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars.githubusercontent.com/u/6154722?v=3","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"repo":{"id":43709699,"name":"OMS-Agent-for-Linux","full_name":"Microsoft/OMS-Agent-for-Linux","owner":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars.githubusercontent.com/u/6154722?v=3","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Microsoft/OMS-Agent-for-Linux","description":null,"fork":false,"url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux","forks_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/forks","keys_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/teams","hooks_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/hooks","issue_events_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues/events{/number}","events_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/events","assignees_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/assignees{/user}","branches_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/branches{/branch}","tags_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/tags","blobs_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/refs{/sha}","trees_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/statuses/{sha}","languages_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/languages","stargazers_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/stargazers","contributors_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/contributors","subscribers_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/subscribers","subscription_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/subscription","commits_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/commits{/sha}","git_commits_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/git/commits{/sha}","comments_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/comments{/number}","issue_comment_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues/comments{/number}","contents_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/contents/{+path}","compare_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/merges","archive_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/downloads","issues_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues{/number}","pulls_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls{/number}","milestones_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/milestones{/number}","notifications_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/labels{/name}","releases_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/releases{/id}","deployments_url":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/deployments","created_at":"2015-10-05T19:50:18Z","updated_at":"2016-12-08T01:28:17Z","pushed_at":"2016-12-09T02:05:20Z","git_url":"git://github.com/Microsoft/OMS-Agent-for-Linux.git","ssh_url":"git@github.com:Microsoft/OMS-Agent-for-Linux.git","clone_url":"https://github.com/Microsoft/OMS-Agent-for-Linux.git","svn_url":"https://github.com/Microsoft/OMS-Agent-for-Linux","homepage":"http://www.microsoft.com/oms","size":18630,"stargazers_count":71,"watchers_count":71,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":33,"mirror_url":null,"open_issues_count":19,"forks":33,"open_issues":19,"watchers":71,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/296"},"html":{"href":"https://github.com/Microsoft/OMS-Agent-for-Linux/pull/296"},"issue":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues/296"},"comments":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/issues/296/comments"},"review_comments":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/296/comments"},"review_comment":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/pulls/296/commits"},"statuses":{"href":"https://api.github.com/repos/Microsoft/OMS-Agent-for-Linux/statuses/c1fd48628a837dae9cf25092843d74a8eae93f33"}}}}
{"actor":{"display_login":"lagalbra"}}