mirror of
https://github.com/inspec/inspec
synced 2024-11-10 15:14:23 +00:00
Merge pull request #6601 from inspec/inspec-6
Make InSpec 6 the main branch
This commit is contained in:
commit
5a4437a201
82 changed files with 2950 additions and 798 deletions
|
@ -4,6 +4,67 @@ Get-CimInstance Win32_OperatingSystem | Select-Object $Properties | Format-Table
|
|||
ruby -v
|
||||
bundle --version
|
||||
|
||||
echo "--- download and install vault"
|
||||
# Define the version of Vault to install
|
||||
$VaultVersion = "1.13.0"
|
||||
|
||||
# Define the installation directory for Vault
|
||||
$VaultDirectory = "$env:USERPROFILE\Vault"
|
||||
|
||||
# Create the installation directory if it doesn't exist
|
||||
if (!(Test-Path $VaultDirectory))
|
||||
{
|
||||
New-Item -ItemType Directory -Path $VaultDirectory | Out-Null
|
||||
}
|
||||
|
||||
$VaultDownloadUrl = "https://releases.hashicorp.com/vault/$VaultVersion/vault_${VaultVersion}_windows_amd64.zip"
|
||||
$VaultZipFilePath = Join-Path $VaultDirectory "vault.zip"
|
||||
|
||||
Invoke-WebRequest -Uri $VaultDownloadUrl -OutFile $VaultZipFilePath
|
||||
|
||||
# Extract the Vault binary from the zip file
|
||||
$VaultExtractPath = Join-Path $VaultDirectory "vault"
|
||||
Expand-Archive -Path $VaultZipFilePath -DestinationPath $VaultExtractPath
|
||||
|
||||
# Add the Vault binary to the system PATH environment variable
|
||||
$env:Path += ";$VaultExtractPath"
|
||||
|
||||
# Verify the installation
|
||||
echo "--- vault version installed is:"
|
||||
vault version
|
||||
|
||||
echo "--- fetching License serverl url and keys from vault"
|
||||
$Env:CHEF_LICENSE_SERVER=vault kv get -field acceptance secret/inspec/licensing/server
|
||||
$Env:CHEF_LICENSE_KEY=vault kv get -field acceptance secret/inspec/licensing/license-key
|
||||
|
||||
echo "--- verifying if environment variables are set"
|
||||
|
||||
function CheckIfEnvVarIsSet {
|
||||
param (
|
||||
[string]$envVarName
|
||||
)
|
||||
if (Test-Path "env:\$envVarName") {
|
||||
Write-Host " ++ $envVarName set successfully"
|
||||
} else {
|
||||
Write-Host " !! $envVarName is not set."
|
||||
}
|
||||
}
|
||||
|
||||
$envVarName = "CHEF_LICENSE_SERVER"
|
||||
CheckIfEnvVarIsSet -envVarName $envVarName
|
||||
|
||||
$envVarName = "CHEF_LICENSE_SERVER_API_KEY"
|
||||
CheckIfEnvVarIsSet -envVarName $envVarName
|
||||
|
||||
$envVarName = "CHEF_LICENSE_KEY"
|
||||
CheckIfEnvVarIsSet -envVarName $envVarName
|
||||
|
||||
if ($Env:CI_ENABLE_COVERAGE)
|
||||
{
|
||||
echo "--- fetching Sonar token from vault"
|
||||
$Env:SONAR_TOKEN=vault kv get -field token secret/inspec/sonar
|
||||
}
|
||||
|
||||
echo "--- bundle install"
|
||||
bundle config set --local without deploy kitchen
|
||||
bundle install --jobs=7 --retry=3
|
||||
|
|
|
@ -20,14 +20,25 @@ mount
|
|||
df /tmp
|
||||
echo ${TMPDIR:-unknown}
|
||||
|
||||
if [ -n "${CI_ENABLE_COVERAGE:-}" ]; then
|
||||
# Fetch token from vault ASAP so that long-running tests don't cause our vault token to expire
|
||||
echo "--- installing vault"
|
||||
export VAULT_VERSION=1.9.3
|
||||
export VAULT_HOME=$HOME/vault
|
||||
curl --create-dirs -sSLo $VAULT_HOME/vault.zip https://releases.hashicorp.com/vault/$VAULT_VERSION/vault_${VAULT_VERSION}_linux_amd64.zip
|
||||
unzip -o $VAULT_HOME/vault.zip -d $VAULT_HOME
|
||||
# Fetch tokens from vault ASAP so that long-running tests don't cause our vault token to expire
|
||||
echo "--- installing vault"
|
||||
export VAULT_VERSION=1.13.0
|
||||
export VAULT_HOME=$HOME/vault
|
||||
curl --create-dirs -sSLo $VAULT_HOME/vault.zip https://releases.hashicorp.com/vault/$VAULT_VERSION/vault_${VAULT_VERSION}_linux_amd64.zip
|
||||
unzip -o $VAULT_HOME/vault.zip -d $VAULT_HOME
|
||||
|
||||
echo "--- fetching License serverl url and keys from vault"
|
||||
export CHEF_LICENSE_SERVER=$($VAULT_HOME/vault kv get -field acceptance secret/inspec/licensing/server)
|
||||
export CHEF_LICENSE_KEY=$($VAULT_HOME/vault kv get -field acceptance secret/inspec/licensing/license-key)
|
||||
if [ -n "${CHEF_LICENSE_KEY:-}" ]; then
|
||||
echo " ++ License Key set successfully"
|
||||
else
|
||||
echo " !! License Key not set - exiting "
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "${CI_ENABLE_COVERAGE:-}" ]; then
|
||||
echo "--- fetching Sonar token from vault"
|
||||
export SONAR_TOKEN=$($VAULT_HOME/vault kv get -field token secret/inspec/sonar)
|
||||
fi
|
||||
|
|
|
@ -3,25 +3,23 @@
|
|||
|
||||
product_key: inspec
|
||||
|
||||
rubygems:
|
||||
- inspec
|
||||
- inspec-core
|
||||
- inspec-bin:
|
||||
gemspec_path: ./inspec-bin/
|
||||
- inspec-core-bin:
|
||||
gemspec_path: ./inspec-bin/
|
||||
|
||||
pipelines:
|
||||
- habitat/build:
|
||||
- verify:
|
||||
definition: .expeditor/verify_public_dummy.pipeline.yml
|
||||
description: Keeping the verify pipeline alive to preserve the history
|
||||
public: true
|
||||
- verify/private:
|
||||
definition: .expeditor/verify_private.pipeline.yml
|
||||
public: false
|
||||
description: Pull Request validation tests
|
||||
env:
|
||||
- HAB_NONINTERACTIVE: "true"
|
||||
- HAB_NOCOLORING: "true"
|
||||
- HAB_STUDIO_SECRET_HAB_NONINTERACTIVE: "true"
|
||||
- docker/build
|
||||
- LANG: "C.UTF-8"
|
||||
- SLOW: 1
|
||||
- NO_AWS: 1
|
||||
- MT_CPU: 5
|
||||
- omnibus/release:
|
||||
env:
|
||||
# The git cache is corrupt more often than not. This always purges the cache.
|
||||
# https://chefio.atlassian.net/wiki/spaces/RELENGKB/pages/2204336129/Resolving+git+cache+build+errors+in+Omnibus
|
||||
- EXPIRE_CACHE: 1
|
||||
- IGNORE_ARTIFACTORY_RUBY_PROXY: true # Artifactory is throwing 500's when downloading some gems like ffi.
|
||||
- omnibus/adhoc:
|
||||
|
@ -29,41 +27,7 @@ pipelines:
|
|||
env:
|
||||
- ADHOC: true
|
||||
- EXPIRE_CACHE: 1
|
||||
- verify:
|
||||
description: Pull Request validation tests
|
||||
public: true
|
||||
env:
|
||||
- LANG: "C.UTF-8"
|
||||
- SLOW: 1
|
||||
- NO_AWS: 1
|
||||
- MT_CPU: 5
|
||||
- coverage:
|
||||
description: Unit test coverage
|
||||
# Private due to use of tokens
|
||||
trigger: pull_request
|
||||
env:
|
||||
- LANG: "C.UTF-8"
|
||||
- SLOW: 1
|
||||
- NO_AWS: 1
|
||||
- MT_CPU: 5
|
||||
# This has been disabled because it regularly hits Docker API rate limits and fails
|
||||
# - integration/resources:
|
||||
# description: Test core resources with test-kitchen.
|
||||
# definition: .expeditor/integration.resources.yml
|
||||
# trigger: pull_request
|
||||
- artifact/habitat:
|
||||
description: Execute tests against the habitat artifact
|
||||
definition: .expeditor/artifact.habitat.yml
|
||||
env:
|
||||
- HAB_NONINTERACTIVE: "true"
|
||||
- HAB_NOCOLORING: "true"
|
||||
- HAB_STUDIO_SECRET_HAB_NONINTERACTIVE: "true"
|
||||
trigger: pull_request
|
||||
|
||||
schedules:
|
||||
- name: integration_schedule
|
||||
description: Periodic Integration Testing
|
||||
cronline: "0 8 * * *"
|
||||
- IGNORE_ARTIFACTORY_RUBY_PROXY: true # Artifactory is throwing 500's when downloading some gems like ffi.
|
||||
|
||||
slack:
|
||||
notify_channel: inspec-notify
|
||||
|
@ -79,8 +43,6 @@ github:
|
|||
|
||||
release_branches:
|
||||
- main:
|
||||
version_constraint: 99.*
|
||||
- inspec-6:
|
||||
version_constraint: 6.*
|
||||
- inspec-5:
|
||||
version_constraint: 5.*
|
||||
|
@ -122,56 +84,11 @@ subscriptions:
|
|||
ignore_labels:
|
||||
- "Expeditor: Skip All"
|
||||
- "Expeditor: Skip Changelog"
|
||||
- trigger_pipeline:omnibus/adhoc:
|
||||
not_if: built_in:bump_version
|
||||
ignore_labels:
|
||||
- "Expeditor: Skip Omnibus"
|
||||
- "Expeditor: Skip All"
|
||||
- trigger_pipeline:artifact/habitat:
|
||||
only_if: built_in:bump_version
|
||||
ignore_labels:
|
||||
- "Expeditor: Skip Habitat"
|
||||
- "Expeditor: Skip All"
|
||||
- trigger_pipeline:omnibus/release:
|
||||
only_if: built_in:bump_version
|
||||
ignore_labels:
|
||||
- "Expeditor: Skip Omnibus"
|
||||
- "Expeditor: Skip All"
|
||||
- trigger_pipeline:habitat/build:
|
||||
only_if: built_in:bump_version
|
||||
ignore_labels:
|
||||
- "Expeditor: Skip Habitat"
|
||||
- "Expeditor: Skip All"
|
||||
- built_in:build_gem:
|
||||
only_if:
|
||||
- built_in:bump_version
|
||||
- workload: artifact_published:unstable:inspec:{{version_constraint}}
|
||||
actions:
|
||||
- trigger_pipeline:docker/build
|
||||
- bash:.expeditor/buildkite/wwwrelease.sh:
|
||||
post_commit: true
|
||||
- workload: artifact_published:current:inspec:{{version_constraint}}
|
||||
actions:
|
||||
- built_in:promote_docker_images
|
||||
- built_in:promote_habitat_packages
|
||||
- workload: project_promoted:{{agent_id}}:*
|
||||
actions:
|
||||
- built_in:promote_artifactory_artifact
|
||||
- workload: artifact_published:stable:inspec:{{version_constraint}}
|
||||
actions:
|
||||
- bash:.expeditor/update_dockerfile.sh
|
||||
- built_in:rollover_changelog
|
||||
- built_in:publish_rubygems
|
||||
- built_in:create_github_release
|
||||
- built_in:promote_docker_images
|
||||
- built_in:promote_habitat_packages
|
||||
- bash:.expeditor/publish-release-notes.sh:
|
||||
post_commit: true
|
||||
- purge_packages_chef_io_fastly:{{target_channel}}/inspec/latest:
|
||||
post_commit: true
|
||||
- bash:.expeditor/announce-release.sh:
|
||||
post_commit: true
|
||||
- built_in:notify_chefio_slack_channels
|
||||
- workload: pull_request_opened:{{github_repo}}:{{release_branch}}:*
|
||||
actions:
|
||||
- post_github_comment:.expeditor/templates/pull_request.mustache:
|
||||
|
|
|
@ -13,22 +13,16 @@ steps:
|
|||
command:
|
||||
- RAKE_TASK=test:lint /workdir/.expeditor/buildkite/verify.sh
|
||||
expeditor:
|
||||
secrets: true
|
||||
executor:
|
||||
docker:
|
||||
image: ruby:3.0
|
||||
|
||||
- label: run-tests-ruby-2.7
|
||||
command:
|
||||
- /workdir/.expeditor/buildkite/verify.sh
|
||||
expeditor:
|
||||
executor:
|
||||
docker:
|
||||
image: ruby:2.7
|
||||
|
||||
- label: run-tests-ruby-3.0
|
||||
command:
|
||||
- /workdir/.expeditor/buildkite/verify.sh
|
||||
expeditor:
|
||||
secrets: true
|
||||
executor:
|
||||
docker:
|
||||
image: ruby:3.0
|
||||
|
@ -37,6 +31,7 @@ steps:
|
|||
command:
|
||||
- /workdir/.expeditor/buildkite/verify.sh
|
||||
expeditor:
|
||||
secrets: true
|
||||
executor:
|
||||
docker:
|
||||
image: ruby:3.1
|
||||
|
@ -45,6 +40,7 @@ steps:
|
|||
command:
|
||||
- RAKE_TASK=test:isolated /workdir/.expeditor/buildkite/verify.sh
|
||||
expeditor:
|
||||
secrets: true
|
||||
executor:
|
||||
docker:
|
||||
image: ruby:3.0
|
||||
|
@ -53,6 +49,7 @@ steps:
|
|||
command:
|
||||
- RAKE_TASK=test:isolated /workdir/.expeditor/buildkite/verify.sh
|
||||
expeditor:
|
||||
secrets: true
|
||||
executor:
|
||||
docker:
|
||||
image: ruby:3.1
|
||||
|
@ -61,6 +58,7 @@ steps:
|
|||
command:
|
||||
- /workdir/.expeditor/buildkite/verify.ps1
|
||||
expeditor:
|
||||
secrets: true
|
||||
executor:
|
||||
docker:
|
||||
environment:
|
13
.expeditor/verify_public_dummy.pipeline.yml
Normal file
13
.expeditor/verify_public_dummy.pipeline.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
# This pipeline is a dummy pipeline that does nothing. It exists simply
|
||||
# because removing the config from expeditor
|
||||
# will delete the pipeline, and we want to preserve the
|
||||
# pipeline history.
|
||||
|
||||
# TODO: Simplify the pipeline in future
|
||||
steps:
|
||||
- label: "keeping-verify-pipeline-alive"
|
||||
expeditor:
|
||||
executor:
|
||||
docker:
|
||||
commands:
|
||||
- "echo ## This pipeline does nothing. The actual verify pipeline is verify/private. This exists as a placeholder to prevent deletion of the historical main verify pipeline."
|
13
.github/dependabot.yml
vendored
13
.github/dependabot.yml
vendored
|
@ -36,16 +36,3 @@ updates:
|
|||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: bundler
|
||||
target-branch: "inspec-6"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: bundler
|
||||
target-branch: "inspec-6"
|
||||
directory: "/omnibus"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
|
|
129
CHANGELOG.md
129
CHANGELOG.md
|
@ -1,17 +1,132 @@
|
|||
# Change Log
|
||||
<!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ -->
|
||||
<!-- latest_release 5.22.13 -->
|
||||
|
||||
<!-- latest_release 6.4.45 -->
|
||||
## [v6.4.45](https://github.com/inspec/inspec/tree/v6.4.45) (2023-08-17)
|
||||
|
||||
#### Merged Pull Requests
|
||||
- Bump omnibus-software from `4b08f0b` to `3268356` in /omnibus [#6587](https://github.com/inspec/inspec/pull/6587) ([dependabot[bot]](https://github.com/dependabot[bot]))
|
||||
<!-- latest_release -->
|
||||
|
||||
<!-- release_rollup since=5.22.13 -->
|
||||
### Changes since 5.22.13 release
|
||||
|
||||
#### Merged Pull Requests
|
||||
- Bump omnibus-software from `4b08f0b` to `3268356` in /omnibus [#6587](https://github.com/inspec/inspec/pull/6587) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.45 -->
|
||||
- Missing changes from main [#6564](https://github.com/inspec/inspec/pull/6564) ([sathish-progress](https://github.com/sathish-progress)) <!-- 6.4.44 -->
|
||||
- CHEF-4010 make a clean exit for License list command [#6552](https://github.com/inspec/inspec/pull/6552) ([sathish-progress](https://github.com/sathish-progress)) <!-- 6.4.43 -->
|
||||
- CHEF-4818 revise inspec parallel docs content [#6586](https://github.com/inspec/inspec/pull/6586) ([IanMadd](https://github.com/IanMadd)) <!-- 6.4.42 -->
|
||||
- CHEF-3916 Add section on inspec license subcommand to online docs [#6583](https://github.com/inspec/inspec/pull/6583) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.41 -->
|
||||
- Revert omnibus-software bump (6576) - ffi-yajl issue [#6585](https://github.com/inspec/inspec/pull/6585) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.40 -->
|
||||
- Disable git caching in omnibus builds [#6584](https://github.com/inspec/inspec/pull/6584) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.39 -->
|
||||
- CHEF-4559 Extended support to inspec parallel for reporters using config [#6578](https://github.com/inspec/inspec/pull/6578) ([Nik08](https://github.com/Nik08)) <!-- 6.4.38 -->
|
||||
- Bump omnibus-software from `4b08f0b` to `3268356` in /omnibus [#6576](https://github.com/inspec/inspec/pull/6576) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.37 -->
|
||||
- Foreport 6568 [#6579](https://github.com/inspec/inspec/pull/6579) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.36 -->
|
||||
- CHEF-4080: Point to latest EULA in GUI installers [#6580](https://github.com/inspec/inspec/pull/6580) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.35 -->
|
||||
- CHEF-3832: Fix for InSpec Parallel fails to fetch remote profiles due to cache contention. [#6546](https://github.com/inspec/inspec/pull/6546) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.4.34 -->
|
||||
- Add chef-licensing, syncing from inspec-prime repo inspec-6 branch [#6559](https://github.com/inspec/inspec/pull/6559) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.33 -->
|
||||
- inspec-6 CI - Add secrets: true to private verify pipeline, delete ruby 2.7 config [#6558](https://github.com/inspec/inspec/pull/6558) ([clintoncwolfe](https://github.com/clintoncwolfe))
|
||||
- forcing private in the configuration file [#6556](https://github.com/inspec/inspec/pull/6556) ([sean-simmons-progress](https://github.com/sean-simmons-progress))
|
||||
- Adds test for licensing_config [#57](https://github.com/inspec/inspec-prime/pull/57) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.4.33 -->
|
||||
- Configure to use `Inspec::Log` in Chef Licensing [#67](https://github.com/inspec/inspec-prime/pull/67) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.32 -->
|
||||
- Crossport public 6549: Drop testing on EOL ruby 2.7, and run linter on Ruby 3.1 [#76](https://github.com/inspec/inspec-prime/pull/76) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.31 -->
|
||||
- Case correction of product name in licensing config [#78](https://github.com/inspec/inspec-prime/pull/78) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.30 -->
|
||||
- Foreport - Add postgres support for custom port with a socket connection [#40](https://github.com/inspec/inspec-prime/pull/40) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.29 -->
|
||||
- Bump omnibus-software from `88169e3` to `4b08f0b` in /omnibus [#73](https://github.com/inspec/inspec-prime/pull/73) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.28 -->
|
||||
- CHEF-3759 Crossport public 6540 Fix for inspec parallel on windows crashing due to error log rename [#74](https://github.com/inspec/inspec-prime/pull/74) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.27 -->
|
||||
- Foreports #6526 and #6541: Update Docker base image to be ubuntu 22.04 [#64](https://github.com/inspec/inspec-prime/pull/64) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.26 -->
|
||||
- Crossport Public 6545 Fix for InSpec Parallel hangs on certain CIS profiles [#71](https://github.com/inspec/inspec-prime/pull/71) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.26 -->
|
||||
- Bump omnibus from `15122f2` to `9c0643a` in /omnibus [#70](https://github.com/inspec/inspec-prime/pull/70) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.25 -->
|
||||
- Bump berkshelf from 8.0.2 to 8.0.7 in /omnibus [#63](https://github.com/inspec/inspec-prime/pull/63) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.24 -->
|
||||
- Foreport #6523: Update RSpec to 3.12 [#65](https://github.com/inspec/inspec-prime/pull/65) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.23 -->
|
||||
- Bump omnibus from `cf97613` to `15122f2` in /omnibus [#62](https://github.com/inspec/inspec-prime/pull/62) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.22 -->
|
||||
- Bump omnibus-software from `225e357` to `88169e3` in /omnibus [#61](https://github.com/inspec/inspec-prime/pull/61) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.21 -->
|
||||
- CHEF-3704 Modify help for local licensing service mode and other distros [#59](https://github.com/inspec/inspec-prime/pull/59) ([Nik08](https://github.com/Nik08)) <!-- 6.4.20 -->
|
||||
- restrict license commands only to inspec distro [#58](https://github.com/inspec/inspec-prime/pull/58) ([sathish-progress](https://github.com/sathish-progress)) <!-- 6.4.19 -->
|
||||
- CHEF-3184 Error handling for inspec license add command - disabled in local mode [#52](https://github.com/inspec/inspec-prime/pull/52) ([Nik08](https://github.com/Nik08)) <!-- 6.4.18 -->
|
||||
- CHEF-3403: Default server URL to production value [#50](https://github.com/inspec/inspec-prime/pull/50) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.17 -->
|
||||
- CHEF-3186: Remove fetching of bearer auth token from vault [#48](https://github.com/inspec/inspec-prime/pull/48) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.16 -->
|
||||
- CHEF 83 Revert attestations changes [#47](https://github.com/inspec/inspec-prime/pull/47) ([sathish-progress](https://github.com/sathish-progress)) <!-- 6.4.15 -->
|
||||
- Foreports 6489 (CHEF-1458 Multiple values changes in SimpleConfig library) [#28](https://github.com/inspec/inspec-prime/pull/28) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.14 -->
|
||||
- Foreport - Add nftables resources (#6499) [#44](https://github.com/inspec/inspec-prime/pull/44) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.13 -->
|
||||
- Foreport - Update host resource to resolve all ipaddresses (#6481) [#39](https://github.com/inspec/inspec-prime/pull/39) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.12 -->
|
||||
- Foreport - Bump rack from 2.2.6.2 to 2.2.6.4 in /omnibus (#6490) [#42](https://github.com/inspec/inspec-prime/pull/42) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.11 -->
|
||||
- Foreport - fix: ensure Invoke-WebRequest headers can be configured (#6484) [#41](https://github.com/inspec/inspec-prime/pull/41) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.10 -->
|
||||
- Foreport - CHEF-2438 Add train-kubernetes to inspec gemspec (#6512) [#43](https://github.com/inspec/inspec-prime/pull/43) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.9 -->
|
||||
- Foreport - Clarify key_rsa docs regarding SSH keys (#6507) [#45](https://github.com/inspec/inspec-prime/pull/45) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.8 -->
|
||||
- CHEF-3105 Fix windows openssl issue [#37](https://github.com/inspec/inspec-prime/pull/37) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.7 -->
|
||||
- CHEF-2743: Set chef executable name to display in help messages of chef-licensing [#34](https://github.com/inspec/inspec-prime/pull/34) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.6 -->
|
||||
- CHEF-2994: Add license command to list of allowed commands [#35](https://github.com/inspec/inspec-prime/pull/35) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.5 -->
|
||||
- CHEF-1957: Update chef-licesing api call `license_keys` to `fetch_and_persist` [#30](https://github.com/inspec/inspec-prime/pull/30) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.4 -->
|
||||
- Remove kitchen group from Gemfile [#31](https://github.com/inspec/inspec-prime/pull/31) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.3 -->
|
||||
- CHEF-52: Add licensing information to help output [#27](https://github.com/inspec/inspec-prime/pull/27) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.2 -->
|
||||
- Add command to list license information [#10](https://github.com/inspec/inspec-prime/pull/10) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.1 -->
|
||||
- Licensing - Integrates Software Entitlement [#13](https://github.com/inspec/inspec-prime/pull/13) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.4.0 -->
|
||||
- Integration of chef licensing with inspec [#12](https://github.com/inspec/inspec-prime/pull/12) ([Nik08](https://github.com/Nik08)) <!-- 6.3.0 -->
|
||||
- CI - Use License Key and API Key Secrets from Vault [#26](https://github.com/inspec/inspec-prime/pull/26) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.49 -->
|
||||
- Update Gemfile to add artifactory as source for chef-licensing gem dependency [#25](https://github.com/inspec/inspec-prime/pull/25) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.48 -->
|
||||
- testing version bump and pipeline creation [#16](https://github.com/inspec/inspec-prime/pull/16) ([sean-simmons-progress](https://github.com/sean-simmons-progress)) <!-- 6.2.47 -->
|
||||
- CHEF-1267 Add omnibus release and adhoc pipelines [#15](https://github.com/inspec/inspec-prime/pull/15) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.46 -->
|
||||
- testing version bump [#9](https://github.com/inspec/inspec-prime/pull/9) ([sean-simmons-progress](https://github.com/sean-simmons-progress)) <!-- 6.2.45 -->
|
||||
- Forport 6388 [#6477](https://github.com/inspec/inspec/pull/6477) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.44 -->
|
||||
- Foreport 6360 [#6476](https://github.com/inspec/inspec/pull/6476) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.43 -->
|
||||
- Foreport-6423 [#6474](https://github.com/inspec/inspec/pull/6474) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.42 -->
|
||||
- Foreport 6442 [#6473](https://github.com/inspec/inspec/pull/6473) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.41 -->
|
||||
- Foreport 6403 [#6470](https://github.com/inspec/inspec/pull/6470) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.40 -->
|
||||
- Foreport 6386 [#6469](https://github.com/inspec/inspec/pull/6469) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.39 -->
|
||||
- Foreport-6410 [#6468](https://github.com/inspec/inspec/pull/6468) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.38 -->
|
||||
- Foreport-6408 Fix profile gem dependency loading issue when dependent gem is required inside profile libraries. [#6467](https://github.com/inspec/inspec/pull/6467) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.37 -->
|
||||
- Foreport #6398: Updates release process docs as per current changes (#6398) [#6439](https://github.com/inspec/inspec/pull/6439) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.36 -->
|
||||
- Foreport 6401: Fix for mongodb_session resource prints debug level of information in profile run result. [#6438](https://github.com/inspec/inspec/pull/6438) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.35 -->
|
||||
- Foreport-6384 [#6466](https://github.com/inspec/inspec/pull/6466) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.34 -->
|
||||
- Foreport #6384 RAKE TEST: Fix rake task for docs:cli [#6437](https://github.com/inspec/inspec/pull/6437) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.33 -->
|
||||
- Foreport #6367 CFINSPEC-522: Remove rake tasks which are no longer used [#6436](https://github.com/inspec/inspec/pull/6436) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.32 -->
|
||||
- Foreport-6395 [#6444](https://github.com/inspec/inspec/pull/6444) ([ahasunos](https://github.com/ahasunos)) <!-- 6.2.31 -->
|
||||
- Foreport-6385 [#6447](https://github.com/inspec/inspec/pull/6447) ([ahasunos](https://github.com/ahasunos)) <!-- 6.2.30 -->
|
||||
- Foreport #6377 CFINSPEC-542 Bug fix for profiles with dependent profiles (#6377) [#6435](https://github.com/inspec/inspec/pull/6435) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.29 -->
|
||||
- Foreport 6369 to inspec-6 Extended file format support for waivers: JSON & CSV only [#6371](https://github.com/inspec/inspec/pull/6371) ([Nik08](https://github.com/Nik08)) <!-- 6.2.28 -->
|
||||
- Foreport-6381 [#6451](https://github.com/inspec/inspec/pull/6451) ([ahasunos](https://github.com/ahasunos)) <!-- 6.2.27 -->
|
||||
- Foreport-6378 [#6453](https://github.com/inspec/inspec/pull/6453) ([ahasunos](https://github.com/ahasunos)) <!-- 6.2.26 -->
|
||||
- Foreport 6341: Use Ruby 3.1.2 in Omnibus build [#6441](https://github.com/inspec/inspec/pull/6441) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.25 -->
|
||||
- Foreport #6337 Fix undefined method 'summary' for Gem::SourceFetchProblem (NoMethodError) when air gapped [#6434](https://github.com/inspec/inspec/pull/6434) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.25 -->
|
||||
- Foreport 6342 Fix env smoke test by updating ERB.new in `inspec env`; add additional test [#6440](https://github.com/inspec/inspec/pull/6440) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.24 -->
|
||||
- Foreport-6344 Fixing typo in user_permissions [#6465](https://github.com/inspec/inspec/pull/6465) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.23 -->
|
||||
- Foreport 6340: Fix for habitat build failure [#6461](https://github.com/inspec/inspec/pull/6461) ([Nik08](https://github.com/Nik08)) <!-- 6.2.22 -->
|
||||
- Foreport-6334: CFINSPEC-393 - Fix train-kubernetes plugin load issue [#6464](https://github.com/inspec/inspec/pull/6464) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.21 -->
|
||||
- Foreport 6240: Update chefstyle from 2.0.x to 2.2.2 to use RuboCop 1.25.1 [#6458](https://github.com/inspec/inspec/pull/6458) ([Nik08](https://github.com/Nik08)) <!-- 6.2.20 -->
|
||||
- Foreport #6262 Prevent negative status from crashing launchctl service resource [#6433](https://github.com/inspec/inspec/pull/6433) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.19 -->
|
||||
- Foreport-6330: Remove Windows Ruby 3.0 testing [#6452](https://github.com/inspec/inspec/pull/6452) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.18 -->
|
||||
- Foreport #6298: CFINSPEC-493 update signing_identity [#6448](https://github.com/inspec/inspec/pull/6448) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.17 -->
|
||||
- Foreport #6324 Fix Ruby 2.7 Bundle Installs on CI Verify Pipeline [#6446](https://github.com/inspec/inspec/pull/6446) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.16 -->
|
||||
- Foreport-6289: Fix for omnibus build failure on Windows [#6463](https://github.com/inspec/inspec/pull/6463) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.15 -->
|
||||
- Foreport-6274 Bump omnibus-software from `1d540dc` to `7d0e0fe` in /omnibus [#6462](https://github.com/inspec/inspec/pull/6462) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.14 -->
|
||||
- Foreport - 6227 [#6460](https://github.com/inspec/inspec/pull/6460) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.13 -->
|
||||
- Foreport #6239: Sync up the default branch as main [#6455](https://github.com/inspec/inspec/pull/6455) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.12 -->
|
||||
- Foreport 6304 RESOURCE-527 Add an inspec init template for alicloud [#6432](https://github.com/inspec/inspec/pull/6432) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.11 -->
|
||||
- Foreport #6329 to inspec-6 - Update readme for usage via Docker (CFINSPEC-516) [#6333](https://github.com/inspec/inspec/pull/6333) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.10 -->
|
||||
- CFINSPEC-479 Inspec parallel breaking fix [#6272](https://github.com/inspec/inspec/pull/6272) ([Nik08](https://github.com/Nik08)) <!-- 6.2.9 -->
|
||||
- Added child-status reporter in features.yaml list [#6288](https://github.com/inspec/inspec/pull/6288) ([Nik08](https://github.com/Nik08)) <!-- 6.2.8 -->
|
||||
- Foreport #6267 to inspec-6 [#6283](https://github.com/inspec/inspec/pull/6283) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.7 -->
|
||||
- Foreport 6257 to inspec 6 [#6279](https://github.com/inspec/inspec/pull/6279) ([Nik08](https://github.com/Nik08)) <!-- 6.2.6 -->
|
||||
- Foreport 6229 to inspec 6 [#6277](https://github.com/inspec/inspec/pull/6277) ([Nik08](https://github.com/Nik08)) <!-- 6.2.5 -->
|
||||
- Foreport 6261 to InSpec 6 [#6276](https://github.com/inspec/inspec/pull/6276) ([ahasunos](https://github.com/ahasunos)) <!-- 6.2.4 -->
|
||||
- Foreport 6043 to inspec-6 [#6278](https://github.com/inspec/inspec/pull/6278) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.3 -->
|
||||
- Foreport 6243 to inspec-6 [#6275](https://github.com/inspec/inspec/pull/6275) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.2 -->
|
||||
- Foreport 6238 to inspec-6 [#6280](https://github.com/inspec/inspec/pull/6280) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.1 -->
|
||||
- Feature Config File and Logger Support [#6260](https://github.com/inspec/inspec/pull/6260) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.0 -->
|
||||
- CFINSPEC-246/CFINSPEC-247 Attestation changes for N/R outcomes [#6222](https://github.com/inspec/inspec/pull/6222) ([Nik08](https://github.com/Nik08)) <!-- 6.1.0 -->
|
||||
- CFINSPEC-452 Added Inspec parallel logging for warn/error [#6245](https://github.com/inspec/inspec/pull/6245) ([Nik08](https://github.com/Nik08)) <!-- 6.0.1 -->
|
||||
|
||||
<!-- latest_stable_release 5.22.13 -->
|
||||
## [v5.22.13](https://github.com/inspec/inspec/tree/v5.22.13) (2023-08-16)
|
||||
|
||||
#### Merged Pull Requests
|
||||
- CHEF-65: Create inspec-5 release branch in Expeditor and Dependabot configuration [#6591](https://github.com/inspec/inspec/pull/6591) ([Vasu1105](https://github.com/Vasu1105))
|
||||
<!-- latest_release -->
|
||||
<!-- latest_stable_release -->
|
||||
|
||||
<!-- release_rollup since=5.22.3 -->
|
||||
### Changes since 5.22.3 release
|
||||
## [v5.22.12](https://github.com/inspec/inspec/tree/v5.22.12) (2023-08-09)
|
||||
|
||||
#### Merged Pull Requests
|
||||
- CHEF-65: Create inspec-5 release branch in Expeditor and Dependabot configuration [#6591](https://github.com/inspec/inspec/pull/6591) ([Vasu1105](https://github.com/Vasu1105)) <!-- 5.22.13 -->
|
||||
- CHEF-5200 Waived controls are not getting waived (skipped) in case of failure at resource level. [#6588](https://github.com/inspec/inspec/pull/6588) ([Vasu1105](https://github.com/Vasu1105)) <!-- 5.22.12 -->
|
||||
- CHEF-4080: Point to latest EULA in GUI installers for InSpec-5 [#6582](https://github.com/inspec/inspec/pull/6582) ([ahasunos](https://github.com/ahasunos)) <!-- 5.22.11 -->
|
||||
- CHEF-4115 Added ability to merge reporter configurations from both CLI and config [#6568](https://github.com/inspec/inspec/pull/6568) ([Nik08](https://github.com/Nik08)) <!-- 5.22.10 -->
|
||||
|
@ -24,9 +139,7 @@
|
|||
- Update Docker base image to be ubuntu 22.04 [#6526](https://github.com/inspec/inspec/pull/6526) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 5.22.5 -->
|
||||
- Update RSpec to 3.12 [#6523](https://github.com/inspec/inspec/pull/6523) ([pirj](https://github.com/pirj)) <!-- 5.22.5 -->
|
||||
- CHEF-1631 Clarify that command timeout default was withdrawn [#6511](https://github.com/inspec/inspec/pull/6511) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 5.22.4 -->
|
||||
<!-- release_rollup -->
|
||||
|
||||
<!-- latest_stable_release -->
|
||||
## [v5.22.3](https://github.com/inspec/inspec/tree/v5.22.3) (2023-05-18)
|
||||
|
||||
#### Merged Pull Requests
|
||||
|
@ -49,7 +162,7 @@
|
|||
- Bump rack from 2.2.6.2 to 2.2.6.4 in /omnibus [#6490](https://github.com/inspec/inspec/pull/6490) ([dependabot[bot]](https://github.com/dependabot[bot]))
|
||||
- fix: ensure Invoke-WebRequest headers can be configured [#6484](https://github.com/inspec/inspec/pull/6484) ([amlodzianowski](https://github.com/amlodzianowski))
|
||||
- For #6493 : Add postgres_session support for custom port with a socket connection [#6494](https://github.com/inspec/inspec/pull/6494) ([Taknok](https://github.com/Taknok))
|
||||
<!-- latest_stable_release -->
|
||||
|
||||
|
||||
## [v5.21.29](https://github.com/inspec/inspec/tree/v5.21.29) (2023-01-24)
|
||||
|
||||
|
|
17
Gemfile
17
Gemfile
|
@ -48,19 +48,6 @@ group :deploy do
|
|||
gem "inquirer"
|
||||
end
|
||||
|
||||
group :kitchen do
|
||||
gem "berkshelf"
|
||||
|
||||
# Chef 18 requires ruby 3
|
||||
if Gem.ruby_version >= Gem::Version.new("3.0.0")
|
||||
gem "chef", ">= 17.0"
|
||||
else
|
||||
# Ruby 2.7 presumably - TODO remove this when 2.7 is sunsetted
|
||||
gem "chef", "~> 16.0"
|
||||
end
|
||||
|
||||
gem "test-kitchen", ">= 2.8"
|
||||
gem "kitchen-inspec", ">= 2.0"
|
||||
gem "kitchen-dokken", ">= 2.11"
|
||||
gem "git"
|
||||
source "https://artifactory-internal.ps.chef.co/artifactory/api/gems/omnibus-gems-local/" do
|
||||
gem "chef-licensing"
|
||||
end
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
5.22.13
|
||||
6.4.45
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
# Attestations
|
||||
|
||||
## Use Cases
|
||||
|
||||
As a compliance officer,
|
||||
I want to mark skipped controls as manually passed or failed
|
||||
so that I can manually complete the profile.
|
||||
|
||||
As a compliance officer,
|
||||
I want to set an expiration date and a justification for my attestations
|
||||
so that I can control their application.
|
||||
|
||||
As a compliance officer,
|
||||
I want flexibility in the file format accepted by the attestations system (XLSX, YAML, CSV, JSON),
|
||||
so that I can use a familiar file format.
|
||||
|
||||
When used with Enhanced Outcomes, this becomes handling `Not Reviewed` controls.
|
||||
|
||||
## Mechanism
|
||||
|
||||
### CLI option desirable
|
||||
|
||||
`inspec exec profilename --attestation-file file.???`
|
||||
|
||||
The new option is named like `--waiver-file` - singular, with `-file`. You may provide multiple arguments for the option.
|
||||
|
||||
The file can be any of the following formats: `YAML`, `XLSX`, `CSV`, or `JSON`.
|
||||
|
||||
#### YAML and JSON
|
||||
|
||||
An array of Hashes.
|
||||
|
||||
#### XLSX and CSV
|
||||
|
||||
XLSX is the first sheet in the file.
|
||||
|
||||
Both formats assume a header row.
|
||||
|
||||
### Fields in the file
|
||||
|
||||
#### control_id
|
||||
|
||||
_Required_. Matches control ID of the control.
|
||||
|
||||
#### justification
|
||||
|
||||
_Required_. Free text field, used as an explanation for the control when displayed.
|
||||
|
||||
#### evidence_url
|
||||
|
||||
_Optional_. URL to some evidence, determined by the user, supports the justification.
|
||||
|
||||
#### expiration_date
|
||||
|
||||
_Optional_. If present, the attestation expires at the end of the date given.
|
||||
|
||||
#### status
|
||||
|
||||
_Optional_.
|
||||
|
||||
Default `passed`. If the attestation should indicate that the control is a failure, set this to `failed`.
|
||||
|
||||
### Implementation
|
||||
|
||||
When running, at the **RunData** stage, attestations are handled by the following process:
|
||||
|
||||
1. Locate matching controls by matching the control ID.
|
||||
|
||||
2. Inject an artificial test result into the control. Use the attestation justification as the result message.
|
||||
|
||||
3. If the attestation is expired, set the new test result to Skip.
|
||||
|
||||
4. If the attestation is not expired, set the new test result to the status given on the attestation data (default pass).
|
||||
|
||||
5. Record a copy of the attestation data structure in the Control RunData structure.
|
||||
|
||||
### Compatibility
|
||||
|
||||
To support backward compatibility with existing MITRE work, support will be added (but not otherwise documented) for the following fields:
|
||||
|
||||
* explanation - the equivalent of justification
|
||||
* updated (Date) and frequency (string enum) - together, the equivalent of the expiration date.
|
|
@ -633,6 +633,30 @@ This subcommand has the following additional options:
|
|||
|
||||
</dl>
|
||||
|
||||
## license
|
||||
|
||||
Subcommands for interacting with the Chef licensing system.
|
||||
|
||||
`inspec license` supports two subcommands, `add` and `list`.
|
||||
|
||||
#### Add
|
||||
|
||||
Add a Chef license.
|
||||
|
||||
Not applicable for users running the local licensing service.
|
||||
|
||||
```bash
|
||||
inspec license add
|
||||
```
|
||||
|
||||
#### List
|
||||
|
||||
Run license diagnostics and output the details of your current Chef license configuration.
|
||||
|
||||
```bash
|
||||
inspec license list
|
||||
```
|
||||
|
||||
## run_context
|
||||
|
||||
Used to test run-context detection
|
||||
|
|
237
docs-chef-io/content/inspec/parallel.md
Normal file
237
docs-chef-io/content/inspec/parallel.md
Normal file
|
@ -0,0 +1,237 @@
|
|||
+++
|
||||
title = "InSpec Parallel"
|
||||
draft = false
|
||||
gh_repo = "inspec"
|
||||
|
||||
[menu]
|
||||
[menu.inspec]
|
||||
title = "InSpec Parallel"
|
||||
identifier = "inspec/parallel.md InSpec Parallel"
|
||||
parent = "inspec"
|
||||
weight = 25
|
||||
+++
|
||||
|
||||
Chef InSpec Parallel can automatically manage multiple profile executions in parallel on a system targeting several remote systems and environments.
|
||||
It manages multiple processes, their status updates, their exit codes, and user updates.
|
||||
All target operating systems and environments that can be addressed using `--target` are supported, and it is supported on Windows, MacOS, and Linux environments.
|
||||
|
||||
InSpec Parallel is a new feature in **Chef InSpec 6**.
|
||||
|
||||
{{< note >}}
|
||||
|
||||
Currently, `inspec parallel` only supports the `exec` command.
|
||||
|
||||
{{< /note >}}
|
||||
|
||||
## How to use InSpec Parallel
|
||||
|
||||
The following example shows you how to execute the **Dev-Sec SSH Baseline** profile against five servers in parallel using `inpec parallel exec`.
|
||||
|
||||
1. Create an [option file](#option-file) that contains the CLI options that are passed to `inspec exec parallel`.
|
||||
|
||||
The option file contains one invocation per line and specifies all options in each invocation.
|
||||
|
||||
```text
|
||||
# five-servers.txt
|
||||
# Option file for running against multiple SSH targets
|
||||
-t ssh://server1 --reporter cli:server1.out
|
||||
-t ssh://server2 --reporter cli:server2.out
|
||||
-t ssh://server3 --reporter cli:server3.out
|
||||
-t ssh://server4 --reporter cli:server4.out
|
||||
-t ssh://server5 --reporter cli:server5.out
|
||||
```
|
||||
|
||||
1. Specify the option file that InSpec Parallel executes using the `-o` or `--option_file` flag in the InSpec CLI.
|
||||
|
||||
```bash
|
||||
inspec parallel exec https://github.com/dev-sec/ssh-baseline -o five-servers.txt -i file_name.pem
|
||||
```
|
||||
|
||||
As InSpec Parallel runs, it shows the progress (percentage of controls completed) of each invocation, the process ID of each job, and writes log and error data to the `logs/` directory with each log file named after the process ID.
|
||||
|
||||
```bash
|
||||
Press CTL+C to stop
|
||||
InSpec Parallel
|
||||
Running 5 invocations in 4 slots
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
Slot 1 Slot 2 Slot 3 Slot 4
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
50132: 0.0% 50133: 12.5% 50134: 12.5% Done
|
||||
```
|
||||
|
||||
## Option file
|
||||
|
||||
An option file is a text file that contains options passed to `inspec parallel`.
|
||||
Chef InSpec ignores comments (starting with a `#`) and blank lines in an option file.
|
||||
Chef InSpec invokes `inspec parallel` on each non-commented and non-blank line.
|
||||
|
||||
The only requirement is that every invocation in an option file must have a `--reporter` option.
|
||||
The reporter option must write to a file or use the `automate` reporter to send an API post to a Chef Automate service.
|
||||
For details of the available reporters and the full syntax of the reporter option, see the [Chef InSpec Reporter documentation]({{< relref "/inspec/reporters" >}}).
|
||||
|
||||
The simplest option file might look like this:
|
||||
|
||||
```text
|
||||
# simple.txt
|
||||
# Run five invocations, saving the output as ordinal names
|
||||
--reporter cli:first.out
|
||||
--reporter cli:second.out
|
||||
--reporter cli:third.out
|
||||
--reporter cli:fourth.out
|
||||
--reporter cli:fifth.out
|
||||
```
|
||||
|
||||
For this example, InSpec Parallel would run the same profile on the same target five times, it would send the output to each of the five reporters listed in the option file, and you would specify the target and profile when you invoke `inspec parallel exec` in the command line.
|
||||
|
||||
You can pass any options on the invocation line, including `--controls` (to divide a profile into sections), `--input` (to parameterize a profile and possibly target different resources), and `--target` (to target different machines or environments).
|
||||
|
||||
See the [Examples section](#examples) for more detail on how you can use an option file.
|
||||
|
||||
### Embedded Ruby templating
|
||||
|
||||
You can add Embedded RuBy (ERB) template escapes and Chef InSpec will evaluate it as an ERB template.
|
||||
You can directly embed Ruby code into your option file, including loops and conditionals.
|
||||
The rendered output of the option file is used as invocations.
|
||||
This is especially useful with the `--dry-run` option.
|
||||
|
||||
The most common ERB templating is to use the `pid` variable to reference the process ID of the child process.
|
||||
See the [Examples](#name-json-output-files-with-process-id) section for more information.
|
||||
|
||||
### Executable script
|
||||
|
||||
If the name of the option file ends in `.sh` (MacOS, Linux) or `.ps1` (Windows), InSpec Parallel executes the script and uses the standard output as the option file.
|
||||
|
||||
{{< note >}}
|
||||
|
||||
This feature is experimental and we would love to hear [feedback](https://github.com/inspec/inspec/issues/new/choose) from you.
|
||||
|
||||
{{< /note >}}
|
||||
|
||||
## Options
|
||||
|
||||
InSpec Parallel accepts options from the subcommand that it's managing. It also accepts the following options:
|
||||
|
||||
`--bg`
|
||||
: The `--bg` option silences all output from the command and runs it in the background. InSpec Parallel will still write log files with the `--bg` option.
|
||||
|
||||
`--dry-run`
|
||||
: The `--dry-run` option interprets the option file but does not execute it. Chef InSpec outputs the lines that would have been executed to the standard output. If you add `--verbose`, you can see all the CLI defaults that implicitly get added.
|
||||
|
||||
: {{< note >}}
|
||||
|
||||
When calling `--dry-run`, you may notice that an extra reporter gets added to your invocation, `--reporter child-status`. This reporter is a special streaming reporter used to report status from the running child processes to the parent process and is a necessary part of the plumbing of InSpec Parallel.
|
||||
|
||||
{{< /note >}}
|
||||
|
||||
`-j`
|
||||
`--jobs`
|
||||
: Use the `-j` or `--jobs` option to specify how many job slots InSpec Parallel uses.
|
||||
InSpec Parallel defaults to the number of hyperthreaded cores on your machine (for example, a dual-core machine with hyperthreading defaults to four jobs).
|
||||
The default is usually reasonable, but experimentation may be rewarding.
|
||||
|
||||
`-o`
|
||||
`--option_file`
|
||||
: Use the `-o` or `--option_file` option in the command line to specify the option file that InSpec Parallel will run.
|
||||
|
||||
## Examples
|
||||
|
||||
### Use the same options for each invocation
|
||||
|
||||
`inspec parallel exec` accepts all options that `inspec exec` does and passes them to each invocation as defaults.
|
||||
This means that you do not have to specify repetitive options that are constant across all the invocations in an option file.
|
||||
|
||||
For example, if all machines take the same SSH key, you can specify it once on the top-level command line.
|
||||
|
||||
```text
|
||||
# three-servers.txt
|
||||
# Option file for running against multiple SSH targets
|
||||
-t ssh://server1 --reporter cli:server1.out
|
||||
-t ssh://server2 --reporter cli:server2.out
|
||||
-t ssh://server3 --reporter cli:server3.out
|
||||
```
|
||||
|
||||
```bash
|
||||
inspec parallel exec profile_name -o three-servers.txt -i file_name.pem
|
||||
```
|
||||
|
||||
### Name JSON output files with process ID
|
||||
|
||||
In this example, the `json` reporter saves output log files in the `logs` directory and names each one after the process ID using the `pid` ERB variable.
|
||||
This technique would work with any [reporter]({{< relref "/inspec/reporters" >}}) that can write to a file.
|
||||
|
||||
```text
|
||||
# pid-named-output.txt
|
||||
# Option file in which the output is named after the PID of the process
|
||||
--reporter json:logs/<%= pid %>.json
|
||||
--reporter json:logs/<%= pid %>.json
|
||||
--reporter json:logs/<%= pid %>.json
|
||||
--reporter json:logs/<%= pid %>.json
|
||||
```
|
||||
|
||||
After this profile is executed, the `logs` directory would have the following files:
|
||||
|
||||
- 1000.log
|
||||
- 1000.json
|
||||
- 1001.log
|
||||
- 1001.json
|
||||
- 1002.log
|
||||
- 1002.json
|
||||
- 1003.log
|
||||
- 1003.json
|
||||
|
||||
### Run the same profile on different targets
|
||||
|
||||
You can run the same profile on multiple targets by specifying each target in the option file using the `-t` or `--target` option.
|
||||
|
||||
```text
|
||||
# five-servers.txt
|
||||
# Option file for running against multiple SSH targets
|
||||
-t ssh://server1 --reporter cli:server1.out
|
||||
-t ssh://server2 --reporter cli:server2.out
|
||||
-t ssh://server3 --reporter cli:server3.out
|
||||
-t ssh://server4 --reporter cli:server4.out
|
||||
-t ssh://server5 --reporter cli:server5.out
|
||||
```
|
||||
|
||||
Then specify the profile and the option file in the command line.
|
||||
|
||||
```bash
|
||||
inspec parallel exec https://github.com/dev-sec/ssh-baseline -o five-servers.txt -i file_name.pem
|
||||
```
|
||||
|
||||
If you have many or variable targets to run against, consider using ERB templating to read the list of targets after reading them from a CSV file or connecting to an API. You can also use a script to list your targets.
|
||||
|
||||
### Run different profiles on the same target
|
||||
|
||||
To run different profiles on the same target, specify the profile at the front of the invocation in the option file.
|
||||
|
||||
```text
|
||||
# multi-profile.txt
|
||||
https://github.com/dev-sec/ssh-baseline --reporter cli:ssh-baseline.out
|
||||
https://github.com/dev-sec/linux-baseline --reporter cli:linux-baseline.out
|
||||
```
|
||||
|
||||
Then invoke InSpec parallel by passing the target as a top-level option and a dummy name for the profile.
|
||||
|
||||
```bash
|
||||
inspec parallel exec dummy -o multi-profile.txt -t ssh://server
|
||||
```
|
||||
|
||||
### Run different parts of a profile in parallel
|
||||
|
||||
If your profile has well-named control IDs, you can use the `--controls` option to divide the profile into sections.
|
||||
Suppose that your profile has sections named **C**, **S**, and **N** and the controls in each section have control IDs that start with the given letter,
|
||||
then you can create an option file that divides the profile as follows:
|
||||
|
||||
```text
|
||||
# divide-aws-bp.txt
|
||||
--reporter cli:C.out --controls /^C/
|
||||
--reporter cli:S.out --controls /^S/
|
||||
--reporter cli:N.out --controls /^N/
|
||||
```
|
||||
|
||||
When you run the following command, `inspec exec` runs three times, once for each of the **C**, **S**, and **N** sections of the profile.
|
||||
|
||||
```bash
|
||||
inspec parallel exec aws-best-practices -o divide-aws-bp.txt -t aws://profile_name@us-east-2
|
||||
```
|
6
etc/features.sig
Normal file
6
etc/features.sig
Normal file
|
@ -0,0 +1,6 @@
|
|||
wNEzKHmtSf1pIdciEC6DOs5SlOs3IbW1psVFLlmZc0NbnHe6MEahAnKWmHUP
|
||||
9YrDv2JMQo1I8MM/cez8XDxpK4O5y4HT66RqoAlfBkg82LmYC7f1Cy34ByCj
|
||||
LBZg5o/IVBGnY+Ksbhtp0mQYEyU048FnXIfh9uOfbKahU8HkPJssTkIw3fjL
|
||||
Vrd5GQ4ssfW1XXFaxx7DjxWlPmWBVhd8c1Y2RlACZyI+w1DQNYimrWvgiFym
|
||||
0VbnndiSX+2x84AZHE9AmsebcAYk9QlqO1N0VeYqBZj45FXLtpsNwYo0amDa
|
||||
D/wyKGxRQLUYXyd2tDVJMWbeHPHy8UIK17RoSctrEg==
|
88
etc/features.yaml
Normal file
88
etc/features.yaml
Normal file
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
features:
|
||||
inspec-cli-exec:
|
||||
description: Run InSpec profile code at the command line.
|
||||
inspec-cli-shell:
|
||||
description: Experiment with InSpec Language interactively.
|
||||
inspec-cli-check:
|
||||
description: Examine a profile for problems.
|
||||
inspec-cli-json:
|
||||
description: Generate JSON summary for inspec profile/s.
|
||||
inspec-cli-export:
|
||||
description: Generate summary in specified formats for profile/s.
|
||||
inspec-cli-vendor:
|
||||
description: Download all profile dependencies and generate a lockfile in vendor directory.
|
||||
inspec-cli-archive:
|
||||
description: Archive a profile to tar.gz (default) or zip.
|
||||
inspec-cli-detect:
|
||||
description: Detect the target OS.
|
||||
inspec-cli-env:
|
||||
description: Output shell-appropriate completion configuration.
|
||||
inspec-cli-schema:
|
||||
description: Print the JSON schema.
|
||||
inspec-cli-run-context:
|
||||
description: Test run-context detection.
|
||||
inspec-cli-version:
|
||||
description: Print the version of InSpec.
|
||||
inspec-cli-clear-cache:
|
||||
description: Clear InSpec cache stored in ~/.inspec/cache or specific vendor cache path.
|
||||
inspec-cli-compliance-login:
|
||||
description: Login to Automate Server using InSpec.
|
||||
inspec-cli-compliance-profiles:
|
||||
description: Lists all uploaded profiles from automate server.
|
||||
inspec-cli-compliance-exec:
|
||||
description: Run InSpec profile from a list of profiles in automate server.
|
||||
inspec-cli-compliance-download:
|
||||
description: Download the InSpec profile from automate server.
|
||||
inspec-cli-compliance-upload:
|
||||
description: Upload InSpec profile to automate server.
|
||||
inspec-cli-compliance-version:
|
||||
description: Print the version of Automate Server.
|
||||
inspec-cli-compliance-logout:
|
||||
description: Logout from Automate Server.
|
||||
inspec-cli-habitat-profile-create:
|
||||
description: Create Habitat Artifact for the InSpec profile.
|
||||
inspec-cli-habitat-profile-setup:
|
||||
description: Configure Habitat Artifact.
|
||||
inspec-cli-habitat-profile-upload:
|
||||
description: Upload Habitat Artifact for the InSpec profile to Habitat Builder Depot.
|
||||
inspec-cli-init-profile:
|
||||
description: Generate a new InSpec profile.
|
||||
inspec-cli-init-plugin:
|
||||
description: Generate a new InSpec plugin.
|
||||
inspec-cli-init-resource:
|
||||
description: Generate a new InSpec resource.
|
||||
inspec-cli-parallel-exec:
|
||||
description: Run list of InSpec exec operations parallely.
|
||||
inspec-cli-sign-generate-keys:
|
||||
description: Generate a RSA key pair for signing and verification.
|
||||
inspec-cli-sign-profile:
|
||||
description: Sign InSpec profile and generate .iaf artifact.
|
||||
inspec-cli-sign-verify:
|
||||
description: Verify a signed profile .iaf artifact.
|
||||
inspec-enhanced-outcomes:
|
||||
description: Use enhanced outcomes in reporters
|
||||
inspec-waivers:
|
||||
description: Use waivers mechanism with one or more waiver files.
|
||||
inspec-reporter-cli:
|
||||
description: Use CLI reporter.
|
||||
inspec-reporter-json:
|
||||
description: Use JSON reporter.
|
||||
inspec-reporter-json-automate:
|
||||
description: Use JSON automate reporter.
|
||||
inspec-reporter-automate:
|
||||
description: Use automate reporter.
|
||||
inspec-reporter-yaml:
|
||||
description: Use YAML reporter.
|
||||
inspec-reporter-json-min:
|
||||
description: Use JSON min reporter for minimal JSON output.
|
||||
inspec-reporter-junit:
|
||||
description: Use JUnit reporter.
|
||||
inspec-reporter-junit2:
|
||||
description: Use JUnit2 reporter.
|
||||
inspec-reporter-html2:
|
||||
description: Use HTML reporter.
|
||||
inspec-reporter-progress-bar:
|
||||
description: Use progress bar streaming reporter
|
||||
inspec-reporter-child-status:
|
||||
description: Child status reporter used in inspec parallel reporting.
|
|
@ -1,5 +1,5 @@
|
|||
# This file managed by automation - do not edit manually
|
||||
module InspecBin
|
||||
INSPECBIN_ROOT = File.expand_path("..", __dir__)
|
||||
VERSION = "5.22.13".freeze
|
||||
VERSION = "6.4.45".freeze
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ libdir = File.dirname(__FILE__)
|
|||
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
||||
|
||||
require "inspec/version"
|
||||
require "inspec/utils/licensing_config"
|
||||
require "inspec/exceptions"
|
||||
require "inspec/utils/deprecation"
|
||||
require "inspec/profile"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
require "thor" # rubocop:disable Chef/Ruby/UnlessDefinedRequire
|
||||
require "chef-licensing"
|
||||
require "inspec/log"
|
||||
require "inspec/ui"
|
||||
require "inspec/config"
|
||||
require "inspec/dist"
|
||||
require "inspec/utils/deprecation/global_method"
|
||||
require "inspec/utils/licensing_config"
|
||||
|
||||
# Allow end of options during array type parsing
|
||||
# https://github.com/erikhuda/thor/issues/631
|
||||
|
@ -30,11 +32,34 @@ module Inspec
|
|||
end
|
||||
|
||||
def self.start(given_args = ARGV, config = {})
|
||||
if Inspec::Dist::EXEC_NAME == "inspec"
|
||||
check_license! if config[:enforce_license] || config[:enforce_license].nil?
|
||||
fetch_and_persist_license
|
||||
end
|
||||
|
||||
super(given_args, config)
|
||||
end
|
||||
|
||||
def self.fetch_and_persist_license
|
||||
allowed_commands = ["-h", "--help", "help", "-v", "--version", "version", "license"]
|
||||
begin
|
||||
if (allowed_commands & ARGV.map(&:downcase)).empty? && !ARGV.empty?
|
||||
license_keys = ChefLicensing.fetch_and_persist
|
||||
|
||||
# Only if EULA acceptance or license key args are present. And licenses are successfully persisted, do clean exit.
|
||||
if ARGV.select { |arg| !(arg.include? "--chef-license") }.empty? && !license_keys.blank?
|
||||
Inspec::UI.new.exit
|
||||
end
|
||||
end
|
||||
rescue ChefLicensing::LicenseKeyFetcher::LicenseKeyNotFetchedError
|
||||
Inspec::Log.error "#{Inspec::Dist::PRODUCT_NAME} cannot execute without valid licenses."
|
||||
Inspec::UI.new.exit(:usage_error)
|
||||
rescue ChefLicensing::Error => e
|
||||
Inspec::Log.error e.message
|
||||
Inspec::UI.new.exit(:usage_error)
|
||||
end
|
||||
end
|
||||
|
||||
# EULA acceptance
|
||||
def self.check_license!
|
||||
allowed_commands = ["-h", "--help", "help", "-v", "--version", "version"]
|
||||
|
@ -48,9 +73,6 @@ module Inspec
|
|||
Inspec::VERSION,
|
||||
logger: Inspec::Log
|
||||
)
|
||||
if license_acceptor_output && ARGV.count == 1 && (ARGV.first.include? "--chef-license")
|
||||
Inspec::UI.new.exit
|
||||
end
|
||||
license_acceptor_output
|
||||
end
|
||||
rescue LicenseAcceptance::LicenseNotAcceptedError
|
||||
|
@ -211,6 +233,30 @@ module Inspec
|
|||
|
||||
def self.help(*args)
|
||||
super(*args)
|
||||
if Inspec::Dist::EXEC_NAME == "inspec"
|
||||
puts <<~CHEF_LICENSE_HELP
|
||||
Chef Compliance has three tiers of licensing:
|
||||
|
||||
* Free-Tier
|
||||
Users are limited to audit maximum of 10 targets
|
||||
Entitled for personal or non-commercial use
|
||||
|
||||
* Trial
|
||||
Entitled for unlimited number of targets
|
||||
Entitled for 30 days only
|
||||
Entitled for commercial use
|
||||
|
||||
* Commercial
|
||||
Entitled for purchased number of targets
|
||||
Entitled for period of subscription purchased
|
||||
Entitled for commercial use
|
||||
|
||||
inspec license add: This command helps users to generate or add an additional license (not applicable to local licensing service)
|
||||
|
||||
For more information please visit:
|
||||
www.chef.io/licensing/faqs
|
||||
CHEF_LICENSE_HELP
|
||||
end
|
||||
puts "\nAbout #{Inspec::Dist::PRODUCT_NAME}:"
|
||||
puts " Patents: chef.io/patents\n\n"
|
||||
end
|
||||
|
|
|
@ -39,12 +39,33 @@ module Inspec
|
|||
end
|
||||
|
||||
def fetch
|
||||
if cache.exists?(cache_key)
|
||||
if cache.exists?(cache_key) && cache.locked?(cache_key)
|
||||
Inspec::Log.debug "Waiting for lock to be released on the cache dir ...."
|
||||
counter = 0
|
||||
until cache.locked?(cache_key) == false
|
||||
if (counter += 1) > 300
|
||||
Inspec::Log.warn "Giving up waiting on cache lock at #{cache_key}"
|
||||
exit 1
|
||||
end
|
||||
sleep 0.1
|
||||
end
|
||||
fetch
|
||||
elsif cache.exists?(cache_key) && !cache.locked?(cache_key)
|
||||
Inspec::Log.debug "Using cached dependency for #{target}"
|
||||
[cache.prefered_entry_for(cache_key), false]
|
||||
else
|
||||
begin
|
||||
Inspec::Log.debug "Dependency does not exist in the cache #{target}"
|
||||
cache.lock(cache.base_path_for(fetcher.cache_key)) if fetcher.requires_locking?
|
||||
fetcher.fetch(cache.base_path_for(fetcher.cache_key))
|
||||
rescue SystemExit => e
|
||||
exit_code = e.status || 1
|
||||
Inspec::Log.error "Error while creating cache for dependency ... #{e.message}"
|
||||
FileUtils.rm_rf(cache.base_path_for(fetcher.cache_key))
|
||||
exit(exit_code)
|
||||
ensure
|
||||
cache.unlock(cache.base_path_for(fetcher.cache_key)) if fetcher.requires_locking?
|
||||
end
|
||||
assert_cache_sanity!
|
||||
[fetcher.archive_path, fetcher.writable?]
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ require "inspec/backend"
|
|||
require "inspec/dependencies/cache"
|
||||
require "inspec/utils/json_profile_summary"
|
||||
require "inspec/utils/yaml_profile_summary"
|
||||
require "inspec/feature"
|
||||
|
||||
module Inspec # TODO: move this somewhere "better"?
|
||||
autoload :BaseCLI, "inspec/base_cli"
|
||||
|
@ -61,6 +62,11 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
require "license_acceptance/cli_flags/thor"
|
||||
include LicenseAcceptance::CLIFlags::Thor
|
||||
|
||||
if Inspec::Dist::EXEC_NAME == "inspec"
|
||||
require "chef-licensing/cli_flags/thor"
|
||||
include ChefLicensing::CLIFlags::Thor
|
||||
end
|
||||
|
||||
desc "json PATH", "read all tests in the PATH and generate a JSON summary."
|
||||
option :output, aliases: :o, type: :string,
|
||||
desc: "Save the created profile to a path."
|
||||
|
@ -70,9 +76,11 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
desc: "A list of tags to filter controls and include only those. Ignore all other tests."
|
||||
profile_options
|
||||
def json(target)
|
||||
Inspec.with_feature("inspec-cli-json") {
|
||||
# This deprecation warning is ignored currently.
|
||||
Inspec.deprecate(:renamed_to_inspec_export)
|
||||
export(target, true)
|
||||
}
|
||||
end
|
||||
|
||||
desc "export PATH", "read the profile in PATH and generate a summary in the given format."
|
||||
|
@ -88,6 +96,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
desc: "For --what=profile, a list of tags to filter controls and include only those. Ignore all other tests."
|
||||
profile_options
|
||||
def export(target, as_json = false)
|
||||
Inspec.with_feature("inspec-cli-export") {
|
||||
begin
|
||||
o = config
|
||||
diagnose(o)
|
||||
o["log_location"] = $stderr
|
||||
|
@ -144,6 +154,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
rescue StandardError => e
|
||||
pretty_handle_exception(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "check PATH", "Verify the metadata in the `inspec.yml` file,\
|
||||
verify that control blocks have the correct fields (title, description, impact),\
|
||||
|
@ -154,6 +166,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
desc: "Enable or disable cookstyle checks.", default: false
|
||||
profile_options
|
||||
def check(path) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
||||
Inspec.with_feature("inspec-cli-check") {
|
||||
begin
|
||||
o = config
|
||||
diagnose(o)
|
||||
o["log_location"] ||= STDERR if o["format"] == "json"
|
||||
|
@ -212,22 +226,25 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
ui.exit Inspec::UI::EXIT_USAGE_ERROR unless result[:summary][:valid]
|
||||
rescue StandardError => e
|
||||
pretty_handle_exception(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "vendor PATH", "Download all dependencies and generate a lockfile in a `vendor` directory"
|
||||
option :overwrite, type: :boolean, default: false,
|
||||
desc: "Overwrite existing vendored dependencies and lockfile."
|
||||
def vendor(path = nil)
|
||||
Inspec.with_feature("inspec-cli-vendor") {
|
||||
o = config
|
||||
configure_logger(o)
|
||||
o[:logger] = Logger.new($stdout)
|
||||
o[:logger].level = get_log_level(o[:log_level])
|
||||
|
||||
vendor_deps(path, o)
|
||||
}
|
||||
end
|
||||
|
||||
desc "archive PATH", "Archive a profile to a tar file (default) or zip file."
|
||||
|
@ -245,6 +262,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
option :ignore_errors, type: :boolean, default: false,
|
||||
desc: "Ignore profile warnings."
|
||||
def archive(path, log_level = nil)
|
||||
Inspec.with_feature("inspec-cli-archive") {
|
||||
begin
|
||||
o = config
|
||||
diagnose(o)
|
||||
|
||||
|
@ -276,6 +295,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
rescue StandardError => e
|
||||
pretty_handle_exception(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "exec LOCATIONS", "Run all test files at the specified locations."
|
||||
long_desc <<~EOT
|
||||
|
@ -353,6 +374,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
EOT
|
||||
exec_options
|
||||
def exec(*targets)
|
||||
Inspec.with_feature("inspec-cli-exec") {
|
||||
begin
|
||||
o = config
|
||||
diagnose(o)
|
||||
deprecate_target_id(config)
|
||||
|
@ -368,11 +391,15 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
rescue StandardError => e
|
||||
pretty_handle_exception(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "detect", "detects the target OS."
|
||||
target_options
|
||||
option :format, type: :string
|
||||
def detect
|
||||
Inspec.with_feature("inspec-cli-detect") {
|
||||
begin
|
||||
o = config
|
||||
deprecate_target_id(config)
|
||||
o[:command] = "platform.params"
|
||||
|
@ -393,6 +420,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
rescue StandardError => e
|
||||
pretty_handle_exception(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "shell", "open an interactive debugging shell."
|
||||
target_options
|
||||
|
@ -416,6 +445,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
option :enhanced_outcomes, type: :boolean,
|
||||
desc: "Show enhanced outcomes in output"
|
||||
def shell_func
|
||||
Inspec.with_feature("inspec-cli-shell") {
|
||||
begin
|
||||
o = config
|
||||
deprecate_target_id(config)
|
||||
diagnose(o)
|
||||
|
@ -452,19 +483,27 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
rescue StandardError => e
|
||||
pretty_handle_exception(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "env", "Outputs shell-appropriate completion configuration."
|
||||
def env(shell = nil)
|
||||
Inspec.with_feature("inspec-cli-env") {
|
||||
begin
|
||||
p = Inspec::EnvPrinter.new(self.class, shell)
|
||||
p.print_and_exit!
|
||||
rescue StandardError => e
|
||||
pretty_handle_exception(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
option :enhanced_outcomes, type: :boolean,
|
||||
desc: "Show enhanced outcomes output"
|
||||
desc "schema NAME", "print the JSON schema", hide: true
|
||||
def schema(name)
|
||||
Inspec.with_feature("inspec-cli-schema") {
|
||||
begin
|
||||
require "inspec/schema/output_schema"
|
||||
o = config
|
||||
puts Inspec::Schema::OutputSchema.json(name, o)
|
||||
|
@ -472,22 +511,28 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
puts e
|
||||
puts "Valid schemas are #{Inspec::Schema::OutputSchema.names.join(", ")}"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "run_context", "used to test run-context detection", hide: true
|
||||
def run_context
|
||||
Inspec.with_feature("inspec-cli-run-context") {
|
||||
require "inspec/utils/telemetry/run_context_probe"
|
||||
puts Inspec::Telemetry::RunContextProbe.guess_run_context
|
||||
}
|
||||
end
|
||||
|
||||
desc "version", "prints the version of this tool."
|
||||
option :format, type: :string
|
||||
def version
|
||||
Inspec.with_feature("inspec-cli-version") {
|
||||
if config["format"] == "json"
|
||||
v = { version: Inspec::VERSION }
|
||||
puts v.to_json
|
||||
else
|
||||
puts Inspec::VERSION
|
||||
end
|
||||
}
|
||||
end
|
||||
map %w{-v --version} => :version
|
||||
|
||||
|
@ -495,6 +540,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
option :vendor_cache, type: :string,
|
||||
desc: "Use the given path for caching dependencies, (default: `~/.inspec/cache`)."
|
||||
def clear_cache
|
||||
Inspec.with_feature("inspec-cli-clear-cache") {
|
||||
o = config
|
||||
configure_logger(o)
|
||||
cache_path = o[:vendor_cache] || "~/.inspec/cache"
|
||||
|
@ -503,6 +549,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
o[:logger] = Logger.new($stdout)
|
||||
o[:logger].level = get_log_level(o[:log_level])
|
||||
o[:logger].info "== InSpec cache cleared successfully =="
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -70,5 +70,38 @@ module Inspec
|
|||
def base_path_for(cache_key)
|
||||
File.join(@path, cache_key)
|
||||
end
|
||||
|
||||
#
|
||||
# For given cache key, return true if the
|
||||
# cache path is locked
|
||||
def locked?(key)
|
||||
locked = false
|
||||
path = base_path_for(key)
|
||||
# For archive there is no need to lock the directory so we skip those and return false for archive formatted cache
|
||||
if File.directory?(path)
|
||||
locked = File.exist?("#{path}/.lock")
|
||||
end
|
||||
locked
|
||||
end
|
||||
|
||||
def lock(cache_path)
|
||||
lock_file_path = File.join(cache_path, ".lock")
|
||||
begin
|
||||
FileUtils.mkdir_p(cache_path)
|
||||
Inspec::Log.debug("Locking cache ..... #{cache_path}")
|
||||
FileUtils.touch(lock_file_path)
|
||||
rescue Errno::EACCES
|
||||
raise "Permission denied while creating cache lock #{cache_path}/.lock."
|
||||
end
|
||||
end
|
||||
|
||||
def unlock(cache_path)
|
||||
Inspec::Log.debug("Unlocking cache..... #{cache_path}")
|
||||
begin
|
||||
FileUtils.rm("#{cache_path}/.lock") if File.exist?("#{cache_path}/.lock")
|
||||
rescue Errno::EACCES
|
||||
raise "Permission denied while removing cache lock #{cache_path}/.lock"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,5 +15,6 @@ module Inspec
|
|||
"passed"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,4 +24,7 @@ module Inspec
|
|||
end
|
||||
|
||||
class InvalidProfileSignature < Error; end
|
||||
|
||||
class FeatureConfigMissingError < Error; end
|
||||
class FeatureConfigTamperedError < Error; end
|
||||
end
|
||||
|
|
17
lib/inspec/feature.rb
Normal file
17
lib/inspec/feature.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
require_relative "feature/config"
|
||||
require_relative "feature/runner"
|
||||
|
||||
module Inspec
|
||||
def self.with_feature(feature_name, opts = {}, &feature_implementation)
|
||||
Inspec::Feature::Runner.with_feature(feature_name, opts, &feature_implementation)
|
||||
end
|
||||
|
||||
class Feature
|
||||
attr_reader :name, :description
|
||||
def initialize(feature_name, feature_yaml_opts)
|
||||
@name = feature_name
|
||||
feature_yaml_opts ||= {}
|
||||
@description = feature_yaml_opts["description"]
|
||||
end
|
||||
end
|
||||
end
|
75
lib/inspec/feature/config.rb
Normal file
75
lib/inspec/feature/config.rb
Normal file
|
@ -0,0 +1,75 @@
|
|||
require "inspec/iaf_file" # Uses some of the same encryption routines
|
||||
|
||||
module Inspec
|
||||
class Feature
|
||||
class Config
|
||||
|
||||
VERIFICATION_KEY_NAME = "progress-2022-05-04".freeze
|
||||
|
||||
attr_reader :cfg_data, :valid
|
||||
|
||||
def initialize(conf_path = nil)
|
||||
# If conf path is nil, read from source installation
|
||||
conf_path ||= File.join(Inspec.src_root, "etc", "features.yaml")
|
||||
|
||||
# Verify path and sig file exists or else throw exception
|
||||
sig_path = conf_path.sub(/\.yaml/, ".sig")
|
||||
[conf_path, sig_path].each do |file|
|
||||
raise Inspec::FeatureConfigMissingError.new("No such file #{file}") unless File.exist?(file)
|
||||
end
|
||||
|
||||
# Verify sig matches contents
|
||||
validation_key_path = Inspec::IafFile.find_validation_key(VERIFICATION_KEY_NAME)
|
||||
verification_key = Inspec::IafFile::KEY_ALG.new File.read validation_key_path
|
||||
signature = Base64.decode64 File.read sig_path
|
||||
digest = Inspec::IafFile::ARTIFACT_DIGEST.new
|
||||
unless verification_key.verify digest, signature, File.read(conf_path)
|
||||
# If not load default empty config and raise exception
|
||||
@cfg_data = load_error_data
|
||||
raise Inspec::FeatureConfigTamperedError.new("Feature yaml file does not match signature - tampered?")
|
||||
end
|
||||
|
||||
# Read YAML data from path
|
||||
@cfg_data = YAML.load_file(conf_path)
|
||||
@features_by_name = {}
|
||||
end
|
||||
|
||||
def with_each_feature
|
||||
cfg_data["features"].each do |feature_name, raw_info|
|
||||
feat = @features_by_name[feature_name] ||= Inspec::Feature.new(feature_name.to_sym, raw_info)
|
||||
yield(feat)
|
||||
end
|
||||
end
|
||||
|
||||
def [](feature_name)
|
||||
raw_info = cfg_data["features"][feature_name]
|
||||
return nil unless raw_info
|
||||
|
||||
@features_by_name[feature_name] ||= Inspec::Feature.new(feature_name.to_sym, raw_info)
|
||||
end
|
||||
|
||||
def feature_name?(query)
|
||||
cfg_data["features"].key?(query.to_s)
|
||||
end
|
||||
|
||||
def features
|
||||
@features ||= load_features
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_features
|
||||
feats = []
|
||||
with_each_feature { |f| feats << f }
|
||||
feats
|
||||
end
|
||||
|
||||
# Default data for when the config is in an error state.
|
||||
def load_error_data
|
||||
{
|
||||
"features": {},
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/inspec/feature/runner.rb
Normal file
21
lib/inspec/feature/runner.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module Inspec
|
||||
class Feature
|
||||
class Runner
|
||||
def self.with_feature(feature_name, opts = {}, &feature_implementation)
|
||||
config = opts[:config] || Inspec::Feature::Config.new
|
||||
logger = opts[:logger] || Inspec::Log
|
||||
|
||||
# Emit log message saying we're running a feature
|
||||
logger.debug("Prepping to run feature '#{feature_name}'")
|
||||
|
||||
# Validate that the feature is recognized
|
||||
feature = config[feature_name]
|
||||
unless feature
|
||||
logger.warn "Unrecognized feature name '#{feature_name}'"
|
||||
end
|
||||
|
||||
yield feature_implementation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -115,6 +115,11 @@ module Inspec::Fetcher
|
|||
%i{branch tag ref}.map { |opt_name| update_ivar_from_opt(opt_name, opts) }.any?
|
||||
end
|
||||
|
||||
# Git fetcher is sensitive to cache contention so it needs cache locking mechanism.
|
||||
def requires_locking?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolved_ref
|
||||
|
|
|
@ -160,7 +160,7 @@ module Inspec::Formatters
|
|||
end
|
||||
|
||||
# added this additionally because stats summary is also used for determining exit code in runner rspec
|
||||
skipped += 1 if control[:results].any? { |r| r[:status] == "skipped" }
|
||||
skipped += 1 if control[:results] && (control[:results].any? { |r| r[:status] == "skipped" })
|
||||
|
||||
end
|
||||
total = error + not_applicable + not_reviewed + failed + passed
|
||||
|
|
|
@ -105,6 +105,13 @@ module Inspec
|
|||
file_provider = Inspec::FileProvider.for_path(archive_path)
|
||||
file_provider.relative_provider
|
||||
end
|
||||
|
||||
# Returns false by default
|
||||
# This is used to regulate cache contention.
|
||||
# Fetchers that are sensitive to cache contention should return true.
|
||||
def requires_locking?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ module Inspec::Plugin::V2::PluginType
|
|||
@control_checks_count_map = {}
|
||||
@controls_count = nil
|
||||
@notifications = {}
|
||||
@enhanced_outcome_control_wise = {}
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -29,16 +30,43 @@ module Inspec::Plugin::V2::PluginType
|
|||
|
||||
# method to identify when the control ended running
|
||||
# this will be useful in executing operations on control's level end
|
||||
def control_ended?(control_id)
|
||||
def control_ended?(notification, control_id)
|
||||
set_control_checks_count_map_value
|
||||
unless @control_checks_count_map[control_id].nil?
|
||||
@control_checks_count_map[control_id] -= 1
|
||||
@control_checks_count_map[control_id] == 0
|
||||
control_ended = @control_checks_count_map[control_id] == 0
|
||||
# after a control has ended it checks for certain operations, like enhanced outcomes
|
||||
run_control_operations(notification, control_id) if control_ended
|
||||
control_ended
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def run_control_operations(notification, control_id)
|
||||
check_for_enhanced_outcomes(notification, control_id)
|
||||
end
|
||||
|
||||
def check_for_enhanced_outcomes(notification, control_id)
|
||||
if enhanced_outcomes
|
||||
control_outcome = add_enhanced_outcomes(control_id)
|
||||
@enhanced_outcome_control_wise[control_id] = control_outcome
|
||||
end
|
||||
end
|
||||
|
||||
def format_message(indicator, control_id, title, full_description)
|
||||
message_to_format = ""
|
||||
message_to_format += "#{indicator} "
|
||||
message_to_format += "#{control_id.to_s.strip.dup.force_encoding(Encoding::UTF_8)} "
|
||||
message_to_format += "#{title.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " if title
|
||||
message_to_format += "#{full_description.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " unless title
|
||||
message_to_format
|
||||
end
|
||||
|
||||
def control_outcome(control_id)
|
||||
@enhanced_outcome_control_wise[control_id]
|
||||
end
|
||||
|
||||
# method to identify total no. of controls
|
||||
def controls_count
|
||||
@controls_count ||= RSpec.configuration.formatters.grep(Inspec::Formatters::Base).first.get_controls_count
|
||||
|
|
|
@ -4,11 +4,13 @@ require "inspec/reporters/json"
|
|||
require "inspec/reporters/json_automate"
|
||||
require "inspec/reporters/automate"
|
||||
require "inspec/reporters/yaml"
|
||||
require "inspec/feature"
|
||||
|
||||
module Inspec::Reporters
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def self.render(reporter, run_data, enhanced_outcomes = false)
|
||||
name, config = reporter.dup
|
||||
Inspec.with_feature("inspec-reporter-#{name}") {
|
||||
config[:run_data] = run_data
|
||||
case name
|
||||
when "cli"
|
||||
|
@ -29,28 +31,38 @@ module Inspec::Reporters
|
|||
activator.activate!
|
||||
reporter = activator.implementation_class.new(config)
|
||||
end
|
||||
|
||||
if enhanced_outcomes
|
||||
Inspec.with_feature("inspec-enhanced-outcomes") {
|
||||
reporter.enhanced_outcomes = enhanced_outcomes
|
||||
}
|
||||
else
|
||||
reporter.enhanced_outcomes = enhanced_outcomes
|
||||
end
|
||||
|
||||
# optional send_report method on reporter
|
||||
return reporter.send_report if defined?(reporter.send_report)
|
||||
|
||||
reporter.render
|
||||
output = reporter.rendered_output
|
||||
|
||||
if config["file"]
|
||||
config_file = config["file"]
|
||||
if config_file
|
||||
config_file.gsub!("CHILD_PID", Process.pid.to_s)
|
||||
# create destination directory if it does not exist
|
||||
dirname = File.dirname(config["file"])
|
||||
dirname = File.dirname(config_file)
|
||||
FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
|
||||
|
||||
File.write(config["file"], output)
|
||||
File.write(config_file, output)
|
||||
elsif config["stdout"] == true
|
||||
print output
|
||||
$stdout.flush
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.report(reporter, run_data)
|
||||
name, config = reporter.dup
|
||||
Inspec.with_feature("inspec-reporter-#{name}") {
|
||||
config[:run_data] = run_data
|
||||
case name
|
||||
when "json"
|
||||
|
@ -75,5 +87,6 @@ module Inspec::Reporters
|
|||
end
|
||||
|
||||
reporter.report
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -317,7 +317,7 @@ module Inspec::Reporters
|
|||
not_applicable = 0
|
||||
|
||||
all_unique_controls.each do |control|
|
||||
next if control[:status].empty?
|
||||
next if control[:status].blank?
|
||||
|
||||
if control[:status] == "failed"
|
||||
failed += 1
|
||||
|
|
|
@ -27,11 +27,13 @@ module Inspec
|
|||
) do
|
||||
include HashLikeStruct
|
||||
def initialize(raw_run_data)
|
||||
self.controls = raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
|
||||
self.profiles = raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
|
||||
self.statistics = Inspec::RunData::Statistics.new(raw_run_data[:statistics])
|
||||
self.platform = Inspec::RunData::Platform.new(raw_run_data[:platform])
|
||||
self.version = raw_run_data[:version]
|
||||
@raw_run_data = raw_run_data
|
||||
|
||||
self.controls = @raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
|
||||
self.profiles = @raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
|
||||
self.statistics = Inspec::RunData::Statistics.new(@raw_run_data[:statistics])
|
||||
self.platform = Inspec::RunData::Platform.new(@raw_run_data[:platform])
|
||||
self.version = @raw_run_data[:version]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ require "inspec/dependencies/cache"
|
|||
require "inspec/dist"
|
||||
require "inspec/reporters"
|
||||
require "inspec/runner_rspec"
|
||||
require "chef-licensing"
|
||||
# spec requirements
|
||||
|
||||
module Inspec
|
||||
|
@ -60,11 +61,13 @@ module Inspec
|
|||
end
|
||||
|
||||
if @conf[:waiver_file]
|
||||
Inspec.with_feature("inspec-waivers") {
|
||||
@conf[:waiver_file].each do |file|
|
||||
unless File.file?(file)
|
||||
raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist."
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# About reading inputs:
|
||||
|
@ -159,16 +162,24 @@ module Inspec
|
|||
end
|
||||
|
||||
def run(with = nil)
|
||||
ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
|
||||
Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
|
||||
load
|
||||
run_tests(with)
|
||||
rescue ChefLicensing::SoftwareNotEntitled
|
||||
Inspec::Log.error "License is not entitled to use InSpec."
|
||||
Inspec::UI.new.exit(:license_not_entitled)
|
||||
rescue ChefLicensing::Error => e
|
||||
Inspec::Log.error e.message
|
||||
Inspec::UI.new.exit(:usage_error)
|
||||
end
|
||||
|
||||
def render_output(run_data)
|
||||
return if @conf["reporter"].nil?
|
||||
|
||||
@conf["reporter"].each do |reporter|
|
||||
result = Inspec::Reporters.render(reporter, run_data, @conf["enhanced_outcomes"])
|
||||
enhanced_outcome_flag = @conf["enhanced_outcomes"]
|
||||
result = Inspec::Reporters.render(reporter, run_data, enhanced_outcome_flag)
|
||||
raise Inspec::ReporterError, "Error generating reporter '#{reporter[0]}'" if result == false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -177,9 +177,10 @@ module Inspec
|
|||
next unless streaming_reporters.include? streaming_reporter_name
|
||||
|
||||
# Activate the plugin so the formatter ID gets registered with RSpec, presumably
|
||||
|
||||
Inspec.with_feature("inspec-reporter-#{streaming_reporter_name}") {
|
||||
activator = reg.find_activator(plugin_type: :streaming_reporter, activator_name: streaming_reporter_name.to_sym)
|
||||
activator.activate!
|
||||
|
||||
# We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
|
||||
if file_target&.[]("file").nil?
|
||||
RSpec.configuration.add_formatter(activator.implementation_class)
|
||||
|
@ -187,6 +188,7 @@ module Inspec
|
|||
RSpec.configuration.add_formatter(activator.implementation_class, file_target["file"])
|
||||
end
|
||||
@conf["reporter"].delete(streaming_reporter_name)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -196,6 +198,7 @@ module Inspec
|
|||
def configure_output
|
||||
RSpec.configuration.output_stream = $stdout
|
||||
@formatter = RSpec.configuration.add_formatter(Inspec::Formatters::Base)
|
||||
|
||||
@formatter.enhanced_outcomes = @conf.final_options["enhanced_outcomes"]
|
||||
RSpec.configuration.add_formatter(Inspec::Formatters::ShowProgress, $stderr) if @conf[:show_progress]
|
||||
set_optional_formatters
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
require "chef-licensing"
|
||||
require "inspec/dist"
|
||||
|
||||
autoload :Pry, "pry"
|
||||
|
||||
module Inspec
|
||||
|
@ -10,6 +13,7 @@ module Inspec
|
|||
end
|
||||
|
||||
def start
|
||||
ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
|
||||
# This will hold a single evaluation binding context as opened within
|
||||
# the instance_eval context of the anonymous class that the profile
|
||||
# context creates to evaluate each individual test file. We want to
|
||||
|
@ -18,6 +22,12 @@ module Inspec
|
|||
@ctx_binding = @runner.eval_with_virtual_profile("binding")
|
||||
configure_pry
|
||||
@ctx_binding.pry
|
||||
rescue ChefLicensing::SoftwareNotEntitled
|
||||
Inspec::Log.error "License is not entitled to use InSpec."
|
||||
Inspec::UI.new.exit(:license_not_entitled)
|
||||
rescue ChefLicensing::Error => e
|
||||
Inspec::Log.error e.message
|
||||
Inspec::UI.new.exit(:usage_error)
|
||||
end
|
||||
|
||||
def configure_pry # rubocop:disable Metrics/AbcSize
|
||||
|
|
|
@ -33,8 +33,11 @@ module Inspec
|
|||
EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4
|
||||
EXIT_BAD_SIGNATURE = 5
|
||||
EXIT_LICENSE_NOT_ACCEPTED = 172
|
||||
EXIT_LICENSE_NOT_ENTITLED = 173
|
||||
EXIT_LICENSE_NOT_SET = 174
|
||||
EXIT_FAILED_TESTS = 100
|
||||
EXIT_SKIPPED_TESTS = 101
|
||||
EXIT_TERMINATED_BY_CTL_C = 130
|
||||
|
||||
attr_reader :io
|
||||
|
||||
|
|
9
lib/inspec/utils/licensing_config.rb
Normal file
9
lib/inspec/utils/licensing_config.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
require_relative "../log"
|
||||
require "chef-licensing"
|
||||
ChefLicensing.configure do |config|
|
||||
config.chef_product_name = "InSpec"
|
||||
config.chef_entitlement_id = "3ff52c37-e41f-4f6c-ad4d-365192205968"
|
||||
config.chef_executable_name = "inspec"
|
||||
config.license_server_url = "https://licensing.chef.co/License"
|
||||
config.logger = Inspec::Log
|
||||
end
|
|
@ -1,3 +1,3 @@
|
|||
module Inspec
|
||||
VERSION = "5.22.13".freeze
|
||||
VERSION = "6.4.45".freeze
|
||||
end
|
||||
|
|
|
@ -15,22 +15,7 @@ module Inspec
|
|||
output = {}
|
||||
|
||||
files.each do |file_path|
|
||||
file_extension = File.extname(file_path)
|
||||
data = nil
|
||||
if [".yaml", ".yml"].include? file_extension
|
||||
data = Secrets::YAML.resolve(file_path)
|
||||
unless data.nil?
|
||||
data = data.inputs
|
||||
validate_json_yaml(data)
|
||||
end
|
||||
elsif file_extension == ".csv"
|
||||
data = Waivers::CSVFileReader.resolve(file_path)
|
||||
headers = Waivers::CSVFileReader.headers
|
||||
validate_headers(headers)
|
||||
elsif file_extension == ".json"
|
||||
data = Waivers::JSONFileReader.resolve(file_path)
|
||||
validate_json_yaml(data) unless data.nil?
|
||||
end
|
||||
data = read_from_file(file_path)
|
||||
output.merge!(data) if !data.nil? && data.is_a?(Hash)
|
||||
|
||||
if data.nil?
|
||||
|
@ -43,10 +28,30 @@ module Inspec
|
|||
@waivers_data[profile_id] = output
|
||||
end
|
||||
|
||||
def self.read_from_file(file_path)
|
||||
data = nil
|
||||
file_extension = File.extname(file_path)
|
||||
if [".yaml", ".yml"].include? file_extension
|
||||
data = Secrets::YAML.resolve(file_path)
|
||||
data = data.inputs unless data.nil?
|
||||
validate_json_yaml(data)
|
||||
elsif file_extension == ".csv"
|
||||
data = Waivers::CSVFileReader.resolve(file_path)
|
||||
headers = Waivers::CSVFileReader.headers
|
||||
validate_headers(headers)
|
||||
elsif file_extension == ".json"
|
||||
data = Waivers::JSONFileReader.resolve(file_path)
|
||||
validate_json_yaml(data) unless data.nil?
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
def self.all_fields
|
||||
%w{control_id justification expiration_date run}
|
||||
end
|
||||
|
||||
def self.validate_headers(headers, json_yaml = false)
|
||||
required_fields = json_yaml ? %w{justification} : %w{control_id justification}
|
||||
all_fields = %w{control_id justification expiration_date run}
|
||||
|
||||
Inspec::Log.warn "Missing column headers: #{(required_fields - headers)}" unless (required_fields - headers).empty?
|
||||
Inspec::Log.warn "Invalid column header: Column can't be nil" if headers.include? nil
|
||||
Inspec::Log.warn "Extra column headers: #{(headers - all_fields)}" unless (headers - all_fields).empty?
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require "inspec/dist"
|
||||
|
||||
require_relative "api"
|
||||
require "inspec/feature"
|
||||
|
||||
module InspecPlugins
|
||||
module Compliance
|
||||
|
@ -32,15 +33,19 @@ module InspecPlugins
|
|||
option :ent, type: :string, required: false,
|
||||
desc: "Enterprise for #{AUTOMATE_PRODUCT_NAME} reporting (#{AUTOMATE_PRODUCT_NAME} Only)"
|
||||
def login(server)
|
||||
Inspec.with_feature("inspec-cli-compliance-login") {
|
||||
options["server"] = server
|
||||
login_response = InspecPlugins::Compliance::API.login(options)
|
||||
puts login_response
|
||||
}
|
||||
end
|
||||
|
||||
desc "profiles", "list all available profiles in #{AUTOMATE_PRODUCT_NAME}"
|
||||
option :owner, type: :string, required: false,
|
||||
desc: "owner whose profiles to list"
|
||||
def profiles
|
||||
Inspec.with_feature("inspec-cli-compliance-profiles") {
|
||||
begin
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
return unless loggedin(config)
|
||||
|
||||
|
@ -65,10 +70,14 @@ module InspecPlugins
|
|||
$stderr.puts "\nServer configuration information is missing. Please login using `#{EXEC_NAME} #{subcommand_name} login`"
|
||||
exit 1
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "exec PROFILE", "executes a #{AUTOMATE_PRODUCT_NAME} profile"
|
||||
exec_options
|
||||
def exec(*tests)
|
||||
Inspec.with_feature("inspec-cli-compliance-exec") {
|
||||
begin
|
||||
compliance_config = InspecPlugins::Compliance::Configuration.new
|
||||
return unless loggedin(compliance_config)
|
||||
|
||||
|
@ -87,11 +96,14 @@ module InspecPlugins
|
|||
$stderr.puts e.message
|
||||
exit 1
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "download PROFILE", "downloads a profile from #{AUTOMATE_PRODUCT_NAME}"
|
||||
option :name, type: :string,
|
||||
desc: "Name of the archive filename (file type will be added)"
|
||||
def download(profile_name)
|
||||
Inspec.with_feature("inspec-cli-compliance-download") {
|
||||
o = options.dup
|
||||
configure_logger(o)
|
||||
|
||||
|
@ -116,6 +128,7 @@ module InspecPlugins
|
|||
puts "Profile #{profile_name} is not available in #{AUTOMATE_PRODUCT_NAME}."
|
||||
exit 1
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "upload PATH", "uploads a local profile to #{AUTOMATE_PRODUCT_NAME}"
|
||||
|
@ -124,6 +137,7 @@ module InspecPlugins
|
|||
option :owner, type: :string, required: false,
|
||||
desc: "Owner that should own the profile"
|
||||
def upload(path) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
||||
Inspec.with_feature("inspec-cli-compliance-upload") {
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
return unless loggedin(config)
|
||||
|
||||
|
@ -213,10 +227,13 @@ module InspecPlugins
|
|||
puts msg
|
||||
exit 1
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "version", "displays the version of the #{AUTOMATE_PRODUCT_NAME} server"
|
||||
def version
|
||||
Inspec.with_feature("inspec-cli-compliance-version") {
|
||||
begin
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
info = InspecPlugins::Compliance::API.version(config)
|
||||
if !info.nil? && info["build_timestamp"]
|
||||
|
@ -231,9 +248,12 @@ module InspecPlugins
|
|||
puts "\nServer configuration information is missing. Please login using `#{EXEC_NAME} #{subcommand_name} login`"
|
||||
exit 1
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
desc "logout", "user logout from #{AUTOMATE_PRODUCT_NAME}"
|
||||
def logout
|
||||
Inspec.with_feature("inspec-cli-compliance-logout") {
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
unless config.supported?(:oidc) || config["token"].nil? || config["server_type"] == "automate"
|
||||
config = InspecPlugins::Compliance::Configuration.new
|
||||
|
@ -247,6 +267,7 @@ module InspecPlugins
|
|||
else
|
||||
puts "Could not log out"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require_relative "profile"
|
||||
require "inspec/dist"
|
||||
require "inspec/feature"
|
||||
|
||||
module InspecPlugins
|
||||
module Habitat
|
||||
|
@ -14,17 +15,23 @@ module InspecPlugins
|
|||
option :output_dir, type: :string, required: false,
|
||||
desc: "Output directory for the Habitat artifact. Default: current directory"
|
||||
def create(path = ".")
|
||||
Inspec.with_feature("inspec-cli-habitat-profile-create") {
|
||||
InspecPlugins::Habitat::Profile.new(path, options).create
|
||||
}
|
||||
end
|
||||
|
||||
desc "setup PATH", "Configure the profile at PATH for Habitat, including a plan and hooks"
|
||||
def setup(path = ".")
|
||||
Inspec.with_feature("inspec-cli-habitat-profile-setup") {
|
||||
InspecPlugins::Habitat::Profile.new(path, options).setup
|
||||
}
|
||||
end
|
||||
|
||||
desc "upload PATH", "Create then upload a Habitat artifact for the profile found at PATH to the Habitat Builder Depot"
|
||||
def upload(path = ".")
|
||||
Inspec.with_feature("inspec-cli-habitat-profile-upload") {
|
||||
InspecPlugins::Habitat::Profile.new(path, options).upload
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require "pathname" unless defined?(Pathname)
|
||||
require_relative "renderer"
|
||||
require "inspec/feature"
|
||||
|
||||
module InspecPlugins
|
||||
module Init
|
||||
|
|
|
@ -27,6 +27,7 @@ module InspecPlugins
|
|||
option :copyright, type: :string, default: nil, desc: "A copyright statement, to be added to LICENSE"
|
||||
|
||||
def plugin(plugin_name)
|
||||
Inspec.with_feature("inspec-cli-init-plugin") {
|
||||
plugin_type = determine_plugin_type(plugin_name)
|
||||
snake_case = plugin_name.tr("-", "_")
|
||||
|
||||
|
@ -54,6 +55,7 @@ module InspecPlugins
|
|||
renderer = InspecPlugins::Init::Renderer.new(ui, render_opts)
|
||||
|
||||
renderer.render_with_values(template_path, plugin_type + " plugin", template_vars)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -25,6 +25,7 @@ module InspecPlugins
|
|||
option :overwrite, type: :boolean, default: false,
|
||||
desc: "Overwrites existing directory"
|
||||
def profile(new_profile_name)
|
||||
Inspec.with_feature("inspec-cli-init-profile") {
|
||||
unless valid_profile_platforms.include?(options[:platform])
|
||||
ui.error "Unable to generate profile: No template available for platform '#{options[:platform]}' (expected one of: #{valid_profile_platforms.join(", ")})"
|
||||
ui.exit(:usage_error)
|
||||
|
@ -41,6 +42,7 @@ module InspecPlugins
|
|||
name: new_profile_name,
|
||||
}
|
||||
renderer.render_with_values(template_path, "profile", vars)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,6 +35,7 @@ module InspecPlugins
|
|||
# + Add --overwrite option
|
||||
|
||||
def resource(resource_name)
|
||||
Inspec.with_feature("inspec-cli-init-resource") {
|
||||
resource_vars_from_opts_resource
|
||||
template_vars = {
|
||||
name: options[:path], # This is used for the path prefix
|
||||
|
@ -50,6 +51,7 @@ module InspecPlugins
|
|||
}
|
||||
renderer = InspecPlugins::Init::Renderer.new(ui, render_opts)
|
||||
renderer.render_with_values(template_path, "resource", template_vars)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
|
16
lib/plugins/inspec-license/README.md
Normal file
16
lib/plugins/inspec-license/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# License Plugin
|
||||
|
||||
## license list
|
||||
|
||||
Implements the `inspec license list` CLI command.
|
||||
|
||||
## license add
|
||||
|
||||
Implements the `inspec license add` CLI command.
|
||||
|
||||
### What This Plugin Does
|
||||
|
||||
This plugin consists of the following subcommands:
|
||||
|
||||
1. `add`: helps to add a new license
|
||||
2. `list`: helps to list all the licenses for the current user
|
6
lib/plugins/inspec-license/inspec-license.gemspec
Normal file
6
lib/plugins/inspec-license/inspec-license.gemspec
Normal file
|
@ -0,0 +1,6 @@
|
|||
Gem::Specification.new do |spec|
|
||||
spec.name = "inspec-license"
|
||||
spec.summary = "Plugin to list user licenses."
|
||||
spec.description = ""
|
||||
spec.license = "Apache-2.0"
|
||||
end
|
14
lib/plugins/inspec-license/lib/inspec-license.rb
Normal file
14
lib/plugins/inspec-license/lib/inspec-license.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
module InspecPlugins
|
||||
module License
|
||||
class Plugin < ::Inspec.plugin(2)
|
||||
plugin_name :"inspec-license"
|
||||
|
||||
if Inspec::Dist::EXEC_NAME == "inspec"
|
||||
cli_command :license do
|
||||
require_relative "inspec-license/cli"
|
||||
InspecPlugins::License::CLI
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
lib/plugins/inspec-license/lib/inspec-license/cli.rb
Normal file
26
lib/plugins/inspec-license/lib/inspec-license/cli.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
require "chef-licensing"
|
||||
module InspecPlugins::License
|
||||
class CLI < Inspec.plugin(2, :cli_command)
|
||||
include Inspec::Dist
|
||||
|
||||
subcommand_desc "license SUBCOMMAND [options]", "Manage #{PRODUCT_NAME} license"
|
||||
desc "list", "List licenses (not applicable to local licensing service)"
|
||||
def list
|
||||
ChefLicensing.list_license_keys_info
|
||||
rescue ChefLicensing::Error => e
|
||||
Inspec::Log.error e.message
|
||||
Inspec::UI.new.exit(Inspec::UI::EXIT_LICENSE_NOT_SET)
|
||||
end
|
||||
|
||||
desc "add", "Add a new license (not applicable to local licensing service)"
|
||||
def add
|
||||
ChefLicensing.add_license
|
||||
rescue ChefLicensing::LicenseKeyFetcher::LicenseKeyAddNotAllowed => e
|
||||
Inspec::Log.error e.message
|
||||
Inspec::UI.new.exit(Inspec::UI::EXIT_LICENSE_NOT_SET)
|
||||
rescue ChefLicensing::Error => e
|
||||
Inspec::Log.error e.message
|
||||
Inspec::UI.new.exit(Inspec::UI::EXIT_LICENSE_NOT_SET)
|
||||
end
|
||||
end
|
||||
end
|
27
lib/plugins/inspec-parallel/README.md
Normal file
27
lib/plugins/inspec-parallel/README.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Parallel Plugin
|
||||
|
||||
Plugin to handle parallel InSpec scan operations over multiple targets.
|
||||
|
||||
## parallel cli_command
|
||||
|
||||
Implements the `inspec parallel exec` CLI command.
|
||||
|
||||
## child-status Plugin
|
||||
|
||||
This reporter is an InSpec Streaming Reporter. It is used internally by inspec parallel to provide status updates on child processes.
|
||||
|
||||
### What This Plugin Does
|
||||
|
||||
For each control executed, after it is complete, the plugin emits a line to STDOUT like:
|
||||
```
|
||||
12/P/24/Control Title Here
|
||||
```
|
||||
When the run is complete, the single line 'EOF_MARKER' is emitted.
|
||||
|
||||
Where:
|
||||
|
||||
- 12 is the number of the control (12th seen out of all controls in all profiles)
|
||||
- P indicates that it Passed (Also F = Failed, S = Skipped, E = Errored)
|
||||
- 24 is the total number of controls in the run
|
||||
- "Control Title Here" is the title (or if title is missing, id) of the last executed control
|
||||
|
6
lib/plugins/inspec-parallel/inspec-parallel.gemspec
Normal file
6
lib/plugins/inspec-parallel/inspec-parallel.gemspec
Normal file
|
@ -0,0 +1,6 @@
|
|||
Gem::Specification.new do |spec|
|
||||
spec.name = "inspec-parallel"
|
||||
spec.summary = "Plugin to handle parallel InSpec scan operations over multiple targets"
|
||||
spec.description = ""
|
||||
spec.license = "Apache-2.0"
|
||||
end
|
18
lib/plugins/inspec-parallel/lib/inspec-parallel.rb
Normal file
18
lib/plugins/inspec-parallel/lib/inspec-parallel.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
module InspecPlugins
|
||||
module Parallelism
|
||||
class Plugin < ::Inspec.plugin(2)
|
||||
plugin_name :"inspec-parallel"
|
||||
|
||||
cli_command :parallel do
|
||||
require_relative "inspec-parallel/cli"
|
||||
InspecPlugins::Parallelism::CLI
|
||||
end
|
||||
|
||||
streaming_reporter :"child-status" do
|
||||
require_relative "inspec-parallel/child_status_reporter"
|
||||
InspecPlugins::Parallelism::StreamingReporter
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
module InspecPlugins::Parallelism
|
||||
class StreamingReporter < Inspec.plugin(2, :streaming_reporter)
|
||||
# Registering these methods with RSpec::Core::Formatters class is mandatory
|
||||
RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending, :close
|
||||
|
||||
def initialize(output)
|
||||
@status_mapping = {}
|
||||
@control_counter = 0
|
||||
initialize_streaming_reporter
|
||||
end
|
||||
|
||||
def example_passed(notification)
|
||||
set_example(notification, "passed")
|
||||
end
|
||||
|
||||
def example_failed(notification)
|
||||
set_example(notification, "failed")
|
||||
end
|
||||
|
||||
def example_pending(notification)
|
||||
set_example(notification, "skipped")
|
||||
end
|
||||
|
||||
def close(notification)
|
||||
# HACK: if we've reached the end of the execution, send a special marker, to ease EOF detection on Windows
|
||||
puts "EOF_MARKER"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_example(notification, status)
|
||||
control_id = notification.example.metadata[:id]
|
||||
title = notification.example.metadata[:title]
|
||||
set_status_mapping(control_id, status)
|
||||
output_status(control_id, title) if control_ended?(notification, control_id)
|
||||
end
|
||||
|
||||
def output_status(control_id, title)
|
||||
@control_counter += 1
|
||||
stat = @status_mapping[control_id]
|
||||
stat = if stat.include?("failed")
|
||||
"F"
|
||||
else
|
||||
if stat.include?("skipped")
|
||||
"S"
|
||||
else
|
||||
stat.include?("passed") ? "P" : "E"
|
||||
end
|
||||
end
|
||||
display_name = title.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8) if title
|
||||
display_name = control_id.to_s.lstrip.force_encoding(Encoding::UTF_8) unless title
|
||||
|
||||
puts "#{@control_counter}/#{stat}/#{controls_count}/#{display_name}"
|
||||
end
|
||||
|
||||
def set_status_mapping(control_id, status)
|
||||
@status_mapping[control_id] ||= []
|
||||
@status_mapping[control_id].push(status)
|
||||
end
|
||||
end
|
||||
end
|
39
lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb
Normal file
39
lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
require_relative "command"
|
||||
require "inspec/dist"
|
||||
require "inspec/base_cli"
|
||||
require "inspec/feature"
|
||||
|
||||
module InspecPlugins::Parallelism
|
||||
class CLI < Inspec.plugin(2, :cli_command)
|
||||
include Inspec::Dist
|
||||
|
||||
subcommand_desc "parallel SUBCOMMAND [options]", "Runs #{PRODUCT_NAME} operations parallely"
|
||||
|
||||
desc "exec", "Executes profile parallely"
|
||||
option :option_file, aliases: :o, type: :string, required: true,
|
||||
desc: "File that contains list of option strings"
|
||||
option :dry_run, type: :boolean,
|
||||
desc: "Print commands that will run"
|
||||
option :verbose, type: :boolean,
|
||||
desc: "Prints all thor options on dry run"
|
||||
option :jobs, aliases: :j, type: :numeric,
|
||||
desc: "Number of jobs to run parallely"
|
||||
option :ui, type: :string, default: "status",
|
||||
desc: "Which UI to use: status, text, silent"
|
||||
option :bg, type: :boolean,
|
||||
desc: "Runs parallel processes in background"
|
||||
option :log_path, type: :string,
|
||||
desc: "Path to the runner and error logs"
|
||||
exec_options
|
||||
def exec(default_profile = nil)
|
||||
Inspec.with_feature("inspec-cli-parallel-exec") {
|
||||
parallel_cmd = InspecPlugins::Parallelism::Command.new(options, default_profile)
|
||||
if options[:dry_run]
|
||||
parallel_cmd.dry_run
|
||||
else
|
||||
parallel_cmd.run
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
219
lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb
Normal file
219
lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb
Normal file
|
@ -0,0 +1,219 @@
|
|||
require_relative "runner"
|
||||
require_relative "validator"
|
||||
require "erb" unless defined?(Erb)
|
||||
|
||||
module InspecPlugins
|
||||
module Parallelism
|
||||
|
||||
class OptionFileNotReadable < RuntimeError
|
||||
end
|
||||
|
||||
class Command
|
||||
attr_accessor :cli_options_to_parallel_cmd, :default_profile, :sub_cmd, :invocations, :run_in_background
|
||||
|
||||
def initialize(cli_options_to_parallel_cmd, default_profile, sub_cmd = "exec")
|
||||
@default_profile = default_profile
|
||||
@cli_options_to_parallel_cmd = cli_options_to_parallel_cmd
|
||||
@sub_cmd = sub_cmd
|
||||
@logger = Inspec::Log
|
||||
@invocations = read_options_file
|
||||
@run_in_background = cli_options_to_parallel_cmd["bg"]
|
||||
end
|
||||
|
||||
def run
|
||||
validate_thor_options
|
||||
validate_invocations!
|
||||
runner = Runner.new(invocations, cli_options_to_parallel_cmd, sub_cmd)
|
||||
catch_ctl_c_and_exit(runner) unless run_in_background
|
||||
runner.run
|
||||
end
|
||||
|
||||
def dry_run
|
||||
validate_invocations!
|
||||
dry_run_commands
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def catch_ctl_c_and_exit(runner)
|
||||
puts "Press CTL+C to stop\n"
|
||||
trap("SIGINT") do
|
||||
puts "\n"
|
||||
puts "Shutting down jobs..."
|
||||
if Inspec.locally_windows?
|
||||
runner.kill_child_processes
|
||||
sleep 1
|
||||
puts "Renaming error log files..."
|
||||
runner.rename_error_log_files
|
||||
end
|
||||
exit Inspec::UI::EXIT_TERMINATED_BY_CTL_C
|
||||
end
|
||||
end
|
||||
|
||||
def validate_thor_options
|
||||
# only log path validation needed for now
|
||||
validate_log_path
|
||||
end
|
||||
|
||||
def validate_log_path
|
||||
error, message = Validator.new(invocations, cli_options_to_parallel_cmd, sub_cmd).validate_log_path
|
||||
if error
|
||||
@logger.error message
|
||||
Inspec::UI.new.exit(:usage_error)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_invocations!
|
||||
# Validation logic stays in Validator class...
|
||||
Validator.new(invocations, cli_options_to_parallel_cmd, sub_cmd).validate
|
||||
# UI logic stays in Command class.
|
||||
valid = true
|
||||
invocations.each do |invocation_data|
|
||||
invocation_data[:validation_errors].each do |error_message|
|
||||
valid = false
|
||||
@logger.error "Line #{invocation_data[:line_no]}: " + error_message
|
||||
end
|
||||
end
|
||||
unless valid
|
||||
@logger.error "Please fix the options to proceed further."
|
||||
Inspec::UI.new.exit(:usage_error)
|
||||
end
|
||||
end
|
||||
|
||||
def dry_run_commands
|
||||
invocations.each do |invocation_data|
|
||||
puts "inspec #{sub_cmd} #{invocation_data[:value]}"
|
||||
end
|
||||
end
|
||||
|
||||
## Utility functions
|
||||
|
||||
def read_options_file
|
||||
opts = []
|
||||
begin
|
||||
content = content_from_file(cli_options_to_parallel_cmd[:option_file])
|
||||
rescue OptionFileNotReadable => e
|
||||
@logger.error "Cannot read options file: #{e.message}"
|
||||
Inspec::UI.new.exit(:usage_error)
|
||||
end
|
||||
content.each.with_index(1) do |str, index|
|
||||
data_hash = { line_no: index }
|
||||
str = ERB.new(str).result_with_hash(pid: "CHILD_PID").strip
|
||||
str_has_comment = str.start_with?("#")
|
||||
next if str.empty? || str_has_comment
|
||||
|
||||
default_options = fetch_default_options(str.split(" ")).lstrip
|
||||
if str.start_with?("-")
|
||||
data_hash[:value] = "#{default_profile} #{str} #{default_options}"
|
||||
else
|
||||
data_hash[:value] = "#{str} #{default_options}"
|
||||
end
|
||||
opts << data_hash
|
||||
end
|
||||
opts
|
||||
end
|
||||
|
||||
def content_from_file(option_file)
|
||||
if File.exist?(option_file)
|
||||
unless [".sh", ".csh", ".ps1"].include? File.extname(option_file)
|
||||
File.readlines(option_file)
|
||||
else
|
||||
if Inspec.locally_windows? && (File.extname(option_file) == ".ps1")
|
||||
begin
|
||||
output = `powershell -File "#{option_file}"`
|
||||
output.split("\n")
|
||||
rescue StandardError => e
|
||||
raise OptionFileNotReadable.new("Error reading powershell file #{option_file}: #{e.message}")
|
||||
end
|
||||
elsif [".sh", ".csh"].include? File.extname(option_file)
|
||||
begin
|
||||
output = `bash "#{option_file}"`
|
||||
output.split("\n")
|
||||
rescue StandardError => e
|
||||
raise OptionFileNotReadable.new("Error reading shell file #{option_file}: #{e.message}")
|
||||
end
|
||||
else
|
||||
raise OptionFileNotReadable.new("Powershell not supported in your system.")
|
||||
end
|
||||
end
|
||||
else
|
||||
raise OptionFileNotReadable.new("Option file not found.")
|
||||
end
|
||||
end
|
||||
|
||||
# this must return empty string or default option string which are not part of option file
|
||||
def fetch_default_options(option_line)
|
||||
option_line = option_line.select { |word| word.start_with?("-") }
|
||||
|
||||
# remove prefixes from the options to compare with default options
|
||||
option_line.map! do |option_key|
|
||||
option_key.gsub(options_prefix(option_key), "").gsub("-", "_")
|
||||
end
|
||||
|
||||
default_opts = ""
|
||||
# iterate through the parallel cli default options and append the option and value which are not present in option file
|
||||
parallel_cmd_default_cli_options.each do |cmd|
|
||||
if cmd.is_a? String
|
||||
append_default_value(default_opts, cmd) unless option_line.include?(cmd)
|
||||
elsif cmd.is_a? Array
|
||||
if !option_line.include?(cmd[0]) && !option_line.include?(cmd[1])
|
||||
append_default_value(default_opts, cmd[0])
|
||||
end
|
||||
end
|
||||
end
|
||||
default_opts
|
||||
end
|
||||
|
||||
# returns array of default options of the subcommand
|
||||
def parallel_cmd_default_cli_options
|
||||
sub_cmd_opts = Inspec::InspecCLI.commands[sub_cmd].options
|
||||
parallel_cmd_default_opts = cli_options_to_parallel_cmd.keys & sub_cmd_opts.keys.map(&:to_s)
|
||||
options_to_append = parallel_cmd_default_opts
|
||||
|
||||
if cli_options_to_parallel_cmd["dry_run"] && !cli_options_to_parallel_cmd["verbose"]
|
||||
# to not show thor default options of inspec commands in dry run
|
||||
sub_cmd_opts_with_defaults = fetch_sub_cmd_default_options(sub_cmd_opts)
|
||||
options_to_append -= sub_cmd_opts_with_defaults
|
||||
end
|
||||
|
||||
default_opts_to_append = []
|
||||
|
||||
# append the options and its aliases if available.
|
||||
options_to_append.each do |option_name|
|
||||
opt_alias = sub_cmd_opts[option_name.to_sym].aliases
|
||||
if opt_alias.empty?
|
||||
default_opts_to_append << option_name
|
||||
else
|
||||
default_opts_to_append << [option_name, opt_alias[0].to_s]
|
||||
end
|
||||
end
|
||||
default_opts_to_append
|
||||
end
|
||||
|
||||
def append_default_value(default_opts, command_name)
|
||||
default_value = cli_options_to_parallel_cmd[command_name.to_sym]
|
||||
default_value = default_value.join(" ") if default_value.is_a? Array
|
||||
default_opts << " --#{command_name.gsub("_", "-")} #{default_value}"
|
||||
end
|
||||
|
||||
def options_prefix(option_name)
|
||||
if option_name.start_with?("--")
|
||||
option_name.start_with?("--no-") ? "--no-" : "--"
|
||||
else
|
||||
"-"
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_sub_cmd_default_options(sub_cmd_opts)
|
||||
default_options_to_remove = []
|
||||
sub_cmd_opts_with_defaults = sub_cmd_opts.select { |_, c| !c.default.nil? }.keys.map(&:to_s)
|
||||
sub_cmd_opts_with_defaults.each do |default_opt_name|
|
||||
if sub_cmd_opts[default_opt_name.to_sym].default == cli_options_to_parallel_cmd[default_opt_name]
|
||||
default_options_to_remove << default_opt_name
|
||||
end
|
||||
end
|
||||
default_options_to_remove
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
265
lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb
Normal file
265
lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb
Normal file
|
@ -0,0 +1,265 @@
|
|||
require "inspec/cli"
|
||||
require "concurrent"
|
||||
require_relative "super_reporter/base"
|
||||
|
||||
module InspecPlugins
|
||||
module Parallelism
|
||||
class Runner
|
||||
attr_accessor :invocations, :sub_cmd, :total_jobs, :run_in_background, :log_path
|
||||
|
||||
def initialize(invocations, cli_options, sub_cmd = "exec")
|
||||
@invocations = invocations
|
||||
@sub_cmd = sub_cmd
|
||||
@total_jobs = cli_options["jobs"] || Concurrent.physical_processor_count
|
||||
@child_tracker = {}
|
||||
@child_tracker_persisted = {}
|
||||
@run_in_background = cli_options["bg"]
|
||||
unless run_in_background
|
||||
@ui = InspecPlugins::Parallelism::SuperReporter.make(cli_options["ui"], total_jobs, invocations)
|
||||
end
|
||||
@log_path = cli_options["log_path"]
|
||||
end
|
||||
|
||||
def run
|
||||
initiate_background_run if run_in_background # running a process as daemon changes parent process pid
|
||||
until invocations.empty? && @child_tracker.empty?
|
||||
while should_start_more_jobs?
|
||||
if Inspec.locally_windows?
|
||||
spawn_another_process
|
||||
else
|
||||
fork_another_process
|
||||
end
|
||||
end
|
||||
|
||||
update_ui_poll_select
|
||||
cleanup_child_processes
|
||||
sleep 0.1
|
||||
end
|
||||
|
||||
# Requires renaming operations on windows only
|
||||
# Do Rename and delete operations after all child processes have exited successfully
|
||||
rename_error_log_files if Inspec.locally_windows?
|
||||
cleanup_empty_error_log_files
|
||||
cleanup_daemon_process if run_in_background
|
||||
end
|
||||
|
||||
def initiate_background_run
|
||||
if Inspec.locally_windows?
|
||||
Inspec::UI.new.exit(:usage_error)
|
||||
else
|
||||
Process.daemon(true, true)
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_daemon_process
|
||||
current_process_id = Process.pid
|
||||
Process.kill(9, current_process_id)
|
||||
# DO NOT TRY TO REFACTOR IT THIS WAY
|
||||
# Calling Process.kill(9,Process.pid) kills the "stopper" process itself, rather than the one it's trying to stop.
|
||||
end
|
||||
|
||||
def cleanup_empty_error_log_files
|
||||
logs_dir_path = log_path || Dir.pwd
|
||||
error_files = Dir.glob("#{logs_dir_path}/logs/*.err")
|
||||
error_files.each do |error_file|
|
||||
if File.exist?(error_file) && !File.size?(error_file)
|
||||
File.delete(error_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def kill_child_processes
|
||||
@child_tracker.each do |pid, info|
|
||||
Process.kill("SIGKILL", pid)
|
||||
rescue Exception => e
|
||||
$stderr.puts "Error while shutting down process #{pid}: #{e.message}"
|
||||
end
|
||||
# Waiting for child processes to die after they have been killed
|
||||
wait_for_child_processes_to_die
|
||||
end
|
||||
|
||||
def wait_for_child_processes_to_die
|
||||
until @child_tracker.empty?
|
||||
begin
|
||||
exited_pid = Process.waitpid(-1, Process::WNOHANG)
|
||||
@child_tracker.delete exited_pid if exited_pid && exited_pid > 0
|
||||
sleep 1
|
||||
rescue Errno::ECHILD
|
||||
Inspec::Log.info "Processes shutdown complete!"
|
||||
rescue Exception => e
|
||||
Inspec::Log.debug "Error while waiting for child processes to shutdown: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def rename_error_log_files
|
||||
@child_tracker_persisted.each do |pid, info|
|
||||
rename_error_log(info[:error_log_file], pid)
|
||||
end
|
||||
end
|
||||
|
||||
def should_start_more_jobs?
|
||||
@child_tracker.length < total_jobs && !invocations.empty?
|
||||
end
|
||||
|
||||
def spawn_another_process
|
||||
invocation = invocations.shift[:value]
|
||||
|
||||
child_reader, parent_writer = IO.pipe
|
||||
begin
|
||||
logs_dir_path = log_path || Dir.pwd
|
||||
log_dir = File.join(logs_dir_path, "logs")
|
||||
FileUtils.mkdir_p(log_dir)
|
||||
error_log_file = File.open("#{log_dir}/#{Time.now.nsec}.err", "a+")
|
||||
cmd = "#{$0} #{sub_cmd} #{invocation}"
|
||||
log_msg = "#{Time.now.iso8601} Start Time: #{Time.now}\n#{Time.now.iso8601} Arguments: #{invocation}\n"
|
||||
child_pid = Process.spawn(cmd, out: parent_writer, err: error_log_file.path)
|
||||
|
||||
# Logging
|
||||
create_logs(child_pid, log_msg)
|
||||
@child_tracker[child_pid] = { io: child_reader }
|
||||
|
||||
# This is used to rename error log files after all child processes are exited
|
||||
@child_tracker_persisted[child_pid] = { error_log_file: error_log_file }
|
||||
@ui.child_spawned(child_pid, invocation)
|
||||
|
||||
# Close the file to unlock the error log files opened by processes
|
||||
error_log_file.close
|
||||
rescue StandardError => e
|
||||
$stderr.puts "#{Time.now.iso8601} Error Message: #{e.message}"
|
||||
$stderr.puts "#{Time.now.iso8601} Error Backtrace: #{e.backtrace}"
|
||||
end
|
||||
end
|
||||
|
||||
def fork_another_process
|
||||
invocation = invocations.shift[:value] # Be sure to do this shift() in parent process
|
||||
# thing_that_reads_from_the_child, thing_that_writes_to_the_parent = IO.pipe
|
||||
child_reader, parent_writer = IO.pipe
|
||||
if (child_pid = Process.fork)
|
||||
# In parent with newly forked child
|
||||
parent_writer.close
|
||||
@child_tracker[child_pid] = { io: child_reader }
|
||||
@ui.child_forked(child_pid, invocation) unless run_in_background
|
||||
else
|
||||
# In child
|
||||
child_reader.close
|
||||
# replace stdout with writer
|
||||
$stdout = parent_writer
|
||||
create_logs(Process.pid, nil, $stderr)
|
||||
|
||||
begin
|
||||
create_logs(
|
||||
Process.pid,
|
||||
"#{Time.now.iso8601} Start Time: #{Time.now}\n#{Time.now.iso8601} Arguments: #{invocation}\n"
|
||||
)
|
||||
runner_invocation(invocation)
|
||||
rescue StandardError => e
|
||||
$stderr.puts "#{Time.now.iso8601} Error Message: #{e.message}"
|
||||
$stderr.puts "#{Time.now.iso8601} Error Backtrace: #{e.backtrace}"
|
||||
end
|
||||
|
||||
# should be unreachable but child MUST exit
|
||||
exit(42)
|
||||
end
|
||||
end
|
||||
|
||||
# Still in parent
|
||||
# Loop over children and check for finished processes
|
||||
def cleanup_child_processes
|
||||
@child_tracker.each do |pid, info|
|
||||
if Process.wait(pid, Process::WNOHANG)
|
||||
# Expect to (probably) find EOF marker on the pipe, and close it if so
|
||||
update_ui_poll_select(pid)
|
||||
|
||||
create_logs(pid, "#{Time.now.iso8601} Exit code: #{$?}\n")
|
||||
|
||||
# child exited - status in $?
|
||||
@ui.child_exited(pid) unless run_in_background
|
||||
@child_tracker.delete pid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_ui_poll_select(target_pid = nil)
|
||||
# Focus on one pid's pipe if specified, otherwise poll all pipes
|
||||
pipes_for_reading = target_pid ? [ @child_tracker[target_pid][:io] ] : @child_tracker.values.map { |i| i[:io] }
|
||||
# Next line is due to a race between the close() and the wait()... shouldn't need it, but it fixes the race.
|
||||
pipes_for_reading.reject!(&:closed?)
|
||||
ready_pipes = IO.select(pipes_for_reading, [], [], 0.1)
|
||||
return unless ready_pipes
|
||||
|
||||
ready_pipes[0].each do |pipe_ready_for_reading|
|
||||
# If we weren't provided a PID, hackishly look up the pid from the matching IO.
|
||||
pid = target_pid || @child_tracker.keys.detect { |p| @child_tracker[p][:io] == pipe_ready_for_reading }
|
||||
begin
|
||||
while (update_line = pipe_ready_for_reading.readline) && !pipe_ready_for_reading.closed?
|
||||
if update_line =~ /EOF_MARKER/
|
||||
pipe_ready_for_reading.close
|
||||
break
|
||||
elsif update_line =~ /WARN/ || update_line =~ /ERROR/ || update_line =~ /INFO/
|
||||
create_logs(
|
||||
pid,
|
||||
"#{Time.now.iso8601} Extra log: #{update_line}\n"
|
||||
)
|
||||
break
|
||||
end
|
||||
update_ui_with_line(pid, update_line) unless run_in_background
|
||||
# Only pull one line if we are doing normal updates; slurp the whole file
|
||||
# if we are doing a final pull on a targeted PID
|
||||
break unless target_pid
|
||||
end
|
||||
rescue EOFError
|
||||
# On unix, readline throws an EOFError when we hit the end. On Windows, nothing apparently happens.
|
||||
pipe_ready_for_reading.close
|
||||
next
|
||||
end
|
||||
end
|
||||
# TODO: loop over ready_pipes[2] and handle errors?
|
||||
end
|
||||
|
||||
def update_ui_with_line(pid, update_line)
|
||||
@ui.child_status_update_line(pid, update_line)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def runner_invocation(runner_option)
|
||||
splitted_result = runner_option.split(" ")
|
||||
profile_to_run = splitted_result[0]
|
||||
splitted_result.delete_at(0)
|
||||
|
||||
# thor invocation
|
||||
arguments = [sub_cmd, profile_to_run, splitted_result].flatten
|
||||
Inspec::InspecCLI.start(arguments, enforce_license: true)
|
||||
end
|
||||
|
||||
def create_logs(child_pid, run_log , stderr = nil)
|
||||
logs_dir_path = log_path || Dir.pwd
|
||||
log_dir = File.join(logs_dir_path, "logs")
|
||||
FileUtils.mkdir_p(log_dir)
|
||||
|
||||
if stderr
|
||||
log_file = File.join(log_dir, "#{child_pid}.err") unless File.exist?("#{child_pid}.err")
|
||||
stderr.reopen(log_file, "a")
|
||||
else
|
||||
log_file = File.join(log_dir, "#{child_pid}.log") unless File.exist?("#{child_pid}.log")
|
||||
File.write(log_file, run_log, mode: "a")
|
||||
end
|
||||
end
|
||||
|
||||
def rename_error_log(error_log_file, child_pid)
|
||||
logs_dir_path = log_path || Dir.pwd
|
||||
log_dir = File.join(logs_dir_path, "logs")
|
||||
FileUtils.mkdir_p(log_dir)
|
||||
|
||||
if error_log_file.closed? && File.exist?(error_log_file.path)
|
||||
begin
|
||||
File.rename("#{error_log_file.path}", "#{log_dir}/#{child_pid}.err")
|
||||
rescue
|
||||
$stderr.puts "Cannot rename error log file #{error_log_file.path} for child pid #{child_pid}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
module InspecPlugins::Parallelism
|
||||
class SuperReporter
|
||||
|
||||
def self.make(type, job_count, invocations)
|
||||
Object.const_get("InspecPlugins::Parallelism::SuperReporter::" + (type[0].upcase + type[1..-1])).new(job_count, invocations)
|
||||
end
|
||||
|
||||
class Base
|
||||
def initialize(job_count, invocations); end
|
||||
|
||||
def child_spawned(pid, invocation); end
|
||||
|
||||
def child_forked(pid, invocation); end
|
||||
|
||||
def child_exited(pid); end
|
||||
|
||||
def child_status_update_line(pid, update_line); end
|
||||
end
|
||||
|
||||
require_relative "text"
|
||||
require_relative "status"
|
||||
require_relative "silent"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
module InspecPlugins::Parallelism
|
||||
class SuperReporter
|
||||
class Silent < InspecPlugins::Parallelism::SuperReporter::Base
|
||||
# This is a silent super reporter with no reporting functionality.
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,124 @@
|
|||
require "highline"
|
||||
|
||||
module InspecPlugins::Parallelism
|
||||
class SuperReporter
|
||||
class Status < InspecPlugins::Parallelism::SuperReporter::Base
|
||||
|
||||
attr_reader :status_by_pid, :slots
|
||||
|
||||
def initialize(job_count, invocations)
|
||||
@status_by_pid = {}
|
||||
@slots = Array.new(job_count)
|
||||
paint_header(job_count, invocations)
|
||||
paint
|
||||
end
|
||||
|
||||
# --------
|
||||
# SuperReporter API
|
||||
# --------
|
||||
def child_spawned(pid, invocation)
|
||||
new_child("spawned", pid, invocation)
|
||||
end
|
||||
|
||||
def child_forked(pid, invocation)
|
||||
new_child("forked", pid, invocation)
|
||||
end
|
||||
|
||||
def child_exited(pid)
|
||||
slots[status_by_pid[pid][:slot]] = "exited"
|
||||
|
||||
status_by_pid[pid][:pct] = 100.0
|
||||
status_by_pid[pid][:slot] = nil
|
||||
status_by_pid[pid][:exit] = $?
|
||||
|
||||
# TODO: consider holding slot in 100 status for UI grace
|
||||
|
||||
paint
|
||||
end
|
||||
|
||||
def child_status_update_line(pid, update_line)
|
||||
control_serial, status, control_count, title = update_line.split("/")
|
||||
percent = 100.0 * control_serial.to_i / control_count.to_i.to_f
|
||||
|
||||
status_by_pid[pid][:pct] = percent
|
||||
status_by_pid[pid][:last_control] = title
|
||||
status_by_pid[pid][:last_status] = status
|
||||
|
||||
paint
|
||||
end
|
||||
|
||||
# --------
|
||||
# Utilities
|
||||
# --------
|
||||
private
|
||||
|
||||
def new_child(how, pid, invocation)
|
||||
# Update status by PID with new info
|
||||
status_by_pid[pid] = {
|
||||
pct: 0.0,
|
||||
inv: invocation,
|
||||
how: how,
|
||||
}
|
||||
|
||||
# Assign first empty slot
|
||||
slots.each_index do |idx|
|
||||
next unless slots[idx].nil? || slots[idx] == "exited"
|
||||
|
||||
slots[idx] = pid
|
||||
status_by_pid[pid][:slot] = idx
|
||||
break
|
||||
end
|
||||
|
||||
# TODO: consider printing log message
|
||||
paint
|
||||
end
|
||||
|
||||
def terminal_width
|
||||
return @terminal_width if @terminal_width
|
||||
|
||||
@highline ||= HighLine.new
|
||||
width = @highline.output_cols.to_i
|
||||
width = 80 if width < 1
|
||||
@terminal_width = width
|
||||
end
|
||||
|
||||
def paint
|
||||
# Determine the width of a slot
|
||||
slot_width = terminal_width / slots.length
|
||||
line = ""
|
||||
# Loop over slots
|
||||
slots.each_index do |idx|
|
||||
if slots[idx].nil?
|
||||
# line += "idle".center(slot_width)
|
||||
# Need to improve UI
|
||||
elsif slots[idx] == "exited"
|
||||
line += "Done".center(slot_width)
|
||||
else
|
||||
pid = slots[idx]
|
||||
with_pid = format("%s: %0.1f%%", pid, status_by_pid[pid][:pct])
|
||||
if with_pid.length <= slot_width - 2
|
||||
line += with_pid.center(slot_width)
|
||||
else
|
||||
line += format("%0.1f%%", status_by_pid[pid][:pct]).center(slot_width)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print "\r" + (" " * terminal_width) + "\r"
|
||||
print line
|
||||
end
|
||||
|
||||
def paint_header(jobs, invocations)
|
||||
puts "InSpec Parallel".center(terminal_width)
|
||||
puts "Running #{invocations.length} invocations in #{jobs} slots".center(terminal_width)
|
||||
puts "-" * terminal_width
|
||||
slot_width = terminal_width / slots.length
|
||||
slots.each_index do |idx|
|
||||
print "Slot #{idx + 1}".center(slot_width)
|
||||
end
|
||||
puts
|
||||
puts "-" * terminal_width
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
module InspecPlugins::Parallelism
|
||||
class SuperReporter
|
||||
class Text < InspecPlugins::Parallelism::SuperReporter::Base
|
||||
def child_spawned(pid, _inv)
|
||||
puts "[#{Time.now.iso8601}] Spawned child PID #{pid}"
|
||||
end
|
||||
|
||||
def child_forked(pid, _inv)
|
||||
puts "[#{Time.now.iso8601}] Forked child PID #{pid}"
|
||||
end
|
||||
|
||||
def child_exited(pid)
|
||||
puts "[#{Time.now.iso8601}] Exited child PID #{pid} status #{$?}"
|
||||
end
|
||||
|
||||
def child_status_update_line(pid, update_line)
|
||||
control_serial, _status, control_count, _title = update_line.split("/")
|
||||
percent = 100.0 * control_serial.to_i / control_count.to_i.to_f
|
||||
puts "[#{Time.now.iso8601}] #{pid} " + format("%.1f%%", percent)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
170
lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb
Normal file
170
lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb
Normal file
|
@ -0,0 +1,170 @@
|
|||
require "inspec/cli"
|
||||
module InspecPlugins
|
||||
module Parallelism
|
||||
class Validator
|
||||
|
||||
# TODO: make this list dynamic so plugins can self-declare
|
||||
PARALLEL_SAFE_REPORTERS = [
|
||||
"automate", # Performs HTTP transactions, silent on STDOUT
|
||||
"child-status", # Writes dedicated protocol to STDOUT, expected by parent
|
||||
].freeze
|
||||
|
||||
attr_accessor :invocations, :sub_cmd, :thor_options_for_sub_cmd, :aliases_mapping, :cli_options, :config_content, :stdin_config
|
||||
|
||||
def initialize(invocations, cli_options, sub_cmd = "exec")
|
||||
@invocations = invocations
|
||||
@sub_cmd = sub_cmd
|
||||
@thor_options_for_sub_cmd = Inspec::InspecCLI.commands[sub_cmd].options
|
||||
@aliases_mapping = create_aliases_mapping
|
||||
@cli_options = cli_options
|
||||
@config_content = nil
|
||||
@stdin_config = nil
|
||||
end
|
||||
|
||||
def validate
|
||||
invocations.each do |invocation_data|
|
||||
invocation_data[:validation_errors] = []
|
||||
|
||||
convert_cli_to_thor_options(invocation_data)
|
||||
check_for_spurious_options(invocation_data)
|
||||
check_for_required_fields(invocation_data)
|
||||
check_for_reporter_options(invocation_data)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def validate_log_path
|
||||
return [] unless cli_options["log_path"]
|
||||
|
||||
if File.directory?(cli_options["log_path"])
|
||||
[]
|
||||
else
|
||||
[true, "Log path #{cli_options["log_path"]} is not accessible"]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_aliases_mapping
|
||||
alias_mapping = {}
|
||||
thor_options_for_sub_cmd.each do |_, sub_cmd_option|
|
||||
aliases = sub_cmd_option.aliases
|
||||
unless aliases.empty?
|
||||
alias_mapping[aliases[0]] = sub_cmd_option.name
|
||||
end
|
||||
end
|
||||
alias_mapping
|
||||
end
|
||||
|
||||
def check_for_spurious_options(invocation_data)
|
||||
# LIMITATION: Assume the first arg is the profile name, and there is exactly one of them.
|
||||
invalid_options = invocation_data[:thor_args][1..-1]
|
||||
invocation_data[:validation_errors].push "No such option: #{invalid_options}" unless invalid_options.empty?
|
||||
end
|
||||
|
||||
def check_for_required_fields(invocation_data)
|
||||
required_fields = thor_options_for_sub_cmd.collect { |_, thor_option| thor_option.name if thor_option.required }.compact
|
||||
option_keys = invocation_data[:thor_opts].keys
|
||||
invocation_data[:thor_opts].keys.map { |key| option_keys.push(aliases_mapping[key.to_sym]) if aliases_mapping[key.to_sym] }
|
||||
if !required_fields.empty? && (option_keys & required_fields).empty?
|
||||
invocation_data[:validation_errors].push "No value provided for required options: #{required_fields}"
|
||||
end
|
||||
end
|
||||
|
||||
def check_for_reporter_options(invocation_data)
|
||||
# if no reporter option, that's an error
|
||||
unless invocation_data[:thor_opts].include?("reporter")
|
||||
# Check for config reporter validation only if --reporter option is missing from options file
|
||||
return if check_reporter_options_in_config(invocation_data)
|
||||
|
||||
invocation_data[:validation_errors] << "A --reporter option must be specified for each invocation in the options file"
|
||||
return
|
||||
end
|
||||
|
||||
have_child_status_reporter = false
|
||||
|
||||
# Reporter option is formatted as an array
|
||||
invocation_data[:thor_opts]["reporter"].each do |reporter_spec|
|
||||
reporter_name, file_output = reporter_spec.split(":")
|
||||
|
||||
have_child_status_reporter = true if reporter_name == "child-status"
|
||||
|
||||
# if there is a reporter option, each entry must either write to a file or
|
||||
# else be the special child-status reporter or the automate reporter
|
||||
next if PARALLEL_SAFE_REPORTERS.include?(reporter_name)
|
||||
|
||||
unless file_output
|
||||
invocation_data[:validation_errors] << "The #{reporter_name} reporter requires being directed to a file, like #{reporter_name}:filename.out"
|
||||
end
|
||||
end
|
||||
|
||||
# if there is no child-status reporter, add one to the raw value and the parsed array
|
||||
unless have_child_status_reporter
|
||||
# Eww
|
||||
invocation_data[:thor_opts]["reporter"] << "child-status"
|
||||
invocation_data[:value].gsub!("--reporter ", "--reporter child-status ")
|
||||
end
|
||||
end
|
||||
|
||||
def check_reporter_options_in_config(invocation_data)
|
||||
config_opts = invocation_data[:thor_opts]["config"] || invocation_data[:thor_opts]["json_config"]
|
||||
cfg_io = check_for_piped_config_from_stdin(config_opts)
|
||||
|
||||
if cfg_io == STDIN
|
||||
# Scenario of using config from STDIN
|
||||
@config_content ||= cfg_io.read
|
||||
else
|
||||
if config_opts.nil?
|
||||
# Scenario of using default config.json file when path not provided
|
||||
default_path = File.join(Inspec.config_dir, "config.json")
|
||||
config_opts = default_path
|
||||
return unless File.exist?(config_opts)
|
||||
elsif !File.exist?(config_opts)
|
||||
invocation_data[:validation_errors] << "Could not read configuration file at #{config_opts}"
|
||||
return
|
||||
end
|
||||
@config_content = File.open(config_opts).read
|
||||
end
|
||||
|
||||
reporter_config = JSON.parse(config_content)["reporter"] unless config_content.nil? || config_content.empty?
|
||||
unless reporter_config
|
||||
invocation_data[:validation_errors] << "Config should have reporter option specified for each invocation which is not using --reporter option in options file"
|
||||
end
|
||||
@config_content
|
||||
end
|
||||
|
||||
def check_for_piped_config_from_stdin(config_opts)
|
||||
return nil unless config_opts
|
||||
return nil unless config_opts == "-"
|
||||
|
||||
@stdin_config ||= STDIN
|
||||
end
|
||||
|
||||
## Utility functions
|
||||
|
||||
# Parse the invocation string using Thor into Thor options
|
||||
# This approach was reverse engineered from studying
|
||||
# https://github.com/rails/thor/blob/ab3b5be455791f4efb79f0efb4f88cc6b59c8ccf/lib/thor/base.rb#L53
|
||||
|
||||
def convert_cli_to_thor_options(invocation_data)
|
||||
invocation_words = invocation_data[:value].split(" ")
|
||||
|
||||
# LIMITATION: this approach is limited to having exactly one profile in the invocation
|
||||
args = [invocation_words.shift] # That is, the profile path
|
||||
|
||||
# Here we're piggybacking on on a hook used by the start() method, and provides the
|
||||
# specifics for the subcommand
|
||||
config = { command_options: thor_options_for_sub_cmd }
|
||||
|
||||
# This performs the parse
|
||||
thor = Inspec::InspecCLI.new(args, invocation_words, config)
|
||||
|
||||
# A hash (with indifferent access) of option names to option config data
|
||||
invocation_data[:thor_opts] = thor.options
|
||||
|
||||
# A list of everything else it could not parse, including the profile
|
||||
invocation_data[:thor_args] = thor.args
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
28
lib/plugins/inspec-parallel/test/fixtures/options-file-1.txt
vendored
Normal file
28
lib/plugins/inspec-parallel/test/fixtures/options-file-1.txt
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
# This option file is used for testing parallel dry run
|
||||
# Test 1: test_parallel_dry_run
|
||||
# Command to execute: bundle exec inspec parallel exec test/fixtures/profiles/complete-profile -o lib/plugins/inspec-parallel/test/fixtures/options-file-1.txt --dry-run
|
||||
|
||||
# Comments (line starting with #),
|
||||
# comments with improper indentation (whitespaces at beginning and end).
|
||||
# and blank lines will be ignored while parsing this option file.
|
||||
|
||||
|
||||
# Scenario 1: Use profile if specified in the cli
|
||||
-t ssh://vagrant@127.0.0.1:2201 --reporter cli:myfile.out --no-create-lockfile --no-sudo
|
||||
|
||||
# Scenario 2: Use profile specified in the option file
|
||||
test/fixtures/profiles/control-tags -t ssh://vagrant@127.0.0.1:2201 --reporter cli:myfile.out
|
||||
test/fixtures/profiles/control-tags --input input1=testvalue1 input2=testvalue2 --command-timeout 10 --reporter cli:myfile.out
|
||||
|
||||
# Test 2: test_parallel_dry_run_with_default_opts
|
||||
# Command to execute: bundle exec inspec parallel exec test/fixtures/profiles/complete-profile -o lib/plugins/inspec-parallel/test/fixtures/options-file-1.txt -t docker://8b5ec1a0344b --reporter json --dry-run
|
||||
|
||||
# Scenario 1: Append no default options from the cli
|
||||
test/fixtures/profiles/basic_profile -t docker://8b5ec1a0344b --reporter json:myfile.json
|
||||
|
||||
# Scenario 2: Append default profile from the cli
|
||||
-t docker://1870886821c3 --reporter cli:myfile.out
|
||||
|
||||
# Scenario 3: Append default profile and target from the cli
|
||||
--reporter cli:myfile.out
|
||||
|
9
lib/plugins/inspec-parallel/test/fixtures/options-file-2.txt
vendored
Normal file
9
lib/plugins/inspec-parallel/test/fixtures/options-file-2.txt
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
# This option file is used for testing below two tests when there is a typo:
|
||||
# Test 1: test_parallel_dry_run_with_typo_in_option
|
||||
# Test 2: test_parallel_run_with_typo_in_option
|
||||
|
||||
# Comments (line starting with #), whitespaces(beginning and end of line)
|
||||
# and blank lines will be ignored while parsing this option file.
|
||||
|
||||
# Scenario 1: Typo with the option keyword
|
||||
--targetss "should_raise_error"
|
16
lib/plugins/inspec-parallel/test/fixtures/options-file-3.txt
vendored
Normal file
16
lib/plugins/inspec-parallel/test/fixtures/options-file-3.txt
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
# This option file is used for testing with no target so that it doesn't fail on buildkite
|
||||
# Test 1: test_parallel_run_without_forking
|
||||
# Test 2: test_parallel_run_without_fork_with_default_opts
|
||||
|
||||
# Comments (line starting with #), whitespaces(beginning and end of line)
|
||||
# and blank lines will be ignored while parsing this option file.
|
||||
|
||||
|
||||
# Scenario 1: Use profile if specified in the cli
|
||||
--reporter json:myfile.json
|
||||
|
||||
# Scenario 2: Use the profile specified in the option file
|
||||
test/fixtures/profiles/control-tags --reporter cli:myfile.out
|
||||
|
||||
# Scenario 3: Using ERB templating syntax in options file
|
||||
test/fixtures/profiles/control-tags --reporter <%= "cli:myfile.out" %>
|
3
lib/plugins/inspec-parallel/test/fixtures/options-file-4.txt
vendored
Normal file
3
lib/plugins/inspec-parallel/test/fixtures/options-file-4.txt
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# This file tests --reporter option validation. In this case, the file is invalid because
|
||||
# the invocation contains a json reporter without a file.
|
||||
-t local:// --reporter json
|
5
lib/plugins/inspec-parallel/test/fixtures/options-file-5.txt
vendored
Normal file
5
lib/plugins/inspec-parallel/test/fixtures/options-file-5.txt
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
# This file tests reporter validation by testing an options
|
||||
# file that does not specify a reporter at all.
|
||||
-t local:// --reporter cli:myfile.out
|
||||
-t local:// --reporter cli:myfile.out
|
||||
-t local://
|
2
lib/plugins/inspec-parallel/test/fixtures/options-file-shell-1.sh
vendored
Normal file
2
lib/plugins/inspec-parallel/test/fixtures/options-file-shell-1.sh
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
cat lib/plugins/inspec-parallel/test/fixtures/options-file-3.txt
|
|
@ -0,0 +1,107 @@
|
|||
require_relative "../../../shared/core_plugin_test_helper"
|
||||
require_relative "../../../../../test/functional/helper"
|
||||
|
||||
class ParallelCli < Minitest::Test
|
||||
include CorePluginFunctionalHelper
|
||||
|
||||
let(:options_file_1) { File.join("lib", "plugins", "inspec-parallel", "test", "fixtures", "options-file-1.txt") }
|
||||
let(:options_file_2) { File.join("lib", "plugins", "inspec-parallel", "test", "fixtures", "options-file-2.txt") }
|
||||
let(:options_file_3) { File.join("lib", "plugins", "inspec-parallel", "test", "fixtures", "options-file-3.txt") }
|
||||
let(:options_file_4) { File.join("lib", "plugins", "inspec-parallel", "test", "fixtures", "options-file-4.txt") }
|
||||
let(:options_file_5) { File.join("lib", "plugins", "inspec-parallel", "test", "fixtures", "options-file-5.txt") }
|
||||
let(:options_shell_file_1) { File.join("lib", "plugins", "inspec-parallel", "test", "fixtures", "options-file-shell-1.sh") }
|
||||
|
||||
def test_help_output
|
||||
out = run_inspec_process("parallel help")
|
||||
assert_includes out.stdout, "inspec parallel exec o"
|
||||
assert_exit_code 0, out
|
||||
end
|
||||
|
||||
def test_parallel_dry_run
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_1} --dry-run --sudo")
|
||||
stdout = out.stdout
|
||||
assert_includes stdout, "complete-profile -t ssh://vagrant@127.0.0.1:2201 --reporter child-status cli:myfile.out --no-create-lockfile --no-sudo"
|
||||
assert_includes stdout, "control-tags -t ssh://vagrant@127.0.0.1:2201 --reporter child-status cli:myfile.out --sudo true"
|
||||
assert_equal stdout.split("\n").count, 6
|
||||
assert_exit_code 0, out
|
||||
end
|
||||
|
||||
def test_parallel_run_without_forking
|
||||
skip_windows!
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_3}")
|
||||
assert_empty out.stderr
|
||||
assert_exit_code 0, out
|
||||
end
|
||||
|
||||
def test_parallel_dry_run_with_typo_in_option
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_2} --dry-run")
|
||||
stdout = out.stdout
|
||||
assert_includes stdout, "No such option: [\"--targetss\""
|
||||
assert_exit_code 1, out
|
||||
end
|
||||
|
||||
def test_parallel_run_with_typo_in_option
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_2}")
|
||||
stdout = out.stdout
|
||||
assert_includes stdout, "No such option: [\"--targetss\""
|
||||
assert_exit_code 1, out
|
||||
end
|
||||
|
||||
def test_parallel_with_default_opts
|
||||
skip_windows!
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_3} --reporter json")
|
||||
assert_empty out.stderr
|
||||
assert_exit_code 0, out
|
||||
end
|
||||
|
||||
def test_parallel_run_with_shell_file_as_options_file
|
||||
skip_windows!
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_shell_file_1} --reporter json")
|
||||
assert_empty out.stderr
|
||||
assert_exit_code 0, out
|
||||
end
|
||||
|
||||
def test_parallel_dry_run_with_shell_file_as_options_file
|
||||
skip_windows!
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_shell_file_1} --dry-run")
|
||||
stdout = out.stdout
|
||||
assert_includes stdout, "complete-profile --reporter child-status json:myfile.json --create-lockfile false"
|
||||
assert_includes stdout, "control-tags --reporter child-status cli:myfile.out --create-lockfile false"
|
||||
assert_empty out.stderr
|
||||
assert_exit_code 0, out
|
||||
end
|
||||
|
||||
def test_parallel_dry_run_with_default_opts
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_1} -t docker://8b5ec1a0344b --dry-run")
|
||||
stdout = out.stdout
|
||||
assert_includes stdout, "basic_profile -t docker://8b5ec1a0344b --reporter child-status json:myfile.json"
|
||||
assert_includes stdout, "complete-profile -t docker://1870886821c3 --reporter child-status cli:myfile.out"
|
||||
assert_includes stdout, "complete-profile --reporter child-status cli:myfile.out --target docker://8b5ec1a0344b"
|
||||
assert_equal stdout.split("\n").count, 6
|
||||
assert_exit_code 0, out
|
||||
end
|
||||
|
||||
def test_parallel_dry_run_with_verbose_option
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_1} --dry-run --verbose")
|
||||
stdout = out.stdout
|
||||
assert_includes stdout, "complete-profile -t ssh://vagrant@127.0.0.1:2201 --reporter child-status cli:myfile.out --no-create-lockfile --no-sudo --winrm-transport negotiate --insecure false --winrm-shell-type powershell --auto-install-gems false --distinct-exit true --diff true --sort-results-by file --filter-empty-profiles false --reporter-include-source false"
|
||||
assert_includes stdout, "control-tags -t ssh://vagrant@127.0.0.1:2201 --reporter child-status cli:myfile.out --winrm-transport negotiate --insecure false --winrm-shell-type powershell --auto-install-gems false --distinct-exit true --diff true --sort-results-by file --filter-empty-profiles false --reporter-include-source false"
|
||||
assert_equal stdout.split("\n").count, 6
|
||||
assert_exit_code 0, out
|
||||
end
|
||||
|
||||
def test_reporter_validation_no_file_output
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_4} --dry-run")
|
||||
stdout = out.stdout
|
||||
assert_includes stdout, "Line 3: The json reporter requires being directed to a file, like json:filename.out"
|
||||
assert_exit_code 1, out
|
||||
end
|
||||
|
||||
def test_reporter_validation_no_reporter
|
||||
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_5} --dry-run")
|
||||
stdout = out.stdout
|
||||
assert_includes stdout, "Line 5: A --reporter option must be specified for each invocation in the options file"
|
||||
assert_exit_code 1, out
|
||||
end
|
||||
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
require_relative "base"
|
||||
require "inspec/dist"
|
||||
require "inspec/feature"
|
||||
|
||||
#
|
||||
# Notes:
|
||||
|
@ -85,8 +86,10 @@ module InspecPlugins
|
|||
option :keydir, type: :string, default: "./",
|
||||
desc: "Directory to search for keys"
|
||||
def generate_keys
|
||||
Inspec.with_feature("inspec-cli-sign-generate-keys") {
|
||||
puts "Generating keys"
|
||||
InspecPlugins::Sign::Base.keygen(options)
|
||||
}
|
||||
end
|
||||
|
||||
desc "profile PATH", "sign the profile in PATH and generate .iaf artifact."
|
||||
|
@ -95,12 +98,16 @@ module InspecPlugins
|
|||
option :profile_content_id, type: :string,
|
||||
desc: "UUID of the profile. This will write the profile_content_id in the metadata file if it does not already exist in the metadata file."
|
||||
def profile(profile_path)
|
||||
Inspec.with_feature("inspec-cli-sign-profile") {
|
||||
InspecPlugins::Sign::Base.profile_sign(profile_path, options)
|
||||
}
|
||||
end
|
||||
|
||||
desc "verify PATH", "Verify a signed profile .iaf artifact at given path."
|
||||
def verify(signed_profile_path)
|
||||
Inspec.with_feature("inspec-cli-sign-verify") {
|
||||
InspecPlugins::Sign::Base.profile_verify(signed_profile_path)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -91,23 +91,20 @@ module InspecPlugins::StreamingReporterProgressBar
|
|||
|
||||
set_status_mapping(control_id, status)
|
||||
collect_notifications(notification, control_id, status)
|
||||
control_ended = control_ended?(control_id)
|
||||
if control_ended
|
||||
control_outcome = add_enhanced_outcomes(control_id) if enhanced_outcomes
|
||||
show_progress(control_id, title, full_description, control_outcome)
|
||||
end
|
||||
show_progress(control_id, title, full_description) if control_ended?(notification, control_id)
|
||||
end
|
||||
|
||||
def show_progress(control_id, title, full_description, control_outcome)
|
||||
def show_progress(control_id, title, full_description)
|
||||
@bar ||= ProgressBar.new(controls_count, :bar, :counter, :percentage)
|
||||
sleep 0.1
|
||||
@bar.increment!
|
||||
@bar.puts format_it(control_id, title, full_description, control_outcome)
|
||||
@bar.puts format_it(control_id, title, full_description)
|
||||
rescue StandardError => e
|
||||
raise "Exception in Progress Bar streaming reporter: #{e}"
|
||||
end
|
||||
|
||||
def format_it(control_id, title, full_description, control_outcome)
|
||||
def format_it(control_id, title, full_description)
|
||||
control_outcome = control_outcome(control_id)
|
||||
if control_outcome
|
||||
control_status = control_outcome
|
||||
else
|
||||
|
@ -121,11 +118,7 @@ module InspecPlugins::StreamingReporterProgressBar
|
|||
end
|
||||
end
|
||||
indicator = INDICATORS[control_status]
|
||||
message_to_format = ""
|
||||
message_to_format += "#{indicator} "
|
||||
message_to_format += "#{control_id.to_s.strip.dup.force_encoding(Encoding::UTF_8)} "
|
||||
message_to_format += "#{title.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " if title
|
||||
message_to_format += "#{full_description.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " unless title
|
||||
message_to_format = format_message(indicator, control_id, title, full_description)
|
||||
format_with_color(control_status, message_to_format)
|
||||
rescue Exception => e
|
||||
raise "Exception in show_progress: #{e}"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
GIT
|
||||
remote: https://github.com/chef/omnibus-software.git
|
||||
revision: 4b08f0bc0688f750bc55a49b8103b2d12815399e
|
||||
revision: 3268356b2eaf80715887e452c89b36d8f86974a0
|
||||
branch: main
|
||||
specs:
|
||||
omnibus-software (23.7.293)
|
||||
omnibus-software (23.7.295)
|
||||
omnibus (>= 9.0.0)
|
||||
|
||||
GIT
|
||||
|
@ -29,18 +29,18 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.8.4)
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
awesome_print (1.9.2)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.784.0)
|
||||
aws-sdk-core (3.177.0)
|
||||
aws-partitions (1.803.0)
|
||||
aws-sdk-core (3.180.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.70.0)
|
||||
aws-sdk-kms (1.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.116.0)
|
||||
|
@ -201,7 +201,7 @@ GEM
|
|||
ffi (~> 1.0)
|
||||
ffi-win32-extensions (1.0.4)
|
||||
ffi
|
||||
ffi-yajl (2.4.0)
|
||||
ffi-yajl (2.6.0)
|
||||
libyajl2 (>= 1.2)
|
||||
fuzzyurl (0.9.0)
|
||||
gssapi (1.3.1)
|
||||
|
@ -302,7 +302,7 @@ GEM
|
|||
net-ssh (>= 2.6.5, < 8.0.0)
|
||||
net-sftp (4.0.0)
|
||||
net-ssh (>= 5.0.0, < 8.0.0)
|
||||
net-ssh (7.1.0)
|
||||
net-ssh (7.2.0)
|
||||
net-ssh-gateway (2.0.0)
|
||||
net-ssh (>= 4.0.0)
|
||||
netrc (0.11.0)
|
||||
|
@ -338,7 +338,7 @@ GEM
|
|||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
public_suffix (5.0.1)
|
||||
public_suffix (5.0.3)
|
||||
rack (2.2.7)
|
||||
rainbow (3.1.1)
|
||||
rest-client (2.1.0)
|
||||
|
@ -359,7 +359,7 @@ GEM
|
|||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
retryable (3.0.5)
|
||||
rexml (3.2.5)
|
||||
rexml (3.2.6)
|
||||
rspec (3.11.0)
|
||||
rspec-core (~> 3.11.0)
|
||||
rspec-expectations (~> 3.11.0)
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
# Disable git caching
|
||||
# ------------------------------
|
||||
# use_git_caching false
|
||||
use_git_caching false
|
||||
|
||||
# Enable S3 asset caching
|
||||
# ------------------------------
|
||||
|
|
6
test/fixtures/features-01.sig
vendored
Normal file
6
test/fixtures/features-01.sig
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
xOZopkIkaGVZsAN9DXdOAB8wbVrtnDJT0pmB8IFwu2vusFSDkznKe4iS4BdE
|
||||
51yUoKi/UptLmxL6A+Itz5ASXDoR/zR+7CtfsIAwbFtxrF46lxKmK7tSc+17
|
||||
b/7cJgE20sSF4FMQTi9SkYPf+e/kyXHL5zqUXBLeJ5Dkufj9nnSrvDUvsaxm
|
||||
alPlUZGjK31HvZTkJIwtd6PtrLL7MrydFz7tuUFmBmWflbmEpV4DPTbEdNLa
|
||||
dPGW0Q1tgZAMI4gOWjGKg7BXrF2WQefFHGoFrWRp57Xf307Ty+dji+WL0TSE
|
||||
yNr7Bh4jYczX7/se4okivdDn5jkfWdncrowCBaZkug==
|
6
test/fixtures/features-01.yaml
vendored
Normal file
6
test/fixtures/features-01.yaml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
features:
|
||||
test-feature-01:
|
||||
description: Fun for the whole security organization
|
||||
test-feature-02:
|
||||
description: A great use of your time
|
6
test/fixtures/features-tampered.sig
vendored
Normal file
6
test/fixtures/features-tampered.sig
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
idN5b9GDnQTm3ayYiynvJknrVinIomSBGEE4f9ZFL2IUxyeztvtUBECaSBfM
|
||||
T9LOVGBT2/kW2dHQl9PHQuXLpDJz0fKGEnQhAZzsRjIBRWvphlIZ3aBXsrCP
|
||||
OaDxjLdkY+lJMV4eD9XRypm0hC3cNezQMSWGtmQIdm6Ez/rDDuKL2K4wsxXt
|
||||
TILhF3/4Rl3N/VWJLVOEem6RF7t+48aLrlxbkh8bo76W7RytZWrM4R0XgUPw
|
||||
XwzNx+XKTwuPjn5TTyMEyH5TGATJfiu6NdxXhNcyg8KvdKOIFbUwF4zk+Vop
|
||||
BQG9lQ5C8lHOX2VwPUtZG+hCuqoo0Ir9RpjOk2bp8w==
|
6
test/fixtures/features-tampered.yaml
vendored
Normal file
6
test/fixtures/features-tampered.yaml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
features:
|
||||
test-feature-01:
|
||||
description: Great fun for the whole security organization
|
||||
test-feature-02:
|
||||
description: A great use of your time
|
|
@ -105,6 +105,7 @@ describe "inputs" do
|
|||
# require inspec
|
||||
require "inspec"
|
||||
require "inspec/runner"
|
||||
require "inspec/utils/licensing_config"
|
||||
|
||||
# inject pretty-printed runner opts
|
||||
runner_args = #{options.inspect}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
require "functional/helper"
|
||||
require "inspec/runner"
|
||||
require "inspec/resources/file"
|
||||
require "inspec/utils/licensing_config"
|
||||
|
||||
describe "inspec report tests" do
|
||||
include FunctionalHelper
|
||||
|
||||
describe "report" do
|
||||
it "loads a json report" do
|
||||
WebMock.allow_net_connect!
|
||||
o = { "reporter" => ["json"], "report" => true }
|
||||
runner = ::Inspec::Runner.new(o)
|
||||
runner.add_target(example_profile)
|
||||
|
|
134
test/unit/feature_test.rb
Normal file
134
test/unit/feature_test.rb
Normal file
|
@ -0,0 +1,134 @@
|
|||
# This file tests Inspec::Feature functionality,
|
||||
# which allows you to declare a group of functionality
|
||||
# for purposes of logging, entitlement, feature flagging,
|
||||
# telemetry, and other future purposes
|
||||
|
||||
require "helper"
|
||||
require "logger"
|
||||
require "stringio"
|
||||
|
||||
require "inspec/feature"
|
||||
|
||||
describe "Inspec::Feature" do
|
||||
let(:fixtures_path) { "test/fixtures" }
|
||||
it "should be a class" do
|
||||
_(Inspec::Feature).must_be_kind_of Class
|
||||
end
|
||||
|
||||
#======================
|
||||
# The global convenience method with_feature
|
||||
#======================
|
||||
# It exists
|
||||
describe "Inspec.with_feature" do
|
||||
it "should have a with_feature class method" do
|
||||
_(Inspec.respond_to?(:with_feature)).must_equal true
|
||||
end
|
||||
it "should take a symbol, options, and a block" do
|
||||
_(Inspec.method(:with_feature).arity).must_equal(-2)
|
||||
end
|
||||
|
||||
it "defaults to calling the block" do
|
||||
called = false
|
||||
Inspec.with_feature(:test_feature) do
|
||||
called = true
|
||||
end
|
||||
_(called).must_equal true
|
||||
end
|
||||
|
||||
let(:feature_config_file) { File.join(fixtures_path, "features-01.yaml") }
|
||||
let(:cfg) { Inspec::Feature::Config.new(feature_config_file) }
|
||||
it "accepts a config as an option" do
|
||||
called = false
|
||||
Inspec.with_feature("test-feature-01", config: cfg) do
|
||||
called = true
|
||||
end
|
||||
# TODO: need a better test to verify that the feature was recognized
|
||||
_(called).must_equal true
|
||||
end
|
||||
|
||||
# Integration with Logger
|
||||
let(:logger_io) { StringIO.new }
|
||||
let(:logger) { l = Logger.new(logger_io); l.level = Logger::DEBUG; l; }
|
||||
it "accepts a logger as an option" do
|
||||
Inspec.with_feature("test-feature-01", config: cfg, logger: logger) do
|
||||
end
|
||||
_(logger_io.string).must_match(/test-feature-01/)
|
||||
end
|
||||
|
||||
# Validation of feature names
|
||||
it "validates feature names" do
|
||||
Inspec.with_feature("test-feature-nonesuch", config: cfg, logger: logger) do
|
||||
end
|
||||
_(logger_io.string).must_match(/WARN/)
|
||||
_(logger_io.string).must_match(/test-feature-nonesuch/)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Integration with Entitlement
|
||||
# TODO: Integration with feature flagging
|
||||
# TODO: Integration with usage telemetry
|
||||
|
||||
#======================
|
||||
# Internals
|
||||
#======================
|
||||
|
||||
#------------------------
|
||||
# Inspec::Feature::Config
|
||||
#------------------------
|
||||
describe "Inspec::Feature::Config" do
|
||||
describe "when you load it from a specified file" do
|
||||
let(:feature_config_file) { File.join(fixtures_path, "features-01.yaml") }
|
||||
# you should be able to load it from a test file
|
||||
let(:cfg) { Inspec::Feature::Config.new(feature_config_file) }
|
||||
it "lists features in a block" do
|
||||
feats = []
|
||||
cfg.with_each_feature do |f|
|
||||
feats << f
|
||||
end
|
||||
# you should be able to list features
|
||||
# as Inspec::Features
|
||||
_(feats.length).must_equal 2
|
||||
_(feats[0]).must_be_kind_of Inspec::Feature
|
||||
end
|
||||
|
||||
it "allows calling features by name with brackets" do
|
||||
_(cfg["test-feature-01"]).must_be_kind_of Inspec::Feature
|
||||
end
|
||||
|
||||
it "allows detecting if a name is a feature" do
|
||||
_(cfg.feature_name?("test-feature-01")).must_equal true
|
||||
_(cfg.feature_name?("test-feature-99")).must_equal false
|
||||
end
|
||||
|
||||
it "allows accessing the array of features as a method" do
|
||||
_(cfg.features).must_be_kind_of Array
|
||||
_(cfg.features.length).must_equal 2
|
||||
_(cfg.features[0]).must_be_kind_of Inspec::Feature
|
||||
end
|
||||
end
|
||||
|
||||
describe "when you load it from the default location" do
|
||||
let(:cfg) { Inspec::Feature::Config.new }
|
||||
it "lists features" do
|
||||
feats = []
|
||||
cfg.with_each_feature do |f|
|
||||
feats << f
|
||||
end
|
||||
# loading from the default location should result in
|
||||
# at least two features
|
||||
|
||||
# you should be able to list features
|
||||
# as Inspec::Features
|
||||
_(feats.length).must_be :>, 2
|
||||
_(feats[0]).must_be_kind_of Inspec::Feature
|
||||
end
|
||||
end
|
||||
|
||||
describe "when you load it from a tampered file" do
|
||||
let(:tampered_config_file) { File.join(fixtures_path, "features-tampered.yaml") }
|
||||
it "throws an exception and loads no features" do
|
||||
_ { Inspec::Feature::Config.new(tampered_config_file) }.must_raise(Inspec::FeatureConfigTamperedError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ require "helper"
|
|||
require "inspec/secrets"
|
||||
require "inspec/runner"
|
||||
require "inspec/fetcher/mock"
|
||||
require "inspec/utils/licensing_config"
|
||||
|
||||
describe Inspec::Runner do
|
||||
let(:runner) { Inspec::Runner.new({ command_runner: :generic, reporter: [] }) }
|
||||
|
@ -73,6 +74,7 @@ describe Inspec::Runner do
|
|||
|
||||
describe "testing runner.run exit codes" do
|
||||
it "returns proper exit code when no profile is added" do
|
||||
WebMock.allow_net_connect!
|
||||
_(runner.run).must_equal 0
|
||||
end
|
||||
end
|
||||
|
|
18
test/unit/utils/license_config_test.rb
Normal file
18
test/unit/utils/license_config_test.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
require "helper"
|
||||
require "inspec/utils/licensing_config"
|
||||
|
||||
describe "ChefLicensing::Config" do
|
||||
it "returns the default chef product name as foo" do
|
||||
expect(ChefLicensing::Config.chef_product_name).must_equal("InSpec")
|
||||
end
|
||||
|
||||
it "returns the default chef_entitlement_id" do
|
||||
expect(ChefLicensing::Config.chef_entitlement_id).must_equal("3ff52c37-e41f-4f6c-ad4d-365192205968")
|
||||
end
|
||||
|
||||
it "returns the default chef_executable_name" do
|
||||
expect(ChefLicensing::Config.chef_executable_name).must_equal("inspec")
|
||||
end
|
||||
|
||||
# TODO: Need to add the test for license_server_url.
|
||||
end
|
Loading…
Reference in a new issue