inspec 2.2.102 → 2.2.112
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 +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +25 -7
- data/Rakefile +8 -2
- data/docs/profiles.md +9 -0
- data/docs/resources/aws_security_group.md.erb +19 -2
- data/docs/resources/gem.md.erb +24 -5
- data/docs/resources/mssql_session.md.erb +8 -0
- data/lib/inspec/plugin/v2/loader.rb +33 -7
- data/lib/inspec/reporters/json_automate.rb +1 -1
- data/lib/inspec/version.rb +1 -1
- data/lib/plugins/README.md +16 -0
- data/lib/plugins/inspec-artifact/lib/inspec-artifact.rb +12 -0
- data/lib/plugins/inspec-artifact/lib/inspec-artifact/base.rb +162 -0
- data/lib/plugins/inspec-artifact/lib/inspec-artifact/cli.rb +114 -0
- data/lib/plugins/inspec-artifact/test/functional/inspec_artifact_test.rb +46 -0
- data/lib/plugins/inspec-habitat/lib/inspec-habitat.rb +11 -0
- data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +39 -0
- data/lib/plugins/inspec-habitat/lib/inspec-habitat/profile.rb +394 -0
- data/lib/plugins/inspec-habitat/test/unit/profile_test.rb +184 -0
- data/lib/{bundles → plugins}/inspec-init/README.md +0 -0
- data/lib/plugins/inspec-init/lib/inspec-init.rb +12 -0
- data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +28 -0
- data/lib/plugins/inspec-init/lib/inspec-init/renderer.rb +81 -0
- data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/README.md +0 -0
- data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/controls/example.rb +0 -0
- data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/inspec.yml +0 -0
- data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/libraries/.gitkeep +0 -0
- data/lib/plugins/inspec-init/test/functional/inspec_init_test.rb +30 -0
- data/lib/plugins/shared/core_plugin_test_helper.rb +50 -0
- data/lib/resources/aws/aws_security_group.rb +38 -6
- data/lib/resources/gem.rb +7 -1
- data/lib/resources/mssql_session.rb +4 -2
- metadata +21 -17
- data/lib/bundles/inspec-artifact.rb +0 -7
- data/lib/bundles/inspec-artifact/README.md +0 -1
- data/lib/bundles/inspec-artifact/cli.rb +0 -278
- data/lib/bundles/inspec-habitat.rb +0 -12
- data/lib/bundles/inspec-habitat/cli.rb +0 -37
- data/lib/bundles/inspec-habitat/log.rb +0 -10
- data/lib/bundles/inspec-habitat/profile.rb +0 -391
- data/lib/bundles/inspec-init.rb +0 -12
- data/lib/bundles/inspec-init/cli.rb +0 -39
- data/lib/bundles/inspec-init/renderer.rb +0 -79
@@ -0,0 +1,114 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require_relative 'base'
|
3
|
+
|
4
|
+
#
|
5
|
+
# Notes:
|
6
|
+
#
|
7
|
+
# Generate keys
|
8
|
+
# The initial implementation uses 2048 bit RSA key pairs (public + private).
|
9
|
+
# Public keys must be available for a customer to install and verify an artifact.
|
10
|
+
# Private keys should be stored in a secure location and NOT be distributed.
|
11
|
+
# (They're only for creating artifacts).
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# .IAF file format
|
15
|
+
# .iaf = "Inspec Artifact File", easy to rename if you'd like something more appropriate.
|
16
|
+
# The iaf file wraps a binary artifact with some metadata. The first implementation
|
17
|
+
# looks like this:
|
18
|
+
#
|
19
|
+
# INSPEC-PROFILE-1
|
20
|
+
# name_of_signing_key
|
21
|
+
# algorithm
|
22
|
+
# signature
|
23
|
+
# <empty line>
|
24
|
+
# binary-blob
|
25
|
+
# <eof>
|
26
|
+
#
|
27
|
+
# Let's look at each line:
|
28
|
+
# INSPEC-PROFILE-1:
|
29
|
+
# This is the artifact version descriptor. It should't change unless the
|
30
|
+
# format of the archive changes.
|
31
|
+
#
|
32
|
+
# name_of_signing_key
|
33
|
+
# The name of the public key that can be used to verify an artifact
|
34
|
+
#
|
35
|
+
# algorithm
|
36
|
+
# The digest used to sign, I picked SHA512 to start with.
|
37
|
+
# If we support multiple digests, we'll need to have the verify() method
|
38
|
+
# support each digest.
|
39
|
+
#
|
40
|
+
# signature
|
41
|
+
# The result of passing the binary artifact through the digest algorithm above.
|
42
|
+
# Result is base64 encoded.
|
43
|
+
#
|
44
|
+
# <empty line>
|
45
|
+
# We use an empty line to separate artifact header from artifact body (binary blob).
|
46
|
+
# The artifact body can be anything you like.
|
47
|
+
#
|
48
|
+
# binary-blob
|
49
|
+
# A binary blob, most likely a .tar.gz or tar.xz file. We'll need to pick one and
|
50
|
+
# stick with it as part of the "INSPEC-PROFILE-1" artifact version. If we change block
|
51
|
+
# format, the artifact version descriptor must be incremented, and the sign()
|
52
|
+
# and verify() methods must be updated to support a newer version.
|
53
|
+
#
|
54
|
+
#
|
55
|
+
# Key revocation
|
56
|
+
# This implementation doesn't support key revocation. However, a customer
|
57
|
+
# can remove the public cert file before installation, and artifacts will then
|
58
|
+
# fail verification.
|
59
|
+
#
|
60
|
+
# Key locations
|
61
|
+
# This implementation uses the current working directory to find public and
|
62
|
+
# private keys. We should establish a common key directory (similar to /hab/cache/keys
|
63
|
+
# or ~/.hab/cache/keys in Habitat).
|
64
|
+
#
|
65
|
+
# Extracting artifacts outside of Inspec
|
66
|
+
# As in Habitat, the artifact format for Inspec allows the use of common
|
67
|
+
# Unix tools to read the header and body of an artifact.
|
68
|
+
# To extract the header from a .iaf:
|
69
|
+
# sed '/^$/q' foo.iaf
|
70
|
+
# To extract the raw content from a .iaf:
|
71
|
+
# sed '1,/^$/d' foo.iaf
|
72
|
+
|
73
|
+
module InspecPlugins
|
74
|
+
module Artifact
|
75
|
+
class CLI < Inspec.plugin(2, :cli_command)
|
76
|
+
subcommand_desc 'artifact SUBCOMMAND', 'Manage InSpec Artifacts'
|
77
|
+
|
78
|
+
desc 'generate', 'Generate a RSA key pair for signing and verification'
|
79
|
+
option :keyname, type: :string, required: true,
|
80
|
+
desc: 'Desriptive name of key'
|
81
|
+
option :keydir, type: :string, default: './',
|
82
|
+
desc: 'Directory to search for keys'
|
83
|
+
def generate_keys
|
84
|
+
puts 'Generating keys'
|
85
|
+
InspecPlugins::Artifact::Base.keygen(options)
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'sign-profile', 'Create a signed .iaf artifact'
|
89
|
+
option :profile, type: :string, required: true,
|
90
|
+
desc: 'Path to profile directory'
|
91
|
+
option :keyname, type: :string, required: true,
|
92
|
+
desc: 'Desriptive name of key'
|
93
|
+
def sign_profile
|
94
|
+
InspecPlugins::Artifact::Base.profile_sign(options)
|
95
|
+
end
|
96
|
+
|
97
|
+
desc 'verify-profile', 'Verify a signed .iaf artifact'
|
98
|
+
option :infile, type: :string, required: true,
|
99
|
+
desc: '.iaf file to verify'
|
100
|
+
def verify_profile
|
101
|
+
InspecPlugins::Artifact::Base.profile_verify(options)
|
102
|
+
end
|
103
|
+
|
104
|
+
desc 'install-profile', 'Verify and install a signed .iaf artifact'
|
105
|
+
option :infile, type: :string, required: true,
|
106
|
+
desc: '.iaf file to install'
|
107
|
+
option :destdir, type: :string, required: true,
|
108
|
+
desc: 'Installation directory'
|
109
|
+
def install_profile
|
110
|
+
InspecPlugins::Artifact::Base.profile_install(options)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../../../shared/core_plugin_test_helper.rb'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
class ArtifactCli < MiniTest::Test
|
8
|
+
include CorePluginFunctionalHelper
|
9
|
+
|
10
|
+
def test_generating_archive_keys
|
11
|
+
Dir.mktmpdir do |dir|
|
12
|
+
unique_key_name = SecureRandom.uuid()
|
13
|
+
out = run_inspec_process("artifact generate --keyname #{unique_key_name}", prefix: "cd #{dir} &&")
|
14
|
+
assert_equal 0, out.exit_status
|
15
|
+
|
16
|
+
stdout = out.stdout.force_encoding(Encoding::UTF_8)
|
17
|
+
assert_includes stdout, 'Generating private key'
|
18
|
+
assert_includes stdout, 'Generating public key'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_verify_and_install_signed_profile
|
23
|
+
Dir.mktmpdir do |dir|
|
24
|
+
unique_key_name = SecureRandom.uuid()
|
25
|
+
install_dir = File.join(dir, SecureRandom.uuid())
|
26
|
+
profile = File.join(dir, 'profile')
|
27
|
+
FileUtils.mkdir(install_dir)
|
28
|
+
|
29
|
+
# create profile
|
30
|
+
profile = File.join(dir, 'artifact-profile')
|
31
|
+
run_inspec_process("init profile artifact-profile", prefix: "cd #{dir} &&")
|
32
|
+
|
33
|
+
out = run_inspec_process("artifact generate --keyname #{unique_key_name}", prefix: "cd #{dir} &&")
|
34
|
+
assert_equal 0, out.exit_status
|
35
|
+
|
36
|
+
out = run_inspec_process("artifact sign-profile --profile #{profile} --keyname #{unique_key_name}", prefix: "cd #{dir} &&")
|
37
|
+
assert_equal 0, out.exit_status
|
38
|
+
|
39
|
+
out = run_inspec_process("artifact install-profile --infile artifact-profile-0.1.0.iaf --destdir #{install_dir}", prefix: "cd #{dir} &&")
|
40
|
+
assert_equal 0, out.exit_status
|
41
|
+
|
42
|
+
assert_includes out.stdout.force_encoding(Encoding::UTF_8), "Installing to #{install_dir}"
|
43
|
+
assert_includes Dir.entries(install_dir).join, 'inspec.yml'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require_relative 'profile'
|
3
|
+
|
4
|
+
module InspecPlugins
|
5
|
+
module Habitat
|
6
|
+
class ProfileCLI < Inspec.plugin(2, :cli_command)
|
7
|
+
# Override banner method to correct missing subcommand.
|
8
|
+
# @see https://github.com/erikhuda/thor/issues/261
|
9
|
+
def self.banner(command, _namespace = nil, _subcommand = false)
|
10
|
+
"#{basename} habitat profile #{command.usage}"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'create PATH', 'Create a one-time Habitat artifact for the profile found at PATH'
|
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)
|
18
|
+
end
|
19
|
+
|
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)
|
23
|
+
end
|
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)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class CLI < Inspec.plugin(2, :cli_command)
|
32
|
+
subcommand_desc 'habitat SUBCOMMAND', 'Manage Habitat with InSpec'
|
33
|
+
namespace 'habitat'
|
34
|
+
|
35
|
+
desc 'profile', 'Manage InSpec profiles as Habitat artifacts'
|
36
|
+
subcommand 'profile', ProfileCLI
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,394 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Adam Leff
|
3
|
+
|
4
|
+
require 'inspec/profile_vendor'
|
5
|
+
require 'mixlib/shellout'
|
6
|
+
require 'tomlrb'
|
7
|
+
|
8
|
+
module InspecPlugins
|
9
|
+
module Habitat
|
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
|
+
|
32
|
+
def initialize(path, options = {})
|
33
|
+
@path = path
|
34
|
+
@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)
|
40
|
+
end
|
41
|
+
|
42
|
+
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_settings_file(work_dir)
|
55
|
+
create_default_config(work_dir)
|
56
|
+
|
57
|
+
# returns the path to the .hart file in the work directory
|
58
|
+
build_hart
|
59
|
+
rescue => e
|
60
|
+
@log.debug(e.backtrace.join("\n"))
|
61
|
+
exit_with_error(
|
62
|
+
'Unable to generate Habitat artifact.',
|
63
|
+
"#{e.class} -- #{e.message}",
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def copy(hart_file)
|
68
|
+
validate_output_dir
|
69
|
+
|
70
|
+
@log.info("Copying artifact to #{output_dir}...")
|
71
|
+
copy_hart(hart_file)
|
72
|
+
end
|
73
|
+
|
74
|
+
def upload
|
75
|
+
validate_habitat_auth_token
|
76
|
+
hart_file = create
|
77
|
+
upload_hart(hart_file)
|
78
|
+
rescue => e
|
79
|
+
@log.debug(e.backtrace.join("\n"))
|
80
|
+
exit_with_error(
|
81
|
+
'Unable to upload Habitat artifact.',
|
82
|
+
"#{e.class} -- #{e.message}",
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def delete_work_dir
|
87
|
+
@log.debug("Deleting work directory #{work_dir}")
|
88
|
+
FileUtils.rm_rf(work_dir) if Dir.exist?(work_dir)
|
89
|
+
end
|
90
|
+
|
91
|
+
def setup
|
92
|
+
@log.info("Setting up profile at #{path} for Habitat...")
|
93
|
+
create_profile_object
|
94
|
+
verify_profile
|
95
|
+
vendor_profile_dependencies
|
96
|
+
create_habitat_directories(path)
|
97
|
+
create_plan(path)
|
98
|
+
create_run_hook(path)
|
99
|
+
create_settings_file(path)
|
100
|
+
create_default_config(path)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def create_profile_object
|
106
|
+
@profile = Inspec::Profile.for_target(
|
107
|
+
path,
|
108
|
+
backend: Inspec::Backend.create(target: 'mock://'),
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def verify_profile
|
113
|
+
@log.info('Checking to see if the profile is valid...')
|
114
|
+
|
115
|
+
unless profile.check[:summary][:valid]
|
116
|
+
exit_with_error('Profile check failed. Please fix the profile before creating a Habitat artifact.')
|
117
|
+
end
|
118
|
+
|
119
|
+
@log.info('Profile is valid.')
|
120
|
+
end
|
121
|
+
|
122
|
+
def vendor_profile_dependencies
|
123
|
+
profile_vendor = Inspec::ProfileVendor.new(path)
|
124
|
+
if profile_vendor.lockfile.exist? && profile_vendor.cache_path.exist?
|
125
|
+
@log.info("Profile's dependencies are already vendored, skipping vendor process.")
|
126
|
+
else
|
127
|
+
@log.info("Vendoring the profile's dependencies...")
|
128
|
+
profile_vendor.vendor!
|
129
|
+
|
130
|
+
@log.info('Ensuring all vendored content has read permissions...')
|
131
|
+
profile_vendor.make_readable
|
132
|
+
|
133
|
+
# refresh the profile object since the profile now has new files
|
134
|
+
create_profile_object
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def validate_habitat_installed
|
139
|
+
@log.info('Checking to see if Habitat is installed...')
|
140
|
+
cmd = Mixlib::ShellOut.new('hab --version')
|
141
|
+
cmd.run_command
|
142
|
+
exit_with_error('Unable to run Habitat commands.', cmd.stderr) if cmd.error?
|
143
|
+
end
|
144
|
+
|
145
|
+
def validate_habitat_origin
|
146
|
+
exit_with_error(
|
147
|
+
'Unable to determine Habitat origin name.',
|
148
|
+
'Run `hab setup` or set the HAB_ORIGIN environment variable.',
|
149
|
+
) if habitat_origin.nil?
|
150
|
+
end
|
151
|
+
|
152
|
+
def validate_habitat_auth_token
|
153
|
+
exit_with_error(
|
154
|
+
'Unable to determine Habitat auth token for publishing.',
|
155
|
+
'Run `hab setup` or set the HAB_AUTH_TOKEN environment variable.',
|
156
|
+
) if habitat_auth_token.nil?
|
157
|
+
end
|
158
|
+
|
159
|
+
def validate_output_dir
|
160
|
+
exit_with_error("Output directory #{output_dir} is not a directory or does not exist.") unless
|
161
|
+
File.directory?(output_dir)
|
162
|
+
end
|
163
|
+
|
164
|
+
def work_dir
|
165
|
+
return @work_dir if @work_dir
|
166
|
+
|
167
|
+
@work_dir ||= Dir.mktmpdir('inspec-habitat-exporter')
|
168
|
+
@log.debug("Generated work directory #{@work_dir}")
|
169
|
+
|
170
|
+
@work_dir
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_habitat_directories(parent_directory)
|
174
|
+
[
|
175
|
+
File.join(parent_directory, 'habitat'),
|
176
|
+
File.join(parent_directory, 'habitat', 'config'),
|
177
|
+
File.join(parent_directory, 'habitat', 'hooks'),
|
178
|
+
].each do |dir|
|
179
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def copy_profile_to_work_dir
|
184
|
+
@log.info('Copying profile contents to the work directory...')
|
185
|
+
profile.files.each do |f|
|
186
|
+
src = File.join(profile.root_path, f)
|
187
|
+
dst = File.join(work_dir, f)
|
188
|
+
if File.directory?(f)
|
189
|
+
@log.debug("Creating directory #{dst}")
|
190
|
+
FileUtils.mkdir_p(dst)
|
191
|
+
else
|
192
|
+
@log.debug("Copying file #{src} to #{dst}")
|
193
|
+
FileUtils.cp_r(src, dst)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def create_plan(directory)
|
199
|
+
plan_file = File.join(directory, 'habitat', 'plan.sh')
|
200
|
+
@log.info("Generating Habitat plan at #{plan_file}...")
|
201
|
+
File.write(plan_file, plan_contents)
|
202
|
+
end
|
203
|
+
|
204
|
+
def create_run_hook(directory)
|
205
|
+
run_hook_file = File.join(directory, 'habitat', 'hooks', 'run')
|
206
|
+
@log.info("Generating a Habitat run hook at #{run_hook_file}...")
|
207
|
+
File.write(run_hook_file, run_hook_contents)
|
208
|
+
end
|
209
|
+
|
210
|
+
def create_settings_file(directory)
|
211
|
+
settings_file = File.join(directory, 'habitat', 'config', 'settings.sh')
|
212
|
+
@log.info("Generating a settings file at #{settings_file}...")
|
213
|
+
File.write(settings_file, "SLEEP_TIME={{cfg.sleep_time}}\n")
|
214
|
+
end
|
215
|
+
|
216
|
+
def create_default_config(directory)
|
217
|
+
default_toml = File.join(directory, 'habitat', 'default.toml')
|
218
|
+
@log.info("Generating Habitat's default.toml configuration...")
|
219
|
+
File.write(default_toml, 'sleep_time = 300')
|
220
|
+
end
|
221
|
+
|
222
|
+
def build_hart
|
223
|
+
@log.info('Building our Habitat artifact...')
|
224
|
+
|
225
|
+
env = {
|
226
|
+
'TERM' => 'vt100',
|
227
|
+
'HAB_ORIGIN' => habitat_origin,
|
228
|
+
'HAB_NONINTERACTIVE' => 'true',
|
229
|
+
}
|
230
|
+
|
231
|
+
env['RUST_LOG'] = 'debug' if @log.level == :debug
|
232
|
+
|
233
|
+
# TODO: Would love to use Mixlib::ShellOut here, but it doesn't
|
234
|
+
# seem to preserve the STDIN tty, and docker gets angry.
|
235
|
+
Dir.chdir(work_dir) do
|
236
|
+
unless system(env, 'hab pkg build .')
|
237
|
+
exit_with_error('Unable to build the Habitat artifact.')
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
hart_files = Dir.glob(File.join(work_dir, 'results', '*.hart'))
|
242
|
+
|
243
|
+
if hart_files.length > 1
|
244
|
+
exit_with_error('More than one Habitat artifact was created which was not expected.')
|
245
|
+
elsif hart_files.empty?
|
246
|
+
exit_with_error('No Habitat artifact was created.')
|
247
|
+
end
|
248
|
+
|
249
|
+
hart_files.first
|
250
|
+
end
|
251
|
+
|
252
|
+
def copy_hart(working_dir_hart)
|
253
|
+
hart_basename = File.basename(working_dir_hart)
|
254
|
+
dst = File.join(output_dir, hart_basename)
|
255
|
+
FileUtils.cp(working_dir_hart, dst)
|
256
|
+
|
257
|
+
dst
|
258
|
+
end
|
259
|
+
|
260
|
+
def upload_hart(hart_file)
|
261
|
+
@log.info('Uploading the Habitat artifact to our Depot...')
|
262
|
+
|
263
|
+
env = {
|
264
|
+
'TERM' => 'vt100',
|
265
|
+
'HAB_AUTH_TOKEN' => habitat_auth_token,
|
266
|
+
'HAB_NONINTERACTIVE' => 'true',
|
267
|
+
}
|
268
|
+
|
269
|
+
env['HAB_DEPOT_URL'] = ENV['HAB_DEPOT_URL'] if ENV['HAB_DEPOT_URL']
|
270
|
+
|
271
|
+
cmd = Mixlib::ShellOut.new("hab pkg upload #{hart_file}", env: env)
|
272
|
+
cmd.run_command
|
273
|
+
if cmd.error?
|
274
|
+
exit_with_error(
|
275
|
+
'Unable to upload Habitat artifact to the Depot.',
|
276
|
+
cmd.stdout,
|
277
|
+
cmd.stderr,
|
278
|
+
)
|
279
|
+
end
|
280
|
+
|
281
|
+
@log.info('Upload complete!')
|
282
|
+
end
|
283
|
+
|
284
|
+
def habitat_origin
|
285
|
+
ENV['HAB_ORIGIN'] || habitat_cli_config['origin']
|
286
|
+
end
|
287
|
+
|
288
|
+
def habitat_auth_token
|
289
|
+
ENV['HAB_AUTH_TOKEN'] || habitat_cli_config['auth_token']
|
290
|
+
end
|
291
|
+
|
292
|
+
def habitat_cli_config
|
293
|
+
return @cli_config if @cli_config
|
294
|
+
|
295
|
+
config_file = File.join(ENV['HOME'], '.hab', 'etc', 'cli.toml')
|
296
|
+
return {} unless File.exist?(config_file)
|
297
|
+
|
298
|
+
@cli_config = Tomlrb.load_file(config_file)
|
299
|
+
end
|
300
|
+
|
301
|
+
def output_dir
|
302
|
+
options[:output_dir] || Dir.pwd
|
303
|
+
end
|
304
|
+
|
305
|
+
def exit_with_error(*errors)
|
306
|
+
errors.each do |error_msg|
|
307
|
+
@log.error(error_msg)
|
308
|
+
end
|
309
|
+
|
310
|
+
exit 1
|
311
|
+
end
|
312
|
+
|
313
|
+
def package_name
|
314
|
+
"inspec-profile-#{profile.name}"
|
315
|
+
end
|
316
|
+
|
317
|
+
def plan_contents
|
318
|
+
plan = <<~EOL
|
319
|
+
pkg_name=#{package_name}
|
320
|
+
pkg_version=#{profile.version}
|
321
|
+
pkg_origin=#{habitat_origin}
|
322
|
+
pkg_deps=(chef/inspec core/ruby core/hab)
|
323
|
+
pkg_svc_user=root
|
324
|
+
EOL
|
325
|
+
|
326
|
+
plan += "pkg_license='#{profile.metadata.params[:license]}'\n\n" if profile.metadata.params[:license]
|
327
|
+
|
328
|
+
plan += <<~EOL
|
329
|
+
|
330
|
+
do_build() {
|
331
|
+
cp -vr $PLAN_CONTEXT/../* $HAB_CACHE_SRC_PATH/$pkg_dirname
|
332
|
+
}
|
333
|
+
|
334
|
+
do_install() {
|
335
|
+
local profile_contents
|
336
|
+
local excludes
|
337
|
+
profile_contents=($(ls))
|
338
|
+
excludes=(habitat results *.hart)
|
339
|
+
|
340
|
+
for item in ${excludes[@]}; do
|
341
|
+
profile_contents=(${profile_contents[@]/$item/})
|
342
|
+
done
|
343
|
+
|
344
|
+
mkdir ${pkg_prefix}/dist
|
345
|
+
cp -r ${profile_contents[@]} ${pkg_prefix}/dist/
|
346
|
+
}
|
347
|
+
EOL
|
348
|
+
|
349
|
+
plan
|
350
|
+
end
|
351
|
+
|
352
|
+
def run_hook_contents
|
353
|
+
<<~EOL
|
354
|
+
#!/bin/sh
|
355
|
+
|
356
|
+
# redirect stderr to stdout
|
357
|
+
# ultimately, we'd like to log this somewhere useful, but due to
|
358
|
+
# https://github.com/habitat-sh/habitat/issues/2395, we need to
|
359
|
+
# avoid doing that for now.
|
360
|
+
exec 2>&1
|
361
|
+
|
362
|
+
# InSpec will try to create a .cache directory in the user's home directory
|
363
|
+
# so this needs to be someplace writeable by the hab user
|
364
|
+
export HOME={{pkg.svc_var_path}}
|
365
|
+
|
366
|
+
PROFILE_IDENT="{{pkg.origin}}/{{pkg.name}}"
|
367
|
+
RESULTS_DIR="{{pkg.svc_var_path}}/inspec_results"
|
368
|
+
RESULTS_FILE="${RESULTS_DIR}/{{pkg.name}}.json"
|
369
|
+
|
370
|
+
# Create a directory for inspec formatter output
|
371
|
+
mkdir -p {{pkg.svc_var_path}}/inspec_results
|
372
|
+
|
373
|
+
while true; do
|
374
|
+
echo "Executing InSpec for ${PROFILE_IDENT}"
|
375
|
+
inspec exec {{pkg.path}}/dist --format=json > ${RESULTS_FILE}
|
376
|
+
|
377
|
+
if [ $? -eq 0 ]; then
|
378
|
+
echo "InSpec run completed successfully."
|
379
|
+
else
|
380
|
+
echo "InSpec run did not complete successfully. If you do not see any errors above,"
|
381
|
+
echo "control failures were detected. Check the InSpec results here for details:"
|
382
|
+
echo ${RESULTS_FILE}
|
383
|
+
echo "Otherwise, troubleshoot any errors shown above."
|
384
|
+
fi
|
385
|
+
|
386
|
+
source {{pkg.svc_config_path}}/settings.sh
|
387
|
+
echo "sleeping for ${SLEEP_TIME} seconds"
|
388
|
+
sleep ${SLEEP_TIME}
|
389
|
+
done
|
390
|
+
EOL
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|