inspec-core 3.7.1 → 3.7.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inspec/config.rb +12 -0
  3. data/lib/inspec/shell.rb +2 -15
  4. data/lib/inspec/version.rb +1 -1
  5. data/lib/plugins/inspec-habitat/Berksfile +5 -0
  6. data/lib/plugins/inspec-habitat/README.md +150 -0
  7. data/lib/plugins/inspec-habitat/kitchen.yml +28 -0
  8. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +9 -9
  9. data/lib/plugins/inspec-habitat/lib/inspec-habitat/profile.rb +164 -280
  10. data/lib/plugins/inspec-habitat/templates/habitat/config/inspec_exec_config.json.erb +25 -0
  11. data/lib/plugins/inspec-habitat/templates/habitat/default.toml.erb +9 -0
  12. data/lib/plugins/inspec-habitat/templates/habitat/hooks/run.erb +32 -0
  13. data/lib/plugins/inspec-habitat/templates/habitat/plan.sh.erb +85 -0
  14. data/lib/plugins/inspec-habitat/test/cookbooks/inspec_habitat_fixture/Berksfile +2 -0
  15. data/lib/plugins/inspec-habitat/test/cookbooks/inspec_habitat_fixture/README.md +3 -0
  16. data/lib/plugins/inspec-habitat/test/cookbooks/inspec_habitat_fixture/files/hab_setup.exp +28 -0
  17. data/lib/plugins/inspec-habitat/test/cookbooks/inspec_habitat_fixture/metadata.rb +9 -0
  18. data/lib/plugins/inspec-habitat/test/cookbooks/inspec_habitat_fixture/recipes/default.rb +61 -0
  19. data/lib/plugins/inspec-habitat/test/functional/inspec_habitat_test.rb +38 -0
  20. data/lib/plugins/inspec-habitat/test/integration/default/inspec_habitat/README.md +3 -0
  21. data/lib/plugins/inspec-habitat/test/integration/default/inspec_habitat/controls/inspec_habitat.rb +40 -0
  22. data/lib/plugins/inspec-habitat/test/integration/default/inspec_habitat/inspec.yml +10 -0
  23. data/lib/plugins/inspec-habitat/test/support/example_profile/README.md +3 -0
  24. data/lib/plugins/inspec-habitat/test/support/example_profile/controls/example.rb +7 -0
  25. data/lib/plugins/inspec-habitat/test/support/example_profile/inspec.yml +10 -0
  26. data/lib/plugins/inspec-habitat/test/unit/profile_test.rb +188 -132
  27. data/lib/plugins/inspec-init/test/functional/inspec_init_profile_test.rb +12 -0
  28. data/lib/resources/aide_conf.rb +2 -2
  29. data/lib/resources/apache.rb +2 -2
  30. data/lib/resources/apache_conf.rb +2 -2
  31. data/lib/resources/apt.rb +2 -2
  32. data/lib/resources/audit_policy.rb +2 -2
  33. data/lib/resources/auditd.rb +2 -2
  34. data/lib/resources/auditd_conf.rb +2 -2
  35. data/lib/resources/bash.rb +2 -2
  36. data/lib/resources/bond.rb +2 -2
  37. data/lib/resources/bridge.rb +2 -2
  38. data/lib/resources/chocolatey_package.rb +2 -2
  39. data/lib/resources/command.rb +2 -2
  40. data/lib/resources/cpan.rb +2 -2
  41. data/lib/resources/cran.rb +2 -2
  42. data/lib/resources/crontab.rb +2 -2
  43. data/lib/resources/csv.rb +2 -2
  44. data/lib/resources/dh_params.rb +2 -2
  45. data/lib/resources/directory.rb +2 -2
  46. data/lib/resources/docker.rb +2 -2
  47. data/lib/resources/docker_container.rb +2 -2
  48. data/lib/resources/docker_image.rb +2 -2
  49. data/lib/resources/docker_plugin.rb +2 -2
  50. data/lib/resources/docker_service.rb +2 -2
  51. data/lib/resources/elasticsearch.rb +2 -2
  52. data/lib/resources/etc_fstab.rb +2 -2
  53. data/lib/resources/etc_group.rb +2 -2
  54. data/lib/resources/etc_hosts.rb +2 -2
  55. data/lib/resources/etc_hosts_allow_deny.rb +4 -4
  56. data/lib/resources/file.rb +2 -2
  57. data/lib/resources/filesystem.rb +2 -2
  58. data/lib/resources/firewalld.rb +2 -2
  59. data/lib/resources/gem.rb +2 -2
  60. data/lib/resources/groups.rb +4 -4
  61. data/lib/resources/grub_conf.rb +2 -2
  62. data/lib/resources/host.rb +2 -2
  63. data/lib/resources/http.rb +25 -5
  64. data/lib/resources/iis_app.rb +2 -2
  65. data/lib/resources/iis_app_pool.rb +6 -3
  66. data/lib/resources/iis_site.rb +4 -4
  67. data/lib/resources/inetd_conf.rb +2 -2
  68. data/lib/resources/ini.rb +2 -2
  69. data/lib/resources/interface.rb +2 -2
  70. data/lib/resources/iptables.rb +2 -2
  71. data/lib/resources/json.rb +2 -3
  72. data/lib/resources/kernel_module.rb +17 -18
  73. data/lib/resources/kernel_parameter.rb +2 -2
  74. data/lib/resources/key_rsa.rb +2 -2
  75. data/lib/resources/ksh.rb +2 -2
  76. data/lib/resources/limits_conf.rb +2 -2
  77. data/lib/resources/login_def.rb +2 -2
  78. data/lib/resources/mount.rb +2 -2
  79. data/lib/resources/mssql_session.rb +2 -2
  80. data/lib/resources/mysql_conf.rb +2 -2
  81. data/lib/resources/mysql_session.rb +2 -2
  82. data/lib/resources/nginx.rb +2 -2
  83. data/lib/resources/nginx_conf.rb +2 -2
  84. data/lib/resources/npm.rb +2 -2
  85. data/lib/resources/ntp_conf.rb +2 -2
  86. data/lib/resources/oneget.rb +2 -2
  87. data/lib/resources/oracledb_session.rb +2 -2
  88. data/lib/resources/os.rb +2 -2
  89. data/lib/resources/os_env.rb +2 -2
  90. data/lib/resources/package.rb +2 -2
  91. data/lib/resources/packages.rb +2 -2
  92. data/lib/resources/parse_config.rb +4 -4
  93. data/lib/resources/passwd.rb +2 -2
  94. data/lib/resources/pip.rb +2 -2
  95. data/lib/resources/platform.rb +2 -2
  96. data/lib/resources/port.rb +2 -2
  97. data/lib/resources/postgres_conf.rb +2 -2
  98. data/lib/resources/postgres_hba_conf.rb +2 -2
  99. data/lib/resources/postgres_ident_conf.rb +2 -2
  100. data/lib/resources/postgres_session.rb +2 -2
  101. data/lib/resources/powershell.rb +2 -2
  102. data/lib/resources/processes.rb +2 -2
  103. data/lib/resources/rabbitmq_conf.rb +2 -2
  104. data/lib/resources/registry_key.rb +2 -2
  105. data/lib/resources/security_identifier.rb +2 -2
  106. data/lib/resources/security_policy.rb +2 -2
  107. data/lib/resources/service.rb +14 -14
  108. data/lib/resources/shadow.rb +2 -2
  109. data/lib/resources/ssh_conf.rb +4 -4
  110. data/lib/resources/ssl.rb +2 -2
  111. data/lib/resources/sys_info.rb +2 -2
  112. data/lib/resources/toml.rb +2 -2
  113. data/lib/resources/users.rb +4 -4
  114. data/lib/resources/vbscript.rb +2 -2
  115. data/lib/resources/virtualization.rb +2 -2
  116. data/lib/resources/windows_feature.rb +2 -2
  117. data/lib/resources/windows_hotfix.rb +2 -2
  118. data/lib/resources/windows_task.rb +2 -2
  119. data/lib/resources/wmi.rb +2 -2
  120. data/lib/resources/x509_certificate.rb +2 -2
  121. data/lib/resources/xinetd.rb +2 -2
  122. data/lib/resources/xml.rb +2 -2
  123. data/lib/resources/yaml.rb +2 -2
  124. data/lib/resources/yum.rb +2 -2
  125. data/lib/resources/zfs_dataset.rb +2 -2
  126. data/lib/resources/zfs_pool.rb +2 -2
  127. metadata +36 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7442d912c4bfc6a39ee598dc8a15da983d73e368a33219870849147466e0da1
4
- data.tar.gz: 3d7dee5115625ed08a8af8a8ba883324862232cb728e50b4d0ce34beb6d80fde
3
+ metadata.gz: cc2e24a1d0645cf7af9be7f62ba7552950180e2691f40202035a9829d4281f0d
4
+ data.tar.gz: 97c73d5944fc426dc72ba45db473dbd4bf0688b8da2119f64aced481a4b64989
5
5
  SHA512:
6
- metadata.gz: 6b92f9ef32f1a79171a4d28fd4a1de61822489a22500e6578abbde8f475b574441730fbfd8bc02f6a2545a65773132e698a21ec7c65122340ad83400ab7b3c95
7
- data.tar.gz: 4e9daea0b2fb3a75c3c9d50058b1b797dbae40cae81a8aee4d196f21b036d72603552f8bb888044f1fd47a1ce50deb7977221805ac0a7d86d8839a2a3d590e9b
6
+ metadata.gz: 4ac5832d0b35aead187e3c262a4febfcf4133d32296ac0ebf4a2532e816415410530d4d9ec146e516b66332708f926c035a530b4fbeec4b3bbfffaecb6b4c263
7
+ data.tar.gz: e6db3be4591461e56cc43f0c1258e43bf7fd625d2e7c24497b22c89c4b7be02272d327aa04ea143252ba5046911da3f12a8e04b71a9c0eb3c6c850bff7489530
data/lib/inspec/config.rb CHANGED
@@ -30,6 +30,17 @@ module Inspec
30
30
  Inspec::Config.new({ backend: :mock }.merge(opts), StringIO.new('{}'))
31
31
  end
32
32
 
33
+ # Use this to get a cached version of the config. This prevents you from
34
+ # being required to pass it around everywhere.
35
+ def self.cached
36
+ @cached_config
37
+ end
38
+
39
+ def self.cached=(cfg)
40
+ @cached_config ||= cfg
41
+ end
42
+
43
+ # This gets called when the first config is created.
33
44
  def initialize(cli_opts = {}, cfg_io = nil, command_name = nil)
34
45
  @command_name = command_name || (ARGV.empty? ? nil : ARGV[0].to_sym)
35
46
  @defaults = Defaults.for_command(@command_name)
@@ -40,6 +51,7 @@ module Inspec
40
51
 
41
52
  @merged_options = merge_options
42
53
  @final_options = finalize_options
54
+ self.class.cached = self
43
55
  end
44
56
 
45
57
  def diagnose
data/lib/inspec/shell.rb CHANGED
@@ -80,19 +80,6 @@ module Inspec
80
80
  "\e[1m\e[39m#{x}\e[0m"
81
81
  end
82
82
 
83
- def print_example(example)
84
- # determine min whitespace that can be removed
85
- min = nil
86
- example.lines.each do |line|
87
- if !line.strip.empty? # ignore empty lines
88
- line_whitespace = line.length - line.lstrip.length
89
- min = line_whitespace if min.nil? || line_whitespace < min
90
- end
91
- end
92
- # remove whitespace from each line
93
- example.gsub(/\n\s{#{min}}/, "\n")
94
- end
95
-
96
83
  def intro
97
84
  puts 'Welcome to the interactive InSpec Shell'
98
85
  puts "To find out how to use it, type: #{mark 'help'}"
@@ -142,8 +129,8 @@ module Inspec
142
129
  end
143
130
 
144
131
  unless topic_info.example.nil?
145
- info += "#{mark 'Example:'}\n"
146
- info += "#{print_example(topic_info.example)}\n\n"
132
+ info += "#{mark 'Example:'}\n\n"
133
+ info += "#{topic_info.example}\n\n"
147
134
  end
148
135
 
149
136
  info += "#{mark 'Web Reference:'}\n\n"
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '3.7.1'
7
+ VERSION = '3.7.11'
8
8
  end
@@ -0,0 +1,5 @@
1
+ source 'https://supermarket.chef.io'
2
+
3
+ group :integration do
4
+ cookbook 'inspec_habitat_fixture', path: 'test/cookbooks/inspec_habitat_fixture/'
5
+ end
@@ -0,0 +1,150 @@
1
+ # InSpec Habitat Plugin
2
+
3
+ ## Summary
4
+
5
+ This plugin allows you to do the following:
6
+ 1. Add Habitat configuration to a profile
7
+ 2. Create/Upload a Habitat package from an InSpec profile
8
+
9
+ Creating a [Habitat](https://www.habitat.sh/) package from an InSpec profile
10
+ allows you to execute that profile as a service (via a Habitat Supervisor) on
11
+ any Linux based platform.
12
+
13
+ When running as a service, an InSpec report will be created in JSON format (by
14
+ default at `/hab/svc/YOUR_SERVICE/logs/inspec_last_run.json`). Additionally, a
15
+ log of the last run will be located at
16
+ `/hab/svc/YOUR_SERVICE/logs/inspec_log.txt` and CLI output is viewable in
17
+ the Supervisor logs by default. You can also configure this service to report
18
+ to [Chef Automate](https://www.chef.io/automate/).
19
+
20
+ See below for usage instructions.
21
+
22
+ ## Plugin Usage
23
+
24
+ ### Adding Habitat Configuration to an InSpec Profile
25
+
26
+ Run the following command:
27
+
28
+ ```
29
+ inspec habitat profile setup PATH
30
+ ```
31
+
32
+ This will create the following files:
33
+ - habitat/plan.sh (Provides build time instructions to Habitat)
34
+ - habitat/default.toml (Used to configure the running Habitat service)
35
+ - habitat/hooks/run (Shell script to execute this profile as a service)
36
+ - habitat/config/inspec_exec_config.json (JSON for `inspec exec` CLI options)
37
+
38
+ ### Creating a Habitat Package
39
+
40
+ > This command requires Habitat to be installed and configured. For instructions
41
+ on how to do that see [here](https://www.habitat.sh/docs/install-habitat/).
42
+
43
+ Run the following command:
44
+
45
+ ```
46
+ inspec habitat profile create PATH
47
+ ```
48
+
49
+ This command will:
50
+ - Create a Habitat artifact (`.hart` file).
51
+
52
+ > NOTE: If you are fetching packages from Chef Automate see
53
+ [below](#Integrating-with-Chef-Automate).
54
+
55
+ ### Uploading a Habitat Package
56
+
57
+ > This command requires Habitat to be installed and configured. For instructions
58
+ on how to do that see [here](https://www.habitat.sh/docs/install-habitat/).
59
+
60
+ Run the following command:
61
+
62
+ ```
63
+ inspec habitat profile upload PATH
64
+ ```
65
+
66
+ This command will:
67
+ - Create a Habitat artifact (`.hart` file).
68
+ - Upload the Habitat artifact to [bldr.habitat.sh](bldr.habitat.sh).
69
+
70
+ > NOTE: If you are fetching packages from Chef Automate see
71
+ [below](#Integrating-with-Chef-Automate).
72
+
73
+ ## Habitat Package Usage
74
+
75
+ > This command requires Habitat to be installed and configured. For instructions
76
+ on how to do that see [here](https://www.habitat.sh/docs/install-habitat/).
77
+
78
+ General usage instructions for using Habitat packages can be found
79
+ [here](https://www.habitat.sh/docs/using-habitat/#Using-Habitat-Packages).
80
+
81
+ Installing the package from a HART file:
82
+
83
+ ```
84
+ # See Habitat docs for more info. The below is for testing only.
85
+ hab pkg install PATH_TO_CREATED_HART_FILE
86
+ hab sup run YOUR_ORIGIN/inspec-profile-YOUR_PROFILE_NAME
87
+ ```
88
+
89
+ Installing the package from the Public Builder Depot:
90
+
91
+ ```
92
+ # See Habitat docs for more info. The below is for testing only.
93
+ hab pkg install YOUR_ORIGIN/inspec-profile-YOUR_PROFILE_NAME
94
+ hab sup run YOUR_ORIGIN/inspec-profile-YOUR_PROFILE_NAME
95
+ ```
96
+
97
+ ## Integrating with Chef Automate
98
+
99
+ ### Fetching Profiles from Chef Automate During Build
100
+
101
+ Fetching profiles from Chef Automate requires authentication.
102
+
103
+ Run the following commands prior to creating/uploading your Habitat package:
104
+
105
+ ```
106
+ # Remove -k if you are not using a self-signed certificate
107
+ inspec compliance login -k --user USER --token API_TOKEN https://AUTOMATE_FQDN
108
+ export HAB_STUDIO_SECRET_COMPLIANCE_CREDS=$(cat ~/.inspec/compliance/config.json)
109
+ ```
110
+
111
+ ### Sending InSpec Reports to Chef Automate
112
+
113
+ After running your Habitat package as a service you can configure it to report
114
+ to Chef Automate via a
115
+ [configuration update](https://www.habitat.sh/docs/using-habitat/#config-updates).
116
+
117
+ For example, create a TOML file (config.toml) that matches the below:
118
+
119
+ ```
120
+ [automate]
121
+ url = 'https://chef-automate.test'
122
+ token = 'TOKEN'
123
+ user = 'admin'
124
+ ```
125
+
126
+ Then apply it like so:
127
+
128
+ ```
129
+ # The '1' here is the config version (increment this with each change)
130
+ hab config apply inspec-profile-PROFILE_NAME.default 1 /path/to/config.toml
131
+ ```
132
+
133
+ This will apply the configuration to all services in the service group. For
134
+ more info on service groups see the
135
+ [Habitat docs](https://www.habitat.sh/docs/using-habitat/#service-groups)
136
+
137
+ ## Testing
138
+
139
+ Lint, unit, and functional tests are ran from the root of the InSpec source:
140
+
141
+ ```
142
+ bundle exec rake test
143
+ ```
144
+
145
+ To execute the integration tests (Test Kitchen + Vagrant + VirtualBox) run the
146
+ following from the directory containing this README.md:
147
+
148
+ ```
149
+ bundle exec kitchen test
150
+ ```
@@ -0,0 +1,28 @@
1
+ ---
2
+ driver:
3
+ name: vagrant
4
+
5
+ provisioner:
6
+ name: chef_solo
7
+ sudo: true
8
+
9
+ verifier:
10
+ name: inspec
11
+
12
+ platforms:
13
+ - name: ubuntu-18.04
14
+
15
+ lifecycle:
16
+ # Build the InSpec gem so it is available to install during `kitchen converge`
17
+ pre_create:
18
+ - cd ../../../ && gem build inspec.gemspec
19
+ - mv ../../../inspec-*.gem test/cookbooks/inspec_habitat_fixture/files/inspec-local.gem
20
+ post_converge:
21
+ - local: sleep 10 # Wait for Habitat to load/run hab service before `verify`
22
+
23
+ suites:
24
+ - name: default
25
+ run_list:
26
+ - recipe[inspec_habitat_fixture]
27
+ attributes:
28
+
@@ -10,21 +10,21 @@ module InspecPlugins
10
10
  "#{basename} habitat profile #{command.usage}"
11
11
  end
12
12
 
13
- desc 'create PATH', 'Create a one-time Habitat artifact for the profile found at PATH'
13
+ desc 'create PATH', 'Create a Habitat artifact for the profile found at PATH'
14
14
  option :output_dir, type: :string, required: false,
15
- desc: 'Directory in which to save the generated Habitat artifact. Default: current directory'
16
- def create(path)
17
- InspecPlugins::Habitat::Profile.create(path, options)
15
+ desc: 'Output directory for the Habitat artifact. Default: current directory'
16
+ def create(path = '.')
17
+ InspecPlugins::Habitat::Profile.new(path, options).create
18
18
  end
19
19
 
20
20
  desc 'setup PATH', 'Configure the profile at PATH for Habitat, including a plan and hooks'
21
- def setup(path)
22
- InspecPlugins::Habitat::Profile.setup(path)
21
+ def setup(path = '.')
22
+ InspecPlugins::Habitat::Profile.new(path, options).setup
23
23
  end
24
24
 
25
- desc 'upload PATH', 'Create a one-time Habitat artifact for the profile found at PATH, and upload it to a Habitat Depot'
26
- def upload(path)
27
- InspecPlugins::Habitat::Profile.upload(path, options)
25
+ desc 'upload PATH', 'Create then upload a Habitat artifact for the profile found at PATH to the Habitat Builder Depot'
26
+ def upload(path = '.')
27
+ InspecPlugins::Habitat::Profile.new(path, options).upload
28
28
  end
29
29
  end
30
30
 
@@ -1,238 +1,226 @@
1
1
  # encoding: utf-8
2
- # author: Adam Leff
3
2
 
4
3
  require 'inspec/profile_vendor'
5
4
  require 'mixlib/shellout'
6
5
  require 'tomlrb'
6
+ require 'ostruct'
7
7
 
8
8
  module InspecPlugins
9
9
  module Habitat
10
10
  class Profile
11
- attr_reader :options, :path, :profile
12
-
13
- def self.create(path, options = {})
14
- creator = new(path, options)
15
- hart_file = creator.create
16
- creator.copy(hart_file)
17
- ensure
18
- creator.delete_work_dir
19
- end
20
-
21
- def self.setup(path)
22
- new(path).setup
23
- end
24
-
25
- def self.upload(path, options = {})
26
- uploader = new(path, options)
27
- uploader.upload
28
- ensure
29
- uploader.delete_work_dir
30
- end
31
-
11
+ attr_reader :logger
32
12
  def initialize(path, options = {})
33
13
  @path = path
34
14
  @options = options
35
- @cli_config = nil
36
-
37
- log_level = options.fetch('log_level', 'info')
38
- @log = Inspec::Log
39
- @log.level(log_level.to_sym)
15
+ @logger = Inspec::Log
16
+ logger.level(options.fetch(:log_level, 'info').to_sym)
40
17
  end
41
18
 
42
19
  def create
43
- @log.info("Creating a Habitat artifact for profile: #{path}")
44
-
45
- validate_habitat_installed
46
- validate_habitat_origin
47
- create_profile_object
48
- verify_profile
49
- vendor_profile_dependencies
50
- copy_profile_to_work_dir
51
- create_habitat_directories(work_dir)
52
- create_plan(work_dir)
53
- create_run_hook(work_dir)
54
- create_default_config(work_dir)
55
-
56
- # returns the path to the .hart file in the work directory
57
- build_hart
20
+ logger.info("Creating a Habitat artifact for '#{@path}'...")
21
+
22
+ # Need to create working directory first so `ensure` doesn't error
23
+ working_dir = create_working_dir
24
+
25
+ habitat_config = read_habitat_config
26
+ verify_habitat_setup(habitat_config)
27
+
28
+ output_dir = @options[:output_dir] || Dir.pwd
29
+ unless File.directory?(output_dir)
30
+ exit_with_error("Output directory #{output_dir} is not a directory " \
31
+ 'or does not exist.')
32
+ end
33
+
34
+ duplicated_profile = duplicate_profile(@path, working_dir)
35
+ prepare_profile!(duplicated_profile)
36
+
37
+ hart_file = build_hart(working_dir, habitat_config)
38
+
39
+ logger.debug("Copying artifact to #{output_dir}...")
40
+ destination = File.join(output_dir, File.basename(hart_file))
41
+ FileUtils.cp(hart_file, destination)
42
+
43
+ logger.info("Habitat artifact '#{@destination}' created.")
44
+ destination
58
45
  rescue => e
59
- @log.debug(e.backtrace.join("\n"))
60
- exit_with_error(
61
- 'Unable to generate Habitat artifact.',
62
- "#{e.class} -- #{e.message}",
63
- )
46
+ logger.debug(e.backtrace.join("\n"))
47
+ exit_with_error('Unable to create Habitat artifact.')
48
+ ensure
49
+ if Dir.exist?(working_dir)
50
+ logger.debug("Deleting working directory #{working_dir}")
51
+ FileUtils.rm_rf(working_dir)
52
+ end
64
53
  end
65
54
 
66
- def copy(hart_file)
67
- validate_output_dir
55
+ def setup(profile = profile_from_path(@path))
56
+ path = profile.root_path
57
+ logger.debug("Setting up #{path} for Habitat...")
58
+
59
+ plan_file = File.join(path, 'habitat', 'plan.sh')
60
+ logger.info("Generating Habitat plan at #{plan_file}...")
61
+ vars = {
62
+ profile: profile,
63
+ habitat_origin: read_habitat_config['origin'],
64
+ }
65
+ create_file_from_template(plan_file, 'plan.sh.erb', vars)
68
66
 
69
- @log.info("Copying artifact to #{output_dir}...")
70
- copy_hart(hart_file)
67
+ run_hook_file = File.join(path, 'habitat', 'hooks', 'run')
68
+ logger.info("Generating a Habitat run hook at #{run_hook_file}...")
69
+ create_file_from_template(run_hook_file, 'hooks/run.erb')
70
+
71
+ default_toml = File.join(path, 'habitat', 'default.toml')
72
+ logger.info("Generating a Habitat default.toml at #{default_toml}...")
73
+ create_file_from_template(default_toml, 'default.toml.erb')
74
+
75
+ config = File.join(path, 'habitat', 'config', 'inspec_exec_config.json')
76
+ logger.info("Generating #{config} for `inspec exec`...")
77
+ create_file_from_template(config, 'config/inspec_exec_config.json.erb')
71
78
  end
72
79
 
73
80
  def upload
74
- validate_habitat_auth_token
75
- hart_file = create
76
- upload_hart(hart_file)
81
+ habitat_config = read_habitat_config
82
+
83
+ if habitat_config['auth_token'].nil?
84
+ exit_with_error(
85
+ 'Unable to determine Habitat auth token for uploading.',
86
+ 'Run `hab setup` or set the HAB_AUTH_TOKEN environment variable.',
87
+ )
88
+ end
89
+
90
+ # Run create command to create habitat artifact
91
+ hart = create
92
+
93
+ logger.info("Uploading Habitat artifact #{hart}...")
94
+ upload_hart(hart, habitat_config)
95
+ logger.info("Habitat artifact #{hart} uploaded.")
77
96
  rescue => e
78
- @log.debug(e.backtrace.join("\n"))
79
- exit_with_error(
80
- 'Unable to upload Habitat artifact.',
81
- "#{e.class} -- #{e.message}",
82
- )
97
+ logger.debug(e.backtrace.join("\n"))
98
+ exit_with_error('Unable to upload Habitat artifact.')
83
99
  end
84
100
 
85
- def delete_work_dir
86
- @log.debug("Deleting work directory #{work_dir}")
87
- FileUtils.rm_rf(work_dir) if Dir.exist?(work_dir)
101
+ private
102
+
103
+ def create_working_dir
104
+ working_dir = Dir.mktmpdir
105
+ logger.debug("Generated working directory #{working_dir}")
106
+ working_dir
88
107
  end
89
108
 
90
- def setup
91
- @log.info("Setting up profile at #{path} for Habitat...")
92
- create_profile_object
93
- verify_profile
94
- vendor_profile_dependencies
95
- create_habitat_directories(path)
96
- create_plan(path)
97
- create_run_hook(path)
98
- create_default_config(path)
109
+ def duplicate_profile(path, working_dir)
110
+ profile = profile_from_path(path)
111
+ copy_profile_to_working_dir(profile, working_dir)
112
+ profile_from_path(working_dir)
99
113
  end
100
114
 
101
- private
115
+ def prepare_profile!(profile)
116
+ vendored_profile = vendor_profile_dependencies!(profile)
117
+ verify_profile(vendored_profile)
118
+ setup(vendored_profile)
119
+ end
102
120
 
103
- def create_profile_object
104
- @profile = Inspec::Profile.for_target(
121
+ def profile_from_path(path)
122
+ Inspec::Profile.for_target(
105
123
  path,
106
124
  backend: Inspec::Backend.create(Inspec::Config.mock),
107
125
  )
108
126
  end
109
127
 
110
- def verify_profile
111
- @log.info('Checking to see if the profile is valid...')
128
+ def copy_profile_to_working_dir(profile, working_dir)
129
+ logger.debug('Copying profile contents to the working directory...')
130
+ profile.files.each do |profile_file|
131
+ next if File.extname(profile_file) == '.hart'
132
+
133
+ src = File.join(profile.root_path, profile_file)
134
+ dst = File.join(working_dir, profile_file)
135
+ if File.directory?(profile_file)
136
+ logger.debug("Creating directory #{dst}")
137
+ FileUtils.mkdir_p(dst)
138
+ else
139
+ logger.debug("Copying file #{src} to #{dst}")
140
+ FileUtils.cp_r(src, dst)
141
+ end
142
+ end
143
+ end
144
+
145
+ def verify_profile(profile)
146
+ logger.debug('Checking to see if the profile is valid...')
112
147
 
113
148
  unless profile.check[:summary][:valid]
114
- exit_with_error('Profile check failed. Please fix the profile before creating a Habitat artifact.')
149
+ exit_with_error('Profile check failed. Please fix the profile ' \
150
+ 'before creating a Habitat artifact.')
115
151
  end
116
152
 
117
- @log.info('Profile is valid.')
153
+ logger.debug('Profile is valid.')
118
154
  end
119
155
 
120
- def vendor_profile_dependencies
121
- profile_vendor = Inspec::ProfileVendor.new(path)
156
+ def vendor_profile_dependencies!(profile)
157
+ profile_vendor = Inspec::ProfileVendor.new(profile.root_path)
122
158
  if profile_vendor.lockfile.exist? && profile_vendor.cache_path.exist?
123
- @log.info("Profile's dependencies are already vendored, skipping vendor process.")
159
+ logger.debug("Profile's dependencies are already vendored, skipping " \
160
+ 'vendor process.')
124
161
  else
125
- @log.info("Vendoring the profile's dependencies...")
162
+ logger.debug("Vendoring the profile's dependencies...")
126
163
  profile_vendor.vendor!
127
164
 
128
- @log.info('Ensuring all vendored content has read permissions...')
165
+ logger.debug('Ensuring all vendored content has read permissions...')
129
166
  profile_vendor.make_readable
130
-
131
- # refresh the profile object since the profile now has new files
132
- create_profile_object
133
167
  end
168
+
169
+ # Return new profile since it has changed
170
+ Inspec::Profile.for_target(
171
+ profile.root_path,
172
+ backend: Inspec::Backend.create(Inspec::Config.mock),
173
+ )
134
174
  end
135
175
 
136
- def validate_habitat_installed
137
- @log.info('Checking to see if Habitat is installed...')
176
+ def verify_habitat_setup(habitat_config)
177
+ logger.debug('Checking to see if Habitat is installed...')
138
178
  cmd = Mixlib::ShellOut.new('hab --version')
139
179
  cmd.run_command
140
- exit_with_error('Unable to run Habitat commands.', cmd.stderr) if cmd.error?
141
- end
142
-
143
- def validate_habitat_origin
144
- exit_with_error(
145
- 'Unable to determine Habitat origin name.',
146
- 'Run `hab setup` or set the HAB_ORIGIN environment variable.',
147
- ) if habitat_origin.nil?
148
- end
149
-
150
- def validate_habitat_auth_token
151
- exit_with_error(
152
- 'Unable to determine Habitat auth token for publishing.',
153
- 'Run `hab setup` or set the HAB_AUTH_TOKEN environment variable.',
154
- ) if habitat_auth_token.nil?
155
- end
156
-
157
- def validate_output_dir
158
- exit_with_error("Output directory #{output_dir} is not a directory or does not exist.") unless
159
- File.directory?(output_dir)
160
- end
161
-
162
- def work_dir
163
- return @work_dir if @work_dir
164
-
165
- @work_dir ||= Dir.mktmpdir('inspec-habitat-exporter')
166
- @log.debug("Generated work directory #{@work_dir}")
167
-
168
- @work_dir
169
- end
170
-
171
- def create_habitat_directories(parent_directory)
172
- [
173
- File.join(parent_directory, 'habitat'),
174
- File.join(parent_directory, 'habitat', 'hooks'),
175
- ].each do |dir|
176
- Dir.mkdir(dir) unless Dir.exist?(dir)
180
+ if cmd.error?
181
+ exit_with_error('Unable to run Habitat commands.', cmd.stderr)
177
182
  end
178
- end
179
183
 
180
- def copy_profile_to_work_dir
181
- @log.info('Copying profile contents to the work directory...')
182
- profile.files.each do |f|
183
- src = File.join(profile.root_path, f)
184
- dst = File.join(work_dir, f)
185
- if File.directory?(f)
186
- @log.debug("Creating directory #{dst}")
187
- FileUtils.mkdir_p(dst)
188
- else
189
- @log.debug("Copying file #{src} to #{dst}")
190
- FileUtils.cp_r(src, dst)
191
- end
184
+ if habitat_config['origin'].nil?
185
+ exit_with_error(
186
+ 'Unable to determine Habitat origin name.',
187
+ 'Run `hab setup` or set the HAB_ORIGIN environment variable.',
188
+ )
192
189
  end
193
190
  end
194
191
 
195
- def create_plan(directory)
196
- plan_file = File.join(directory, 'habitat', 'plan.sh')
197
- @log.info("Generating Habitat plan at #{plan_file}...")
198
- File.write(plan_file, plan_contents)
192
+ def create_file_from_template(file, template, vars = {})
193
+ FileUtils.mkdir_p(File.dirname(file))
194
+ template_path = File.join(__dir__, '../../templates/habitat', template)
195
+ contents = ERB.new(File.read(template_path))
196
+ .result(OpenStruct.new(vars).instance_eval { binding })
197
+ File.write(file, contents)
199
198
  end
200
199
 
201
- def create_run_hook(directory)
202
- run_hook_file = File.join(directory, 'habitat', 'hooks', 'run')
203
- @log.info("Generating a Habitat run hook at #{run_hook_file}...")
204
- File.write(run_hook_file, run_hook_contents)
205
- end
206
-
207
- def create_default_config(directory)
208
- default_toml = File.join(directory, 'habitat', 'default.toml')
209
- @log.info("Generating Habitat's default.toml configuration...")
210
- File.write(default_toml, 'sleep_time = 300')
211
- end
212
-
213
- def build_hart
214
- @log.info('Building our Habitat artifact...')
200
+ def build_hart(working_dir, habitat_config)
201
+ logger.debug('Building our Habitat artifact...')
215
202
 
216
203
  env = {
217
204
  'TERM' => 'vt100',
218
- 'HAB_ORIGIN' => habitat_origin,
205
+ 'HAB_ORIGIN' => habitat_config['origin'],
219
206
  'HAB_NONINTERACTIVE' => 'true',
220
207
  }
221
208
 
222
- env['RUST_LOG'] = 'debug' if @log.level == :debug
209
+ env['RUST_LOG'] = 'debug' if logger.level == :debug
223
210
 
224
211
  # TODO: Would love to use Mixlib::ShellOut here, but it doesn't
225
212
  # seem to preserve the STDIN tty, and docker gets angry.
226
- Dir.chdir(work_dir) do
213
+ Dir.chdir(working_dir) do
227
214
  unless system(env, 'hab pkg build .')
228
215
  exit_with_error('Unable to build the Habitat artifact.')
229
216
  end
230
217
  end
231
218
 
232
- hart_files = Dir.glob(File.join(work_dir, 'results', '*.hart'))
219
+ hart_files = Dir.glob(File.join(working_dir, 'results', '*.hart'))
233
220
 
234
221
  if hart_files.length > 1
235
- exit_with_error('More than one Habitat artifact was created which was not expected.')
222
+ exit_with_error('More than one Habitat artifact was created which ' \
223
+ 'was not expected.')
236
224
  elsif hart_files.empty?
237
225
  exit_with_error('No Habitat artifact was created.')
238
226
  end
@@ -240,21 +228,16 @@ module InspecPlugins
240
228
  hart_files.first
241
229
  end
242
230
 
243
- def copy_hart(working_dir_hart)
244
- hart_basename = File.basename(working_dir_hart)
245
- dst = File.join(output_dir, hart_basename)
246
- FileUtils.cp(working_dir_hart, dst)
247
-
248
- dst
249
- end
231
+ def upload_hart(hart_file, habitat_config)
232
+ logger.debug("Uploading '#{hart_file}' to the Habitat Builder Depot...")
250
233
 
251
- def upload_hart(hart_file)
252
- @log.info('Uploading the Habitat artifact to our Depot...')
234
+ config = habitat_config
253
235
 
254
236
  env = {
255
- 'TERM' => 'vt100',
256
- 'HAB_AUTH_TOKEN' => habitat_auth_token,
237
+ 'HAB_AUTH_TOKEN' => config['auth_token'],
257
238
  'HAB_NONINTERACTIVE' => 'true',
239
+ 'HAB_ORIGIN' => config['origin'],
240
+ 'TERM' => 'vt100',
258
241
  }
259
242
 
260
243
  env['HAB_DEPOT_URL'] = ENV['HAB_DEPOT_URL'] if ENV['HAB_DEPOT_URL']
@@ -269,124 +252,25 @@ module InspecPlugins
269
252
  )
270
253
  end
271
254
 
272
- @log.info('Upload complete!')
273
- end
274
-
275
- def habitat_origin
276
- ENV['HAB_ORIGIN'] || habitat_cli_config['origin']
277
- end
278
-
279
- def habitat_auth_token
280
- ENV['HAB_AUTH_TOKEN'] || habitat_cli_config['auth_token']
281
- end
282
-
283
- def habitat_cli_config
284
- return @cli_config if @cli_config
285
-
286
- config_file = File.join(ENV['HOME'], '.hab', 'etc', 'cli.toml')
287
- return {} unless File.exist?(config_file)
288
-
289
- @cli_config = Tomlrb.load_file(config_file)
255
+ logger.debug('Upload complete!')
290
256
  end
291
257
 
292
- def output_dir
293
- options[:output_dir] || Dir.pwd
258
+ def read_habitat_config
259
+ cli_toml = File.join(ENV['HOME'], '.hab', 'etc', 'cli.toml')
260
+ cli_toml = '/hab/etc/cli.toml' unless File.exist?(cli_toml)
261
+ cli_config = File.exist?(cli_toml) ? Tomlrb.load_file(cli_toml) : {}
262
+ cli_config['origin'] ||= ENV['HAB_ORIGIN']
263
+ cli_config['auth_token'] ||= ENV['HAB_AUTH_TOKEN']
264
+ cli_config
294
265
  end
295
266
 
296
267
  def exit_with_error(*errors)
297
268
  errors.each do |error_msg|
298
- @log.error(error_msg)
269
+ logger.error(error_msg)
299
270
  end
300
271
 
301
272
  exit 1
302
273
  end
303
-
304
- def package_name
305
- "inspec-profile-#{profile.name}"
306
- end
307
-
308
- def plan_contents
309
- plan = <<~EOL
310
- pkg_name=#{package_name}
311
- pkg_version=#{profile.version}
312
- pkg_origin=#{habitat_origin}
313
- pkg_deps=(chef/inspec)
314
- EOL
315
-
316
- plan += "pkg_license='#{profile.metadata.params[:license]}'\n\n" if profile.metadata.params[:license]
317
-
318
- plan += <<~EOL
319
- do_setup_environment() {
320
- ARCHIVE_PATH="$HAB_CACHE_SRC_PATH/$pkg_dirname/$pkg_name-$pkg_version.tar.gz"
321
- }
322
-
323
- do_build() {
324
- if [ ! -f $PLAN_CONTEXT/../inspec.yml ]; then
325
- exit_with 'Cannot find inspec.yml. Please build from profile root.' 1
326
- fi
327
-
328
- local profile_files=($(ls $PLAN_CONTEXT/../ -I habitat -I results))
329
- local profile_location="$HAB_CACHE_SRC_PATH/$pkg_dirname/build"
330
- mkdir -p $profile_location
331
-
332
- build_line "Copying profile files to $profile_location"
333
- cp -R ${profile_files[@]} $profile_location
334
-
335
- build_line "Archiving $ARCHIVE_PATH"
336
- inspec archive "$HAB_CACHE_SRC_PATH/$pkg_dirname/build" \
337
- -o $ARCHIVE_PATH \
338
- --overwrite
339
- }
340
-
341
- do_install() {
342
- mkdir -p $pkg_prefix/profiles
343
- cp $ARCHIVE_PATH $pkg_prefix/profiles
344
- }
345
- EOL
346
-
347
- plan
348
- end
349
-
350
- def run_hook_contents
351
- <<~EOL
352
- #!{{pkgPathFor "core/bash"}}/bin/bash
353
-
354
- # Redirect stderr to stdout
355
- # This will be captured by Habitat and viewable via `journalctl`
356
- # NOTE: We might want log to "{{pkg.svc_path}}/logs" and handle rotation
357
- exec 2>&1
358
-
359
- # InSpec will try to create a .cache directory in the user's home directory
360
- # so this needs to be someplace writeable by the hab user
361
- export HOME={{pkg.svc_var_path}}
362
-
363
- RESULTS_DIR="{{pkg.svc_var_path}}/inspec_results"
364
- RESULTS_FILE="${RESULTS_DIR}/{{pkg.name}}.json"
365
-
366
- # Create a directory for InSpec reporter output
367
- mkdir -p $(dirname $RESULTS_FILE)
368
-
369
- while true; do
370
- echo "Executing InSpec for {{pkg.ident}}"
371
- inspec exec "{{pkg.path}}/profiles/*" --reporter=json > ${RESULTS_FILE}
372
-
373
- EXIT_STATUS=$?
374
- if [ $EXIT_STATUS -eq 0 ]; then
375
- echo "InSpec run completed successfully."
376
- elif [ $EXIT_STATUS -eq 100 ]; then
377
- echo "InSpec run completed successfully, with at least 1 failed test"
378
- elif [ $EXIT_STATUS -eq 101 ]; then
379
- echo "InSpec run completed successfully, with skipped tests and no failures"
380
- else
381
- echo "InSpec run did not complete successfully. Exited with status: $?"
382
- fi
383
- echo "Results located here: ${RESULTS_FILE}"
384
-
385
- echo "Sleeping for {{cfg.sleep_time}} seconds"
386
- sleep {{cfg.sleep_time}}
387
- done
388
- EOL
389
- end
390
274
  end
391
275
  end
392
276
  end