smart_proxy_acd 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee781c1809e07078b77575c2a7276462859ca133fcb7206e0cf75d611f1151d4
4
- data.tar.gz: 937f4a477377b425127f44fb7690e2c59479a6f8c8a66b412e986f5a5980cba6
3
+ metadata.gz: 881bee215ecdb92ddd19b7b60397274f9e5eb72911660a289d235b5ab09aa8bc
4
+ data.tar.gz: 25abeb5af5ca858c7a18fbce66b4727a8bfe7d8b120089a7ecfd370792a75931
5
5
  SHA512:
6
- metadata.gz: fd2c0a61f8a4dfc53124deb0f17303ae9250babc12671b60ff96645547f5b0b0ecd4b173e4f0fb18cf6248c776e96d27b52386a30f1760129f1affb2db70fe6d
7
- data.tar.gz: dee25ae9c01bbe7de4577790f3772ad613fba257e7a8b7c32ccca9da2a65d9de82071195fa560282cb21a6de91fa562b32fc0e47ef1ee7d9d9f6effaabebd763
6
+ metadata.gz: 05a4fa40b1df875b7526b584b2ac5a2a544abd3d2248aa04dc31bc8b4592f094163929e6790d3abd826f5d7c9d0401a2add3476b5dc813aae027e5cd0354ffa1
7
+ data.tar.gz: ae1ad0260d9c7c95c3ae78be4bcc05e68a679ff6c3d453311598dcf2c51c4428c8e03056b2293ae4fcc85480566da7e48a5576e8473783f5816668b388d05f0c
@@ -5,8 +5,17 @@ module Proxy
5
5
  # Implement a SmartProxy Plugin
6
6
  class Plugin < ::Proxy::Plugin
7
7
  plugin 'acd', Proxy::Acd::VERSION
8
- http_rackup_path File.expand_path('acd_http_config.ru', File.expand_path('../', __FILE__))
9
- https_rackup_path File.expand_path('acd_http_config.ru', File.expand_path('../', __FILE__))
8
+ rackup_path File.expand_path('acd_http_config.ru', __dir__)
9
+
10
+ load_classes do
11
+ require 'smart_proxy_dynflow'
12
+ require 'smart_proxy_acd/acd_runner'
13
+ require 'smart_proxy_acd/acd_task_launcher'
14
+ end
15
+
16
+ load_dependency_injection_wirings do |_container_instance, _settings|
17
+ Proxy::Dynflow::TaskLauncherRegistry.register('acd', AcdTaskLauncher)
18
+ end
10
19
  end
11
20
  end
12
21
  end
@@ -0,0 +1,190 @@
1
+ require 'smart_proxy_dynflow/runner/command_runner'
2
+ require 'tempfile'
3
+ require 'rest-client'
4
+ require 'tmpdir'
5
+ require 'socket'
6
+
7
+ # rubocop:disable ClassLength
8
+
9
+ module Proxy
10
+ module Acd
11
+ # Implements the AcdRunner to be used by foreman_remote_execution
12
+ class AcdRunner < Proxy::Dynflow::Runner::CommandRunner
13
+ DEFAULT_REFRESH_INTERVAL = 1
14
+
15
+ class << self
16
+ def parse_dynflow_settings(path)
17
+ return @dynflow_settings if defined? @dynflow_settings
18
+ if File.exist?(path)
19
+ @dynflow_settings = {}
20
+ YAML.load_file(path).each do |key, value|
21
+ @dynflow_settings[key.to_s] = value
22
+ end
23
+ end
24
+ @dynflow_settings
25
+ end
26
+
27
+ def parse_ssl_options
28
+ return @ssl_options if defined? @ssl_options
29
+
30
+ @ssl_options = {}
31
+ return @ssl_options unless URI.parse(@dynflow_settings['foreman_url']).scheme == 'https'
32
+
33
+ @ssl_options[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
34
+
35
+ private_key_file = @dynflow_settings['foreman_ssl_key'] || @dynflow_settings['ssl_private_key']
36
+ if private_key_file
37
+ private_key = File.read(private_key_file)
38
+ @ssl_options[:ssl_client_key] = OpenSSL::PKey::RSA.new(private_key)
39
+ end
40
+ certificate_file = @dynflow_settings['foreman_ssl_cert'] || @dynflow_settings['ssl_certificate']
41
+ if certificate_file
42
+ certificate = File.read(certificate_file)
43
+ @ssl_options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(certificate)
44
+ end
45
+ ca_file = @dynflow_settings['foreman_ssl_ca'] || @dynflow_settings['ssl_ca_file']
46
+ @ssl_options[:ssl_ca_file] = ca_file if ca_file
47
+ @ssl_options
48
+ end
49
+ end
50
+
51
+ def initialize(options, suspended_action:)
52
+ super(options, :suspended_action => suspended_action)
53
+ @options = options
54
+ end
55
+
56
+ def get_playbook(playbook_id)
57
+ logger.debug("Get playbook with id #{playbook_id}")
58
+ response = playbook_resource(playbook_id).get
59
+ if response.code.to_s != '200'
60
+ raise "Failed performing callback to Foreman server: #{response.code} #{response.body}"
61
+ end
62
+ tmp_file = Tempfile.new.path
63
+ File.write(tmp_file, response)
64
+ @playbook_tmp_base64_file = tmp_file
65
+ end
66
+
67
+ def playbook_resource(playbook_id)
68
+ dynflow_settings = self.class.parse_dynflow_settings('/etc/foreman-proxy/settings.yml')
69
+ playbook_url = dynflow_settings['foreman_url'] + "/acd/api/v2/ansible_playbooks/#{playbook_id}/grab"
70
+ @resource ||= RestClient::Resource.new(playbook_url, self.class.parse_ssl_options)
71
+ end
72
+
73
+ def store_playbook
74
+ logger.debug('Unpack ansible playbook')
75
+ dir = Dir.mktmpdir
76
+ raise 'Could not create temporary directory to run ansible playbook' if dir.nil? || !Dir.exist?(dir)
77
+ command = "base64 -d #{@playbook_tmp_base64_file} | tar xz -C #{dir}"
78
+ system(command)
79
+ @playbook_tmp_dir = dir
80
+ end
81
+
82
+ def cleanup
83
+ File.unlink(@playbook_tmp_base64_file) if File.exist?(@playbook_tmp_base64_file)
84
+ FileUtils.rm_rf(@playbook_tmp_dir) if Dir.exist?(@playbook_tmp_dir)
85
+ end
86
+
87
+ def start
88
+ parse_acd_job
89
+
90
+ publish_data("Grab playbook to configure application #{@application_name}...", 'stdout')
91
+ get_playbook(@playbook_id)
92
+ store_playbook
93
+
94
+ @playbook_path = File.join(@playbook_tmp_dir, @playbook_file)
95
+ raise "Could not run playbook: playbook file #{@playbook_file} not found in playbook dir #{@playbook_tmp_dir}" unless File.exist?(@playbook_path)
96
+
97
+ publish_data('Write temporary inventory', 'stdout')
98
+ write_inventory
99
+
100
+ command = generate_command
101
+ logger.debug("Running command #{command.join(' ')}")
102
+ initialize_command(*command)
103
+ end
104
+
105
+ def close
106
+ logger.debug("Cleanup ansible playbook #{@playbook_tmp_dir} and #{@playbook_tmp_base64_file}")
107
+ cleanup
108
+ end
109
+
110
+ def kill
111
+ publish_data('== TASK ABORTED BY USER ==', 'stdout')
112
+ publish_exit_status(1)
113
+ ::Process.kill('SIGTERM', @command_pid)
114
+ end
115
+
116
+ private
117
+
118
+ def parse_acd_job
119
+ @acd_job = YAML.safe_load(@options['script'])
120
+ @application_name = @acd_job['application_name']
121
+ @playbook_id = @acd_job['playbook_id']
122
+ @playbook_file = @acd_job['playbook_file']
123
+
124
+ raise "'playbook_file' need to be specified" if @playbook_file.nil? || @playbook_file.empty?
125
+ raise "'playbook_id' need to be specified" if @playbook_id.nil?
126
+ end
127
+
128
+ def proxy_hostname
129
+ Socket.gethostbyname(Socket.gethostname).first
130
+ end
131
+
132
+ def write_inventory
133
+ complete_inventory = YAML.safe_load(@acd_job['inventory'])
134
+ my_inventory = complete_inventory[proxy_hostname]
135
+
136
+ tmp_inventory_file = Tempfile.new('acd_inventory')
137
+ tmp_inventory_file << my_inventory.to_yaml
138
+ tmp_inventory_file << "\n"
139
+ tmp_inventory_file.close
140
+ @inventory_path = tmp_inventory_file.path
141
+ end
142
+
143
+ def environment
144
+ env = {}
145
+ env['ANSIBLE_CALLBACK_WHITELIST'] = ''
146
+ env['ANSIBLE_LOAD_CALLBACK_PLUGINS'] = '0'
147
+ env
148
+ end
149
+
150
+ def setup_verbosity
151
+ verbosity = ''
152
+ if @acd_job['verbose']
153
+ verbosity_level = @acd_job['verbose'].split(' ').first.to_i
154
+ if verbosity_level.positive?
155
+ verbosity = '-'
156
+ verbosity_level.times do
157
+ verbosity += 'v'
158
+ end
159
+ end
160
+ end
161
+ verbosity
162
+ end
163
+
164
+ def valid_tags(tag)
165
+ re = /^\w+(\s*,\s*\w+)*$/
166
+ return true if @acd_job[tag] && @acd_job[tag].match?(re)
167
+ end
168
+
169
+ def generate_command
170
+ logger.debug("Generate command with #{@inventory_path} to run #{@playbook_id} with path #{@playbook_path}")
171
+ command = [environment]
172
+ command << 'ansible-playbook'
173
+ command << '-i'
174
+ command << @inventory_path
175
+ verbose = setup_verbosity
176
+ command << verbose unless verbose.empty?
177
+ command << "--tags '#{@acd_job['tags']}'" if valid_tags('tags')
178
+ command << "--skip-tags '#{@acd_job['skip_tags']}'" if valid_tags('skip_tags')
179
+ if @acd_job.key?('extra_vars') && !@acd_job['extra_vars'].nil? && !@acd_job['extra_vars'].empty?
180
+ command << '--extra-vars'
181
+ command << "'#{@acd_job['extra_vars']}'"
182
+ end
183
+ command << @playbook_path.to_s
184
+ command
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ # rubocop:enable ClassLength
@@ -0,0 +1,25 @@
1
+ module Proxy
2
+ module Acd
3
+ # Implements the TaskLauncher::Batch for Acd
4
+ class AcdTaskLauncher < Proxy::Dynflow::TaskLauncher::Batch
5
+ # Implements the Runner::Action for Acd
6
+ class AcdRunnerAction < Proxy::Dynflow::Action::Runner
7
+ def initiate_runner
8
+ additional_options = {
9
+ :step_id => run_step_id,
10
+ :uuid => execution_plan_id
11
+ }
12
+ ::Proxy::Acd::AcdRunner.new(
13
+ input.merge(additional_options),
14
+ :suspended_action => suspended_action
15
+ )
16
+ end
17
+ end
18
+
19
+ def child_launcher(parent)
20
+ Proxy::Dynflow::TaskLauncher::Single.new(world, callback, :parent => parent,
21
+ :action_class_override => AcdRunnerAction)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
  module Proxy
2
2
  module Acd
3
- VERSION = '0.2.0'.freeze
3
+ VERSION = '0.3.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_acd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Bucher
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-31 00:00:00.000000000 Z
11
+ date: 2021-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.50.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: smart_proxy_dynflow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.5.0
27
41
  description: Application Centric Deployment smart proxy plugin
28
42
  email: info@atix.de
29
43
  executables: []
@@ -40,6 +54,8 @@ files:
40
54
  - lib/smart_proxy_acd/acd_api.rb
41
55
  - lib/smart_proxy_acd/acd_http_config.ru
42
56
  - lib/smart_proxy_acd/acd_main.rb
57
+ - lib/smart_proxy_acd/acd_runner.rb
58
+ - lib/smart_proxy_acd/acd_task_launcher.rb
43
59
  - lib/smart_proxy_acd/version.rb
44
60
  - settings.d/acd.yml.example
45
61
  homepage: http://github.com/ATIX-AG/smart_proxy_acd