diff --git a/.expeditor/habitat-test.pipeline.yml b/.expeditor/habitat-test.pipeline.yml index da3af8d02..afc6244e4 100644 --- a/.expeditor/habitat-test.pipeline.yml +++ b/.expeditor/habitat-test.pipeline.yml @@ -32,3 +32,4 @@ steps: - FORCE_FFI_YAJL=ext - EXPIRE_CACHE=true - CHEF_LICENSE=accept-no-persist + - CHEF_LICENSE_SERVER=http://hosted-license-service-lb-8000-606952349.us-west-2.elb.amazonaws.com:8000/ diff --git a/.expeditor/verify.pipeline.yml b/.expeditor/verify.pipeline.yml index 8b6d77356..f900c5548 100644 --- a/.expeditor/verify.pipeline.yml +++ b/.expeditor/verify.pipeline.yml @@ -17,6 +17,9 @@ steps: executor: docker: image: ruby:3.1 + environment: + - CHEF_LICENSE=accept-no-persist + - CHEF_LICENSE_SERVER=http://hosted-license-service-lb-8000-606952349.us-west-2.elb.amazonaws.com:8000/ - label: run-specs-ruby-3.3 command: @@ -25,6 +28,9 @@ steps: executor: docker: image: ruby:3.3 + environment: + - CHEF_LICENSE=accept-no-persist + - CHEF_LICENSE_SERVER=http://hosted-license-service-lb-8000-606952349.us-west-2.elb.amazonaws.com:8000/ - label: run-specs-windows-ruby-3.1 command: @@ -40,6 +46,7 @@ steps: - FORCE_FFI_YAJL=ext - EXPIRE_CACHE=true - CHEF_LICENSE=accept-no-persist + - CHEF_LICENSE_SERVER=http://hosted-license-service-lb-8000-606952349.us-west-2.elb.amazonaws.com:8000/ - label: run-specs-windows-ruby-3.3 command: @@ -55,3 +62,4 @@ steps: - FORCE_FFI_YAJL=ext - EXPIRE_CACHE=true - CHEF_LICENSE=accept-no-persist + - CHEF_LICENSE_SERVER=http://hosted-license-service-lb-8000-606952349.us-west-2.elb.amazonaws.com:8000/ diff --git a/kitchen.dokken.yml b/kitchen.dokken.yml index e3063cb50..2d99e701b 100644 --- a/kitchen.dokken.yml +++ b/kitchen.dokken.yml @@ -6,6 +6,8 @@ driver: provisioner: name: dokken chef_license: accept-no-persist + chef_license_server: + - http://hosted-license-service-lb-8000-606952349.us-west-2.elb.amazonaws.com:8000/ transport: name: dokken diff --git a/lib/kitchen/cli.rb b/lib/kitchen/cli.rb index 73d098d1f..8b9fe5446 100644 --- a/lib/kitchen/cli.rb +++ b/lib/kitchen/cli.rb @@ -301,6 +301,11 @@ def console perform("console", "console") end + desc "license", "Manage the chef licenses" + def license(*args) + perform("license", "license", args) + end + register Kitchen::Generator::Init, "init", "init", "Adds some configuration to your cookbook so Kitchen can rock" long_desc <<-D, for: "init" diff --git a/lib/kitchen/command/license.rb b/lib/kitchen/command/license.rb new file mode 100644 index 000000000..9f0b31abb --- /dev/null +++ b/lib/kitchen/command/license.rb @@ -0,0 +1,22 @@ +require_relative "../command" +require "kitchen/licensing/base" + +module Kitchen + module Command + # Command to manage the licenses + class License < Kitchen::Command::Base + def call + case args[0] + when "list" + ChefLicensing.list_license_keys_info + when "add" + ChefLicensing.add_license + else + ChefLicensing.fetch_and_persist.each do |key| + puts "License_key: #{key}" + end + end + end + end + end +end diff --git a/lib/kitchen/licensing/base.rb b/lib/kitchen/licensing/base.rb new file mode 100644 index 000000000..7ac3709dd --- /dev/null +++ b/lib/kitchen/licensing/base.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# Copyright:: Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require_relative "config" +require "chef-licensing" +require "faraday_middleware" + +module Kitchen + module Licensing + class Base + + OMNITRUCK_URLS = { + "free" => "https://chefdownload-trial.chef.io", + "trial" => "https://chefdownload-trial.chef.io", + "commercial" => "https://chefdownload-commerical.chef.io", + }.freeze + + class << self + def get_license_keys + keys = ChefLicensing.license_keys + raise ChefLicensing::InvalidLicense, "A valid license is required to perform this action." if keys.blank? + + client = get_license_client(keys) + + [keys.last, client.license_type, install_sh_url(client.license_type, keys)] + end + + def get_license_client(keys) + ChefLicensing::Api::Client.info(license_keys: keys) + end + + def install_sh_url(type, keys, ext = "sh") + OMNITRUCK_URLS[type] + "/install.#{ext}?license_id=#{keys.join(",")}" + end + end + end + end +end \ No newline at end of file diff --git a/lib/kitchen/licensing/config.rb b/lib/kitchen/licensing/config.rb new file mode 100644 index 000000000..b4cc5d3d1 --- /dev/null +++ b/lib/kitchen/licensing/config.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# Copyright:: Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef-licensing" + +ChefLicensing.configure do |config| + config.chef_product_name = "Test Kitchen" + config.chef_entitlement_id = "x6f3bc76-a94f-4b6c-bc97-4b7ed2b045c0" + config.chef_executable_name = "kitchen" + config.license_server_url = "https://services.chef.io/licensing" + # config.license_server_url = "https://licensing-acceptance.chef.co/License" +end diff --git a/lib/kitchen/provisioner/base.rb b/lib/kitchen/provisioner/base.rb index 894944392..7d6c621ed 100644 --- a/lib/kitchen/provisioner/base.rb +++ b/lib/kitchen/provisioner/base.rb @@ -192,7 +192,7 @@ def sandbox_dirs # will persist after the process terminates. In other words, cleanup is # explicit. This method is safe to call multiple times. def cleanup_sandbox - return if sandbox_path.nil? + return if @sandbox_path.nil? debug("Cleaning up local sandbox in #{sandbox_path}") FileUtils.rmtree(sandbox_path) diff --git a/lib/kitchen/provisioner/chef_base.rb b/lib/kitchen/provisioner/chef_base.rb index 742e473c2..5c41fe0e6 100644 --- a/lib/kitchen/provisioner/chef_base.rb +++ b/lib/kitchen/provisioner/chef_base.rb @@ -345,7 +345,7 @@ def install_options add_omnibus_directory_option if instance.driver.cache_directory project = /\s*-P (\w+)\s*/.match(config[:chef_omnibus_install_options]) { - omnibus_url: config[:chef_omnibus_url], + omnibus_url: config[:install_sh_url] || config[:chef_omnibus_url], project: project.nil? ? nil : project[1], install_flags: config[:chef_omnibus_install_options], sudo_command:, @@ -544,6 +544,15 @@ def script_for_product prox.delete_if { |p| %i{https_proxy ftp_proxy no_proxy}.include?(p) } if powershell_shell? end opts[:install_command_options].merge!(proxies) + + if config[:install_sh_url] || config[:install_ps1_url] + opts[:new_omnibus_download_url] = if powershell_shell? + config[:install_ps1_url] + else + config[:install_sh_url] + end + + end end) config[:chef_omnibus_root] = installer.root if powershell_shell? @@ -580,8 +589,10 @@ def install_from_file(command) # @api private def script_for_omnibus_version require "mixlib/install/script_generator" + opts = install_options + opts[:omnibus_url] = config[:install_sh_url] if config[:install_sh_url] installer = Mixlib::Install::ScriptGenerator.new( - config[:require_chef_omnibus], powershell_shell?, install_options + config[:require_chef_omnibus], powershell_shell?, opts ) config[:chef_omnibus_root] = installer.root sudo(installer.install_command) diff --git a/lib/kitchen/provisioner/chef_infra.rb b/lib/kitchen/provisioner/chef_infra.rb index e8c4c2240..2eb8dcc74 100644 --- a/lib/kitchen/provisioner/chef_infra.rb +++ b/lib/kitchen/provisioner/chef_infra.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # # Author:: Fletcher Nichol () # @@ -16,6 +17,7 @@ # limitations under the License. require_relative "chef_base" +require "kitchen/licensing/base" module Kitchen module Provisioner @@ -23,6 +25,7 @@ module Provisioner # # @author Fletcher Nichol class ChefInfra < ChefBase + kitchen_provisioner_api_version 2 plugin_version Kitchen::VERSION @@ -32,6 +35,8 @@ class ChefInfra < ChefBase default_config :json_attributes, true default_config :chef_zero_host, nil default_config :chef_zero_port, 8889 + default_config :chef_license_key, nil + default_config :chef_license_server, [] default_config :chef_client_path do |provisioner| provisioner @@ -51,12 +56,47 @@ def create_sandbox prepare_config_rb end + def prepare_command + nonce = Base64.encode64(SecureRandom.random_bytes(16)).strip + timestamp = Time.now.utc.to_i.to_s + + message = "#{nonce}:#{timestamp}" + signature = OpenSSL::HMAC.hexdigest("SHA256", context_key, message) + + file_content = "nonce:#{nonce}\ntimestamp:#{timestamp}\nsignature:#{signature}" + file_location = config[:root_path] + "/#{context_key}" + + sudo("echo '#{file_content}' > #{file_location}") + end + def run_command - cmd = "#{sudo(config[:chef_client_path])} --local-mode".tap { |str| str.insert(0, "& ") if powershell_shell? } + cmd = "#{context_env_command} #{sudo(config[:chef_client_path])} --local-mode --chef-license-key=#{config[:chef_license_key]} " chef_cmd(cmd) end + def check_license + super + + info("Fetching the Chef license key") + unless config[:chef_license_server].nil? || config[:chef_license_server].empty? + ENV["CHEF_LICENSE_SERVER"] = config[:chef_license_server].join(",") + end + + key, type, install_sh_url = if config[:chef_license_key].nil? + Licensing::Base.get_license_keys + else + key = config[:chef_license_key] + client = Licensing::Base.get_license_client([key]) + + [key, client.license_type, Licensing::Base.install_sh_url(client.license_type, [key])] + end + + config[:chef_license_key] = key + config[:install_sh_url] = install_sh_url + config[:chef_license_type] = type + end + private # Adds optional flags to a chef-client command, depending on @@ -162,6 +202,18 @@ def shim_command def supports_policyfile? true end + + def context_key + @context_key ||= SecureRandom.hex(16) + end + + def context_env_command + if powershell_shell? + "$env:TEST_KITCHEN_CONTEXT = '#{context_key}'; &" + else + "export TEST_KITCHEN_CONTEXT=#{context_key};" + end + end end end end diff --git a/test-kitchen.gemspec b/test-kitchen.gemspec index 4cfdbef05..19bf72b3c 100644 --- a/test-kitchen.gemspec +++ b/test-kitchen.gemspec @@ -37,4 +37,5 @@ Gem::Specification.new do |gem| # Required to run the Chef provisioner local license check for remote systems # TK is not under Chef EULA gem.add_dependency "license-acceptance", ">= 1.0.11", "< 3.0" # pinning until we can confirm 3+ works + gem.add_dependency "chef-licensing", "~> 1.0" end