chef 17.2.29 → 17.3.48

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -3
  3. data/chef.gemspec +1 -0
  4. data/lib/chef/client.rb +1 -1
  5. data/lib/chef/data_bag.rb +1 -2
  6. data/lib/chef/data_bag_item.rb +1 -2
  7. data/lib/chef/deprecated.rb +10 -4
  8. data/lib/chef/dsl.rb +1 -0
  9. data/lib/chef/dsl/render_helpers.rb +44 -0
  10. data/lib/chef/dsl/secret.rb +64 -0
  11. data/lib/chef/dsl/toml.rb +116 -0
  12. data/lib/chef/dsl/universal.rb +5 -0
  13. data/lib/chef/exceptions.rb +22 -0
  14. data/lib/chef/handler/slow_report.rb +1 -1
  15. data/lib/chef/json_compat.rb +1 -1
  16. data/lib/chef/policy_builder/policyfile.rb +88 -45
  17. data/lib/chef/provider/file.rb +2 -2
  18. data/lib/chef/provider/lwrp_base.rb +1 -1
  19. data/lib/chef/provider/package/habitat.rb +168 -0
  20. data/lib/chef/provider/package/powershell.rb +5 -0
  21. data/lib/chef/providers.rb +1 -0
  22. data/lib/chef/resource/chef_client_config.rb +7 -2
  23. data/lib/chef/resource/chef_client_cron.rb +1 -1
  24. data/lib/chef/resource/chef_client_launchd.rb +1 -1
  25. data/lib/chef/resource/chef_client_scheduled_task.rb +1 -1
  26. data/lib/chef/resource/chef_client_systemd_timer.rb +1 -1
  27. data/lib/chef/resource/chef_client_trusted_certificate.rb +2 -2
  28. data/lib/chef/resource/chef_vault_secret.rb +2 -2
  29. data/lib/chef/resource/dsc_resource.rb +1 -1
  30. data/lib/chef/resource/execute.rb +3 -3
  31. data/lib/chef/resource/gem_package.rb +2 -1
  32. data/lib/chef/resource/habitat/_habitat_shared.rb +28 -0
  33. data/lib/chef/resource/habitat/habitat_package.rb +129 -0
  34. data/lib/chef/resource/habitat/habitat_sup.rb +329 -0
  35. data/lib/chef/resource/habitat/habitat_sup_systemd.rb +67 -0
  36. data/lib/chef/resource/habitat/habitat_sup_windows.rb +90 -0
  37. data/lib/chef/resource/habitat_config.rb +107 -0
  38. data/lib/chef/resource/habitat_install.rb +247 -0
  39. data/lib/chef/resource/habitat_service.rb +451 -0
  40. data/lib/chef/resource/habitat_user_toml.rb +92 -0
  41. data/lib/chef/resource/lwrp_base.rb +1 -1
  42. data/lib/chef/resource/support/HabService.dll.config.erb +19 -0
  43. data/lib/chef/resource/support/client.erb +8 -1
  44. data/lib/chef/resource/support/sup.toml.erb +179 -0
  45. data/lib/chef/resource/windows_defender.rb +163 -0
  46. data/lib/chef/resource/windows_defender_exclusion.rb +125 -0
  47. data/lib/chef/resource/windows_printer.rb +78 -44
  48. data/lib/chef/resource/windows_printer_port.rb +1 -1
  49. data/lib/chef/resource/windows_update_settings.rb +259 -0
  50. data/lib/chef/resources.rb +12 -1
  51. data/lib/chef/secret_fetcher.rb +54 -0
  52. data/lib/chef/secret_fetcher/aws_secrets_manager.rb +53 -0
  53. data/lib/chef/secret_fetcher/azure_key_vault.rb +56 -0
  54. data/lib/chef/secret_fetcher/base.rb +72 -0
  55. data/lib/chef/secret_fetcher/example.rb +46 -0
  56. data/lib/chef/version.rb +1 -1
  57. data/spec/functional/mixin/from_file_spec.rb +1 -1
  58. data/spec/integration/recipes/recipe_dsl_spec.rb +1 -1
  59. data/spec/integration/recipes/resource_action_spec.rb +4 -4
  60. data/spec/support/shared/unit/provider/file.rb +2 -8
  61. data/spec/unit/data_bag_item_spec.rb +2 -2
  62. data/spec/unit/data_bag_spec.rb +1 -1
  63. data/spec/unit/dsl/render_helpers_spec.rb +102 -0
  64. data/spec/unit/dsl/secret_spec.rb +65 -0
  65. data/spec/unit/policy_builder/dynamic_spec.rb +0 -5
  66. data/spec/unit/policy_builder/policyfile_spec.rb +144 -56
  67. data/spec/unit/provider/apt_update_spec.rb +3 -1
  68. data/spec/unit/provider/mount/aix_spec.rb +1 -1
  69. data/spec/unit/provider/package/powershell_spec.rb +74 -12
  70. data/spec/unit/resource/windows_defender_exclusion_spec.rb +62 -0
  71. data/spec/unit/resource/windows_defender_spec.rb +71 -0
  72. data/spec/unit/resource/windows_update_settings_spec.rb +64 -0
  73. data/spec/unit/secret_fetcher/azure_key_vault_spec.rb +63 -0
  74. data/spec/unit/secret_fetcher_spec.rb +82 -0
  75. metadata +51 -7
@@ -0,0 +1,107 @@
1
+ # Copyright:: Chef Software Inc.
2
+ # License:: Apache License, Version 2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ require_relative "../http"
17
+ require_relative "../json_compat"
18
+ require_relative "../resource"
19
+
20
+ class Chef
21
+ class Resource
22
+ class HabitatConfig < Chef::Resource
23
+ unified_mode true
24
+
25
+ provides :habitat_config
26
+
27
+ description "Use the **habitat_config** resource to apply a configuration to a Chef Habitat service."
28
+ introduced "17.3"
29
+ examples <<~DOC
30
+ **Configure your nginx defaults**
31
+
32
+ ```ruby
33
+ habitat_config 'nginx.default' do
34
+ config({
35
+ worker_count: 2,
36
+ http: {
37
+ keepalive_timeout: 120
38
+ }
39
+ })
40
+ end
41
+ ```
42
+ DOC
43
+
44
+ property :config, Mash, required: true, coerce: proc { |m| m.is_a?(Hash) ? Mash.new(m) : m },
45
+ description: "The configuration to apply as a ruby hash, for example, `{ worker_count: 2, http: { keepalive_timeout: 120 } }`."
46
+
47
+ property :service_group, String, name_property: true, desired_state: false,
48
+ description: "The service group to apply the configuration to. For example, `nginx.default`"
49
+
50
+ property :remote_sup, String, default: "127.0.0.1:9632", desired_state: false,
51
+ description: "Address to a remote supervisor's control gateway."
52
+
53
+ # Http port needed for querying/comparing current config value
54
+ property :remote_sup_http, String, default: "127.0.0.1:9631", desired_state: false,
55
+ description: "Address for remote supervisor http port. Used to pull existing."
56
+
57
+ property :gateway_auth_token, String, desired_state: false,
58
+ description: "Auth token for accessing the remote supervisor's http port."
59
+
60
+ property :user, String, desired_state: false,
61
+ description: "Name of user key to use for encryption. Passes `--user` to `hab config apply`."
62
+
63
+ load_current_value do
64
+ http_uri = "http://#{remote_sup_http}"
65
+
66
+ begin
67
+ headers = {}
68
+ headers["Authorization"] = "Bearer #{gateway_auth_token}" if property_is_set?(:gateway_auth_token)
69
+ census = Mash.new(Chef::HTTP::SimpleJSON.new(http_uri).get("/census", headers))
70
+ sc = census["census_groups"][service_group]["service_config"]["value"]
71
+ rescue
72
+ # Default to a blank config if anything (http error, json parsing, finding
73
+ # the config object) goes wrong
74
+ sc = {}
75
+ end
76
+ config sc
77
+ end
78
+
79
+ action :apply, description: "applies the given configuration" do
80
+ converge_if_changed do
81
+ # Use the current timestamp as the serial number/incarnation
82
+ incarnation = Time.now.tv_sec
83
+
84
+ opts = []
85
+ # opts gets flattened by shell_out_compact later
86
+ opts << ["--remote-sup", new_resource.remote_sup] if new_resource.remote_sup
87
+ opts << ["--user", new_resource.user] if new_resource.user
88
+
89
+ tempfile = Tempfile.new(["habitat_config", ".toml"])
90
+ begin
91
+ tempfile.write(render_toml(new_resource.config))
92
+ tempfile.close
93
+
94
+ hab("config", "apply", opts, new_resource.service_group, incarnation, tempfile.path)
95
+ ensure
96
+ tempfile.close
97
+ tempfile.unlink
98
+ end
99
+ end
100
+ end
101
+
102
+ action_class do
103
+ use "../resource/habitat/habitat_shared"
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,247 @@
1
+ #
2
+ # Copyright:: Chef Software, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ require_relative "../http/simple"
18
+ require_relative "../resource"
19
+ class Chef
20
+ class Resource
21
+ class HabitatInstall < Chef::Resource
22
+ unified_mode true
23
+ provides :habitat_install
24
+
25
+ description "Use the **habitat_install** resource to install Chef Habitat."
26
+ introduced "17.3"
27
+ examples <<~DOC
28
+ **Installation Without a Resource Name**
29
+
30
+ ```ruby
31
+ habitat_install
32
+ ```
33
+
34
+ **Installation specifying a habitat builder URL**
35
+
36
+ ```ruby
37
+ habitat_install 'install habitat' do
38
+ bldr_url 'http://localhost'
39
+ end
40
+ ```
41
+
42
+ **Installation specifying version and habitat builder URL**
43
+
44
+ ```ruby
45
+ habitat_install 'install habitat' do
46
+ bldr_url 'http://localhost'
47
+ hab_version '1.5.50'
48
+ end
49
+ ```
50
+ DOC
51
+
52
+ property :name, String, default: "install habitat",
53
+ description: "Name of the resource block. This has no impact other than logging."
54
+
55
+ property :install_url, String, default: "https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh",
56
+ description: "URL to the install script, default is from the [habitat repo](https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh) ."
57
+
58
+ property :bldr_url, String,
59
+ description: "Optional URL to an alternate Habitat Builder."
60
+
61
+ property :create_user, [true, false], default: true,
62
+ description: "Creates the `hab` system user."
63
+
64
+ property :tmp_dir, String,
65
+ description: "Sets TMPDIR environment variable for location to place temp files. Note: This is required if `/tmp` and `/var/tmp` are mounted `noexec`."
66
+
67
+ property :license, String, equal_to: ["accept"],
68
+ description: "Specifies acceptance of habitat license when set to `accept`."
69
+
70
+ property :hab_version, String,
71
+ description: "Specify the version of `Habitat` you would like to install."
72
+
73
+ action :install, description: "Installs Habitat. Does nothing if the `hab` binary is found in the default location for the system (`/bin/hab` on Linux, `/usr/local/bin/hab` on macOS, `C:/habitat/hab.exe` on Windows)" do
74
+ if ::File.exist?(hab_path)
75
+ cmd = shell_out!([hab_path, "--version"].flatten.compact.join(" "))
76
+ version = %r{hab (\d*\.\d*\.\d[^\/]*)}.match(cmd.stdout)[1]
77
+ return if version == new_resource.hab_version
78
+ end
79
+
80
+ if windows?
81
+ # Retrieve version information
82
+ uri = "https://packages.chef.io/files"
83
+ package_name = "hab-x86_64-windows"
84
+ habfile = "#{Chef::Config[:file_cache_path]}/#{package_name}.zip"
85
+
86
+ # TODO: Figure out how to properly validate the shasum for windows. Doesn't seem it's published
87
+ # as a .sha265sum like for the linux .tar.gz
88
+ download = "#{uri}/stable/habitat/latest/hab-x86_64-windows.zip"
89
+
90
+ remote_file habfile do
91
+ source download
92
+ end
93
+
94
+ archive_file "#{package_name}.zip" do
95
+ path habfile
96
+ destination "#{Chef::Config[:file_cache_path]}/habitat"
97
+ action :extract
98
+ not_if { ::Dir.exist?('c:\habitat') }
99
+ end
100
+
101
+ directory 'c:\habitat' do
102
+ notifies :run, "powershell_script[installing from archive]", :immediately
103
+ end
104
+
105
+ powershell_script "installing from archive" do
106
+ code <<-EOH
107
+ Move-Item -Path #{Chef::Config[:file_cache_path]}/habitat/hab-*/* -Destination C:/habitat -Force
108
+ EOH
109
+ action :nothing
110
+ end
111
+
112
+ # TODO: This won't self heal if missing until the next upgrade
113
+ windows_path 'C:\habitat' do
114
+ action :add
115
+ end
116
+ else
117
+ package %w{curl tar gzip}
118
+
119
+ if new_resource.create_user
120
+ group "hab"
121
+
122
+ user "hab" do
123
+ gid "hab"
124
+ system true
125
+ end
126
+ end
127
+
128
+ remote_file ::File.join(Chef::Config[:file_cache_path], "hab-install.sh") do
129
+ source new_resource.install_url
130
+ sensitive true
131
+ end
132
+
133
+ execute "installing with hab-install.sh" do
134
+ command hab_command
135
+ environment(
136
+ {
137
+ "HAB_BLDR_URL" => "bldr_url",
138
+ "TMPDIR" => "tmp_dir",
139
+ }.each_with_object({}) do |(var, property), env|
140
+ env[var] = new_resource.send(property.to_sym) if new_resource.send(property.to_sym)
141
+ end
142
+ )
143
+ end
144
+ end
145
+ execute "hab license accept" if new_resource.license == "accept"
146
+ end
147
+
148
+ # TODO: Work out cleanest method to implement upgrade that will support effortless installs as well as standard chef-client
149
+ # action :upgrade do
150
+ # if platform_family?('windows')
151
+ # # Retrieve version information
152
+ # uri = 'https://packages.chef.io/files'
153
+ # package_name = 'hab-x86_64-windows'
154
+ # zipfile = "#{Chef::Config[:file_cache_path]}/#{package_name}.zip"
155
+
156
+ # # TODO: Figure out how to properly validate the shasum for windows. Doesn't seem it's published
157
+ # # as a .sha265sum like for the linux .tar.gz
158
+ # download = "#{uri}/stable/habitat/latest/hab-x86_64-windows.zip"
159
+
160
+ # remote_file zipfile do
161
+ # source download
162
+ # end
163
+
164
+ # if Chef::VERSION.to_i < 15
165
+ # ruby_block "#{package_name}.zip" do
166
+ # block do
167
+ # require 'zip'
168
+ # Zip::File.open(zipfile) do |zip_file|
169
+ # zip_file.each do |f|
170
+ # fpath = "#{Chef::Config[:file_cache_path]}/habitat/" + f.name
171
+ # zip_file.extract(f, fpath) # unless ::File.exist?(fpath)
172
+ # end
173
+ # end
174
+ # end
175
+ # action :run
176
+ # end
177
+ # else
178
+ # archive_file "#{package_name}.zip" do
179
+ # path zipfile
180
+ # destination "#{Chef::Config[:file_cache_path]}/habitat"
181
+ # action :extract
182
+ # end
183
+ # end
184
+
185
+ # powershell_script 'installing from archive' do
186
+ # code <<-EOH
187
+ # Move-Item -Path #{Chef::Config[:file_cache_path]}/habitat/hab-*/* -Destination C:/habitat -Force
188
+ # EOH
189
+ # end
190
+
191
+ # # TODO: This won't self heal if missing until the next upgrade
192
+ # if Chef::VERSION.to_i < 14
193
+ # env 'PATH_c-habitat' do
194
+ # key_name 'PATH'
195
+ # delim ';' # this was missing
196
+ # value 'C:\habitat'
197
+ # action :modify
198
+ # end
199
+ # else
200
+ # windows_path 'C:\habitat' do
201
+ # action :add
202
+ # end
203
+ # end
204
+ # else
205
+ # remote_file ::File.join(Chef::Config[:file_cache_path], 'hab-install.sh') do
206
+ # source new_resource.install_url
207
+ # sensitive true
208
+ # end
209
+
210
+ # execute 'installing with hab-install.sh' do
211
+ # command hab_command
212
+ # environment(
213
+ # {
214
+ # 'HAB_BLDR_URL' => 'bldr_url',
215
+ # 'TMPDIR' => 'tmp_dir',
216
+ # }.each_with_object({}) do |(var, property), env|
217
+ # env[var] = new_resource.send(property.to_sym) if new_resource.send(property.to_sym)
218
+ # end
219
+ # )
220
+ # not_if { ::File.exist?('/bin/hab') }
221
+ # end
222
+ # end
223
+ # end
224
+
225
+ action_class do
226
+ use "../resource/habitat/habitat_shared"
227
+
228
+ def hab_path
229
+ if macos?
230
+ "/usr/local/bin/hab"
231
+ elsif windows?
232
+ "C:/habitat/hab.exe"
233
+ else
234
+ "/bin/hab"
235
+ end
236
+ end
237
+
238
+ def hab_command
239
+ cmd = "bash #{Chef::Config[:file_cache_path]}/hab-install.sh"
240
+ cmd << " -v #{new_resource.hab_version} " if new_resource.hab_version
241
+ cmd << " -t x86_64-linux-kernel2" if node["kernel"]["release"].to_i < 3
242
+ cmd
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,451 @@
1
+ # Copyright:: Chef Software, Inc.
2
+ # License:: Apache License, Version 2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require_relative "../resource"
18
+ require "chef-utils/dist" unless defined?(ChefUtils::Dist)
19
+
20
+ class Chef
21
+ class Resource
22
+ class HabitatService < Chef::Resource
23
+ unified_mode true
24
+ provides :habitat_service
25
+
26
+ description "Use the **habitat_service** resource to manage Chef Habitat services. This requires that `core/hab-sup` be running as a service. See the `habitat_sup` resource documentation for more information. Note: Applications may run as a specific user. Often with Habitat, the default is `hab`, or `root`. If the application requires another user, then it should be created with Chef's `user` resource."
27
+ introduced "17.3"
28
+ examples <<~DOC
29
+ **Install and load nginx**
30
+
31
+ ```ruby
32
+ habitat_package 'core/nginx'
33
+ habitat_service 'core/nginx'
34
+
35
+ habitat_service 'core/nginx unload' do
36
+ service_name 'core/nginx'
37
+ action :unload
38
+ end
39
+ ```
40
+
41
+ **Pass the `strategy` and `topology` options to hab service commands**
42
+
43
+ ```ruby
44
+ habitat_service 'core/redis' do
45
+ strategy 'rolling'
46
+ topology 'standalone'
47
+ end
48
+ ```
49
+
50
+ **Using update_condition**
51
+
52
+ ```ruby
53
+ habitat_service 'core/redis' do
54
+ strategy 'rolling'
55
+ update_condition 'track-channel'
56
+ topology 'standalone'
57
+ end
58
+ ```
59
+
60
+ **If the service has it's own user specified that is not the `hab` user, don't create the `hab` user on install, and instead create the application user with Chef's `user` resource**
61
+
62
+ ```ruby
63
+ habitat_install 'install habitat' do
64
+ create_user false
65
+ end
66
+
67
+ user 'acme-apps' do
68
+ system true
69
+ end
70
+
71
+ habitat_service 'acme/apps'
72
+ ```
73
+ DOC
74
+
75
+ property :service_name, String, name_property: true,
76
+ description: "The name of the service, must be in the form of `origin/name`"
77
+
78
+ property :loaded, [true, false], default: false, skip_docs: true,
79
+ description: "state property indicating whether the service is loaded in the supervisor"
80
+
81
+ property :running, [true, false], default: false, skip_docs: true,
82
+ description: "state property indicating whether the service is running in the supervisor"
83
+
84
+ # hab svc options which get included based on the action of the resource
85
+ property :strategy, [Symbol, String], equal_to: [:none, "none", :'at-once', "at-once", :rolling, "rolling"], default: :none, coerce: proc { |s| s.is_a?(String) ? s.to_sym : s },
86
+ description: "Passes `--strategy` with the specified update strategy to the hab command. Defaults to `:none`. Other options are `:'at-once'` and `:rolling`"
87
+
88
+ property :topology, [Symbol, String], equal_to: [:standalone, "standalone", :leader, "leader"], default: :standalone, coerce: proc { |s| s.is_a?(String) ? s.to_sym : s },
89
+ description: "Passes `--topology` with the specified service topology to the hab command"
90
+
91
+ property :bldr_url, String, default: "https://bldr.habitat.sh/",
92
+ description: "Passes `--url` with the specified Habitat Builder URL to the hab command. Depending on the type of Habitat Builder you are connecting to, this URL will look different, here are the **3** current types:
93
+ - Public Habitat Builder (default) - `https://bldr.habitat.sh`
94
+ - On-Prem Habitat Builder installed using the [Source Install Method](https://github.com/habitat-sh/on-prem-builder) - `https://your.bldr.url`
95
+ - On-Prem Habitat Builder installed using the [Automate Installer](https://automate.chef.io/docs/on-prem-builder/) - `https://your.bldr.url/bldr/v1`"
96
+
97
+ property :channel, [Symbol, String], default: :stable, coerce: proc { |s| s.is_a?(String) ? s.to_sym : s },
98
+ description: "Passes `--channel` with the specified channel to the hab command"
99
+
100
+ property :bind, [String, Array], coerce: proc { |b| b.is_a?(String) ? [b] : b }, default: [],
101
+ description: "Passes `--bind` with the specified services to bind to the hab command. If an array of multiple service binds are specified then a `--bind` flag is added for each."
102
+
103
+ property :binding_mode, [Symbol, String], equal_to: [:strict, "strict", :relaxed, "relaxed"], default: :strict, coerce: proc { |s| s.is_a?(String) ? s.to_sym : s },
104
+ description: "Passes `--binding-mode` with the specified binding mode. Defaults to `:strict`. Options are `:strict` or `:relaxed`"
105
+
106
+ property :service_group, String, default: "default",
107
+ description: " Passes `--group` with the specified service group to the hab command"
108
+
109
+ property :shutdown_timeout, Integer, default: 8,
110
+ description: "The timeout in seconds allowed during shutdown."
111
+
112
+ property :health_check_interval, Integer, default: 30,
113
+ description: "The interval (seconds) on which to run health checks."
114
+
115
+ property :remote_sup, String, default: "127.0.0.1:9632", desired_state: false,
116
+ description: "Address to a remote Supervisor's Control Gateway"
117
+
118
+ # Http port needed for querying/comparing current config value
119
+ property :remote_sup_http, String, default: "127.0.0.1:9631", desired_state: false,
120
+ description: "IP address and port used to communicate with the remote supervisor. If this value is invalid, the resource will update the supervisor configuration each time #{ChefUtils::Dist::Server::PRODUCT} runs."
121
+
122
+ property :gateway_auth_token, String, desired_state: false,
123
+ description: "Auth token for accessing the remote supervisor's http port."
124
+
125
+ property :update_condition, [Symbol, String], equal_to: [:latest, "latest", :'track-channel', "track-channel"], default: :latest, coerce: proc { |s| s.is_a?(String) ? s.to_sym : s },
126
+ description: "Passes `--update-condition` dictating when this service should updated. Defaults to `latest`. Options are `latest` or `track-channel` **_Note: This requires a minimum habitat version of 1.5.71_**
127
+ - `latest`: Runs the latest package that can be found in the configured channel and local packages.
128
+ - `track-channel`: Always run the package at the head of a given channel. This enables service rollback, where demoting a package from a channel will cause the package to rollback to an older version of the package. A ramification of enabling this condition is that packages that are newer than the package at the head of the channel are also uninstalled during a service rollback."
129
+
130
+ load_current_value do
131
+ service_details = get_service_details(service_name)
132
+
133
+ running service_up?(service_details)
134
+ loaded service_loaded?(service_details)
135
+
136
+ if loaded
137
+ service_name get_spec_identifier(service_details)
138
+ strategy get_update_strategy(service_details)
139
+ update_condition get_update_condition(service_details)
140
+ topology get_topology(service_details)
141
+ bldr_url get_builder_url(service_details)
142
+ channel get_channel(service_details)
143
+ bind get_binds(service_details)
144
+ binding_mode get_binding_mode(service_details)
145
+ service_group get_service_group(service_details)
146
+ shutdown_timeout get_shutdown_timeout(service_details)
147
+ health_check_interval get_health_check_interval(service_details)
148
+ end
149
+
150
+ Chef::Log.debug("service #{service_name} service name: #{service_name}")
151
+ Chef::Log.debug("service #{service_name} running state: #{running}")
152
+ Chef::Log.debug("service #{service_name} loaded state: #{loaded}")
153
+ Chef::Log.debug("service #{service_name} strategy: #{strategy}")
154
+ Chef::Log.debug("service #{service_name} update condition: #{update_condition}")
155
+ Chef::Log.debug("service #{service_name} topology: #{topology}")
156
+ Chef::Log.debug("service #{service_name} builder url: #{bldr_url}")
157
+ Chef::Log.debug("service #{service_name} channel: #{channel}")
158
+ Chef::Log.debug("service #{service_name} binds: #{bind}")
159
+ Chef::Log.debug("service #{service_name} binding mode: #{binding_mode}")
160
+ Chef::Log.debug("service #{service_name} service group: #{service_group}")
161
+ Chef::Log.debug("service #{service_name} shutdown timeout: #{shutdown_timeout}")
162
+ Chef::Log.debug("service #{service_name} health check interval: #{health_check_interval}")
163
+ end
164
+
165
+ # This method is defined here otherwise it isn't usable in the
166
+ # `load_current_value` method.
167
+ #
168
+ # It performs a check with TCPSocket to ensure that the HTTP API is
169
+ # available first. If it cannot connect, it assumes that the service
170
+ # is not running. It then attempts to reach the `/services` path of
171
+ # the API to get a list of services. If this fails for some reason,
172
+ # then it assumes the service is not running.
173
+ #
174
+ # Finally, it walks the services returned by the API to look for the
175
+ # service we're configuring. If it is "Up", then we know the service
176
+ # is running and fully operational according to Habitat. This is
177
+ # wrapped in a begin/rescue block because if the service isn't
178
+ # present and `sup_for_service_name` will be nil and we will get a
179
+ # NoMethodError.
180
+ #
181
+ def get_service_details(svc_name)
182
+ http_uri = "http://#{remote_sup_http}"
183
+
184
+ begin
185
+ TCPSocket.new(URI(http_uri).host, URI(http_uri).port).close
186
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
187
+ Chef::Log.debug("Could not connect to #{http_uri} to retrieve status for #{service_name}")
188
+ return false
189
+ end
190
+
191
+ begin
192
+ headers = {}
193
+ headers["Authorization"] = "Bearer #{gateway_auth_token}" if property_is_set?(:gateway_auth_token)
194
+ svcs = Chef::HTTP::SimpleJSON.new(http_uri).get("/services", headers)
195
+ rescue
196
+ Chef::Log.debug("Could not connect to #{http_uri}/services to retrieve status for #{service_name}")
197
+ return false
198
+ end
199
+
200
+ origin, name, _version, _release = svc_name.split("/")
201
+ svcs.find do |s|
202
+ s["pkg"]["origin"] == origin && s["pkg"]["name"] == name
203
+ end
204
+ end
205
+
206
+ def service_up?(service_details)
207
+ service_details["process"]["state"] == "up"
208
+ rescue
209
+ Chef::Log.debug("#{service_name} not found on the Habitat supervisor")
210
+ false
211
+ end
212
+
213
+ def service_loaded?(service_details)
214
+ if service_details
215
+ true
216
+ else
217
+ false
218
+ end
219
+ end
220
+
221
+ def get_spec_identifier(service_details)
222
+ service_details["spec_ident"]["spec_identifier"]
223
+ rescue
224
+ Chef::Log.debug("#{service_name} not found on the Habitat supervisor")
225
+ nil
226
+ end
227
+
228
+ def get_update_strategy(service_details)
229
+ service_details["update_strategy"].to_sym
230
+ rescue
231
+ Chef::Log.debug("Update Strategy for #{service_name} not found on Supervisor API")
232
+ "none"
233
+ end
234
+
235
+ def get_update_condition(service_details)
236
+ service_details["update_condition"].to_sym
237
+ rescue
238
+ Chef::Log.debug("Update condition #{service_name} not found on Supervisor API")
239
+ "latest"
240
+ end
241
+
242
+ def get_topology(service_details)
243
+ service_details["topology"].to_sym
244
+ rescue
245
+ Chef::Log.debug("Topology for #{service_name} not found on Supervisor API")
246
+ "standalone"
247
+ end
248
+
249
+ def get_builder_url(service_details)
250
+ service_details["bldr_url"]
251
+ rescue
252
+ Chef::Log.debug("Habitat Builder URL for #{service_name} not found on Supervisor API")
253
+ "https://bldr.habitat.sh"
254
+ end
255
+
256
+ def get_channel(service_details)
257
+ service_details["channel"].to_sym
258
+ rescue
259
+ Chef::Log.debug("Channel for #{service_name} not found on Supervisor API")
260
+ "stable"
261
+ end
262
+
263
+ def get_binds(service_details)
264
+ service_details["binds"]
265
+ rescue
266
+ Chef::Log.debug("Update Strategy for #{service_name} not found on Supervisor API")
267
+ []
268
+ end
269
+
270
+ def get_binding_mode(service_details)
271
+ service_details["binding_mode"].to_sym
272
+ rescue
273
+ Chef::Log.debug("Binding mode for #{service_name} not found on Supervisor API")
274
+ "strict"
275
+ end
276
+
277
+ def get_service_group(service_details)
278
+ service_details["service_group"].split(".").last
279
+ rescue
280
+ Chef::Log.debug("Service Group for #{service_name} not found on Supervisor API")
281
+ "default"
282
+ end
283
+
284
+ def get_shutdown_timeout(service_details)
285
+ service_details["pkg"]["shutdown_timeout"]
286
+ rescue
287
+ Chef::Log.debug("Shutdown Timeout for #{service_name} not found on Supervisor API")
288
+ 8
289
+ end
290
+
291
+ def get_health_check_interval(service_details)
292
+ service_details["health_check_interval"]["secs"]
293
+ rescue
294
+ Chef::Log.debug("Health Check Interval for #{service_name} not found on Supervisor API")
295
+ 30
296
+ end
297
+
298
+ action :load, description: "(default action) runs `hab service load` to load and start the specified application service" do
299
+ modified = false
300
+ converge_if_changed :service_name do
301
+ modified = true
302
+ end
303
+ converge_if_changed :strategy do
304
+ modified = true
305
+ end
306
+ converge_if_changed :update_condition do
307
+ modified = true
308
+ end
309
+ converge_if_changed :topology do
310
+ modified = true
311
+ end
312
+ converge_if_changed :bldr_url do
313
+ modified = true
314
+ end
315
+ converge_if_changed :channel do
316
+ modified = true
317
+ end
318
+ converge_if_changed :bind do
319
+ modified = true
320
+ end
321
+ converge_if_changed :binding_mode do
322
+ modified = true
323
+ end
324
+ converge_if_changed :service_group do
325
+ modified = true
326
+ end
327
+ converge_if_changed :shutdown_timeout do
328
+ modified = true
329
+ end
330
+ converge_if_changed :health_check_interval do
331
+ modified = true
332
+ end
333
+
334
+ options = svc_options
335
+ if current_resource.loaded && modified
336
+ Chef::Log.debug("Reloading #{current_resource.service_name} using --force due to parameter change")
337
+ options << "--force"
338
+ end
339
+
340
+ unless current_resource.loaded && !modified
341
+ execute "test" do
342
+ command "hab svc load #{new_resource.service_name} #{options.join(" ")}"
343
+ retry_delay 10
344
+ retries 5
345
+ end
346
+ end
347
+ end
348
+
349
+ action :unload, description: "runs `hab service unload` to unload and stop the specified application service" do
350
+ if current_resource.loaded
351
+ execute "hab svc unload #{new_resource.service_name} #{svc_options.join(" ")}"
352
+ wait_for_service_unloaded
353
+ end
354
+ end
355
+
356
+ action :start, description: "runs `hab service start` to start the specified application service" do
357
+ unless current_resource.loaded
358
+ Chef::Log.fatal("No service named #{new_resource.service_name} is loaded on the Habitat supervisor")
359
+ raise "No service named #{new_resource.service_name} is loaded on the Habitat supervisor"
360
+ end
361
+
362
+ execute "hab svc start #{new_resource.service_name} #{svc_options.join(" ")}" unless current_resource.running
363
+ end
364
+
365
+ action :stop, description: "runs `hab service stop` to stop the specified application service" do
366
+ unless current_resource.loaded
367
+ Chef::Log.fatal("No service named #{new_resource.service_name} is loaded on the Habitat supervisor")
368
+ raise "No service named #{new_resource.service_name} is loaded on the Habitat supervisor"
369
+ end
370
+
371
+ if current_resource.running
372
+ execute "hab svc stop #{new_resource.service_name} #{svc_options.join(" ")}"
373
+ wait_for_service_stopped
374
+ end
375
+ end
376
+
377
+ action :restart, description: "runs the `:stop` and then `:start` actions" do
378
+ action_stop
379
+ action_start
380
+ end
381
+
382
+ action :reload, description: "runs the `:unload` and then `:load` actions" do
383
+ action_unload
384
+ action_load
385
+ end
386
+
387
+ action_class do
388
+ def svc_options
389
+ opts = []
390
+
391
+ # certain options are only valid for specific `hab svc` subcommands.
392
+ case action
393
+ when :load
394
+ opts.push(*new_resource.bind.map { |b| "--bind #{b}" }) if new_resource.bind
395
+ opts << "--binding-mode #{new_resource.binding_mode}"
396
+ opts << "--url #{new_resource.bldr_url}" if new_resource.bldr_url
397
+ opts << "--channel #{new_resource.channel}" if new_resource.channel
398
+ opts << "--group #{new_resource.service_group}" if new_resource.service_group
399
+ opts << "--strategy #{new_resource.strategy}" if new_resource.strategy
400
+ opts << "--update-condition #{new_resource.update_condition}" if new_resource.update_condition
401
+ opts << "--topology #{new_resource.topology}" if new_resource.topology
402
+ opts << "--health-check-interval #{new_resource.health_check_interval}" if new_resource.health_check_interval
403
+ opts << "--shutdown-timeout #{new_resource.shutdown_timeout}" if new_resource.shutdown_timeout
404
+ when :unload, :stop
405
+ opts << "--shutdown-timeout #{new_resource.shutdown_timeout}" if new_resource.shutdown_timeout
406
+ end
407
+
408
+ opts << "--remote-sup #{new_resource.remote_sup}" if new_resource.remote_sup
409
+
410
+ opts.map(&:split).flatten.compact
411
+ end
412
+
413
+ def wait_for_service_unloaded
414
+ ruby_block "wait-for-service-unloaded" do
415
+ block do
416
+ raise "#{new_resource.service_name} still loaded" if service_loaded?(get_service_details(new_resource.service_name))
417
+ end
418
+ retries get_shutdown_timeout(new_resource.service_name) + 1
419
+ retry_delay 1
420
+ end
421
+
422
+ ruby_block "update current_resource" do
423
+ block do
424
+ current_resource.loaded = service_loaded?(get_service_details(new_resource.service_name))
425
+ end
426
+ action :nothing
427
+ subscribes :run, "ruby_block[wait-for-service-unloaded]", :immediately
428
+ end
429
+ end
430
+
431
+ def wait_for_service_stopped
432
+ ruby_block "wait-for-service-stopped" do
433
+ block do
434
+ raise "#{new_resource.service_name} still running" if service_up?(get_service_details(new_resource.service_name))
435
+ end
436
+ retries get_shutdown_timeout(new_resource.service_name) + 1
437
+ retry_delay 1
438
+
439
+ ruby_block "update current_resource" do
440
+ block do
441
+ current_resource.running = service_up?(get_service_details(new_resource.service_name))
442
+ end
443
+ action :nothing
444
+ subscribes :run, "ruby_block[wait-for-service-stopped]", :immediately
445
+ end
446
+ end
447
+ end
448
+ end
449
+ end
450
+ end
451
+ end