aws-codedeploy-agent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGES.md +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +177 -0
  5. data/NOTICE +2 -0
  6. data/README.md +16 -0
  7. data/aws-codedeploy-agent.gemspec +39 -0
  8. data/bin/codedeploy-agent +78 -0
  9. data/bin/codedeploy-install +15 -0
  10. data/bin/codedeploy-uninstall +13 -0
  11. data/certs/host-agent-deployment-signer-ca-chain.pem +76 -0
  12. data/conf/codedeployagent.yml +9 -0
  13. data/init.d/codedeploy-agent +61 -0
  14. data/lib/core_ext.rb +71 -0
  15. data/lib/instance_agent.rb +35 -0
  16. data/lib/instance_agent/agent/base.rb +34 -0
  17. data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +133 -0
  18. data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +163 -0
  19. data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +142 -0
  20. data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +23 -0
  21. data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +23 -0
  22. data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +121 -0
  23. data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +66 -0
  24. data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +134 -0
  25. data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +27 -0
  26. data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +72 -0
  27. data/lib/instance_agent/codedeploy_plugin/command_executor.rb +357 -0
  28. data/lib/instance_agent/codedeploy_plugin/command_poller.rb +146 -0
  29. data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +150 -0
  30. data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +206 -0
  31. data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +374 -0
  32. data/lib/instance_agent/codedeploy_plugin/installer.rb +143 -0
  33. data/lib/instance_agent/codedeploy_plugin/request_helper.rb +28 -0
  34. data/lib/instance_agent/config.rb +43 -0
  35. data/lib/instance_agent/log.rb +3 -0
  36. data/lib/instance_agent/platform.rb +17 -0
  37. data/lib/instance_agent/platform/linux_util.rb +57 -0
  38. data/lib/instance_agent/runner/child.rb +57 -0
  39. data/lib/instance_agent/runner/master.rb +103 -0
  40. data/lib/instance_metadata.rb +47 -0
  41. data/test/certificate_helper.rb +120 -0
  42. data/test/helpers/instance_agent_helper.rb +25 -0
  43. data/test/instance_agent/agent/base_test.rb +49 -0
  44. data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +1710 -0
  45. data/test/instance_agent/codedeploy_plugin/codedeploy_control_test.rb +51 -0
  46. data/test/instance_agent/codedeploy_plugin/command_executor_test.rb +513 -0
  47. data/test/instance_agent/codedeploy_plugin/command_poller_test.rb +459 -0
  48. data/test/instance_agent/codedeploy_plugin/deployment_specification_test.rb +335 -0
  49. data/test/instance_agent/codedeploy_plugin/hook_executor_test.rb +250 -0
  50. data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +566 -0
  51. data/test/instance_agent/codedeploy_plugin/installer_test.rb +519 -0
  52. data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +37 -0
  53. data/test/instance_agent/config_test.rb +64 -0
  54. data/test/instance_agent/runner/child_test.rb +87 -0
  55. data/test/instance_metadata_test.rb +97 -0
  56. data/test/test_helper.rb +16 -0
  57. data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -0
  58. data/vendor/gems/codedeploy-commands/apis/CodeDeployCommand.api.json +372 -0
  59. data/vendor/gems/codedeploy-commands/codedeploy-commands-1.0.0.gemspec +28 -0
  60. data/vendor/gems/codedeploy-commands/lib/aws/codedeploy_commands.rb +18 -0
  61. data/vendor/gems/codedeploy-commands/lib/aws/plugins/certificate_authority.rb +12 -0
  62. data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +22 -0
  63. data/vendor/gems/process_manager/README.md +1 -0
  64. data/vendor/gems/process_manager/lib/blank.rb +153 -0
  65. data/vendor/gems/process_manager/lib/core_ext.rb +73 -0
  66. data/vendor/gems/process_manager/lib/process_manager.rb +49 -0
  67. data/vendor/gems/process_manager/lib/process_manager/child.rb +119 -0
  68. data/vendor/gems/process_manager/lib/process_manager/config.rb +112 -0
  69. data/vendor/gems/process_manager/lib/process_manager/log.rb +107 -0
  70. data/vendor/gems/process_manager/lib/process_manager/master.rb +322 -0
  71. data/vendor/gems/process_manager/process_manager-0.0.13.gemspec +42 -0
  72. data/vendor/specifications/aws-sdk-core-2.0.5.gemspec +39 -0
  73. data/vendor/specifications/builder-3.2.2.gemspec +29 -0
  74. data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +28 -0
  75. data/vendor/specifications/gli-2.5.6.gemspec +51 -0
  76. data/vendor/specifications/jamespath-0.5.1.gemspec +35 -0
  77. data/vendor/specifications/little-plugger-1.1.3.gemspec +32 -0
  78. data/vendor/specifications/logging-1.8.1.gemspec +44 -0
  79. data/vendor/specifications/multi_json-1.7.7.gemspec +30 -0
  80. data/vendor/specifications/multi_json-1.8.4.gemspec +30 -0
  81. data/vendor/specifications/multi_xml-0.5.5.gemspec +30 -0
  82. data/vendor/specifications/process_manager-0.0.13.gemspec +42 -0
  83. data/vendor/specifications/simple_pid-0.2.1.gemspec +28 -0
  84. metadata +377 -0
@@ -0,0 +1,28 @@
1
+ require 'instance_metadata'
2
+
3
+ module InstanceAgent
4
+ module CodeDeployPlugin
5
+ class RequestHelper
6
+
7
+ def initialize(options = {})
8
+ @deploy_control_client = options[:deploy_control_client]
9
+ end
10
+
11
+ def verify_clients_configuration
12
+ errors = []
13
+ errors << "Invalid aws sdk security configuration" unless valid_aws_sdk_security_config?
14
+ errors << "Invalid server certificate" unless valid_server_certificate?
15
+ errors
16
+ end
17
+
18
+ def valid_aws_sdk_security_config?
19
+ @deploy_control_client.ssl_verify_peer
20
+ end
21
+
22
+ def valid_server_certificate?
23
+ @deploy_control_client.verify_cert_fields
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: UTF-8
2
+ require 'process_manager/config'
3
+
4
+ module InstanceAgent
5
+ class Config < ProcessManager::Config
6
+ def self.init
7
+ @config = Config.new
8
+ ProcessManager::Config.instance_variable_set("@config", @config)
9
+ end
10
+
11
+ def validate
12
+ errors = super
13
+ validate_children(errors)
14
+ errors
15
+ end
16
+
17
+ def initialize
18
+ super
19
+ @config.update({
20
+ :program_name => 'codedeploy-agent',
21
+ :wait_between_spawning_children => 1,
22
+ :log_dir => nil,
23
+ :pid_dir => nil,
24
+ :shared_dir => nil,
25
+ :user => nil,
26
+ :children => 1,
27
+ :http_read_timeout => 80,
28
+ :instance_service_region => nil,
29
+ :instance_service_endpoint => nil,
30
+ :instance_service_port => nil,
31
+ :wait_between_runs => 30,
32
+ :wait_after_error => 30,
33
+ :codedeploy_test_profile => 'prod'
34
+ })
35
+ end
36
+
37
+ def validate_children(errors = [])
38
+ errors << 'children can only be set to 1' unless config[:children] == 1
39
+ errors
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ require 'process_manager/log'
2
+
3
+ InstanceAgent::Log = ProcessManager::Log
@@ -0,0 +1,17 @@
1
+ module InstanceAgent
2
+
3
+ class Platform
4
+
5
+ attr_accessor :util
6
+
7
+ def self.util
8
+ @util
9
+ end
10
+
11
+ def self.util=(klass)
12
+ @util = klass
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,57 @@
1
+ module InstanceAgent
2
+ class LinuxUtil
3
+ def self.supported_versions()
4
+ [0.0]
5
+ end
6
+
7
+ def self.supported_oses()
8
+ ['linux']
9
+ end
10
+
11
+ def self.prepare_script_command(script, absolute_path)
12
+ script_command = absolute_path
13
+ if(!script.runas.nil?)
14
+ script_command = 'su ' + script.runas + ' -c ' + absolute_path
15
+ end
16
+ script_command
17
+ end
18
+
19
+ def self.script_executable?(path)
20
+ File.executable?(path)
21
+ end
22
+
23
+ def self.extract_tar(bundle_file, dst)
24
+ FileUtils.mkdir_p(dst)
25
+ execute_tar_command("/bin/tar -xpsf #{bundle_file} -C #{dst}")
26
+ end
27
+
28
+ def self.extract_tgz(bundle_file, dst)
29
+ FileUtils.mkdir_p(dst)
30
+ execute_tar_command("/bin/tar -zxpsf #{bundle_file} -C #{dst}")
31
+ end
32
+
33
+ private
34
+ def self.execute_tar_command(cmd)
35
+ log(:debug, "Executing #{cmd}")
36
+
37
+ output = `#{cmd} 2>&1`
38
+ exit_status = $?.exitstatus
39
+
40
+ log(:debug, "Command status: #{$?}")
41
+ log(:debug, "Command output: #{output}")
42
+
43
+ if exit_status != 0
44
+ msg = "Error extracting tar archive: #{exit_status}"
45
+ log(:error, msg)
46
+ raise msg
47
+ end
48
+ end
49
+
50
+ private
51
+ def self.log(severity, message)
52
+ raise ArgumentError, "Unknown severity #{severity.inspect}" unless InstanceAgent::Log::SEVERITIES.include?(severity.to_s)
53
+ InstanceAgent::Log.send(severity.to_sym, "#{self.to_s}: #{message}")
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+ require 'process_manager/child'
3
+
4
+ module InstanceAgent
5
+ module Runner
6
+ class Child < ProcessManager::Daemon::Child
7
+ AGENTS = {
8
+ 0 => InstanceAgent::CodeDeployPlugin::CommandPoller
9
+ }
10
+
11
+ attr_accessor :runner
12
+
13
+ def prepare_run
14
+ validate_index
15
+ with_error_handling do
16
+ @runner = AGENTS[index].runner
17
+ ProcessManager.set_program_name(description)
18
+ end
19
+ end
20
+
21
+ def run
22
+ with_error_handling do
23
+ runner.run
24
+ end
25
+ end
26
+
27
+ def description
28
+ if runner
29
+ "#{runner.description} of master #{master_pid.inspect}"
30
+ else
31
+ 'booting child'
32
+ end
33
+ end
34
+
35
+ def validate_index
36
+ raise ArgumentError, "Invalid index #{index.inspect}: only 0-2 possible" unless AGENTS.keys.include?(index)
37
+ end
38
+
39
+ def with_error_handling
40
+ yield
41
+ rescue SocketError => e
42
+ ProcessManager::Log.info "#{description}: failed to run as the connection failed! #{e.class} - #{e.message} - #{e.backtrace.join("\n")}"
43
+ sleep ProcessManager::Config.config[:wait_after_connection_problem]
44
+ exit 1
45
+ rescue Exception => e
46
+ if (e.message.to_s.match(/throttle/i) || e.message.to_s.match(/rateexceeded/i) rescue false)
47
+ ProcessManager::Log.error "#{description}: ran into throttling - waiting for #{ProcessManager::Config.config[:wait_after_throttle_error]}s until retrying"
48
+ sleep ProcessManager::Config.config[:wait_after_throttle_error]
49
+ else
50
+ ProcessManager::Log.error "#{description}: error during start or run: #{e.class} - #{e.message} - #{e.backtrace.join("\n")}"
51
+ end
52
+ exit 1
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,103 @@
1
+ # encoding: UTF-8
2
+ require 'process_manager/master'
3
+ require 'instance_agent/codedeploy_plugin/request_helper'
4
+ require 'instance_agent/codedeploy_plugin/codedeploy_control'
5
+ require 'instance_metadata'
6
+
7
+ module InstanceAgent
8
+ module Runner
9
+ class Master < ProcessManager::Daemon::Master
10
+
11
+ ChildTerminationMaxWaitTime = 80
12
+
13
+ def self.description(pid = $$)
14
+ "master #{pid}"
15
+ end
16
+
17
+ def self.child_class
18
+ ::InstanceAgent::Runner::Child
19
+ end
20
+
21
+ def self.pid_description
22
+ ProcessManager::Config.config[:program_name]
23
+ end
24
+
25
+ def validate_ssl_config
26
+ if !InstanceMetadata.instance_id.blank?
27
+ region = InstanceMetadata.region
28
+ request_helper = InstanceAgent::CodeDeployPlugin::RequestHelper.new(:deploy_control_client => InstanceAgent::CodeDeployPlugin::CodeDeployControl.new(:region => region))
29
+
30
+ if (errors = request_helper.verify_clients_configuration)
31
+ errors.each{|error| ProcessManager::Log.error("Stopping CodeDeploy agent. " + error)}
32
+ end
33
+ self.class.abort unless errors.empty?
34
+ end
35
+ end
36
+
37
+ def self.log_file
38
+ File.join(ProcessManager::Config.config[:log_dir], "#{ProcessManager::Config.config[:program_name]}.log")
39
+ end
40
+
41
+ def self.pid_file
42
+ File.join(ProcessManager::Config.config[:pid_dir], "#{ProcessManager::Config.config[:program_name]}.pid")
43
+ end
44
+
45
+ def stop
46
+ if (pid = self.class.find_pid)
47
+ puts "Stopping #{description(pid)}"
48
+ ProcessManager::Log.info("Stopping #{description(pid)}")
49
+ begin
50
+ Process.kill('TERM', pid)
51
+ rescue Errno::ESRCH
52
+ end
53
+
54
+ begin
55
+ Timeout.timeout(ChildTerminationMaxWaitTime) do
56
+ loop do
57
+ begin
58
+ Process.kill(0, pid)
59
+ sleep(1)
60
+ rescue Errno::ESRCH
61
+ break
62
+ end
63
+ end
64
+ end
65
+ rescue Timeout::Error
66
+ puts "Child processes still running. Master going down."
67
+ ProcessManager::Log.warn("Master process (#{pid}) going down before terminating child")
68
+ end
69
+ else
70
+ puts "Nothing running that could be stopped"
71
+ end
72
+ end
73
+
74
+ def kill_children(sig)
75
+ children.each do |index, child_pid|
76
+ begin
77
+ Process.kill(sig, child_pid)
78
+ rescue Errno::ESRCH
79
+ end
80
+ end
81
+
82
+ begin
83
+ Timeout.timeout(ChildTerminationMaxWaitTime) do
84
+ children.each do |index, child_pid|
85
+ begin
86
+ Process.wait(child_pid)
87
+ rescue Errno::ESRCH
88
+ end
89
+ end
90
+ end
91
+ rescue Timeout::Error
92
+ children.each do |index, child_pid|
93
+ if ProcessManager.process_running?(child_pid)
94
+ puts "Stopping #{ProcessManager::Config.config[:program_name]} agent(#{pid}) but child(#{child_pid}) still processing."
95
+ ProcessManager::Log.warn("Stopping #{ProcessManager::Config.config[:program_name]} agent(#{pid}) but child(#{child_pid}) is still processing.")
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,47 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'instance_agent'
4
+
5
+ class InstanceMetadata
6
+
7
+ IP_ADDRESS = '169.254.169.254'
8
+ PORT = 80
9
+
10
+ def self.host_identifier
11
+ doc = JSON.parse(http_get('/latest/dynamic/instance-identity/document').strip)
12
+ "arn:aws:ec2:#{doc['region']}:#{doc['accountId']}:instance/#{doc['instanceId']}"
13
+ end
14
+
15
+ def self.region
16
+ az = http_get('/latest/meta-data/placement/availability-zone').strip
17
+ raise "Invalid availability zone name: #{az}" unless
18
+ az =~ /[a-z]{2}-[a-z]+-\d+[a-z]/
19
+ az.chop
20
+ end
21
+
22
+ def self.instance_id
23
+ begin
24
+ Net::HTTP.start(IP_ADDRESS, PORT) do |http|
25
+ response = http.get('/latest/meta-data/instance-id')
26
+ if response.code.to_i != 200
27
+ return nil
28
+ end
29
+ return response.body
30
+ end
31
+ rescue
32
+ return nil
33
+ end
34
+ end
35
+
36
+ private
37
+ def self.http_get(path)
38
+ Net::HTTP.start(IP_ADDRESS, PORT) do |http|
39
+ response = http.get(path)
40
+ if response.code.to_i != 200
41
+ InstanceAgent::Log.send(:debug, "HTTP error from metadata service, code #{reponse.code}")
42
+ raise "HTTP error from metadata service, code #{reponse.code}"
43
+ end
44
+ return response.body
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,120 @@
1
+ require 'openssl'
2
+ require 'tempfile'
3
+
4
+ class CertificateHelper
5
+ def initialize()
6
+ generate_root()
7
+ generate_intermediate()
8
+ generate_signer()
9
+
10
+ ca_chain_file = generate_ca_chain()
11
+
12
+ ENV['AWS_REGION'] = 'us-east-1'
13
+ InstanceAgent::CodeDeployPlugin::DeploymentSpecification.init_cert_store(ca_chain_file)
14
+ end
15
+
16
+ def generate_root()
17
+ @root_key = OpenSSL::PKey::RSA.new(1024)
18
+ @root_cert = OpenSSL::X509::Certificate.new
19
+ @root_cert.version = 2
20
+ @root_cert.serial = 1
21
+ @root_cert.subject = OpenSSL::X509::Name.new [
22
+ ['C', 'US'], ['ST', 'Washington'], ['L', 'Seattle'],
23
+ ['O', 'Amazon.com, Inc.'], ['CN', 'Host Agent TEST CA Root G1']
24
+ ]
25
+ @root_cert.issuer = @root_cert.subject
26
+ @root_cert.public_key = @root_key.public_key
27
+ @root_cert.not_before = Time.now
28
+ @root_cert.not_after = Time.now + 60
29
+ ef = OpenSSL::X509::ExtensionFactory.new
30
+
31
+ ef.subject_certificate = @root_cert
32
+ ef.issuer_certificate = @root_cert
33
+ @root_cert.extensions = [
34
+ ef.create_extension("basicConstraints","CA:TRUE",true),
35
+ ef.create_extension("keyUsage","keyCertSign, cRLSign", true),
36
+ ef.create_extension("subjectKeyIdentifier","hash",false),
37
+ ]
38
+
39
+ @root_cert.sign(@root_key, OpenSSL::Digest::SHA1.new)
40
+
41
+ return @root_cert
42
+ end
43
+
44
+ def generate_intermediate()
45
+ @intermediate_key = OpenSSL::PKey::RSA.new(1024)
46
+ @intermediate_cert = OpenSSL::X509::Certificate.new
47
+ @intermediate_cert.version = 2
48
+ @intermediate_cert.serial = 1
49
+ @intermediate_cert.subject = OpenSSL::X509::Name.new [
50
+ ['C', 'US'], ['ST', 'Washington'], ['L', 'Seattle'],
51
+ ['O', 'Amazon.com, Inc.'], ['CN', 'Host Agent TEST CA Intermediate G1']
52
+ ]
53
+ @intermediate_cert.issuer = @root_cert.subject
54
+ @intermediate_cert.public_key = @intermediate_key.public_key
55
+ @intermediate_cert.not_before = Time.now
56
+ @intermediate_cert.not_after = Time.now + 60
57
+
58
+ ef = OpenSSL::X509::ExtensionFactory.new
59
+ ef.subject_certificate = @intermediate_cert
60
+ ef.issuer_certificate = @root_cert
61
+ @intermediate_cert.extensions = [
62
+ ef.create_extension("basicConstraints","CA:TRUE",true),
63
+ ef.create_extension("keyUsage","keyCertSign, cRLSign", true),
64
+ ef.create_extension("subjectKeyIdentifier","hash",false)
65
+ ]
66
+
67
+ @intermediate_cert.sign(@root_key, OpenSSL::Digest::SHA1.new)
68
+
69
+ return @intermediate_cert
70
+ end
71
+
72
+ def generate_signer()
73
+ @signer_key = OpenSSL::PKey::RSA.new(1024)
74
+ @signer_cert = OpenSSL::X509::Certificate.new
75
+ @signer_cert.version = 2
76
+ @signer_cert.serial = 1
77
+ @signer_cert.subject = OpenSSL::X509::Name.new [
78
+ ['C', 'US'], ['ST', 'Washington'], ['L', 'Seattle'],
79
+ ['O', 'Amazon.com, Inc.'], ['CN', 'codedeploy-signer-us-east-1.amazonaws.com']
80
+ ]
81
+ @signer_cert.issuer = @intermediate_cert.subject
82
+ @signer_cert.public_key = @signer_key.public_key
83
+ @signer_cert.not_before = Time.now
84
+ @signer_cert.not_after = Time.now + 60
85
+
86
+ ef = OpenSSL::X509::ExtensionFactory.new
87
+ ef.subject_certificate = @signer_cert
88
+ ef.issuer_certificate = @intermediate_cert
89
+ @signer_cert.extensions = [
90
+ ef.create_extension('basicConstraints', 'CA:FALSE', true),
91
+ ef.create_extension('keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature', true),
92
+ ef.create_extension('subjectKeyIdentifier', 'hash')
93
+ ]
94
+
95
+ @signer_cert.sign(@intermediate_key, OpenSSL::Digest::SHA1.new)
96
+
97
+ return @signer_cert
98
+ end
99
+
100
+ def generate_ca_chain()
101
+ ca_chain_file = Tempfile.new('host-agent-deployment-signer-ca-chain')
102
+
103
+ File.open(ca_chain_file.path, "wb") do |ca_chain|
104
+ ca_chain.print @root_cert.to_pem
105
+ ca_chain.print @intermediate_cert.to_pem
106
+ end
107
+
108
+ return ca_chain_file.path
109
+ end
110
+
111
+ def sign_message(message)
112
+ if @signer_key.nil?
113
+ raise "Signer key not initialized"
114
+ end
115
+
116
+ pkcs7 = OpenSSL::PKCS7::sign(@signer_cert, @signer_key, message, [], OpenSSL::PKCS7::BINARY)
117
+
118
+ return pkcs7.to_pem
119
+ end
120
+ end