pax_global_header 0000666 0000000 0000000 00000000064 13645125472 0014523 g ustar 00root root 0000000 0000000 52 comment=1b09580afe5fcc5b3e4c0e0865b3fe0929cc24b8
train-3.2.28/ 0000775 0000000 0000000 00000000000 13645125472 0012734 5 ustar 00root root 0000000 0000000 train-3.2.28/.codeclimate.yml 0000664 0000000 0000000 00000000367 13645125472 0016014 0 ustar 00root root 0000000 0000000 version: "2"
checks:
file-lines:
enabled: false
plugins:
fixme:
enabled: true
config:
strings:
- TODO
- rubocop:disable
flog:
enabled: true
markdownlint:
enabled: true
rubocop:
enabled: true
train-3.2.28/.expeditor/ 0000775 0000000 0000000 00000000000 13645125472 0015015 5 ustar 00root root 0000000 0000000 train-3.2.28/.expeditor/buildkite/ 0000775 0000000 0000000 00000000000 13645125472 0016771 5 ustar 00root root 0000000 0000000 train-3.2.28/.expeditor/buildkite/cache_support.sh 0000775 0000000 0000000 00000002675 13645125472 0022201 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -ue
S3_URL="s3://public-cd-buildkite-cache/${BUILDKITE_PIPELINE_SLUG}/${BUILDKITE_LABEL}"
pull_s3_file() {
aws s3 cp "${S3_URL}/$1" "$1" || echo "Could not pull $1 from S3"
}
push_s3_file() {
if [ -f "$1" ]; then
aws s3 cp "$1" "${S3_URL}/$1" || echo "Could not push $1 to S3 for caching."
fi
}
install_cache_deps() {
apt-get update -y
if [ -n "${1:-}" ]; then
apt-get install "$@" -y
fi
if [ -z "${SKIP_BUNDLE_CACHE:-}" ]; then
apt-get install awscli -y
fi
}
pull_bundle() {
if [ -z "${SKIP_BUNDLE_CACHE:-}" ]; then
pull_s3_file "bundle.tar.gz"
pull_s3_file "bundle.sha256"
if [ -f bundle.tar.gz ]; then
tar -xzf bundle.tar.gz
mv Gemfile.lock Gemfile.lock.old || true
fi
if [ -n "${RESET_BUNDLE_CACHE:-}" ]; then
rm bundle.sha256
fi
fi
}
push_bundle() {
if [ -z "${SKIP_BUNDLE_CACHE:-}" ]; then
if test -f bundle.sha256 && shasum --check bundle.sha256 --status; then
echo "Bundled gems have not changed. Skipping upload to s3"
else
echo "Bundled gems have changed. Uploading to s3"
diff -u Gemfile.lock.old Gemfile.lock || true
shasum -a 256 Gemfile.lock > bundle.sha256
tar -czf bundle.tar.gz Gemfile.lock vendor/
push_s3_file bundle.tar.gz
push_s3_file bundle.sha256
fi
fi
}
train-3.2.28/.expeditor/buildkite/coverage.sh 0000775 0000000 0000000 00000000407 13645125472 0021124 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -ueo pipefail
echo "--- system details"
uname -a
ruby -v
bundle --version
echo "--- system environment"
env
echo "--- bundle install"
bundle install --jobs=7 --retry=3 --without tools integration
echo "+++ bundle exec rake"
bundle exec rake
train-3.2.28/.expeditor/buildkite/verify.ps1 0000664 0000000 0000000 00000000565 13645125472 0020730 0 ustar 00root root 0000000 0000000 echo "--- system details"
$Properties = 'Caption', 'CSName', 'Version', 'BuildType', 'OSArchitecture'
Get-CimInstance Win32_OperatingSystem | Select-Object $Properties | Format-Table -AutoSize
ruby -v
bundle --version
echo "--- bundle install"
bundle install --jobs=7 --retry=3 --without tools integration
echo "+++ bundle exec rake"
bundle exec rake
exit $LASTEXITCODE
train-3.2.28/.expeditor/buildkite/verify.sh 0000775 0000000 0000000 00000000660 13645125472 0020636 0 ustar 00root root 0000000 0000000 #!/bin/bash
echo "--- dependencies"
. .expeditor/buildkite/cache_support.sh
install_cache_deps
echo "--- system details"
uname -a
ruby -v
bundle --version
echo "--- pull bundle cache"
pull_bundle
echo "--- bundle install"
bundle config --local path vendor/bundle
bundle install --jobs=7 --retry=3 --without tools integration
echo "--- push bundle cache"
push_bundle
echo "+++ bundle exec rake"
bundle exec rake ${RAKE_TASK:-}
train-3.2.28/.expeditor/config.yml 0000664 0000000 0000000 00000004372 13645125472 0017013 0 ustar 00root root 0000000 0000000 # Documentation available at https://expeditor.chef.io/docs/getting-started/
---
# Slack channel in Chef Software slack to send notifications about build failures, etc
slack:
notify_channel:
- chef-found-notify
- inspec-notify
# This publish is triggered by the `built_in:publish_rubygems` artifact_action.
rubygems:
- train
- train-core
github:
# This deletes the GitHub PR branch after successfully merged into the release branch
delete_branch_on_merge: true
# The tag format to use (e.g. v1.0.0)
version_tag_format: "v{{version}}"
# allow bumping the minor release via label
minor_bump_labels:
- "Expeditor: Bump Minor Version"
major_bump_labels:
- "Expeditor: Bump Major Version"
release_branch:
- 1-stable:
version_constraint: 1.*
- 2-stable:
version_constraint: 2.*
- master:
version_constraint: 3.*
changelog:
rollup_header: Changes not yet released to rubygems.org
# These actions are taken, in order they are specified, anytime a Pull Request is merged.
merge_actions:
- built_in:bump_version:
ignore_labels:
- "Expeditor: Skip Version Bump"
- "Expeditor: Skip All"
- bash:.expeditor/update_version.sh:
only_if: built_in:bump_version
- built_in:update_changelog:
ignore_labels:
- "Expeditor: Skip Changelog"
- "Expeditor: Skip All"
- built_in:build_gem:
only_if: built_in:bump_version
- trigger_pipeline:coverage:
post_commit: true
subscriptions:
- workload: pull_request_opened:{{agent_id}}:*
actions:
- post_github_comment:.expeditor/templates/pull_request.mustache:
ignore_team_members:
- inspec/owners
- inspec/inspec-core-team
- built_in:github_auto_assign_author:
only_if_team_member:
- inspec/owners
- inspec/inspec-core-team
- workload: project_promoted:{{agent_id}}:*
actions:
- built_in:rollover_changelog
- built_in:publish_rubygems
# - built_in:create_github_release # This is not yet supported for rubygems - see https://github.com/chef/expeditor/pull/1182
pipelines:
- verify:
description: Pull Request validation tests
public: true
- coverage:
description: Generate test coverage report
train-3.2.28/.expeditor/coverage.pipeline.yml 0000664 0000000 0000000 00000000763 13645125472 0021145 0 ustar 00root root 0000000 0000000 ---
steps:
- label: coverage
commands:
- /workdir/.expeditor/buildkite/coverage.sh
expeditor:
secrets:
COVERALLS_REPO_TOKEN:
path: secret/coveralls/inspec/train
field: repo_token
executor:
docker:
environment:
- CI_ENABLE_COVERAGE=true
- CI_NAME=Buildkite
- CI_BUILD_NUMBER=$BUILDKITE_BUILD_NUMBER
- CI_BUILD_URL=$BUILDKITE_BUILD_URL
- CI_BRANCH=$BUILDKITE_BRANCH
train-3.2.28/.expeditor/templates/ 0000775 0000000 0000000 00000000000 13645125472 0017013 5 ustar 00root root 0000000 0000000 train-3.2.28/.expeditor/templates/pull_request.mustache 0000664 0000000 0000000 00000001266 13645125472 0023277 0 ustar 00root root 0000000 0000000 Hello {{author}}! Thanks for the pull request!
Here is what will happen next:
1. Your PR will be reviewed by the maintainers.
2. Possible Outcomes
a. If everything looks good, one of them will approve it, and your PR will be merged.
b. The maintainer may request follow-on work (e.g. code fix, linting, etc). We would encourage you to address this work in 2-3 business days to keep the conversation going and to get your contribution in sooner.
c. Cases exist where a PR is neither aligned to Chef InSpec's product roadmap, or something the team can own or maintain long-term. In these cases, the maintainer will provide justification and close out the PR.
Thank you for contributing!
train-3.2.28/.expeditor/update_version.sh 0000775 0000000 0000000 00000000671 13645125472 0020407 0 ustar 00root root 0000000 0000000 #!/bin/bash
#
# After a PR merge, Chef Expeditor will bump the PATCH version in the VERSION file.
# It then executes this file to update any other files/components with that new version.
#
set -evx
sed -i -r "s/VERSION = \".*\"/VERSION = \"$(cat VERSION)\"/" lib/train/version.rb
# Once Expeditor finshes executing this script, it will commit the changes and push
# the commit as a new tag corresponding to the value in the VERSION file.
train-3.2.28/.expeditor/verify.pipeline.yml 0000664 0000000 0000000 00000002130 13645125472 0020644 0 ustar 00root root 0000000 0000000 ---
steps:
- label: lint-ruby-2.6
command:
- RAKE_TASK=lint /workdir/.expeditor/buildkite/verify.sh
expeditor:
executor:
docker:
image: ruby:2.6
- label: run-tests-ruby-2.4
command:
- /workdir/.expeditor/buildkite/verify.sh
expeditor:
executor:
docker:
image: ruby:2.4
- label: run-tests-ruby-2.5
command:
- /workdir/.expeditor/buildkite/verify.sh
expeditor:
executor:
docker:
image: ruby:2.5
- label: run-tests-ruby-2.6
command:
- /workdir/.expeditor/buildkite/verify.sh
expeditor:
executor:
docker:
image: ruby:2.6
- label: run-tests-ruby-2.7
command:
- /workdir/.expeditor/buildkite/verify.sh
expeditor:
executor:
docker:
image: ruby:2.7
- label: run-tests-ruby-2.6-windows
command:
- /workdir/.expeditor/buildkite/verify.ps1
expeditor:
executor:
docker:
environment:
- BUILDKITE
host_os: windows
shell: ["powershell", "-Command"]
train-3.2.28/.github/ 0000775 0000000 0000000 00000000000 13645125472 0014274 5 ustar 00root root 0000000 0000000 train-3.2.28/.github/CODEOWNERS 0000664 0000000 0000000 00000000221 13645125472 0015662 0 ustar 00root root 0000000 0000000 # Order is important. The last matching pattern has the most precedence.
* @inspec/inspec-core-team @chef/foundation-team-reviewers
train-3.2.28/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 13645125472 0016457 5 ustar 00root root 0000000 0000000 train-3.2.28/.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md 0000664 0000000 0000000 00000001152 13645125472 0020710 0 ustar 00root root 0000000 0000000 ---
name: � Bug Report
about: If something isn't working as expected �.
labels: "Status: Untriaged"
---
# Version:
[Version of the project installed]
# Environment:
[Details about the environment such as the Operating System, cookbook details, etc...]
# Scenario:
[What you are trying to achieve and you can't?]
# Steps to Reproduce:
[If you are filing an issue what are the things we need to do in order to repro your problem?]
# Expected Result:
[What are you expecting to happen as the consequence of above reproduction steps?]
# Actual Result:
[What actually happens after the reproduction steps?]
train-3.2.28/.github/ISSUE_TEMPLATE/DESIGN_PROPOSAL.md 0000664 0000000 0000000 00000002343 13645125472 0021273 0 ustar 00root root 0000000 0000000 ---
name: Design Proposal
about: I have a significant change I would like to propose and discuss before starting
labels: "Status: Untriaged"
---
### When a Change Needs a Design Proposal
A design proposal should be opened any time a change meets one of the following qualifications:
- Significantly changes the user experience of a project in a way that impacts users.
- Significantly changes the underlying architecture of the project in a way that impacts other developers.
- Changes the development or testing process of the project such as a change of CI systems or test frameworks.
### Why We Use This Process
- Allows all interested parties (including any community member) to discuss large impact changes to a project.
- Serves as a durable paper trail for discussions regarding project architecture.
- Forces design discussions to occur before PRs are created.
- Reduces PR refactoring and rejected PRs.
---
## Motivation
## Specification
## Downstream Impact
train-3.2.28/.github/ISSUE_TEMPLATE/ENHANCEMENT_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000001407 13645125472 0023233 0 ustar 00root root 0000000 0000000 ---
name: 🚀 Enhancement Request
about: I have a suggestion (and may want to implement it 🙂)!
labels: "Status: Untriaged"
---
### Describe the Enhancement:
### Describe the Need:
### Current Alternative
### Can We Help You Implement This?:
train-3.2.28/.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md 0000664 0000000 0000000 00000000660 13645125472 0021506 0 ustar 00root root 0000000 0000000 ---
name: 🤗 Support Question
about: If you have a question 💬, please check out our Slack!
---
We use GitHub issues to track bugs and feature requests. If you need help please post to our Mailing List or join the Chef Community Slack.
* Chef Community Slack at http://community-slack.chef.io/.
* Chef Mailing List https://discourse.chef.io/
Support issues opened here will be closed and redirected to Slack or Discourse.
train-3.2.28/.github/lock.yml 0000664 0000000 0000000 00000000022 13645125472 0015741 0 ustar 00root root 0000000 0000000 daysUntilLock: 60
train-3.2.28/.gitignore 0000664 0000000 0000000 00000000200 13645125472 0014714 0 ustar 00root root 0000000 0000000 train-*.gem
r-train-*.gem
Gemfile.lock
Gemfile.local
.kitchen/
TAGS
terraform.tfstate.backup
.terraform
.bundle
.gems
coverage/
train-3.2.28/CHANGELOG.md 0000664 0000000 0000000 00000212017 13645125472 0014550 0 ustar 00root root 0000000 0000000
## [v3.2.28](https://github.com/inspec/train/tree/v3.2.28) (2020-04-13)
#### Merged Pull Requests
- [chef#9635] Add reason for sudo root password [#583](https://github.com/inspec/train/pull/583) ([vsingh-msys](https://github.com/vsingh-msys))
### Changes not yet released to rubygems.org
#### Merged Pull Requests
- [chef#9635] Add reason for sudo root password [#583](https://github.com/inspec/train/pull/583) ([vsingh-msys](https://github.com/vsingh-msys))
## [v3.2.27](https://github.com/inspec/train/tree/v3.2.27) (2020-04-08)
#### Merged Pull Requests
- Silence deprecation warning under ruby 2.7 [#582](https://github.com/inspec/train/pull/582) ([clintoncwolfe](https://github.com/clintoncwolfe))
## [v3.2.26](https://github.com/inspec/train/tree/v3.2.26) (2020-03-20)
#### Merged Pull Requests
- Update rake dep for security issue [#577](https://github.com/inspec/train/pull/577) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Move dependency on inifile from train-core to train [#579](https://github.com/inspec/train/pull/579) ([terceiro](https://github.com/terceiro))
- Avoid explicit /tmp in favor of $TMPDIR [#578](https://github.com/inspec/train/pull/578) ([majioa](https://github.com/majioa))
## [v3.2.23](https://github.com/inspec/train/tree/v3.2.23) (2020-03-02)
#### Merged Pull Requests
- Expeditor - Disable nonfunctional github release option [#573](https://github.com/inspec/train/pull/573) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Attempt to fix --sudo. [#576](https://github.com/inspec/train/pull/576) ([skpaterson](https://github.com/skpaterson))
## [v3.2.22](https://github.com/inspec/train/tree/v3.2.22) (2020-02-18)
#### Merged Pull Requests
- Revert to regular require to fix transport loading across gem boundary [#572](https://github.com/inspec/train/pull/572) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Include the LICENSE file in the gem [#571](https://github.com/inspec/train/pull/571) ([btm](https://github.com/btm))
## [v3.2.20](https://github.com/inspec/train/tree/v3.2.20) (2020-02-06)
#### Merged Pull Requests
- Kali Linux platform detection support [#556](https://github.com/inspec/train/pull/556) ([mattray](https://github.com/mattray))
- Refactor OS detection. [#561](https://github.com/inspec/train/pull/561) ([zenspider](https://github.com/zenspider))
- Unified gemspec and fixed dependencies across train and train-core. [#563](https://github.com/inspec/train/pull/563) ([zenspider](https://github.com/zenspider))
- Rebase #339 [#566](https://github.com/inspec/train/pull/566) ([zenspider](https://github.com/zenspider))
- Improve debugging experience by making platform and connection less noisy. [#565](https://github.com/inspec/train/pull/565) ([zenspider](https://github.com/zenspider))
- Added a blank line to the readme where we needed one. [#567](https://github.com/inspec/train/pull/567) ([zenspider](https://github.com/zenspider))
## [v3.2.14](https://github.com/inspec/train/tree/v3.2.14) (2020-01-23)
#### Merged Pull Requests
- Substitute require for require_relative [#549](https://github.com/inspec/train/pull/549) ([tas50](https://github.com/tas50))
- allow overriding follow_symlink on Train::File [#550](https://github.com/inspec/train/pull/550) ([miah](https://github.com/miah))
- Fix README typo [#551](https://github.com/inspec/train/pull/551) ([multani](https://github.com/multani))
- LinuxCommand#verify cleaned up [#530](https://github.com/inspec/train/pull/530) ([vsingh-msys](https://github.com/vsingh-msys))
- Add azure_mgmt_storage to train.gemspec [#552](https://github.com/inspec/train/pull/552) ([rmoles](https://github.com/rmoles))
- Refactor with_sudo_pty to BaseConnection (no-op) and SshConnection. [#554](https://github.com/inspec/train/pull/554) ([zenspider](https://github.com/zenspider))
- Yocto Project family and Yocto Linux and balenaOS platform detection [#558](https://github.com/inspec/train/pull/558) ([mattray](https://github.com/mattray))
- Make stat command use '-c' for Yocto OS [#559](https://github.com/inspec/train/pull/559) ([michaellihs](https://github.com/michaellihs))
- Fix verify step for sudo [#557](https://github.com/inspec/train/pull/557) ([zenspider](https://github.com/zenspider))
## [v3.2.5](https://github.com/inspec/train/tree/v3.2.5) (2019-12-12)
#### Merged Pull Requests
- Add extended metadata [#546](https://github.com/inspec/train/pull/546) ([tas50](https://github.com/tas50))
- Move built_in:create_github_release to a workload? [#547](https://github.com/inspec/train/pull/547) ([zenspider](https://github.com/zenspider))
## [v3.2.3](https://github.com/inspec/train/tree/v3.2.3) (2019-12-12)
#### Merged Pull Requests
- Correctly verify ssh w/ sudo [#544](https://github.com/inspec/train/pull/544) ([zenspider](https://github.com/zenspider))
- Adding bundle artifact caching to BK. [#545](https://github.com/inspec/train/pull/545) ([zenspider](https://github.com/zenspider))
- Return exit code for local Windows command [#533](https://github.com/inspec/train/pull/533) ([james-stocks](https://github.com/james-stocks))
## [v3.2.0](https://github.com/inspec/train/tree/v3.2.0) (2019-12-02)
#### Merged Pull Requests
- Fix inspec detect on SLES [#515](https://github.com/inspec/train/pull/515) ([christian-wtd](https://github.com/christian-wtd))
- Added create_github_release action to stable promotion. [#536](https://github.com/inspec/train/pull/536) ([zenspider](https://github.com/zenspider))
- Minor cleanup [#537](https://github.com/inspec/train/pull/537) ([zenspider](https://github.com/zenspider))
- Rolling back #515. [#539](https://github.com/inspec/train/pull/539) ([zenspider](https://github.com/zenspider))
- Add azure_mgmt_security to train.gemspec [#541](https://github.com/inspec/train/pull/541) ([rmoles](https://github.com/rmoles))
- Bump minor version [#543](https://github.com/inspec/train/pull/543) ([clintoncwolfe](https://github.com/clintoncwolfe))
## [v3.1.8](https://github.com/inspec/train/tree/v3.1.8) (2019-11-18)
#### Merged Pull Requests
- Stop using global expectation methods and switch to using _. [#524](https://github.com/inspec/train/pull/524) ([zenspider](https://github.com/zenspider))
- Adds activesupport gem to allow XML>JSON parsing from Azure APIs [#534](https://github.com/inspec/train/pull/534) ([r-fennell](https://github.com/r-fennell))
- Update google-api-client version. [#531](https://github.com/inspec/train/pull/531) ([skpaterson](https://github.com/skpaterson))
- Fix contributor url pointing to 404 [#532](https://github.com/inspec/train/pull/532) ([vsingh-msys](https://github.com/vsingh-msys))
## [v3.1.4](https://github.com/inspec/train/tree/v3.1.4) (2019-10-10)
#### Merged Pull Requests
- Add powershell detection [#523](https://github.com/inspec/train/pull/523) ([miah](https://github.com/miah))
## [v3.1.3](https://github.com/inspec/train/tree/v3.1.3) (2019-10-03)
#### Merged Pull Requests
- Let expeditor respond to pull request [#512](https://github.com/inspec/train/pull/512) ([miah](https://github.com/miah))
- add def forward_remote to Transports::SSH [#457](https://github.com/inspec/train/pull/457) ([sawanoboly](https://github.com/sawanoboly))
- Fix chefstyle warnings [#514](https://github.com/inspec/train/pull/514) ([tas50](https://github.com/tas50))
- Add reason field while raising Train::ClientError. [#517](https://github.com/inspec/train/pull/517) ([samshinde](https://github.com/samshinde))
- Fix method called on string in os parse function [#519](https://github.com/inspec/train/pull/519) ([noisleahcim](https://github.com/noisleahcim))
## [v3.0.3](https://github.com/inspec/train/tree/v3.0.3) (2019-08-29)
#### Merged Pull Requests
- Ensure UserError is raised with a reason value [#511](https://github.com/inspec/train/pull/511) ([marcparadise](https://github.com/marcparadise))
## [v3.0.2](https://github.com/inspec/train/tree/v3.0.2) (2019-08-15)
#### Merged Pull Requests
- Fix raise invalid arguments [#508](https://github.com/inspec/train/pull/508) ([vsingh-msys](https://github.com/vsingh-msys))
## [v3.0.1](https://github.com/inspec/train/tree/v3.0.1) (2019-08-07)
#### Merged Pull Requests
- Add Windows to the verify pipeline [#484](https://github.com/inspec/train/pull/484) ([miah](https://github.com/miah))
- garbo [#485](https://github.com/inspec/train/pull/485) ([miah](https://github.com/miah))
- Fix up windows testing on buildkite [#487](https://github.com/inspec/train/pull/487) ([zenspider](https://github.com/zenspider))
- Remove coverage from general test runs and add its own pipeline [#486](https://github.com/inspec/train/pull/486) ([miah](https://github.com/miah))
- Testing coverage pipeline [#488](https://github.com/inspec/train/pull/488) ([miah](https://github.com/miah))
- yah-mail [#490](https://github.com/inspec/train/pull/490) ([miah](https://github.com/miah))
- Use our coverage.sh rather than embedded commands [#491](https://github.com/inspec/train/pull/491) ([miah](https://github.com/miah))
- chmod 755 coverage.sh [#492](https://github.com/inspec/train/pull/492) ([miah](https://github.com/miah))
- Load simplecov too [#493](https://github.com/inspec/train/pull/493) ([miah](https://github.com/miah))
- less clever to worry about here since this isn't InSpec ;) [#494](https://github.com/inspec/train/pull/494) ([miah](https://github.com/miah))
- Move coverage private and use our repo_token [#495](https://github.com/inspec/train/pull/495) ([miah](https://github.com/miah))
- Add newline to Rakefile [#496](https://github.com/inspec/train/pull/496) ([miah](https://github.com/miah))
- I understand how vault secrets in expeditor work now. [#497](https://github.com/inspec/train/pull/497) ([miah](https://github.com/miah))
- that image isnt supported for accounts/secrets [#498](https://github.com/inspec/train/pull/498) ([miah](https://github.com/miah))
- the secret doesnt seem to be propagating to docker? [#499](https://github.com/inspec/train/pull/499) ([miah](https://github.com/miah))
- correct the path to the secret in vault [#500](https://github.com/inspec/train/pull/500) ([miah](https://github.com/miah))
- Move this secrets stuff into the label [#501](https://github.com/inspec/train/pull/501) ([miah](https://github.com/miah))
- get more debugging details [#502](https://github.com/inspec/train/pull/502) ([miah](https://github.com/miah))
- Add codeclimate setup. [#460](https://github.com/inspec/train/pull/460) ([miah](https://github.com/miah))
- Minor cleanup to regexp for correctness. [#458](https://github.com/inspec/train/pull/458) ([zenspider](https://github.com/zenspider))
- Remove travis and appveyor [#503](https://github.com/inspec/train/pull/503) ([miah](https://github.com/miah))
- Remove WinRM support in favor of train-winrm plugin [#448](https://github.com/inspec/train/pull/448) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Use /etc/os-release for SUSE detection (Adopted) [#505](https://github.com/inspec/train/pull/505) ([clintoncwolfe](https://github.com/clintoncwolfe))
## [v2.1.19](https://github.com/inspec/train/tree/v2.1.19) (2019-07-23)
#### Merged Pull Requests
- Resolve chefstyle warnings in 0.13 [#470](https://github.com/inspec/train/pull/470) ([tas50](https://github.com/tas50))
- Add verify script for pipeline [#477](https://github.com/inspec/train/pull/477) ([miah](https://github.com/miah))
- chefstyle -a [#478](https://github.com/inspec/train/pull/478) ([miah](https://github.com/miah))
- TRYING to get things normalized across chef, inspec, and train. [#480](https://github.com/inspec/train/pull/480) ([zenspider](https://github.com/zenspider))
- This fixes cisco_ios? being defined to return true by default [#481](https://github.com/inspec/train/pull/481) ([zenspider](https://github.com/zenspider))
- Add inspec-notify to the notify_channel list [#483](https://github.com/inspec/train/pull/483) ([zenspider](https://github.com/zenspider))
## [v2.1.13](https://github.com/inspec/train/tree/v2.1.13) (2019-07-01)
#### Merged Pull Requests
- Add github templates & fix the expeditor version bump script [#469](https://github.com/inspec/train/pull/469) ([tas50](https://github.com/tas50))
## [v2.1.12](https://github.com/inspec/train/tree/v2.1.12) (2019-06-26)
#### Merged Pull Requests
- Fixing inspec/train to be green again and improving test stability. [#463](https://github.com/inspec/train/pull/463) ([zenspider](https://github.com/zenspider))
- More cleanup of test output and stability fixes [#464](https://github.com/inspec/train/pull/464) ([zenspider](https://github.com/zenspider))
- Minor cleanup of logic in OSCommon#unix_uuid. [#465](https://github.com/inspec/train/pull/465) ([zenspider](https://github.com/zenspider))
- Apply Chefstyle to train [#459](https://github.com/inspec/train/pull/459) ([miah](https://github.com/miah))
- Fix to raise specific error when ssh user is not provided and root is used as default user. [#466](https://github.com/inspec/train/pull/466) ([Vasu1105](https://github.com/Vasu1105))
## [v2.1.7](https://github.com/inspec/train/tree/v2.1.7) (2019-05-23)
#### Merged Pull Requests
- Add Code of Conduct to train [#453](https://github.com/inspec/train/pull/453) ([miah](https://github.com/miah))
- Add codeclimate to train [#454](https://github.com/inspec/train/pull/454) ([miah](https://github.com/miah))
- Fix failing unit tests verify_host_key in ssh [#452](https://github.com/inspec/train/pull/452) ([marcparadise](https://github.com/marcparadise))
- Fix fallback regex for OpenSuse [#451](https://github.com/inspec/train/pull/451) ([n-rodriguez](https://github.com/n-rodriguez))
- Set chef-foundation as the project owner and update expeditor [#456](https://github.com/inspec/train/pull/456) ([tas50](https://github.com/tas50))
## [v2.1.2](https://github.com/inspec/train/tree/v2.1.2) (2019-05-15)
#### Merged Pull Requests
- Add Coveralls.io to Train [#440](https://github.com/inspec/train/pull/440) ([miah](https://github.com/miah))
- Rename ca_trust_file to ca_trust_path [#450](https://github.com/inspec/train/pull/450) ([marcparadise](https://github.com/marcparadise))
## [v2.1.0](https://github.com/inspec/train/tree/v2.1.0) (2019-05-06)
#### Merged Pull Requests
- Ensure we're using the latest OS on Appveyor [#441](https://github.com/inspec/train/pull/441) ([miah](https://github.com/miah))
- Fixes enable password and catches failure [#383](https://github.com/inspec/train/pull/383) ([btm](https://github.com/btm))
- Update backend test to match updated appveyor os [#442](https://github.com/inspec/train/pull/442) ([miah](https://github.com/miah))
- The Windows release also changed.. [#443](https://github.com/inspec/train/pull/443) ([miah](https://github.com/miah))
- Detect windows also with ssh transport [#416](https://github.com/inspec/train/pull/416) ([StefanScherer](https://github.com/StefanScherer))
## [v2.0.12](https://github.com/inspec/train/tree/v2.0.12) (2019-04-23)
#### Merged Pull Requests
- Add project state and SLA [#434](https://github.com/inspec/train/pull/434) ([miah](https://github.com/miah))
- Remove dependency on ed25519 gems [#439](https://github.com/inspec/train/pull/439) ([tas50](https://github.com/tas50))
- Require Ruby 2.4 or later [#438](https://github.com/inspec/train/pull/438) ([tas50](https://github.com/tas50))
- Add missed cisco_ios_connection to train-core. [#436](https://github.com/inspec/train/pull/436) ([marcparadise](https://github.com/marcparadise))
## [v2.0.8](https://github.com/inspec/train/tree/v2.0.8) (2019-04-22)
#### Merged Pull Requests
- Silence verify_host_key warning from net-ssh [#430](https://github.com/inspec/train/pull/430) ([clintoncwolfe](https://github.com/clintoncwolfe))
- add ssh/winrm to core [#433](https://github.com/inspec/train/pull/433) ([marcparadise](https://github.com/marcparadise))
- Require ed25519 and bcrypt_pbkdf gems for ed25519 support [#435](https://github.com/inspec/train/pull/435) ([tas50](https://github.com/tas50))
## [v2.0.5](https://github.com/inspec/train/tree/v2.0.5) (2019-04-15)
#### Merged Pull Requests
- [SUSTAIN-955] Provide ssh option to enable host key verify [#427](https://github.com/inspec/train/pull/427) ([marcparadise](https://github.com/marcparadise))
- [SUSTAIN-955] Add data callbacks to WinRM and SSH (adopted) [#431](https://github.com/inspec/train/pull/431) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Add a passthrough option for winrm_operation_timeout under winrm [#432](https://github.com/inspec/train/pull/432) ([marcparadise](https://github.com/marcparadise))
## [v2.0.2](https://github.com/inspec/train/tree/v2.0.2) (2019-03-19)
#### Merged Pull Requests
- Add v2 to expeditor [#407](https://github.com/inspec/train/pull/407) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Add api_call cache type [#411](https://github.com/inspec/train/pull/411) ([clintoncwolfe](https://github.com/clintoncwolfe))
- 2.x: Remove AWS transport [#408](https://github.com/inspec/train/pull/408) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Fix detection of 'SLES Expanded Support' of Red Hat [#361](https://github.com/inspec/train/pull/361) ([jabofh](https://github.com/jabofh))
- Update gem deps to allow mixlib-shellout 3.x and net-scp 2.x [#421](https://github.com/inspec/train/pull/421) ([tas50](https://github.com/tas50))
- [SUSTAIN-955] Add kerberos and additional winrm options [#426](https://github.com/inspec/train/pull/426) ([marcparadise](https://github.com/marcparadise))
## [v1.7.5](https://github.com/inspec/train/tree/v1.7.5) (2019-03-13)
#### Merged Pull Requests
- Replace the usage of const_defined? in the WinRM detection helper [#406](https://github.com/inspec/train/pull/406) ([RoboticCheese](https://github.com/RoboticCheese))
## [v1.7.4](https://github.com/inspec/train/tree/v1.7.4) (2019-03-01)
#### Merged Pull Requests
- Add v2 to expeditor [#407](https://github.com/inspec/train/pull/407) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Add api_call cache type [#411](https://github.com/inspec/train/pull/411) ([clintoncwolfe](https://github.com/clintoncwolfe))
## [v1.7.2](https://github.com/inspec/train/tree/v1.7.2) (2019-01-31)
#### Merged Pull Requests
- Prepare train for using credential sets [#394](https://github.com/inspec/train/pull/394) ([clintoncwolfe](https://github.com/clintoncwolfe))
## [v1.7.1](https://github.com/inspec/train/tree/v1.7.1) (2019-01-26)
#### Merged Pull Requests
- Update copyright year to 2019 [#403](https://github.com/inspec/train/pull/403) ([clintoncwolfe](https://github.com/clintoncwolfe))
## [v1.7.0](https://github.com/inspec/train/tree/v1.7.0) (2019-01-26)
#### Merged Pull Requests
- drop ruby 2.0-2.2 support, allow bundler 2.x, bump integration gems, allow mixlib-shellout 3.x [#396](https://github.com/inspec/train/pull/396) ([lamont-granquist](https://github.com/lamont-granquist))
- Fix Azure Test by adding a require [#402](https://github.com/inspec/train/pull/402) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Remove compat with unsupported Ruby 1.9 in the Gemfile [#389](https://github.com/inspec/train/pull/389) ([tas50](https://github.com/tas50))
## [v1.6.3](https://github.com/inspec/train/tree/v1.6.3) (2018-12-19)
#### Merged Pull Requests
- Expose additional winrm options [#392](https://github.com/inspec/train/pull/392) ([frezbo](https://github.com/frezbo))
- Only ship the runtime files in the gem to slim install sizes [#388](https://github.com/inspec/train/pull/388) ([tas50](https://github.com/tas50))
- Small style/spelling changes for Train example plugin [#364](https://github.com/inspec/train/pull/364) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Update Travis config for Xenial / Ruby 2.6 and latest patchlevels of Ruby 2.2-2.5 [#390](https://github.com/inspec/train/pull/390) ([tas50](https://github.com/tas50))
## [v1.5.11](https://github.com/inspec/train/tree/v1.5.11) (2018-12-10)
#### Merged Pull Requests
- Add Google API application info [#378](https://github.com/inspec/train/pull/378) ([nathenharvey](https://github.com/nathenharvey))
- Fix shallow_link_path on remote unix [#373](https://github.com/inspec/train/pull/373) ([mheiges](https://github.com/mheiges))
- Remove `#local?` [#365](https://github.com/inspec/train/pull/365) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Added a new matcher for amazon linux 2 [#380](https://github.com/inspec/train/pull/380) ([artyomtkachenko](https://github.com/artyomtkachenko))
- Pass logger to Cisco IOS transport [#381](https://github.com/inspec/train/pull/381) ([btm](https://github.com/btm))
## [v1.5.6](https://github.com/inspec/train/tree/v1.5.6) (2018-11-01)
#### Merged Pull Requests
- Fix Cisco IOS detection when banners lack a `\r\n` [#372](https://github.com/inspec/train/pull/372) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Adds cached_client method in BaseConnection [#371](https://github.com/inspec/train/pull/371) ([dmccown](https://github.com/dmccown))
## [v1.5.4](https://github.com/inspec/train/tree/v1.5.4) (2018-10-18)
#### Merged Pull Requests
- Fixes the link pointing back to the plugin docs [#362](https://github.com/inspec/train/pull/362) ([cattywampus](https://github.com/cattywampus))
- Remove the legacy version bumping from the rakefile [#359](https://github.com/inspec/train/pull/359) ([tas50](https://github.com/tas50))
- Adds Azure Vault Client [#351](https://github.com/inspec/train/pull/351) ([r-fennell](https://github.com/r-fennell))
- Correct example plugin link [#363](https://github.com/inspec/train/pull/363) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
## [v1.5.0](https://github.com/inspec/train/tree/v1.5.0) (2018-09-27)
#### Merged Pull Requests
- Update google-api-client version. [#348](https://github.com/inspec/train/pull/348) ([skpaterson](https://github.com/skpaterson))
- Adding GCP admin_client helper. [#349](https://github.com/inspec/train/pull/349) ([skpaterson](https://github.com/skpaterson))
- Plugins: Test harness, test fixture, docs, and local-type example [#356](https://github.com/inspec/train/pull/356) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Bump minor version. [#357](https://github.com/inspec/train/pull/357) ([jquick](https://github.com/jquick))
## [v1.4.37](https://github.com/inspec/train/tree/v1.4.37) (2018-09-13)
#### Merged Pull Requests
- Rescues SystemCallError instead of Errno [#346](https://github.com/inspec/train/pull/346) ([dmccown](https://github.com/dmccown))
- Add a export method for platforms [#347](https://github.com/inspec/train/pull/347) ([jquick](https://github.com/jquick))
## [v1.4.35](https://github.com/inspec/train/tree/v1.4.35) (2018-08-23)
#### Merged Pull Requests
- Ensure unique_identifier returns something meaningful for service acc… [#338](https://github.com/inspec/train/pull/338) ([skpaterson](https://github.com/skpaterson))
- Modify Cisco UUID detection to use processor ID [#342](https://github.com/inspec/train/pull/342) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Fixes failing test when you have a cred file [#343](https://github.com/inspec/train/pull/343) ([dmccown](https://github.com/dmccown))
- Adds connection to Graph RBAC API [#327](https://github.com/inspec/train/pull/327) ([r-fennell](https://github.com/r-fennell))
## [v1.4.31](https://github.com/inspec/train/tree/v1.4.31) (2018-08-17)
#### Merged Pull Requests
- Fixes an issue where the credential file was nil [#337](https://github.com/inspec/train/pull/337) ([dmccown](https://github.com/dmccown))
- Enable using rubygems as plugins [#335](https://github.com/inspec/train/pull/335) ([clintoncwolfe](https://github.com/clintoncwolfe))
## [v1.4.29](https://github.com/inspec/train/tree/v1.4.29) (2018-08-15)
#### Features & Enhancements
- Pulls file credentials parsing out of Azure class [#324](https://github.com/inspec/train/pull/324) ([dmccown](https://github.com/dmccown))
#### Merged Pull Requests
- Modify checksum logic to use system binaries [#251](https://github.com/inspec/train/pull/251) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Require Ruby 2.0 and allow net-ssh 5.0 [#334](https://github.com/inspec/train/pull/334) ([tas50](https://github.com/tas50))
- Add non_interactive support for SSH [#336](https://github.com/inspec/train/pull/336) ([marcparadise](https://github.com/marcparadise))
## [v1.4.25](https://github.com/inspec/train/tree/v1.4.25) (2018-08-01)
#### Merged Pull Requests
- Remove not needed google-cloud dependency (see #328) and correct GCP … [#329](https://github.com/inspec/train/pull/329) ([skpaterson](https://github.com/skpaterson))
## [v1.4.24](https://github.com/inspec/train/tree/v1.4.24) (2018-07-26)
#### Merged Pull Requests
- Add shallow_link_path to inspect symlink direct link [#309](https://github.com/inspec/train/pull/309) ([ColinHebert](https://github.com/ColinHebert))
- Retry SSH command on IOError (Cisco IOS specific) [#326](https://github.com/inspec/train/pull/326) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
## [v1.4.22](https://github.com/inspec/train/tree/v1.4.22) (2018-07-16)
#### Merged Pull Requests
- Add VMware transport [#321](https://github.com/inspec/train/pull/321) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
## [v1.4.21](https://github.com/inspec/train/tree/v1.4.21) (2018-07-05)
#### Merged Pull Requests
- Remove the delivery cookbook [#317](https://github.com/inspec/train/pull/317) ([tas50](https://github.com/tas50))
- Modify `WindowsPipeRunner` stderr to use String [#320](https://github.com/inspec/train/pull/320) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
## [v1.4.19](https://github.com/inspec/train/tree/v1.4.19) (2018-06-29)
#### Merged Pull Requests
- Fix detection of amazon linux 2 [#312](https://github.com/inspec/train/pull/312) ([artem-sidorenko](https://github.com/artem-sidorenko))
- Adding proper bastion support [#310](https://github.com/inspec/train/pull/310) ([frezbo](https://github.com/frezbo))
- Remove github_changelog_generator [#313](https://github.com/inspec/train/pull/313) ([tas50](https://github.com/tas50))
- Remove the deploy config from Travis [#315](https://github.com/inspec/train/pull/315) ([tas50](https://github.com/tas50))
## [v1.4.15](https://github.com/inspec/train/tree/v1.4.15) (2018-06-14)
#### Merged Pull Requests
- Allow TrainError to provide a supplement reason [#303](https://github.com/inspec/train/pull/303) ([marcparadise](https://github.com/marcparadise))
- Adding Oneview to platform detection. [#307](https://github.com/inspec/train/pull/307) ([skpaterson](https://github.com/skpaterson))
- Add the mock transport to train-core [#308](https://github.com/inspec/train/pull/308) ([jquick](https://github.com/jquick))
- Don't double-escape paths [#306](https://github.com/inspec/train/pull/306) ([voroniys](https://github.com/voroniys))
## [v1.4.11](https://github.com/inspec/train/tree/v1.4.11) (2018-05-17)
#### Merged Pull Requests
- Add required env for azure shell msi headers [#302](https://github.com/inspec/train/pull/302) ([jquick](https://github.com/jquick))
## [v1.4.10](https://github.com/inspec/train/tree/v1.4.10) (2018-05-17)
#### Merged Pull Requests
- support sudo passwords for cisco [#301](https://github.com/inspec/train/pull/301) ([arlimus](https://github.com/arlimus))
## [v1.4.9](https://github.com/inspec/train/tree/v1.4.9) (2018-05-16)
#### Bug Fixes
- Allow nil password and www_form_encoded_password to work together. [#297](https://github.com/inspec/train/pull/297) ([marcparadise](https://github.com/marcparadise))
#### Merged Pull Requests
- Support encoded passwords in target url [#296](https://github.com/inspec/train/pull/296) ([marcparadise](https://github.com/marcparadise))
- Initial import of transport for GCP. [#283](https://github.com/inspec/train/pull/283) ([skpaterson](https://github.com/skpaterson))
- Change Cisco IOS transport log level to INFO [#298](https://github.com/inspec/train/pull/298) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Unpin google-protobuf now that we are building it as a gem [#300](https://github.com/inspec/train/pull/300) ([scotthain](https://github.com/scotthain))
## [v1.4.4](https://github.com/inspec/train/tree/v1.4.4) (2018-05-02)
#### Merged Pull Requests
- Enable expeditor release tasks [#294](https://github.com/inspec/train/pull/294) ([jquick](https://github.com/jquick))
- Split train into a core gem. [#293](https://github.com/inspec/train/pull/293) ([miah](https://github.com/miah))
# Change Log
## [1.4.2](https://github.com/inspec/train/tree/1.4.2) (2018-04-26)
[Full Changelog](https://github.com/inspec/train/compare/v1.4.1...1.4.2)
**Merged pull requests:**
- switched method of determining aws account id to STS [\#286](https://github.com/inspec/train/pull/286) ([tkrueger](https://github.com/tkrueger))
## [v1.4.1](https://github.com/inspec/train/tree/v1.4.1) (2018-04-19)
[Full Changelog](https://github.com/inspec/train/compare/v1.4.0...v1.4.1)
**Merged pull requests:**
- Release 1.4.1 [\#287](https://github.com/inspec/train/pull/287) ([jquick](https://github.com/jquick))
- Add UUID for Cisco IOS and Nexus devices [\#285](https://github.com/inspec/train/pull/285) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Add handling for privileged exec mode [\#284](https://github.com/inspec/train/pull/284) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
## [v1.4.0](https://github.com/inspec/train/tree/v1.4.0) (2018-04-12)
[Full Changelog](https://github.com/inspec/train/compare/v1.3.0...v1.4.0)
**Closed issues:**
- Train reports directories with the archive bit set as files on the windows platform [\#274](https://github.com/inspec/train/issues/274)
**Merged pull requests:**
- Release 1.4.0 [\#282](https://github.com/inspec/train/pull/282) ([jquick](https://github.com/jquick))
- Add CloudLinux as a detected platform [\#281](https://github.com/inspec/train/pull/281) ([tarcinil](https://github.com/tarcinil))
- Move Cisco IOS connection under SSH transport [\#279](https://github.com/inspec/train/pull/279) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Initialize FileManager using '@service' [\#278](https://github.com/inspec/train/pull/278) ([marcparadise](https://github.com/marcparadise))
- small fix to make sure windows directories with the archive bit set a… [\#275](https://github.com/inspec/train/pull/275) ([devoptimist](https://github.com/devoptimist))
## [v1.3.0](https://github.com/inspec/train/tree/v1.3.0) (2018-03-29)
[Full Changelog](https://github.com/inspec/train/compare/v1.2.0...v1.3.0)
**Implemented enhancements:**
- Update errors to have a base type of Train::Error [\#273](https://github.com/inspec/train/pull/273) ([marcparadise](https://github.com/marcparadise))
**Closed issues:**
- RFC: Generate unique uuid for platforms [\#264](https://github.com/inspec/train/issues/264)
**Merged pull requests:**
- Release Train 1.3.0 [\#276](https://github.com/inspec/train/pull/276) ([jquick](https://github.com/jquick))
- Add MSI connection option for azure. [\#272](https://github.com/inspec/train/pull/272) ([jquick](https://github.com/jquick))
- Add transport for Cisco IOS [\#271](https://github.com/inspec/train/pull/271) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Add platform uuid information. [\#270](https://github.com/inspec/train/pull/270) ([jquick](https://github.com/jquick))
## [v1.2.0](https://github.com/inspec/train/tree/v1.2.0) (2018-03-15)
[Full Changelog](https://github.com/inspec/train/compare/v1.1.1...v1.2.0)
**Implemented enhancements:**
- Change error message to use `connection` [\#263](https://github.com/inspec/train/pull/263) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
**Closed issues:**
- Force 64bit powershell if using ruby32 on a 64bit os [\#265](https://github.com/inspec/train/issues/265)
- Master OS detect family [\#260](https://github.com/inspec/train/issues/260)
**Merged pull requests:**
- Release train 1.2.0 [\#269](https://github.com/inspec/train/pull/269) ([jquick](https://github.com/jquick))
- Force 64bit powershell for 32bit ruby running on 64bit windows [\#266](https://github.com/inspec/train/pull/266) ([jquick](https://github.com/jquick))
- support cisco ios xe [\#262](https://github.com/inspec/train/pull/262) ([arlimus](https://github.com/arlimus))
- Create a master OS family and refactor specifications [\#261](https://github.com/inspec/train/pull/261) ([jquick](https://github.com/jquick))
- Support for Brocade FOS-based SAN devices [\#254](https://github.com/inspec/train/pull/254) ([marcelhuth](https://github.com/marcelhuth))
- ProxyCommand support [\#227](https://github.com/inspec/train/pull/227) ([cbeckr](https://github.com/cbeckr))
## [v1.1.1](https://github.com/inspec/train/tree/v1.1.1) (2018-02-14)
[Full Changelog](https://github.com/inspec/train/compare/v1.1.0...v1.1.1)
**Merged pull requests:**
- Release train 1.1.1 [\#259](https://github.com/inspec/train/pull/259) ([jquick](https://github.com/jquick))
- Add api sdk versions as platform release [\#258](https://github.com/inspec/train/pull/258) ([jquick](https://github.com/jquick))
- Add plat helper methods to api direct platforms. [\#257](https://github.com/inspec/train/pull/257) ([jquick](https://github.com/jquick))
## [v1.1.0](https://github.com/inspec/train/tree/v1.1.0) (2018-02-08)
[Full Changelog](https://github.com/inspec/train/compare/v1.0.0...v1.1.0)
**Closed issues:**
- Add azure:// target [\#233](https://github.com/inspec/train/issues/233)
**Merged pull requests:**
- Release train 1.1.0 [\#255](https://github.com/inspec/train/pull/255) ([jquick](https://github.com/jquick))
- Add qnx platform support [\#253](https://github.com/inspec/train/pull/253) ([jquick](https://github.com/jquick))
- Add azure transport [\#250](https://github.com/inspec/train/pull/250) ([jquick](https://github.com/jquick))
- Fix AIX and QNX file support [\#249](https://github.com/inspec/train/pull/249) ([adamleff](https://github.com/adamleff))
## [v1.0.0](https://github.com/inspec/train/tree/v1.0.0) (2018-02-01)
[Full Changelog](https://github.com/inspec/train/compare/v0.32.0...v1.0.0)
**Closed issues:**
- Add aws:// target [\#229](https://github.com/inspec/train/issues/229)
**Merged pull requests:**
- Update version to 1.0.0 [\#248](https://github.com/inspec/train/pull/248) ([jquick](https://github.com/jquick))
- cisco nexus + ios12 [\#247](https://github.com/inspec/train/pull/247) ([arlimus](https://github.com/arlimus))
- Add a CONTRIBUTING.md to Train [\#245](https://github.com/inspec/train/pull/245) ([miah](https://github.com/miah))
- catch detect failing to parse json [\#243](https://github.com/inspec/train/pull/243) ([arlimus](https://github.com/arlimus))
- if ssh closes the session force it to reset and reopen [\#242](https://github.com/inspec/train/pull/242) ([arlimus](https://github.com/arlimus))
- Add AWS transport [\#239](https://github.com/inspec/train/pull/239) ([clintoncwolfe](https://github.com/clintoncwolfe))
- Fix detection of Scientific Linux [\#237](https://github.com/inspec/train/pull/237) ([schrd](https://github.com/schrd))
## [v0.32.0](https://github.com/inspec/train/tree/v0.32.0) (2018-01-04)
[Full Changelog](https://github.com/inspec/train/compare/v0.31.1...v0.32.0)
**Fixed bugs:**
- platform names should be lower case [\#191](https://github.com/inspec/train/issues/191)
- Return platform name that is lower case and underscored [\#228](https://github.com/inspec/train/pull/228) ([jquick](https://github.com/jquick))
**Merged pull requests:**
- Release 0.32.0 [\#232](https://github.com/inspec/train/pull/232) ([adamleff](https://github.com/adamleff))
- Set mock transport to use the platform instance variable [\#230](https://github.com/inspec/train/pull/230) ([jquick](https://github.com/jquick))
## [v0.31.1](https://github.com/inspec/train/tree/v0.31.1) (2017-12-06)
[Full Changelog](https://github.com/inspec/train/compare/v0.31.0...v0.31.1)
**Merged pull requests:**
- Release 0.31.1 [\#226](https://github.com/inspec/train/pull/226) ([adamleff](https://github.com/adamleff))
- Allow runner specifications for local connections [\#225](https://github.com/inspec/train/pull/225) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
## [v0.31.0](https://github.com/inspec/train/tree/v0.31.0) (2017-12-05)
[Full Changelog](https://github.com/inspec/train/compare/v0.30.0...v0.31.0)
**Fixed bugs:**
- Add release detect for failback debian [\#223](https://github.com/inspec/train/pull/223) ([jquick](https://github.com/jquick))
**Merged pull requests:**
- Release 0.31.0 [\#224](https://github.com/inspec/train/pull/224) ([adamleff](https://github.com/adamleff))
- Use named pipe to decrease local Windows runtime [\#220](https://github.com/inspec/train/pull/220) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
## [v0.30.0](https://github.com/inspec/train/tree/v0.30.0) (2017-12-04)
[Full Changelog](https://github.com/inspec/train/compare/v0.29.2...v0.30.0)
**Merged pull requests:**
- Release 0.30.0 [\#222](https://github.com/inspec/train/pull/222) ([adamleff](https://github.com/adamleff))
- Change the mock transport name to be 'mock' [\#221](https://github.com/inspec/train/pull/221) ([jquick](https://github.com/jquick))
- Enable caching on connections [\#214](https://github.com/inspec/train/pull/214) ([jquick](https://github.com/jquick))
## [v0.29.2](https://github.com/inspec/train/tree/v0.29.2) (2017-11-21)
[Full Changelog](https://github.com/inspec/train/compare/v0.29.1...v0.29.2)
**Fixed bugs:**
- Add unix\_mode\_mask method to Train::File::Local::Unix [\#215](https://github.com/inspec/train/pull/215) ([adamleff](https://github.com/adamleff))
**Merged pull requests:**
- Fix regressions in 0.29.1 [\#219](https://github.com/inspec/train/pull/219) ([adamleff](https://github.com/adamleff))
- Use the sanitized file path for remote linux files [\#218](https://github.com/inspec/train/pull/218) ([RoboticCheese](https://github.com/RoboticCheese))
- Remove bundler install during Appveyor tests [\#217](https://github.com/inspec/train/pull/217) ([adamleff](https://github.com/adamleff))
- Fix inspec mock tests [\#216](https://github.com/inspec/train/pull/216) ([jquick](https://github.com/jquick))
- Platform framework and detect DSL [\#209](https://github.com/inspec/train/pull/209) ([jquick](https://github.com/jquick))
## [v0.29.1](https://github.com/inspec/train/tree/v0.29.1) (2017-11-13)
[Full Changelog](https://github.com/inspec/train/compare/v0.29.0...v0.29.1)
**Merged pull requests:**
- Release 0.29.1 [\#213](https://github.com/inspec/train/pull/213) ([adamleff](https://github.com/adamleff))
- Allow for a nil value when mocking OS [\#212](https://github.com/inspec/train/pull/212) ([adamleff](https://github.com/adamleff))
- Ensure a `mounted?` method exists for all File classes, including Mock [\#211](https://github.com/inspec/train/pull/211) ([adamleff](https://github.com/adamleff))
## [v0.29.0](https://github.com/inspec/train/tree/v0.29.0) (2017-11-13)
[Full Changelog](https://github.com/inspec/train/compare/v0.28.0...v0.29.0)
**Merged pull requests:**
- Release 0.29.0 [\#210](https://github.com/inspec/train/pull/210) ([adamleff](https://github.com/adamleff))
- Reverting accidental push to master re: \#204 [\#208](https://github.com/inspec/train/pull/208) ([adamleff](https://github.com/adamleff))
- clearer error if no auth methods are available [\#207](https://github.com/inspec/train/pull/207) ([thommay](https://github.com/thommay))
- Build a complete mock OS object [\#206](https://github.com/inspec/train/pull/206) ([adamleff](https://github.com/adamleff))
- Platform framework and detect DSL [\#204](https://github.com/inspec/train/pull/204) ([jquick](https://github.com/jquick))
- add basic qnx support for train [\#203](https://github.com/inspec/train/pull/203) ([chris-rock](https://github.com/chris-rock))
- Add CODEOWNERS for train [\#202](https://github.com/inspec/train/pull/202) ([adamleff](https://github.com/adamleff))
- implement uploads and downloads for ssh and winrm [\#201](https://github.com/inspec/train/pull/201) ([thommay](https://github.com/thommay))
- \[MSYS-649\] Fix InSpec file size in Windows, refactor File classes [\#193](https://github.com/inspec/train/pull/193) ([Vasu1105](https://github.com/Vasu1105))
## [v0.28.0](https://github.com/inspec/train/tree/v0.28.0) (2017-09-25)
[Full Changelog](https://github.com/inspec/train/compare/v0.27.0...v0.28.0)
**Merged pull requests:**
- Release 0.28.0 [\#200](https://github.com/inspec/train/pull/200) ([adamleff](https://github.com/adamleff))
- Continue to support older net-ssh while fixing 4.2 deprecation [\#199](https://github.com/inspec/train/pull/199) ([adamleff](https://github.com/adamleff))
## [v0.27.0](https://github.com/inspec/train/tree/v0.27.0) (2017-09-25)
[Full Changelog](https://github.com/inspec/train/compare/v0.26.2...v0.27.0)
**Merged pull requests:**
- Release v0.27.0 [\#198](https://github.com/inspec/train/pull/198) ([adamleff](https://github.com/adamleff))
- Bump to net-ssh 4.2, fix bad net-ssh deprecation [\#197](https://github.com/inspec/train/pull/197) ([adamleff](https://github.com/adamleff))
## [v0.26.2](https://github.com/inspec/train/tree/v0.26.2) (2017-09-05)
[Full Changelog](https://github.com/inspec/train/compare/v0.26.1...v0.26.2)
**Merged pull requests:**
- Release 0.26.2 [\#195](https://github.com/inspec/train/pull/195) ([adamleff](https://github.com/adamleff))
- Fix inconsistent link\_path behavior [\#194](https://github.com/inspec/train/pull/194) ([adamleff](https://github.com/adamleff))
## [v0.26.1](https://github.com/inspec/train/tree/v0.26.1) (2017-08-14)
[Full Changelog](https://github.com/inspec/train/compare/v0.26.0...v0.26.1)
**Merged pull requests:**
- Release 0.26.1 [\#188](https://github.com/inspec/train/pull/188) ([adamleff](https://github.com/adamleff))
- Return non-zero exit code for unknown mock command [\#187](https://github.com/inspec/train/pull/187) ([chris-rock](https://github.com/chris-rock))
## [v0.26.0](https://github.com/inspec/train/tree/v0.26.0) (2017-08-10)
[Full Changelog](https://github.com/inspec/train/compare/v0.25.0...v0.26.0)
**Fixed bugs:**
- AIX operating system name is not detected properly [\#181](https://github.com/inspec/train/issues/181)
**Closed issues:**
- Add support for ssh-agent to ssh transport [\#129](https://github.com/inspec/train/issues/129)
**Merged pull requests:**
- Release v0.26.0 [\#186](https://github.com/inspec/train/pull/186) ([adamleff](https://github.com/adamleff))
- typo - should $g for group instead of doulbe $u [\#185](https://github.com/inspec/train/pull/185) ([aklyachkin](https://github.com/aklyachkin))
- update ruby requirements to 2.2 - 2.4 range [\#184](https://github.com/inspec/train/pull/184) ([arlimus](https://github.com/arlimus))
- detect operating system name for AIX [\#182](https://github.com/inspec/train/pull/182) ([chris-rock](https://github.com/chris-rock))
## [v0.25.0](https://github.com/inspec/train/tree/v0.25.0) (2017-06-15)
[Full Changelog](https://github.com/inspec/train/compare/v0.24.0...v0.25.0)
**Merged pull requests:**
- Fix CoreOS platform detection [\#180](https://github.com/inspec/train/pull/180) ([rarenerd](https://github.com/rarenerd))
- Remove autoloads in favor of eager loading [\#178](https://github.com/inspec/train/pull/178) ([Sharpie](https://github.com/Sharpie))
- Fixed IPv6 URI parsing [\#176](https://github.com/inspec/train/pull/176) ([zfjagann](https://github.com/zfjagann))
## [v0.24.0](https://github.com/inspec/train/tree/v0.24.0) (2017-05-30)
[Full Changelog](https://github.com/inspec/train/compare/v0.23.0...v0.24.0)
**Merged pull requests:**
- prevent sudo on localhost targets [\#179](https://github.com/inspec/train/pull/179) ([arlimus](https://github.com/arlimus))
## [v0.23.0](https://github.com/inspec/train/tree/v0.23.0) (2017-03-29)
[Full Changelog](https://github.com/inspec/train/compare/v0.22.1...v0.23.0)
**Merged pull requests:**
- Release 0.23.0 [\#173](https://github.com/inspec/train/pull/173) ([adamleff](https://github.com/adamleff))
- Fix Net::SSH warning when passing nil option values [\#172](https://github.com/inspec/train/pull/172) ([tylercloke](https://github.com/tylercloke))
- winrm: hide password [\#171](https://github.com/inspec/train/pull/171) ([crepetl](https://github.com/crepetl))
## [v0.22.1](https://github.com/inspec/train/tree/v0.22.1) (2017-01-17)
[Full Changelog](https://github.com/inspec/train/compare/v0.22.0...v0.22.1)
**Merged pull requests:**
- Release 0.22.1 [\#169](https://github.com/inspec/train/pull/169) ([tduffield](https://github.com/tduffield))
- Relax net-ssh dep to allow 4.0 [\#168](https://github.com/inspec/train/pull/168) ([tduffield](https://github.com/tduffield))
- Fix Oracle Linux detection [\#167](https://github.com/inspec/train/pull/167) ([carldjohnston](https://github.com/carldjohnston))
- Add support for parallels & virtuozzo linux [\#166](https://github.com/inspec/train/pull/166) ([jaxxstorm](https://github.com/jaxxstorm))
## [v0.22.0](https://github.com/inspec/train/tree/v0.22.0) (2016-11-29)
[Full Changelog](https://github.com/inspec/train/compare/v0.21.1...v0.22.0)
**Implemented enhancements:**
- Try to use ssh agent if no password or key files have been specified [\#165](https://github.com/inspec/train/pull/165) ([alexpop](https://github.com/alexpop))
**Merged pull requests:**
- Add openvms detection [\#159](https://github.com/inspec/train/pull/159) ([briandoodyie](https://github.com/briandoodyie))
## [v0.21.1](https://github.com/inspec/train/tree/v0.21.1) (2016-11-04)
[Full Changelog](https://github.com/inspec/train/compare/v0.21.0...v0.21.1)
**Closed issues:**
- detect\_arista\_eos raises exception against unix host [\#160](https://github.com/inspec/train/issues/160)
**Merged pull requests:**
- ensure the os detection works in pty mode [\#161](https://github.com/inspec/train/pull/161) ([chris-rock](https://github.com/chris-rock))
## [v0.21.0](https://github.com/inspec/train/tree/v0.21.0) (2016-11-04)
[Full Changelog](https://github.com/inspec/train/compare/v0.20.1...v0.21.0)
**Implemented enhancements:**
- Train doesn't create a login shell [\#148](https://github.com/inspec/train/issues/148)
**Merged pull requests:**
- Add detection for Arista EOS [\#158](https://github.com/inspec/train/pull/158) ([jerearista](https://github.com/jerearista))
## [v0.20.1](https://github.com/inspec/train/tree/v0.20.1) (2016-10-15)
[Full Changelog](https://github.com/inspec/train/compare/v0.20.0...v0.20.1)
**Fixed bugs:**
- support empty URIs [\#154](https://github.com/inspec/train/pull/154) ([arlimus](https://github.com/arlimus))
## [v0.20.0](https://github.com/inspec/train/tree/v0.20.0) (2016-09-21)
[Full Changelog](https://github.com/inspec/train/compare/v0.19.1...v0.20.0)
**Fixed bugs:**
- get `Preparing modules for first use.` when I use train on Windows [\#153](https://github.com/inspec/train/issues/153)
**Merged pull requests:**
- `Preparing modules for first use.` error message on Windows [\#152](https://github.com/inspec/train/pull/152) ([chris-rock](https://github.com/chris-rock))
- Convert `wmic` architecture to a normal standard [\#151](https://github.com/inspec/train/pull/151) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
- Login shell [\#149](https://github.com/inspec/train/pull/149) ([jonathanmorley](https://github.com/jonathanmorley))
## [v0.19.1](https://github.com/inspec/train/tree/v0.19.1) (2016-09-16)
[Full Changelog](https://github.com/inspec/train/compare/v0.19.0...v0.19.1)
**Implemented enhancements:**
- hostname property for WinRM::Connection [\#128](https://github.com/inspec/train/issues/128)
- Return hostname from WinRM::Connection same as SSH::Connection [\#150](https://github.com/inspec/train/pull/150) ([alexpop](https://github.com/alexpop))
## [v0.19.0](https://github.com/inspec/train/tree/v0.19.0) (2016-09-05)
[Full Changelog](https://github.com/inspec/train/compare/v0.18.0...v0.19.0)
**Fixed bugs:**
- use stat -c for alpine linux [\#146](https://github.com/inspec/train/pull/146) ([chris-rock](https://github.com/chris-rock))
**Merged pull requests:**
- support ruby 2.2.1 [\#145](https://github.com/inspec/train/pull/145) ([chris-rock](https://github.com/chris-rock))
- Use winrm v2 implementation [\#122](https://github.com/inspec/train/pull/122) ([mwrock](https://github.com/mwrock))
## [v0.18.0](https://github.com/inspec/train/tree/v0.18.0) (2016-08-26)
[Full Changelog](https://github.com/inspec/train/compare/v0.17.0...v0.18.0)
**Merged pull requests:**
- Allow JSON 2.0 on Ruby 2.2 and above [\#144](https://github.com/inspec/train/pull/144) ([jkeiser](https://github.com/jkeiser))
- Enable Ruby 2.3 in Travis, make it default suite [\#143](https://github.com/inspec/train/pull/143) ([jkeiser](https://github.com/jkeiser))
- Add the darwin platform family [\#141](https://github.com/inspec/train/pull/141) ([tas50](https://github.com/tas50))
- update integration test dependencies [\#139](https://github.com/inspec/train/pull/139) ([tas50](https://github.com/tas50))
- Add badges to the readme [\#138](https://github.com/inspec/train/pull/138) ([tas50](https://github.com/tas50))
- use --decode on base64 command to maintain compatibility with Darwin. [\#137](https://github.com/inspec/train/pull/137) ([thomascate](https://github.com/thomascate))
## [v0.17.0](https://github.com/inspec/train/tree/v0.17.0) (2016-08-19)
[Full Changelog](https://github.com/inspec/train/compare/v0.16.0...v0.17.0)
**Implemented enhancements:**
- return owner for files on windows [\#132](https://github.com/inspec/train/pull/132) ([chris-rock](https://github.com/chris-rock))
**Closed issues:**
- prefix powershell commands with `$ProgressPreference = "SilentlyContinue"` [\#134](https://github.com/inspec/train/issues/134)
**Merged pull requests:**
- CI improvements [\#133](https://github.com/inspec/train/pull/133) ([chris-rock](https://github.com/chris-rock))
- Rescue EPIPE on connect in ssh transport [\#130](https://github.com/inspec/train/pull/130) ([stevendanna](https://github.com/stevendanna))
## [v0.16.0](https://github.com/inspec/train/tree/v0.16.0) (2016-08-08)
[Full Changelog](https://github.com/inspec/train/compare/v0.15.1...v0.16.0)
**Merged pull requests:**
- provide file\_version and product\_version for windows files [\#127](https://github.com/inspec/train/pull/127) ([chris-rock](https://github.com/chris-rock))
- Bring train platform data more in line with ohai's platform data [\#126](https://github.com/inspec/train/pull/126) ([stevendanna](https://github.com/stevendanna))
## [v0.15.1](https://github.com/inspec/train/tree/v0.15.1) (2016-07-11)
[Full Changelog](https://github.com/inspec/train/compare/v0.15.0...v0.15.1)
**Fixed bugs:**
- bugfix: higher mode bits on local connection [\#125](https://github.com/inspec/train/pull/125) ([arlimus](https://github.com/arlimus))
**Merged pull requests:**
- Test ruby 2.1 instead of 1.9.3 and only launch one test group per travis/appveyor [\#123](https://github.com/inspec/train/pull/123) ([mwrock](https://github.com/mwrock))
## [v0.15.0](https://github.com/inspec/train/tree/v0.15.0) (2016-07-01)
[Full Changelog](https://github.com/inspec/train/compare/v0.14.2...v0.15.0)
**Implemented enhancements:**
- have net-ssh request a pty [\#60](https://github.com/inspec/train/issues/60)
**Merged pull requests:**
- Allow requesting a PTY [\#121](https://github.com/inspec/train/pull/121) ([srenatus](https://github.com/srenatus))
## [v0.14.2](https://github.com/inspec/train/tree/v0.14.2) (2016-06-28)
[Full Changelog](https://github.com/inspec/train/compare/v0.14.1...v0.14.2)
**Merged pull requests:**
- do not log password in ssh connection output [\#120](https://github.com/inspec/train/pull/120) ([marcparadise](https://github.com/marcparadise))
## [v0.14.1](https://github.com/inspec/train/tree/v0.14.1) (2016-06-27)
[Full Changelog](https://github.com/inspec/train/compare/v0.14.0...v0.14.1)
**Fixed bugs:**
- bugfix: add mock backend initialization [\#119](https://github.com/inspec/train/pull/119) ([arlimus](https://github.com/arlimus))
## [v0.14.0](https://github.com/inspec/train/tree/v0.14.0) (2016-06-27)
[Full Changelog](https://github.com/inspec/train/compare/v0.13.1...v0.14.0)
**Implemented enhancements:**
- json in and out for base connection [\#118](https://github.com/inspec/train/pull/118) ([arlimus](https://github.com/arlimus))
- ESX support [\#116](https://github.com/inspec/train/pull/116) ([Anirudh-Gupta](https://github.com/Anirudh-Gupta))
**Fixed bugs:**
- sporadic appveyor failure on `winrm delete ...` [\#105](https://github.com/inspec/train/issues/105)
- bugfix: run frozen string commands via ssh [\#117](https://github.com/inspec/train/pull/117) ([arlimus](https://github.com/arlimus))
## [v0.13.1](https://github.com/inspec/train/tree/v0.13.1) (2016-06-16)
[Full Changelog](https://github.com/inspec/train/compare/v0.13.0...v0.13.1)
**Implemented enhancements:**
- use train as gem name. Thanks @halo [\#115](https://github.com/inspec/train/pull/115) ([chris-rock](https://github.com/chris-rock))
## [v0.13.0](https://github.com/inspec/train/tree/v0.13.0) (2016-06-16)
[Full Changelog](https://github.com/inspec/train/compare/v0.12.1...v0.13.0)
**Implemented enhancements:**
- provide uri-formatted information on all connections [\#113](https://github.com/inspec/train/pull/113) ([arlimus](https://github.com/arlimus))
**Fixed bugs:**
- Authentication with SSH Server on OSX is failing [\#111](https://github.com/inspec/train/issues/111)
**Merged pull requests:**
- adding support for vmware's esx server [\#114](https://github.com/inspec/train/pull/114) ([Anirudh-Gupta](https://github.com/Anirudh-Gupta))
- add missing keyboard-interactive authentication method [\#112](https://github.com/inspec/train/pull/112) ([chris-rock](https://github.com/chris-rock))
## [v0.12.1](https://github.com/inspec/train/tree/v0.12.1) (2016-05-23)
[Full Changelog](https://github.com/inspec/train/compare/v0.12.0...v0.12.1)
**Fixed bugs:**
- loosen restriction for docker api [\#109](https://github.com/inspec/train/pull/109) ([chris-rock](https://github.com/chris-rock))
**Closed issues:**
- docker-api conflict when using docker cookbook [\#108](https://github.com/inspec/train/issues/108)
## [v0.12.0](https://github.com/inspec/train/tree/v0.12.0) (2016-05-16)
[Full Changelog](https://github.com/inspec/train/compare/v0.11.4...v0.12.0)
**Merged pull requests:**
- Custom sudo command [\#107](https://github.com/inspec/train/pull/107) ([jeremymv2](https://github.com/jeremymv2))
## [v0.11.4](https://github.com/inspec/train/tree/v0.11.4) (2016-05-13)
[Full Changelog](https://github.com/inspec/train/compare/v0.11.3...v0.11.4)
**Fixed bugs:**
- mount resource incorrect matching [\#103](https://github.com/inspec/train/issues/103)
- Add a space to avoid matching partial paths [\#104](https://github.com/inspec/train/pull/104) ([alexpop](https://github.com/alexpop))
- Update README.md [\#102](https://github.com/inspec/train/pull/102) ([mcquin](https://github.com/mcquin))
**Merged pull requests:**
- 0.11.4 [\#106](https://github.com/inspec/train/pull/106) ([arlimus](https://github.com/arlimus))
## [v0.11.3](https://github.com/inspec/train/tree/v0.11.3) (2016-05-10)
[Full Changelog](https://github.com/inspec/train/compare/v0.11.2...v0.11.3)
**Fixed bugs:**
- appveyor fixing... [\#98](https://github.com/inspec/train/pull/98) ([arlimus](https://github.com/arlimus))
**Merged pull requests:**
- fix: winrm https listener is not configured anymore in appveyor [\#100](https://github.com/inspec/train/pull/100) ([chris-rock](https://github.com/chris-rock))
- use aix stats implementation for hpux as well [\#99](https://github.com/inspec/train/pull/99) ([Anirudh-Gupta](https://github.com/Anirudh-Gupta))
## [v0.11.2](https://github.com/inspec/train/tree/v0.11.2) (2016-04-29)
[Full Changelog](https://github.com/inspec/train/compare/v0.11.1...v0.11.2)
**Fixed bugs:**
- bugfix: windows file failed to initialize with new symlink handler [\#96](https://github.com/inspec/train/pull/96) ([arlimus](https://github.com/arlimus))
**Merged pull requests:**
- 0.11.2 [\#97](https://github.com/inspec/train/pull/97) ([alexpop](https://github.com/alexpop))
## [v0.11.1](https://github.com/inspec/train/tree/v0.11.1) (2016-04-28)
[Full Changelog](https://github.com/inspec/train/compare/v0.11.0...v0.11.1)
**Fixed bugs:**
- fix nil file paths [\#94](https://github.com/inspec/train/pull/94) ([arlimus](https://github.com/arlimus))
**Merged pull requests:**
- provide a source path for filecommon [\#95](https://github.com/inspec/train/pull/95) ([arlimus](https://github.com/arlimus))
- restructure docker tests to balance load between 2 runs [\#93](https://github.com/inspec/train/pull/93) ([arlimus](https://github.com/arlimus))
## [v0.11.0](https://github.com/inspec/train/tree/v0.11.0) (2016-04-28)
[Full Changelog](https://github.com/inspec/train/compare/v0.10.8...v0.11.0)
**Implemented enhancements:**
- Overhault file\(...\) and stat\(...\); point to destination of symlinks [\#92](https://github.com/inspec/train/pull/92) ([arlimus](https://github.com/arlimus))
**Fixed bugs:**
- validate the backend configuration [\#91](https://github.com/inspec/train/pull/91) ([arlimus](https://github.com/arlimus))
## [v0.10.8](https://github.com/inspec/train/tree/v0.10.8) (2016-04-25)
[Full Changelog](https://github.com/inspec/train/compare/v0.10.7...v0.10.8)
**Implemented enhancements:**
- loose restriction for mixlib-shellout [\#89](https://github.com/inspec/train/pull/89) ([chris-rock](https://github.com/chris-rock))
**Merged pull requests:**
- use gemspec for travis [\#90](https://github.com/inspec/train/pull/90) ([chris-rock](https://github.com/chris-rock))
- Don't strip off the second byte of the octal mode. [\#88](https://github.com/inspec/train/pull/88) ([justindossey](https://github.com/justindossey))
## [v0.10.7](https://github.com/inspec/train/tree/v0.10.7) (2016-04-21)
[Full Changelog](https://github.com/inspec/train/compare/v0.10.6...v0.10.7)
**Merged pull requests:**
- 0.10.7 [\#87](https://github.com/inspec/train/pull/87) ([arlimus](https://github.com/arlimus))
- Revert "add -L to get stat for symlink" [\#86](https://github.com/inspec/train/pull/86) ([arlimus](https://github.com/arlimus))
## [v0.10.6](https://github.com/inspec/train/tree/v0.10.6) (2016-04-20)
[Full Changelog](https://github.com/inspec/train/compare/v0.10.5...v0.10.6)
**Merged pull requests:**
- add -L to get stat for symlink [\#85](https://github.com/inspec/train/pull/85) ([vjeffrey](https://github.com/vjeffrey))
- release via travis + test via rubygems [\#84](https://github.com/inspec/train/pull/84) ([arlimus](https://github.com/arlimus))
## [v0.10.5](https://github.com/inspec/train/tree/v0.10.5) (2016-04-18)
[Full Changelog](https://github.com/inspec/train/compare/v0.10.4...v0.10.5)
**Merged pull requests:**
- 0.10.5 [\#83](https://github.com/inspec/train/pull/83) ([srenatus](https://github.com/srenatus))
- detection for hp-ux machine [\#82](https://github.com/inspec/train/pull/82) ([Anirudh-Gupta](https://github.com/Anirudh-Gupta))
## [v0.10.4](https://github.com/inspec/train/tree/v0.10.4) (2016-03-31)
[Full Changelog](https://github.com/inspec/train/compare/v0.10.3...v0.10.4)
**Fixed bugs:**
- bugfix: do not use unix path escape for windows [\#79](https://github.com/inspec/train/pull/79) ([chris-rock](https://github.com/chris-rock))
**Merged pull requests:**
- 0.10.4 [\#80](https://github.com/inspec/train/pull/80) ([arlimus](https://github.com/arlimus))
## [v0.10.3](https://github.com/inspec/train/tree/v0.10.3) (2016-03-07)
[Full Changelog](https://github.com/inspec/train/compare/v0.10.1...v0.10.3)
**Fixed bugs:**
- set default value for ssh compression to false [\#77](https://github.com/inspec/train/pull/77) ([chris-rock](https://github.com/chris-rock))
- avoid mock backend error on nil commands [\#75](https://github.com/inspec/train/pull/75) ([arlimus](https://github.com/arlimus))
**Merged pull requests:**
- 0.10.3 [\#78](https://github.com/inspec/train/pull/78) ([chris-rock](https://github.com/chris-rock))
- 0.10.2 [\#76](https://github.com/inspec/train/pull/76) ([arlimus](https://github.com/arlimus))
## [v0.10.1](https://github.com/inspec/train/tree/v0.10.1) (2016-02-29)
[Full Changelog](https://github.com/inspec/train/compare/v0.10.0...v0.10.1)
**Merged pull requests:**
- 0.10.1 [\#74](https://github.com/inspec/train/pull/74) ([chris-rock](https://github.com/chris-rock))
- fix gem build license warning [\#73](https://github.com/inspec/train/pull/73) ([chris-rock](https://github.com/chris-rock))
- depend on docker-api 1.26.2 [\#72](https://github.com/inspec/train/pull/72) ([someara](https://github.com/someara))
## [v0.10.0](https://github.com/inspec/train/tree/v0.10.0) (2016-02-19)
[Full Changelog](https://github.com/inspec/train/compare/v0.9.7...v0.10.0)
**Implemented enhancements:**
- show mock failures for commands [\#69](https://github.com/inspec/train/pull/69) ([arlimus](https://github.com/arlimus))
- update gems and rubocop [\#68](https://github.com/inspec/train/pull/68) ([arlimus](https://github.com/arlimus))
**Fixed bugs:**
- complete rewrite of windows version detection [\#70](https://github.com/inspec/train/pull/70) ([chris-rock](https://github.com/chris-rock))
**Merged pull requests:**
- 0.10.0 [\#71](https://github.com/inspec/train/pull/71) ([chris-rock](https://github.com/chris-rock))
## [v0.9.7](https://github.com/inspec/train/tree/v0.9.7) (2016-02-05)
[Full Changelog](https://github.com/inspec/train/compare/v0.9.6...v0.9.7)
**Implemented enhancements:**
- feature: add file.basename [\#64](https://github.com/inspec/train/pull/64) ([arlimus](https://github.com/arlimus))
- add `requiretty` workaround measures [\#63](https://github.com/inspec/train/pull/63) ([srenatus](https://github.com/srenatus))
**Fixed bugs:**
- ensure bundler is installed on travis [\#66](https://github.com/inspec/train/pull/66) ([chris-rock](https://github.com/chris-rock))
**Merged pull requests:**
- 0.9.7 [\#67](https://github.com/inspec/train/pull/67) ([chris-rock](https://github.com/chris-rock))
## [v0.9.6](https://github.com/inspec/train/tree/v0.9.6) (2016-01-29)
[Full Changelog](https://github.com/inspec/train/compare/v0.9.5...v0.9.6)
**Implemented enhancements:**
- add solaris support [\#61](https://github.com/inspec/train/pull/61) ([chris-rock](https://github.com/chris-rock))
**Merged pull requests:**
- 0.9.6 [\#62](https://github.com/inspec/train/pull/62) ([chris-rock](https://github.com/chris-rock))
## [v0.9.5](https://github.com/inspec/train/tree/v0.9.5) (2016-01-25)
[Full Changelog](https://github.com/inspec/train/compare/v0.9.4...v0.9.5)
**Implemented enhancements:**
- use minitest for windows tests [\#56](https://github.com/inspec/train/pull/56) ([chris-rock](https://github.com/chris-rock))
- use negotiate auth for winrm and not basic\_auth [\#55](https://github.com/inspec/train/pull/55) ([mwrock](https://github.com/mwrock))
- bugfix: pin net-ssh 2.9 in gem file [\#54](https://github.com/inspec/train/pull/54) ([chris-rock](https://github.com/chris-rock))
- Add appveyor and Windows test [\#53](https://github.com/inspec/train/pull/53) ([chris-rock](https://github.com/chris-rock))
- Deprecating winrm-tansport gem [\#46](https://github.com/inspec/train/pull/46) ([mwrock](https://github.com/mwrock))
**Fixed bugs:**
- Cannot install train on Windows with ChefDK if username \>9 chars in length due to spec filename lengths in docker-api gem. [\#28](https://github.com/inspec/train/issues/28)
- Properly wrap commands in powershell for local backend [\#57](https://github.com/inspec/train/pull/57) ([chris-rock](https://github.com/chris-rock))
- Copying https://github.com/test-kitchen/test-kitchen/pull/919 to this repo [\#52](https://github.com/inspec/train/pull/52) ([tyler-ball](https://github.com/tyler-ball))
**Merged pull requests:**
- 0.9.5 [\#58](https://github.com/inspec/train/pull/58) ([chris-rock](https://github.com/chris-rock))
## [v0.9.4](https://github.com/inspec/train/tree/v0.9.4) (2016-01-15)
[Full Changelog](https://github.com/inspec/train/compare/v0.9.3...v0.9.4)
**Implemented enhancements:**
- 0.9.3 is empty on Windows [\#48](https://github.com/inspec/train/pull/48) ([tyler-ball](https://github.com/tyler-ball))
- Updating to the latest release of net-ssh to consume https://github.com/net-ssh/net-ssh/pull/280 [\#47](https://github.com/inspec/train/pull/47) ([tyler-ball](https://github.com/tyler-ball))
**Fixed bugs:**
- bugfix: command wrapper always return nil [\#50](https://github.com/inspec/train/pull/50) ([chris-rock](https://github.com/chris-rock))
**Merged pull requests:**
- 0.9.4 [\#51](https://github.com/inspec/train/pull/51) ([chris-rock](https://github.com/chris-rock))
## [v0.9.3](https://github.com/inspec/train/tree/v0.9.3) (2016-01-03)
[Full Changelog](https://github.com/inspec/train/compare/v0.9.2...v0.9.3)
**Implemented enhancements:**
- introduce `mounted` as a separate method to retrieve the content [\#44](https://github.com/inspec/train/pull/44) ([chris-rock](https://github.com/chris-rock))
- Support for local transport on Windows [\#43](https://github.com/inspec/train/pull/43) ([chris-rock](https://github.com/chris-rock))
- Split integration test preparation from executing [\#42](https://github.com/inspec/train/pull/42) ([chris-rock](https://github.com/chris-rock))
- Support for AIX and targeted SSH testing [\#41](https://github.com/inspec/train/pull/41) ([foobarbam](https://github.com/foobarbam))
**Merged pull requests:**
- 0.9.3 [\#45](https://github.com/inspec/train/pull/45) ([chris-rock](https://github.com/chris-rock))
## [v0.9.2](https://github.com/inspec/train/tree/v0.9.2) (2015-12-11)
[Full Changelog](https://github.com/inspec/train/compare/v0.9.1...v0.9.2)
**Implemented enhancements:**
- add changelog [\#38](https://github.com/inspec/train/pull/38) ([chris-rock](https://github.com/chris-rock))
- activate integration tests in travis [\#37](https://github.com/inspec/train/pull/37) ([chris-rock](https://github.com/chris-rock))
- Adding support for Wind River Linux in support of Cisco devices [\#33](https://github.com/inspec/train/pull/33) ([adamleff](https://github.com/adamleff))
**Fixed bugs:**
- Integration test failures [\#34](https://github.com/inspec/train/issues/34)
- Implemented WindowsFile\#exist? [\#36](https://github.com/inspec/train/pull/36) ([docwhat](https://github.com/docwhat))
- adapt integration test to changes in command\_wrapper [\#35](https://github.com/inspec/train/pull/35) ([srenatus](https://github.com/srenatus))
**Closed issues:**
- WinRM plaintext transport is hardcoded \(cannot use SSL\) [\#29](https://github.com/inspec/train/issues/29)
**Merged pull requests:**
- 0.9.2 [\#40](https://github.com/inspec/train/pull/40) ([arlimus](https://github.com/arlimus))
- add rake version helpers [\#39](https://github.com/inspec/train/pull/39) ([arlimus](https://github.com/arlimus))
## [v0.9.1](https://github.com/inspec/train/tree/v0.9.1) (2015-11-03)
[Full Changelog](https://github.com/inspec/train/compare/0.9.0...v0.9.1)
**Implemented enhancements:**
- R train [\#27](https://github.com/inspec/train/pull/27) ([arlimus](https://github.com/arlimus))
- Update style of readme [\#26](https://github.com/inspec/train/pull/26) ([chris-rock](https://github.com/chris-rock))
- Add Apache 2.0 License [\#25](https://github.com/inspec/train/pull/25) ([jamesc](https://github.com/jamesc))
## [0.9.0](https://github.com/inspec/train/tree/0.9.0) (2015-11-03)
**Implemented enhancements:**
- set windows name in :release [\#23](https://github.com/inspec/train/pull/23) ([arlimus](https://github.com/arlimus))
- basic file transport via winrm [\#21](https://github.com/inspec/train/pull/21) ([chris-rock](https://github.com/chris-rock))
- dont return nil on command errors stdout/stderr [\#20](https://github.com/inspec/train/pull/20) ([arlimus](https://github.com/arlimus))
- skip .delivery in gemspec [\#19](https://github.com/inspec/train/pull/19) ([arlimus](https://github.com/arlimus))
- Verify sudo is working and fail with error messages if it isn't [\#18](https://github.com/inspec/train/pull/18) ([arlimus](https://github.com/arlimus))
- improve file eposure [\#16](https://github.com/inspec/train/pull/16) ([chris-rock](https://github.com/chris-rock))
- add delivery [\#13](https://github.com/inspec/train/pull/13) ([arlimus](https://github.com/arlimus))
- Sudo [\#12](https://github.com/inspec/train/pull/12) ([arlimus](https://github.com/arlimus))
- Extract options handling for transport [\#11](https://github.com/inspec/train/pull/11) ([arlimus](https://github.com/arlimus))
- don't let mock commands return nil on stdout or stderr [\#10](https://github.com/inspec/train/pull/10) ([arlimus](https://github.com/arlimus))
- allow mock command to support sha256 mocking of commands [\#9](https://github.com/inspec/train/pull/9) ([arlimus](https://github.com/arlimus))
- register plugins with both names and symbols [\#8](https://github.com/inspec/train/pull/8) ([arlimus](https://github.com/arlimus))
- split of mock into transport and connection [\#7](https://github.com/inspec/train/pull/7) ([arlimus](https://github.com/arlimus))
- bugfix: add docker dependency to gemspec [\#6](https://github.com/inspec/train/pull/6) ([arlimus](https://github.com/arlimus))
- move train/plugins/common to train/extras [\#2](https://github.com/inspec/train/pull/2) ([arlimus](https://github.com/arlimus))
- add Travis [\#1](https://github.com/inspec/train/pull/1) ([arlimus](https://github.com/arlimus))
**Fixed bugs:**
- bugfix: prevent debugging info to stdout on winrm [\#22](https://github.com/inspec/train/pull/22) ([arlimus](https://github.com/arlimus))
- bugfix: fail ssh connections correctly [\#17](https://github.com/inspec/train/pull/17) ([arlimus](https://github.com/arlimus))
- bugfix: initialize mock transport to correct family [\#14](https://github.com/inspec/train/pull/14) ([arlimus](https://github.com/arlimus))
**Merged pull requests:**
- bump train version to 0.9.0 [\#24](https://github.com/inspec/train/pull/24) ([chris-rock](https://github.com/chris-rock)) train-3.2.28/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000031546 13645125472 0015544 0 ustar 00root root 0000000 0000000 # Chef Contributor Covenant Code of Conduct
**Note**: If you are in a physical space (e.g. ChefConf, Meetup, etc), please
see the [Physical Spaces Code of Conduct](physical-spaces-code-of-conduct.md)
## Our Pledge
Diversity is one of the greatest strengths that a community can have, and many times
that strength is born from the friction that can only come through sharing of
differing perspectives.
In the interest of fostering an open, welcoming, and encouraging environment, we
as contributors, maintainers, and community members pledge to making
participation in our projects and our community a harassment-free experience for
everyone, regardless of age, body size, disability, ethnicity, gender identity
and expression, level of experience, nationality, personal appearance, race,
religion, veteran status, or sexual identity and orientation.
## Our Standards
The Chef community contains a diverse group of professionals and volunteers
who come from all over the world to make Chef better. Community members may
fulfill many roles including mentoring, teaching, and connecting with other
members of the community.
Be careful in the words that you choose. Be kind to others. Practice empathy.
Don't insult or put down others. Remember that sexist, racist, ableist, ageist, and other
exclusionary jokes can be offensive to those around you. If you think your
conversation is making another community member uncomfortable _or_ if they tell
you so, stop immediately, make amends, and move forward.
As you are working with other members of the community, please keep in mind that the
following guidelines apply equally to founders, mentors, those who submit
new features/pull requests, and anyone who is seeking help and guidance.
The following list isn’t exhaustive, but these few examples can help all of us
communicate well, so that the community can work better together:
- Use welcoming and inclusive language
- Exercise patience and friendliness
- Be respectful of differing viewpoints and experiences
- Gracefully accept constructive criticism
- Focus on what is best for the community
- Show empathy towards other community members
The previous list applies to all forms of communication: Slack (or any web
chat), Discourse, the issue tracker, and any other forum that is used by the
community.
Please keep in mind that:
- Your work will be used by other people, and you, in turn, will depend on the
work of others
- Decisions that you make often will affect others in the community
- Disagreements happen, but should not be an excuse for poor behavior and bad
manners. When disagreements do happen, let’s work together to solve them
effectively and in a way that ensures that everyone understands what the
disagreements were
- Our community spans languages, cultures, perspectives (and continents!), and
as such people may not understand jokes, sarcasm, and oblique references in
the same way that you do. Remember that and be kind to the other members
of the community
- Be cautious about making assumptions about what someone does or does not know
about something - assuming that someone does not understand an issue and over
explaining can be condescending (even when not intended to be so)
- Sexist, racist, ableist, ageist, and other prejudicial or exclusionary comments are
not welcome in the community
## Unacceptable Behavior
Harassment comes in many forms, including but not limited to:
- Offensive comments related to gender, sexual orientation, age, disability,
physical appearance, body size, race, veteran status, or religion
- Posting/Exposing sexually explicit or violent images
- Deliberate (or implied) intimidation
- Trolling, insulting/derogatory comments, and personal or political attacks
particularly those related to gender, sexual orientation, age, race, religion or
disability
- Publishing others' private
information, such as a physical or electronic address, without explicit
permission ("doxing")
As a community that meets in physical public spaces, harassment also includes:
- Stalking or persistent following
- Intrusive or otherwise unwanted photography or recording
- Sustained disruption of talks or other events
- Inappropriate physical contact or unwelcome sexual attention
**NOTE**: If you are in a physical space -- e.g. Chef Conf, Meetup, etc. -- please
see the [Physical Spaces Code of Conduct](physical-spaces-code-of-conduct.md).
If you have any lack of clarity about behaviors we include in the definition of
"harassment", please read the [Citizen Code of
Conduct](http://citizencodeofconduct.org/). In particular, we do not tolerate
behavior that excludes people in socially marginalized groups.
## Enforcement/Getting Help
Instances of abusive, harassing, or otherwise unacceptable behavior should be
reported by contacting any of the Community Advocates directly. Each person's
contact information and role is listed in the repo that links to this document.
If you were not linked here, then contact the [individuals listed below](#roles). All
complaints will be reviewed, investigated, and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an
incident. Further details of specific enforcement policies may be posted
separately.
Community Organizers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Roles
The following are the various roles of our **Community Organizers** and the
person(s) assigned to each role:
- The **Deciders** have final say on community guidelines and final authority on correct actions and appeals
- The **Community Advocates** may be assigned for each area where the
community convenes online (Slack, email list, GitHub, etc.). Community
Advocates are volunteers who have the best interests of our community in
mind. They act in good faith to help enforce our community guidelines and
respond to incidents when they occur
- The **Project Maintainers** are expected to conduct their
behavior in line with the Code of Conduct and are individually responsible
for both escalating to a **Community Advocate** in case of witnessing an
incident, and helping to foster the community
- A **Community Member** is anyone who participates with the community
whether in-person or via online channels. Community members are responsible
for following the community guidelines, suggesting updates to the guidelines
when warranted, and helping enforce community guidelines
| Role | Name | Contact Info |
| ------------------- | ------------------------ | ---------------------- |
| Decider | Jenny Armstrong-Own | jowen@chef.io |
| Community Advocate | Mandi Walls | mandi@chef.io |
| Community Advocate | Nell Shamrell-Harrington | nshamrell@chef.io |
| Community Advocate | Robb Kidd | rkidd@chef.io |
## Consequences of Unacceptable Behavior
Unacceptable behavior from any community member, including sponsors and those
with decision-making authority, will *not* be tolerated.
Anyone who is asked to stop unacceptable behavior is expected to comply immediately.
If a community member engages in unacceptable behavior, the community organizers
may take action that they deem appropriate -- up to and including a temporary ban or
permanent expulsion from the community _without warning_ (and without refund, in
the case of a paid event). If you have been involved in unacceptable behavior
with current Chef community members outside the boundaries of the Chef
Community, the Community Organizers retain the right to treat those external
incidents in the same manner as internal incidents.
Any physical violence _or_ intimidation, threatened or acted on, is a serious
offense and will result in immediate exclusion from the community and
appropriate follow up with law enforcement. No, we are not kidding.
## Procedure for Handling Disagreements and Incidents
Disagreements are inherent to a group of impassioned people. When they occur, we
seek to resolve disagreements and differing views constructively and with the
help of the community and community processes. When disagreements escalate, we
ask our Community Advocates to step in to moderate, mediate, and help resolve
tense situations.
The Chef Community Advocates are well informed on how to deal with incidents.
Report the incident (preferably in writing) to one of the Community Advocates
listed in the [Roles](#roles) section.
### Handling Incidents
**NOTE**: If you are in a physical space -- e.g. Chef Conf, Meetup, etc. -- please
see the [Physical Spaces Code of Conduct](physical-spaces-code-of-conduct.md).
When a Community Organizer or Project Maintainer notices someone behaving in a
way that is outside of our guidelines (a violator), the Community Advocate should
make every reasonable attempt to help curtail that behavior. The Community
Advocate may:
- Remind the violator about our Community Code of Conduct and provide a link
to this document
- Ask the violator to stop the unacceptable behavior
- Raise the issue with a maintainer, the community manager, or any member of
the core project team
- Allow time for the violator to correct the behavior
The Community Advocate should take the following steps if the behavior is not
brought in-line with our guidelines or the incident is not resolved:
- Consult with another Community Organizer to make a judgment call about what
reasonable corrective actions are warranted
- In the case that no conclusion can be made, escalate to include the next
level of Community Organizers
- If still no conclusion can be made, report the incident to the **Deciders** listed above
- Apply the corrective action
- Document the incident as described below
#### Documenting Incidents
All incident reports will be kept in a private repository that is shared with
the aforementioned Community Advocates and Deciders under the [Roles]((#roles)
section. No other individuals or project contributors will be given access to
these incident reports. **This repo will hold no personal information on the
victim of an incident.** On the displacement of any Community Organizer in the
[Roles](#roles) list above, that individual will immediately lose access to this
repository and will terminate any local copies of the repository.
The important information to report consists of:
- Identifying information (name, email address, Slack username, etc.) of the
person doing the harassing
- The behavior that was in violation
- The approximate time and date of the behavior
- The circumstances surrounding the incident
- Where applicable, contextual information/proof (email body, chat log, GitHub
Issue, etc.)
- Contact information for witnesses to the incident
If you feel your safety is in jeopardy, please do not hesitate to contact local
law enforcement.
**Note:** Incidents that violate the Community Code of Conduct are extremely
damaging to the community. The silver lining is that, in many cases, these
incidents present a chance for the community as a whole to grow, learn, and
become better.
## Our Responsibilities
Community Organizers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Community Organizers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, messages, tweets, and other
contributions that are not aligned with this Code of Conduct, or to ban
temporarily or permanently any contributor for other behaviors that they deem
inappropriate, threatening, offensive, or harmful.
## Scope
Our community will convene in both physical and virtual spaces. This Code of
Conduct applies within project spaces and in public spaces when an individual is
representing the project or its community. Examples of representing a project or
community include using an official project e-mail address, posting via an
official social media account, or acting as an appointed representative at an
online or offline event. Representation of a project may be further defined and
clarified by project maintainers and community organizers.
## Attribution
This Code of Conduct is adapted from the following:
- [Contributor Covenant](http://contributor-covenant.org), version 1.4, available [here](http://contributor-covenant.org/version/1/4/)
- [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html)
- [Citizen Code of Conduct](http://citizencodeofconduct.org/)
## Community Members in Crisis
If a community member is in or appears to be in crisis, please refer to the [Community Members in Crisis Guide](./communication/community-members-in-crisis.md). train-3.2.28/CONTRIBUTING.md 0000664 0000000 0000000 00000016561 13645125472 0015176 0 ustar 00root root 0000000 0000000 # Contributing to Train
We are glad you want to contribute to Train! This document will help answer common questions you may have during your first contribution.
## Submitting Issues
We utilize **Github Issues** for issue tracking and contributions. You can contribute in two ways:
1. Reporting an issue or making a feature request [here](https://github.com/inspec/train/issues/new).
2. Adding features or fixing bugs yourself and contributing your code to Train.
We ask you not to submit security concerns via Github. For details on submitting potential security issues please see
## Contribution Process
We have a 3 step process for contributions:
1. Commit changes to a git branch, making sure to sign-off those changes for the [Developer Certificate of Origin](#developer-certification-of-origin-dco).
2. Create a Github Pull Request for your change, following the instructions in the pull request template.
3. Perform a [Code Review](#code-review-process) with the project maintainers on the pull request.
### Pull Request Requirements
Chef Projects are built to last. We strive to ensure high quality throughout the experience. In order to ensure this, we require that all pull requests to Chef projects meet these specifications:
1. **Tests:** To ensure high quality code and protect against future regressions, we require all the code in Chef Projects to have at least unit test coverage. See the [test/unit](https://github.com/chef/train/tree/master/test/unit)
directory for the existing tests and use ```bundle exec rake test``` to run them.
2. **Green CI Tests:** We use [Travis CI](https://travis-ci.org/) and/or [AppVeyor](https://www.appveyor.com/) CI systems to test all pull requests. We require these test runs to succeed on every pull request before being merged.
In addition to this it would be nice to include the description of the problem you are solving
with your change. You can use [Issue Template](https://github.com/chef/train/tree/master/ISSUE_TEMPLATE.md) in the description section
of the pull request.
### Code Review Process
Code review takes place in Github pull requests. See [this article](https://help.github.com/articles/about-pull-requests/) if you're not familiar with Github Pull Requests.
Once you open a pull request, project maintainers will review your code and respond to your pull request with any feedback they might have. The process at this point is as follows:
1. Two thumbs-up (:+1:) are required from project maintainers. See the master maintainers document for Train projects at .
2. When ready, your pull request will be merged into `master`, we may require you to rebase your PR to the latest `master`.
3. Once the PR is merged, you will be included in `CHANGELOG.md`.
### Developer Certification of Origin (DCO)
Licensing is very important to open source projects. It helps ensure the software continues to be available under the terms that the author desired.
Chef uses [the Apache 2.0 license](https://github.com/chef/chef/blob/master/LICENSE) to strike a balance between open contribution and allowing you to use the software however you would like to.
The license tells you what rights you have that are provided by the copyright holder. It is important that the contributor fully understands what rights they are licensing and agrees to them. Sometimes the copyright holder isn't the contributor, such as when the contributor is doing work on behalf of a company.
To make a good faith effort to ensure these criteria are met, Chef requires the Developer Certificate of Origin (DCO) process to be followed.
The DCO is an attestation attached to every contribution made by every developer. In the commit message of the contribution, the developer simply adds a Signed-off-by statement and thereby agrees to the DCO, which you can find below or at .
```
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the
best of my knowledge, is covered under an appropriate open
source license and I have the right under that license to
submit that work with modifications, whether created in whole
or in part by me, under the same open source license (unless
I am permitted to submit under a different license), as
Indicated in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including
all personal information I submit with it, including my
sign-off) is maintained indefinitely and may be redistributed
consistent with this project or the open source license(s)
involved.
```
For more information on the change see the Chef Blog post [Introducing Developer Certificate of Origin](https://blog.chef.io/2016/09/19/introducing-developer-certificate-of-origin/)
#### DCO Sign-Off Methods
The DCO requires a sign-off message in the following format appear on each commit in the pull request:
```
Signed-off-by: Julia Child
```
The DCO text can either be manually added to your commit body, or you can add either **-s** or **--signoff** to your usual git commit commands. If you forget to add the sign-off you can also amend a previous commit with the sign-off by running **git commit --amend -s**. If you've pushed your changes to Github already you'll need to force push your branch after this with **git push -f**.
### Obvious Fix Policy
Small contributions, such as fixing spelling errors, where the content is small enough to not be considered intellectual property, can be submitted without signing the contribution for the DCO.
As a rule of thumb, changes are obvious fixes if they do not introduce any new functionality or creative thinking. Assuming the change does not affect functionality, some common obvious fix examples include the following:
- Spelling / grammar fixes
- Typo correction, white space and formatting changes
- Comment clean up
- Bug fixes that change default return values or error codes stored in constants
- Adding logging messages or debugging output
- Changes to 'metadata' files like Gemfile, .gitignore, build scripts, etc.
- Moving source files from one directory or package to another
**Whenever you invoke the "obvious fix" rule, please say so in your commit message:**
```
------------------------------------------------------------------------
commit 370adb3f82d55d912b0cf9c1d1e99b132a8ed3b5
Author: Julia Child
Date: Wed Sep 18 11:44:40 2015 -0700
Fix typo in the README.
Obvious fix.
------------------------------------------------------------------------
```
## Train Community
Train is made possible by a strong community of developers, system administrators, auditor and security experts. If you have any questions or if you would like to get involved in the Train community you can check out:
- [Chef Community Slack](https://community-slack.chef.io/)
Also here are some additional pointers to some awesome Chef content:
- [Learn Chef](https://learn.chef.io/)
- [Chef Website](https://www.chef.io/)
train-3.2.28/Gemfile 0000664 0000000 0000000 00000002011 13645125472 0014221 0 ustar 00root root 0000000 0000000 # encoding: utf-8
source "https://rubygems.org"
gemspec name: "train"
group :test do
gem "coveralls", require: false
gem "minitest", "~> 5.8"
gem "rake", ["~> 12.3", ">= 12.3.3"]
gem "chefstyle", "0.13.2"
gem "simplecov", "~> 0.10"
gem "concurrent-ruby", "~> 1.0"
gem "pry-byebug"
gem "m"
gem "ed25519" # ed25519 ssh key support
gem "bcrypt_pbkdf" # ed25519 ssh key support
# This is not a true gem installation
# (Gem::Specification.find_by_path('train-gem-fixture') will return nil)
# but it's close enough to show the gempath handler can find a plugin
# See test/unit/
gem "train-test-fixture", path: "test/fixtures/plugins/train-test-fixture"
gem "mocha", "~> 1.1"
end
group :integration do
gem "berkshelf", "~> 6.3.0"
gem "test-kitchen", ">= 1.2.4"
gem "kitchen-vagrant"
end
group :tools do
gem "pry", "~> 0.10"
gem "rb-readline"
gem "license_finder"
end
# add these additional dependencies into Gemfile.local
eval_gemfile(__FILE__ + ".local") if File.exist?(__FILE__ + ".local")
train-3.2.28/LICENSE 0000664 0000000 0000000 00000025142 13645125472 0013745 0 ustar 00root root 0000000 0000000 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
train-3.2.28/MAINTAINERS.md 0000664 0000000 0000000 00000001311 13645125472 0015024 0 ustar 00root root 0000000 0000000 # Maintainers
This file lists how the train project is maintained. When making changes to the
project, this file tells you who needs to review your patch - you need at least
two maintainers to provide a :+1: on your pull request. Additionally, you need
to not receive a veto from a Lieutenant or the Project Lead.
Check out [How Chef is Maintained](https://github.com/chef/chef-rfc/blob/master/rfc030-maintenance-policy.md#how-the-project-is-maintained)
for details on the process, how to become a maintainer, lieutenant, or the
project lead.
## List of Maintainers
The [Maintainers of the InSpec project](https://github.com/inspec/inspec/blob/master/MAINTAINERS.md) serve as maintainers of the train project.
train-3.2.28/README.md 0000664 0000000 0000000 00000013751 13645125472 0014222 0 ustar 00root root 0000000 0000000 # Train - Transport Interface
* **Project State: Active**
* **Issues Response SLA: 3 business days**
* **Pull Request Response SLA: 3 business days**
For more information on project states and SLAs, see [this documentation](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md).
[](https://buildkite.com/chef-oss/inspec-train-master-verify)
[](https://badge.fury.io/rb/train)
[](https://coveralls.io/github/inspec/train?branch=master)
**Umbrella Project**: [Chef Foundation](https://github.com/chef/chef-oss-practices/blob/master/projects/chef-foundation.md)
**Project State**: [Active](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md#active)
**Issues [Response Time Maximum](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md)**: 14 days
**Pull Request [Response Time Maximum](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md)**: 14 days
Train lets you talk to your local or remote operating systems and APIs with a unified interface.
It allows you to:
* execute commands via `run_command`
* interact with files via `file`
* identify the target operating system via `os`
* authenticate to API-based services and treat them like a platform
Train supports:
* Local execution
* SSH
* WinRM
* Docker
* Mock (for testing and debugging)
* AWS as an API
* Azure as an API
* VMware via PowerCLI
* Habitat
# Examples
## Setup
**Local**
```ruby
require 'train'
train = Train.create('local')
```
**SSH**
```ruby
require 'train'
train = Train.create('ssh',
host: '1.2.3.4', port: 22, user: 'root', key_files: '/vagrant')
```
If you don't specify the `key_files` and `password` options, SSH agent authentication will be attempted. For example:
```ruby
require 'train'
train = Train.create('ssh', host: '1.2.3.4', port: 22, user: 'root')
```
**WinRM**
```ruby
require 'train'
train = Train.create('winrm',
host: '1.2.3.4', user: 'Administrator', password: '...', ssl: true, self_signed: true)
```
**Docker**
```ruby
require 'train'
train = Train.create('docker', host: 'container_id...')
```
**AWS**
To use AWS API authentication, setup an AWS client profile to store the Access Key ID and Secret Access Key.
```ruby
require 'train'
train = Train.create('aws', region: 'us-east-2', profile: 'my-profile')
```
You may also use the standard AWS CLI environment variables, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION`.
```ruby
require 'train'
train = Train.create('aws')
```
**VMware**
```ruby
require 'train'
Train.create('vmware', viserver: '10.0.0.10', user: 'demouser', password: 'securepassword')
```
You may also use environment variables by setting `VISERVER`, `VISERVER__USERNAME`, and `VISERVER_PASSWORD`
```ruby
require 'train'
Train.create('vmware')
```
## Configuration
To get a list of available options for a plugin:
```ruby
puts Train.options('ssh')
```
This will provide all configuration options:
```ruby
{
:host => { :required => true},
:port => { :default => 22, :required => true},
:user => { :default => "root", :required => true},
:keys => { :default => nil},
:password => { :default => nil},
...
```
## Usage
```ruby
# start or reuse a connection
conn = train.connection
# run a command on Linux/Unix/Mac
puts conn.run_command('whoami').stdout
# get OS info
puts conn.os[:family]
puts conn.os[:release]
# access files
puts conn.file('/proc/version').content
# access specific API client functionality
ec2_client = train.connection.aws_client(Aws::EC2::Client)
puts ec2_client.describe_instances
# close the connection
conn.close
```
# Testing
We perform `unit`, `integration` and `windows` tests.
* `unit` tests ensure the intended behaviour of the implementation
* `integration` tests run against VMs and docker containers
* `windows` tests that run on appveyor for windows integration tests
## Mac/Linux
```
bundle exec ruby -W -Ilib:test/unit test/unit/extras/stat_test.rb
```
## Windows
```
# run windows tests
bundle exec rake test:windows
# run single tests
bundle exec ruby -I .\test\windows\ .\test\windows\local_test.rb
```
# Kudos and Contributors
Train is heavily based on the work of:
* [test-kitchen](https://github.com/test-kitchen/test-kitchen)
by [Fletcher Nichol](mailto:fnichol@nichol.ca)
and [a great community of contributors](https://github.com/test-kitchen/test-kitchen/graphs/contributors)
* [ohai](https://github.com/chef/ohai)
by Adam Jacob, Chef Software Inc.
and [a great community of contributors](https://github.com/chef/ohai/graphs/contributors)
We also want to thank [halo](https://github.com/halo) who did a great contribution by handing over the `train` gem name.
## Contributing
1. Fork it
1. Create your feature branch (git checkout -b my-new-feature)
1. Commit your changes (git commit -am 'Add some feature')
1. Push to the branch (git push origin my-new-feature)
1. Create new Pull Request
## License
| **Author:** | Dominik Richter ()
| **Author:** | Christoph Hartmann ()
| **Copyright:** | Copyright (c) 2015-2019 Chef Software Inc.
| **Copyright:** | Copyright (c) 2015 Vulcano Security GmbH.
| **License:** | Apache License, Version 2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
train-3.2.28/Rakefile 0000775 0000000 0000000 00000003660 13645125472 0014411 0 ustar 00root root 0000000 0000000 #!/usr/bin/env rake
# encoding: utf-8
require "bundler"
require "bundler/gem_helper"
require "rake/testtask"
require "chefstyle"
require "rubocop/rake_task"
Bundler::GemHelper.install_tasks name: "train"
RuboCop::RakeTask.new(:lint) do |task|
task.options << "--display-cop-names"
end
# run tests
task default: %i{test}
Rake::TestTask.new do |t|
t.libs << "test"
t.pattern = "test/unit/**/*_test.rb"
t.warning = false
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
namespace :test do
task :docker do
path = File.join(File.dirname(__FILE__), "test", "integration")
sh("sh", "-c", "cd #{path} && ruby -I ../../lib docker_test.rb tests/*")
end
task :windows do
Dir.glob("test/windows/*_test.rb").all? do |file|
sh(Gem.ruby, "-w", '-I .\test\windows', file)
end || raise("Failures")
end
task :vm do
concurrency = ENV["CONCURRENCY"] || 4
path = File.join(File.dirname(__FILE__), "test", "integration")
sh("sh", "-c", "cd #{path} && kitchen test -c #{concurrency}")
end
# Target required:
# rake "test:ssh[user@server]"
# sh -c cd /home/foobarbam/src/gems/train/test/integration \
# && target=user@server ruby -I ../../lib test_ssh.rb tests/*
# ...
# Turn debug logging back on:
# debug=1 rake "test:ssh[user@server]"
# Use a different ssh key:
# key_files=/home/foobarbam/.ssh/id_rsa2 rake "test:ssh[user@server]"
# Run with a specific test:
# test=path_block_device_test.rb rake "test:ssh[user@server]"
task :ssh, [:target] do |t, args|
path = File.join(File.dirname(__FILE__), "test", "integration")
key_files = ENV["key_files"] || File.join(ENV["HOME"], ".ssh", "id_rsa")
sh_cmd = "cd #{path} && target=#{args[:target]} key_files=#{key_files}"
sh_cmd += " debug=#{ENV["debug"]}" if ENV["debug"]
sh_cmd += " ruby -I ../../lib test_ssh.rb tests/"
sh_cmd += ENV["test"] || "*"
sh("sh", "-c", sh_cmd)
end
end
train-3.2.28/VERSION 0000664 0000000 0000000 00000000006 13645125472 0014000 0 ustar 00root root 0000000 0000000 3.2.28 train-3.2.28/contrib/ 0000775 0000000 0000000 00000000000 13645125472 0014374 5 ustar 00root root 0000000 0000000 train-3.2.28/contrib/fixup_requiretty.rb 0000664 0000000 0000000 00000002574 13645125472 0020361 0 ustar 00root root 0000000 0000000 # encoding: utf-8
# author: Stephan Renatus
#
# chef-apply script to fix sudoers configuration
#
# This script can be used to setup a user's sudoers configuration to allow for
# using non-interactive sessions. It's main use case is fixing the default
# configuration on RHEL and SEL distributions.
#
# The user name has to be provided in the env varibale "TRAIN_SUDO_USER".
# If any configuration for the user is present (user is in /etc/sudoers or
# /etc/sudoers.d/user exists), this script will do nothing
# (unless you set TRAIN_SUDO_VERY_MUCH=yes)
# FIXME
user = ENV["TRAIN_SUDO_USER"] || "todo-some-clever-default-maybe-current-user"
sudoer = "/etc/sudoers.d/#{user}"
log "Warning: a sudoers configuration for user #{user} already exists, "\
"doing nothing (override with TRAIN_SUDO_VERY_MUCH=yes)" do
only_if "test -f #{sudoer} || grep #{user} /etc/sudoers"
end
file sudoer do
content "#{user} ALL=(root) NOPASSWD:ALL\n"\
"Defaults:#{user} !requiretty\n"
mode 0600
action ENV["TRAIN_SUDO_VERY_MUCH"] == "yes" ? :create : :create_if_missing
# Do not add something here if the user is mentioned explicitly in /etc/sudoers
not_if "grep #{user} /etc/sudoers"
end
# /!\ broken files in /etc/sudoers.d/ will break sudo for ALL USERS /!\
execute "revert: delete the file if it's broken" do
command "rm #{sudoer}"
not_if "visudo -c -f #{sudoer}" # file is ok
end
train-3.2.28/docs/ 0000775 0000000 0000000 00000000000 13645125472 0013664 5 ustar 00root root 0000000 0000000 train-3.2.28/docs/plugins.md 0000664 0000000 0000000 00000015154 13645125472 0015675 0 ustar 00root root 0000000 0000000 # Train Plugins
## Introducing Plugins
Train plugins are a way to add new transports and platform detection to Train.
If you are familiar with InSpec plugins, be forewarned; the two plugin systems are not similar.
### Why Plugins?
#### Benefits of plugins
Plugins address two main needs that the Chef InSpec Engineering team (which maintains Train) encountered in 2017-2018:
* Passionate contributor parties can develop and release new Train transports at their own pace, without gating by Chef engineers.
* Reduction of dependency bloat within Train. For example, since only the AWS transport needs the aws-sdk, we can move the gem dependency into the plugin gem, and out of train itself.
#### Future of existing Transports
The Chef InSpec Engineering team currently (October 2018) plans to migrate most existing Train transports into plugins. For example, AWS, Azure, and GCP are all excellent candidates for migration to plugin status. The team commits to keeping SSH, WinRM, and Local transports in Train core. All other transports may be migrated.
In the near-term, InSpec will carry a gemspec dependency on the migrated plugins. This will continue a smooth experience for users relying on (for example) Azure.
## Managing Plugins
### Installing and Managing Train Plugins as an InSpec User
InSpec has a command-line plugin management interface, which is used for managing both InSpec plugins and Train plugins. For example, to install `train-aws`, simply run:
```bash
$ inspec plugin install train-aws
```
The management facility can install, update, and remove plugins, including their dependencies.
### Installing Train Plugins outside of InSpec
If you need a train plugin installed, and `inspec plugin` is not available to you, you can install a train plugin like any other gem. Just be sure to use the `gem` binary that comes with the application you wish to extend. For example, to add a Train Plugin to a ChefDK installation, use:
```bash
$ chef exec gem install train-something
```
### Finding Train plugins
Train plugins can be found by running:
```bash
$ inspec plugin search train-
```
If you are not an InSpec user, you may also perform a RubyGems search:
```bash
$ gem search train-
```
## Developing Train Plugins for the Train Plugin API v1
Train plugins are gems. Their names must start with 'train-'.
You can use the example plugin at [the Train github project](https://github.com/inspec/train/tree/master/examples/plugins/train-local-rot13) as a starting point.
### The Entry Point
As with any Gem library, you should create a file with the name of your plugin, which loads the remaining files you need. Some plugins place them in 1 file, but it is cleaner to place them in 4: a version file, then transport, connection and platform files.
### The Transport File
In this file, you should define a class that inherits from `Train.plugin(1)`. The class returned will be `Train::Plugins::Transport` or a descendant. This superclass provides DSL methods, abstract methods, instance variables, and accessors for you to configure your plugin.
Feedback about providing a clearer Plugin API for a future Plugin V2 API is welcome.
#### `name` DSL method
Required. Use the `name` call to register your plugin. Pass a String, which should have the 'train-' portion removed.
#### `option` DSL method
The option method is used to register new information into your transport options hash. This hash contains all the information your transport will need for its connection and runtime support. These options calls are a good place to pull in defaults or information from environment variables.
#### @options Instance Variable
This variable includes any options you passed in from the DSL method when defining a transport. It will also merge in any options passed from the URL definition for your transport (schema, host, etc).
#### `connection` abstract method
Required to be implemented. Called with a single arg which is usually ignored. You must return an instance of a class that is a descendant of `Train::Plugins::Transports::BaseConnection`. Typically you will call the constructor with the `@options`.
### Connection File
The your Connection class must inherit from `Train::Plugins::Transports::BaseConnection`. Abstract methods it should implement include:
#### initialize
Not required but is a good place to set option defaults for options that were passed with the transport URL. Example:
```Ruby
def initialize(options)
# Override for cli region from url host
# aws://region/my-profile
options[:region] = options[:host] if options.key?(:host)
super(options)
end
```
#### run_command_via_connection
If your transport is OS based and has the option to read a file you can set this method. It is expected to return a `Train::File::Remote::*` class here to be used upstream in InSpec. Currently the file resource is restricted to Unix and Windows platforms. Caching is enabled by default for this method.
#### file_via_connection
If your transport is OS based and has the option to run a command you can set this method. It is expected to return a `CommandResult` class here to be used upstream in InSpec. Currently the command resource is restricted to Unix and Windows platforms. Caching is enabled by default for this method.
#### API Access Methods
When working with API's it's often helpful to create methods to return client information or API objects. These are then accessed upstream in InSpec. Here is an example of a API method you may have:
```Ruby
def aws_client(klass)
return klass.new unless cache_enabled?(:api_call)
@cache[:api_call][klass.to_s.to_sym] ||= klass.new
end
```
This will return a class and cache the client object accordingly if caching is enabled. You can call this from a inspec resource by calling `inspec.backend.aws_client(AWS::TEST::CLASS)`.
#### platform
`platform` is called when InSpec is trying to detect the platform (OS family, etc). We recommend that you implement platform in a separate Module, and include it.
### Platform Detection
Platform detection is used if you do not specify a platform method for your transport. Currently it is only used for OS (Unix, Windows) platforms. The detection system will run a series of commands on your target to try and determine what platform it is. This information can be found here [OS Specifications](https://github.com/inspec/train/blob/master/lib/train/platforms/detect/specifications/os.rb).
When using an API or a fixed platform for your transport it's suggested you skip the detection process and specify a direct platform. Here is an example:
```Ruby
def platform
Train::Platforms.name('Aws').in_family('cloud')
force_platform!('Aws',
release: '1.2',
)
end
```
train-3.2.28/examples/ 0000775 0000000 0000000 00000000000 13645125472 0014552 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/ 0000775 0000000 0000000 00000000000 13645125472 0016233 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/train-local-rot13/ 0000775 0000000 0000000 00000000000 13645125472 0021406 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/train-local-rot13/Gemfile 0000664 0000000 0000000 00000001202 13645125472 0022674 0 ustar 00root root 0000000 0000000 # encoding: utf-8
source "https://rubygems.org"
# This is Gemfile, which is used by bundler
# to ensure a coherent set of gems is installed.
# This file lists dependencies needed when outside
# of a gem (the gemspec lists deps for gem deployment)
# Bundler should refer to the gemspec for any dependencies.
gemspec
# Remaining group is only used for development.
group :development do
gem "bundler"
gem "byebug"
gem "inspec", ">= 2.2.112" # We need InSpec for the test harness while developing.
gem "minitest"
gem "rake"
gem "rubocop", "= 0.49.1" # Need to keep in sync with main InSpec project, so config files will work
end
train-3.2.28/examples/plugins/train-local-rot13/LICENSE 0000664 0000000 0000000 00000001057 13645125472 0022416 0 ustar 00root root 0000000 0000000 Copyright 2018 Chef Software Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
train-3.2.28/examples/plugins/train-local-rot13/README.md 0000664 0000000 0000000 00000006054 13645125472 0022672 0 ustar 00root root 0000000 0000000 # Example Train Plugin - train-local-rot13
This plugin is provided as a teaching example for building a Train plugin. Train plugins allow you to connect to remote systems or APIs, so that other tools such as InSpec or Chef Workstation can talk over the connection.
train-local-rot13's functionality is simple: it acts as a local transport (targeting the local machine), but it applies the [rot13](https://en.wikipedia.org/wiki/ROT13) trivial cypher transformation on the contents of each file it reads, and on the stdout of every command it executes.
Please note that ROT13 is an incredibly weak cypher, and can be broken by most elementary school students. Do not use this plugin for security purposes.
## Relationship between InSpec and Train
Train itself has no CLI, nor a sophisticated test harness. InSpec does have such facilities, so installing Train plugins will require an InSpec installation. You do not need to use or understand InSpec.
Train plugins may be developed without an InSpec installation.
## To Install this as a User
You will need InSpec v2.3 or later.
If you just want to use this (not learn how to write a plugin), you can so by simply running:
```
$ inspec plugin install train-local-rot13
```
You can then run:
```
$ inspec detect -t local-rot13://
== Platform Details
Name: local-rot13
Families: unix, os, windows, os
Release: 0.1.0
Arch: example
$ inspec shell -t local-rot13:// -c 'command("echo hello")'
uryyb
```
## Features of This Example Kit
This example plugin is a full-fledged plugin example, with everything a real-world, industrial grade plugin would have, including:
* an implementation of a Train plugin, using the Train Plugin V1 API, including
* a Transport
* a Connection
* Platform configuration
* documentation (you are reading it now)
* tests, at the unit and functional level
* a .gemspec, for packaging and publishing it as a gem
* a Gemfile, for managing its dependencies
* a Rakefile, for running development tasks
* Rubocop linting support for using the base Train project rubocop.yml (See Rakefile)
You are encouraged to use this plugin as a starting point for real plugins.
## Development of a Plugin
[Plugin Development](https://github.com/inspec/train/blob/master/docs/plugins.md) is documented on the `train` project on GitHub. Additionally, this example
plugin has extensive comments explaining what is happening, and why.
### A Tour of the Plugin
One nice circuit of the plugin might be:
* look at the gemspec, to see what the plugin thinks it does
* look at the functional tests, to see the plugin proving it does what it says
* look at the unit tests, to see how the plugin claims it is internally structured
* look at the Rakefile, to see how to interact with the project
* look at lib/train-local-rot13.rb, the entry point which InSpec will always load if the plugin is installed
* look at lib/train-local-rot13/transport.rb, the plugin "backbone"
* look at lib/train-local-rot13/connection.rb, the plugin implementation
* look at lib/train-local-rot13/platform.rb, OS platform support declaration
train-3.2.28/examples/plugins/train-local-rot13/Rakefile 0000664 0000000 0000000 00000002666 13645125472 0023065 0 ustar 00root root 0000000 0000000 # A Rakefile defines tasks to help maintain your project.
# Rake provides several task templates that are useful.
#------------------------------------------------------------------#
# Test Runner Tasks
#------------------------------------------------------------------#
# This task template will make a task named 'test', and run
# the tests that it finds.
require "rake/testtask"
Rake::TestTask.new do |t|
t.libs.push "lib"
t.test_files = FileList[
"test/unit/*_test.rb",
"test/integration/*_test.rb",
"test/functional/*_test.rb",
]
t.verbose = true
# Ideally, we'd run tests with warnings enabled,
# but the dependent gems have many warnings. As this
# is an example, let's disable them so the testing
# experience is cleaner.
t.warning = false
end
#------------------------------------------------------------------#
# Code Style Tasks
#------------------------------------------------------------------#
require "rubocop/rake_task"
RuboCop::RakeTask.new(:lint) do |t|
# Choices of rubocop rules to enforce are deeply personal.
# Here, we set things up so that your plugin will use the Bundler-installed
# train gem's copy of the Train project's rubocop.yml file (which
# is indeed packaged with the train gem).
require "train/globals"
train_rubocop_yml = File.join(Train.src_root, ".rubocop.yml")
t.options = ["--display-cop-names", "--config", train_rubocop_yml]
end
train-3.2.28/examples/plugins/train-local-rot13/lib/ 0000775 0000000 0000000 00000000000 13645125472 0022154 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/train-local-rot13/lib/train-local-rot13.rb 0000664 0000000 0000000 00000001525 13645125472 0025657 0 ustar 00root root 0000000 0000000 # This file is known as the "entry point."
# This is the file Train will try to load if it
# thinks your plugin is needed.
# The *only* thing this file should do is setup the
# load path, then load plugin files.
# Next two lines simply add the path of the gem to the load path.
# This is not needed when being loaded as a gem; but when doing
# plugin development, you may need it. Either way, it's harmless.
libdir = File.dirname(__FILE__)
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
# It's traditional to keep your gem version in a separate file, so CI can find it easier.
require "train-local-rot13/version"
# A train plugin has three components: Transport, Connection, and Platform.
# Transport acts as the glue.
require "train-local-rot13/transport"
require "train-local-rot13/platform"
require "train-local-rot13/connection"
train-3.2.28/examples/plugins/train-local-rot13/lib/train-local-rot13/ 0000775 0000000 0000000 00000000000 13645125472 0025327 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/train-local-rot13/lib/train-local-rot13/connection.rb 0000664 0000000 0000000 00000006124 13645125472 0030016 0 ustar 00root root 0000000 0000000 # Connection definition file for an example Train plugin.
# Most of the work of a Train plugin happens in this file.
# Connections derive from Train::Plugins::Transport::BaseConnection,
# and provide a variety of services. Later generations of the plugin
# API will likely separate out these responsibilities, but for now,
# some of the responsibilities include:
# * authentication to the target
# * platform / release /family detection
# * caching
# * filesystem access
# * remote command execution
# * API execution
# * marshalling to / from JSON
# You don't have to worry about most of this.
# This allow us to inherit from Train::Plugins::Transport::BaseConnection
require "train"
# Push platform detection out to a mixin, as it tends
# to develop at a different cadence than the rest
require "train-local-rot13/platform"
# This is a support library for our file content meddling
require "train-local-rot13/file_content_rotator"
# This is a support library for our command meddling
require "mixlib/shellout"
require "ostruct"
module TrainPlugins
module LocalRot13
# You must inherit from BaseConnection.
class Connection < Train::Plugins::Transport::BaseConnection
# We've placed platform detection in a separate module; pull it in here.
include TrainPlugins::LocalRot13::Platform
def initialize(options)
# 'options' here is a hash, Symbol-keyed,
# of what Train.target_config decided to do with the URI that it was
# passed by `inspec -t` (or however the application gathered target information)
# Some plugins might use this moment to capture credentials from the URI,
# and the configure an underlying SDK accordingly.
# You might also take a moment to manipulate the options.
# Have a look at the Local, SSH, and AWS transports for ideas about what
# you can do with the options.
# Regardless, let the BaseConnection have a chance to configure itself.
super(options)
# If you need to attempt a connection to a remote system, or verify your
# credentials, now is a good time.
end
# Filesystem access.
# If your plugin is for an API, don't implement this.
# If your plugin supports reading files, you'll need to implement this.
def file_via_connection(path)
train_file = Train::File::Local::Unix.new(self, path)
# But then we wrap the return in a class that meddles with the content.
FileContentRotator.new(train_file)
end
# Command execution.
# If your plugin is for an API, don't implement this.
# If your plugin supports executing commands, you'll need to implement this.
def run_command_via_connection(cmd)
# Run the command.
run_result = Mixlib::ShellOut.new(cmd)
run_result.run_command
# Wrap the results in a structure that Train expects...
OpenStruct.new(
# And meddle with the stdout along the way.
stdout: Rot13.rotate(run_result.stdout),
stderr: run_result.stderr,
exit_status: run_result.exitstatus
)
end
end
end
end
train-3.2.28/examples/plugins/train-local-rot13/lib/train-local-rot13/file_content_rotator.rb 0000664 0000000 0000000 00000001340 13645125472 0032075 0 ustar 00root root 0000000 0000000 # Here's our helper class for the file object. This is just some
# silliness specific to the task of applying rot13 to the file content.
require "rot13"
module TrainPlugins
module LocalRot13
class FileContentRotator
# The FileContentRotator has-a Train::File
def initialize(train_file)
@train_file = train_file
end
# We implement content ourselves, rotating the contents of the file
def content
Rot13.rotate(@train_file.content)
end
# Everything else, we delegate to the Train::File object.
# This is not a safe or efficient implementation.
def method_missing(meth, *args, &block)
@train_file.send(meth, *args, &block)
end
end
end
end
train-3.2.28/examples/plugins/train-local-rot13/lib/train-local-rot13/platform.rb 0000664 0000000 0000000 00000003403 13645125472 0027500 0 ustar 00root root 0000000 0000000 # Platform definition file. This is a good place to separate out any
# logic regarding the identification of the OS or API at the far end
# of the connection.
# Abbreviate the namespace here, if you like.
module TrainPlugins::LocalRot13
# Since we're mixing in the platform detection facility into Connection,
# this has to come in as a Module.
module Platform
# The method `platform` is called when platform detection is
# about to be performed. Train core defines a sophisticated
# system for platform detection, but for most plugins, you'll
# only ever run on the special platform for which you are targeting.
def platform
# If you are declaring a new platform, you will need to tell
# Train a bit about it.
# If you were defining a cloud API, you should say you are a member
# of the cloud family.
# This plugin makes up a new platform. Train (or rather InSpec) only
# know how to read files on Windows and Un*x (MacOS is a kind of Un*x),
# so we'll say we're part of those families.
Train::Platforms.name("local-rot13").in_family("unix")
Train::Platforms.name("local-rot13").in_family("windows")
# When you know you will only ever run on your dedicated platform
# (for example, a plugin named train-aws would only run on the AWS
# API, which we report as the 'aws' platform).
# force_platform! lets you bypass platform detection.
# The options to this are not currently documented completely.
# Use release to report a version number. You might use the version
# of the plugin, or a version of an important underlying SDK, or a
# version of a remote API.
force_platform!("local-rot13", release: TrainPlugins::LocalRot13::VERSION)
end
end
end
train-3.2.28/examples/plugins/train-local-rot13/lib/train-local-rot13/transport.rb 0000664 0000000 0000000 00000002012 13645125472 0027703 0 ustar 00root root 0000000 0000000 # Train Plugins v1 are usually declared under the TrainPlugins namespace.
# Each plugin has three components: Transport, Connection, and Platform.
# We'll only define the Transport here, but we'll refer to the others.
require "train-local-rot13/connection"
module TrainPlugins
module LocalRot13
class Transport < Train.plugin(1)
name "local-rot13"
# The only thing you MUST do in a transport is a define a
# connection() method that returns a instance that is a
# subclass of BaseConnection.
# The options passed to this are undocumented and rarely used.
def connection(_instance_opts = nil)
# Typical practice is to cache the connection as an instance variable.
# Do what makes sense for your platform.
# @options here is the parsed options that the calling
# app handed to us at process invocation. See the Connection class
# for more details.
@connection ||= TrainPlugins::LocalRot13::Connection.new(@options)
end
end
end
end
train-3.2.28/examples/plugins/train-local-rot13/lib/train-local-rot13/version.rb 0000664 0000000 0000000 00000000540 13645125472 0027340 0 ustar 00root root 0000000 0000000 # This file exists simply to record the version number of the plugin.
# It is kept in a separate file, so that your gemspec can load it and
# learn the current version without loading the whole plugin. Also,
# many CI servers can update this file when "version bumping".
module TrainPlugins
module LocalRot13
VERSION = "0.1.0".freeze
end
end
train-3.2.28/examples/plugins/train-local-rot13/test/ 0000775 0000000 0000000 00000000000 13645125472 0022365 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/train-local-rot13/test/fixtures/ 0000775 0000000 0000000 00000000000 13645125472 0024236 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/train-local-rot13/test/fixtures/README.md 0000664 0000000 0000000 00000000412 13645125472 0025512 0 ustar 00root root 0000000 0000000 # Test Fixtures Area
In this directory, you would place things that you need during testing.
When writing your functional tests, you can point Train at the various test fixture files.
To use them, see the helper.rb file included in the example at test/helper.rb . train-3.2.28/examples/plugins/train-local-rot13/test/fixtures/hello 0000664 0000000 0000000 00000000005 13645125472 0025257 0 ustar 00root root 0000000 0000000 hello train-3.2.28/examples/plugins/train-local-rot13/test/functional/ 0000775 0000000 0000000 00000000000 13645125472 0024527 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/train-local-rot13/test/functional/local-rot13_test.rb 0000664 0000000 0000000 00000006466 13645125472 0030167 0 ustar 00root root 0000000 0000000 # Functional tests for the Train Local Rot13 Example Plugin.
# Functional tests are used to verify the behaviors of the plugin are as
# expected, to a user.
# For train, a "user" is a developer using your plugin to access things from
# their app. Unlike unit tests, we don't assume any knowledge of how anything
# works; we just know what it is supposed to do.
# Include our test harness
require_relative "../helper"
# Because InSpec is a Spec-style test suite, and Train has a close relationship
# to InSpec, we're going to use MiniTest::Spec here, for familiar look and
# feel. However, this isn't InSpec (or RSpec) code.
describe "train-local-rot13" do
# Our helper.rb locates this library from the Train install that
# Bundler installed for us. If we want its methods, we still must
# import it. Including it here will make it available in all child
# 'describe' blocks.
include TrainPluginFunctionalHelper
# When thinking up scenarios to test, start with the simplest.
# Then think of each major feature, and exercise them.
# Running combinations of features makes sense if it is very likely,
# or a difficult / dangerous case. You can always add more tests
# here as users find subtle problems. In fact, having a user submit
# a PR that creates a failing functional test is a great way to
# capture the reproduction case.
# Some tests through here use minitest Expectations, which attach to all
# Objects, and begin with 'must' (positive) or 'wont' (negative)
# See https://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest/Expectations.html
# LocalRot13 should do at least this:
# * Not explode when you run Train with it
# * Apply rot13 when you use Train to read a file
# * Apply rot13 when you use Train to run a command
describe "creating a train instance with this transport" do
# This is a bit of an awkward test. There is no 'wont_raise', so
# we just execute the risky code; if it breaks, the test will be
# registered as an Error.
it "should not explode on create" do
# This checks for uncaught exceptions.
Train.create("local-rot13")
# This checks for warnings (or any other output) to stdout/stderr
proc { Train.create("local-rot13") }.must_be_silent
end
it "should not explode on connect" do
# This checks for uncaught exceptions.
Train.create("local-rot13").connection
# This checks for warnings (or any other output) to stdout/stderr
proc { Train.create("local-rot13").connection }.must_be_silent
end
end
describe "reading a file" do
it "should rotate the text by 13 positions" do
conn = Train.create("local-rot13").connection
# Here, plugin_fixtures_path is provided by the TrainPluginFunctionalHelper,
# and refers to the absolute path to the test fixtures directory.
# The file 'hello' simply has the text 'hello' in it.
file_obj = conn.file(File.join(plugin_fixtures_path, "hello"))
file_obj.content.wont_include("hello")
file_obj.content.must_include("uryyb")
end
end
describe "running a command" do
it "should rotate the stdout by 13 positions" do
conn = Train.create("local-rot13").connection
file_obj = conn.run_command("echo hello")
file_obj.stdout.wont_include("hello")
file_obj.stdout.must_include("uryyb")
end
end
end
train-3.2.28/examples/plugins/train-local-rot13/test/helper.rb 0000664 0000000 0000000 00000000330 13645125472 0024165 0 ustar 00root root 0000000 0000000 # Test helper file for example Train plugins
# This file's job is to collect any libraries needed for testing, as well as provide
# any utilities to make testing a plugin easier.
require "train/plugin_test_helper"
train-3.2.28/examples/plugins/train-local-rot13/test/unit/ 0000775 0000000 0000000 00000000000 13645125472 0023344 5 ustar 00root root 0000000 0000000 train-3.2.28/examples/plugins/train-local-rot13/test/unit/connection_test.rb 0000664 0000000 0000000 00000002752 13645125472 0027075 0 ustar 00root root 0000000 0000000 # This is a unit test for the example Train plugin, LocalRot13.
# Its job is to verify that the Connection class is setup correctly.
# Include our test harness
require_relative "../helper"
# Load the class under test, the Connection definition.
require "train-local-rot13/connection"
# Because InSpec is a Spec-style test suite, we're going to use MiniTest::Spec
# here, for familiar look and feel. However, this isn't InSpec (or RSpec) code.
describe TrainPlugins::LocalRot13::Connection do
# When writing tests, you can use `let` to create variables that you
# can reference easily.
# This is a long name. Shorten it for clarity.
let(:connection_class) { TrainPlugins::LocalRot13::Connection }
# Some tests through here use minitest Expectations, which attach to all
# Objects, and begin with 'must' (positive) or 'wont' (negative)
# See https://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest/Expectations.html
it "should inherit from the Train Connection base" do
# For Class, '<' means 'is a descendant of'
(connection_class < Train::Plugins::Transport::BaseConnection).must_equal(true)
end
# Since this is a Local-type connection, we MUST implement these three.
%i{
file_via_connection
run_command_via_connection
}.each do |method_name|
it "should provide a #{method_name}() method" do
# false passed to instance_methods says 'don't use inheritance'
connection_class.instance_methods(false).must_include(method_name)
end
end
end
train-3.2.28/examples/plugins/train-local-rot13/test/unit/transport_test.rb 0000664 0000000 0000000 00000003030 13645125472 0026760 0 ustar 00root root 0000000 0000000 # This is a unit test for the example Train plugin, LocalRot13.
# Its job is to verify that the Transport class is setup correctly.
# Include our test harness
require_relative "../helper"
# Load the class under test, the Plugin definition.
require "train-local-rot13/transport"
# Because InSpec is a Spec-style test suite, we're going to use MiniTest::Spec
# here, for familiar look and feel. However, this isn't InSpec (or RSpec) code.
describe TrainPlugins::LocalRot13::Transport do
# When writing tests, you can use `let` to create variables that you
# can reference easily.
# This is a long name. Shorten it for clarity.
let(:plugin_class) { TrainPlugins::LocalRot13::Transport }
# Some tests through here use minitest Expectations, which attach to all
# Objects, and begin with 'must' (positive) or 'wont' (negative)
# See https://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest/Expectations.html
it "should be registered with the plugin registry without the train- prtefix" do
# Note that Train uses String keys here, not Symbols
Train::Plugins.registry.keys.wont_include("train-local-rot13")
Train::Plugins.registry.keys.must_include("local-rot13")
end
it "should inherit from the Train plugin base" do
# For Class, '<' means 'is a descendant of'
(plugin_class < Train.plugin(1)).must_equal(true)
end
it "should provide a connection() method" do
# false passed to instance_methods says 'don't use inheritance'
plugin_class.instance_methods(false).must_include(:connection)
end
end
train-3.2.28/examples/plugins/train-local-rot13/train-local-rot13.gemspec 0000664 0000000 0000000 00000004123 13645125472 0026126 0 ustar 00root root 0000000 0000000 # As plugins are usually packaged and distributed as a RubyGem,
# we have to provide a .gemspec file, which controls the gembuild
# and publish process. This is a fairly generic gemspec.
# It is traditional in a gemspec to dynamically load the current version
# from a file in the source tree. The next three lines make that happen.
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "train-local-rot13/version"
Gem::Specification.new do |spec|
# Importantly, all Train plugins must be prefixed with `train-`
spec.name = "train-local-rot13"
# It is polite to namespace your plugin under TrainPlugins::YourPluginInCamelCase
spec.version = TrainPlugins::LocalRot13::VERSION
spec.authors = ["Chef InSpec Team"]
spec.email = ["inspec@chef.io"]
spec.summary = "Train Plugin example, rot13's file content and command output"
spec.description = "Example for implementing a Train plugin. This simply performs the ROT13 substitution cipher on file content and command output."
spec.homepage = "https://github.com/inspec/train/tree/master/examples/plugin"
spec.license = "Apache-2.0"
# Though complicated-looking, this is pretty standard for a gemspec.
# It just filters what will actually be packaged in the gem (leaving
# out tests, etc)
spec.files = %w{
README.md train-local-rot13.gemspec Gemfile
} + Dir.glob(
"lib/**/*", File::FNM_DOTMATCH
).reject { |f| File.directory?(f) }
spec.require_paths = ["lib"]
# If you rely on any other gems, list them here with any constraints.
# This is how `inspec plugin install` is able to manage your dependencies.
# For example, perhaps you are writing a thing that talks to AWS, and you
# want to ensure you have `aws-sdk` in a certain version.
# If you only need certain gems during development or testing, list
# them in Gemfile, not here.
# Do not list inspec as a dependency of the train plugin.
# All plugins should mention train, > 1.4
spec.add_dependency "train", "~> 1.4"
spec.add_dependency "rot13", "~> 0.1"
end
train-3.2.28/lib/ 0000775 0000000 0000000 00000000000 13645125472 0013502 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train.rb 0000664 0000000 0000000 00000016052 13645125472 0015150 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Dominik Richter ()
require_relative "train/version"
require_relative "train/options"
require_relative "train/plugins"
require_relative "train/errors"
require_relative "train/platforms"
require "addressable/uri"
module Train
# Create a new transport instance, with the plugin indicated by the
# given name.
#
# @param [String] name of the plugin
# @param [Array] *args list of arguments for the plugin
# @return [Transport] instance of the new transport or nil
def self.create(name, *args)
cls = load_transport(name)
cls.new(*args) unless cls.nil?
end
# Retrieve the configuration options of a transport plugin.
#
# @param [String] name of the plugin
# @return [Hash] map of default options
def self.options(name)
cls = load_transport(name)
cls.default_options unless cls.nil?
end
# Load the transport plugin indicated by name. If the plugin is not
# yet found in the plugin registry, it will be attempted to load from
# `train/transports/plugin_name`.
#
# @param [String] name of the plugin
# @return [Train::Transport] the transport plugin
def self.load_transport(transport_name)
transport_name = transport_name.to_s
transport_class = Train::Plugins.registry[transport_name]
return transport_class unless transport_class.nil?
# Try to load the transport name from the core transports...
require "train/transports/" + transport_name
Train::Plugins.registry[transport_name]
rescue LoadError => _
begin
# If it's not in the core transports, try loading from a train plugin gem.
gem_name = "train-" + transport_name
require gem_name
return Train::Plugins.registry[transport_name]
# rubocop: disable Lint/HandleExceptions
rescue LoadError => _
# rubocop: enable Lint/HandleExceptions
# Intentionally empty rescue - we're handling it below anyway
end
ex = Train::PluginLoadError.new("Can't find train plugin #{transport_name}. Please install it first.")
ex.transport_name = transport_name
raise ex
end
# Legacy code to unpack a series of items from an incoming Hash
# Inspec::Config.unpack_train_credentials now handles this in most cases that InSpec needs
# If you need to unpack a URI, use unpack_target_from_uri
# TODO: deprecate; can't issue a warning because train doesn't have a logger until the connection is setup (See base_connection.rb)
def self.target_config(config = nil)
conf = config.dup
# Symbolize keys
conf.keys.each do |key|
unless key.is_a? Symbol
conf[key.to_sym] = conf.delete(key)
end
end
group_keys_and_keyfiles(conf) # TODO: move logic into SSH plugin
return conf if conf[:target].to_s.empty?
unpack_target_from_uri(conf[:target], conf).merge(conf)
end
# Given a string that looks like a URI, unpack connection credentials.
# The name of the desired transport is always taken from the 'scheme' slot of the URI;
# the remaining portion of the URI is parsed as if it were an HTTP URL, and then
# the URL components are stored in the credentials hash. It is up to the transport
# to interpret the fields in a sensible way for that transport.
# New transport authors are encouraged to use transport://credset format (see
# inspec/inspec/issues/3661) rather than inventing a new field mapping.
def self.unpack_target_from_uri(uri_string, opts = {}) # rubocop: disable Metrics/AbcSize
creds = {}
return creds if uri_string.empty?
# split up the target's host/scheme configuration
uri = parse_uri(uri_string)
unless uri.host.nil? && uri.scheme.nil?
creds[:backend] ||= uri.scheme
creds[:host] ||= uri.hostname
creds[:port] ||= uri.port
creds[:user] ||= uri.user
creds[:path] ||= uri.path
creds[:password] ||=
if opts[:www_form_encoded_password] && !uri.password.nil?
Addressable::URI.unencode_component(uri.password)
else
uri.password
end
end
# ensure path is nil, if its empty; e.g. required to reset defaults for winrm # TODO: move logic into winrm plugin
creds[:path] = nil if !creds[:path].nil? && creds[:path].to_s.empty?
# compact! is available in ruby 2.4+
# TODO: rewrite next line using compact! once we drop support for ruby 2.3
creds = creds.delete_if { |_, value| value.nil? }
# return the updated config
creds
end
# Parse a URI. Supports empty URI's with paths, e.g. `mock://`
#
# @param string [string] URI string, e.g. `schema://domain.com`
# @return [Addressable::URI] parsed URI object
def self.parse_uri(string)
u = Addressable::URI.parse(string)
# A use-case we want to catch is parsing empty URIs with a schema
# e.g. mock://. To do this, we match it manually and fake the hostname
if u.scheme && (u.host.nil? || u.host.empty?) && u.path.empty?
case string
when %r{^([a-z]+)://$}
string += "dummy"
when /^([a-z]+):$/
string += "//dummy"
end
u = Addressable::URI.parse(string)
u.host = nil
end
u
rescue Addressable::URI::InvalidURIError => e
raise Train::UserError, e
end
private_class_method :parse_uri
# Examine the given credential information, and if all is well,
# return the transport name.
# TODO: this actually does no validation of the credential options whatsoever
def self.validate_backend(credentials, default_transport_name = "local")
return default_transport_name if credentials.nil?
transport_name = credentials[:backend]
# TODO: Determine if it is ever possible (or supported) for transport_name to be 'localhost'
# TODO: After inspec/inspec/pull/3750 is merged, should be able to remove nil from the list
if credentials[:sudo] && [nil, "local", "localhost"].include?(transport_name)
raise Train::UserError, "Sudo is only valid when running against a remote host. "\
"To run this locally with elevated privileges, run the command with `sudo ...`."
end
return transport_name unless transport_name.nil?
unless credentials[:target].nil?
# We should not get here, because if target_uri unpacking was successful,
# it would have set credentials[:backend]
raise Train::UserError, "Cannot determine backend from target "\
"configuration #{credentials[:target]}. Valid example: ssh://192.168.0.1"
end
unless credentials[:host].nil?
raise Train::UserError, "Host configured, but no backend was provided. Please "\
"specify how you want to connect. Valid example: ssh://192.168.0.1"
end
credentials[:backend] = default_transport_name
end
def self.group_keys_and_keyfiles(conf)
# in case the user specified a key-file, register it that way
# we will clear the list of keys and put keys and key_files separately
keys_mixed = conf[:keys]
return if keys_mixed.nil?
conf[:key_files] = []
conf[:keys] = []
keys_mixed.each do |key|
if !key.nil? && File.file?(key)
conf[:key_files].push(key)
else
conf[:keys].push(key)
end
end
end
end
train-3.2.28/lib/train/ 0000775 0000000 0000000 00000000000 13645125472 0014617 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/errors.rb 0000664 0000000 0000000 00000002363 13645125472 0016464 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Fletcher Nichol ()
# Author:: Dominik Richter ()
# Author:: Christoph Hartmann ()
#
# Copyright (C) 2013, Fletcher Nichol
#
# Licensed under the Apache License, Version 2.0 (the "License");
module Train
# Base exception for any exception explicitly raised by the Train library.
class Error < ::StandardError
attr_reader :reason
def initialize(message = "", reason = :not_provided)
super(message)
@reason = reason
end
end
# Base exception class for all exceptions that are caused by user input
# errors.
class UserError < Error; end
# We could not load a plugin, because of a user error
class PluginLoadError < UserError
attr_accessor :transport_name
end
# Base exception class for all exceptions that are caused by incorrect use
# of an API.
class ClientError < Error; end
# Base exception class for all exceptions that are caused by other failures
# in the transport layer.
class TransportError < Error; end
# Exception for when no platform can be detected.
class PlatformDetectionFailed < Error; end
# Exception for when a invalid cache type is passed.
class UnknownCacheType < Error; end
end
train-3.2.28/lib/train/extras.rb 0000664 0000000 0000000 00000000444 13645125472 0016454 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Dominik Richter ()
module Train::Extras
require_relative "extras/command_wrapper"
require_relative "extras/stat"
CommandResult = Struct.new(:stdout, :stderr, :exit_status)
LoginCommand = Struct.new(:command, :arguments)
end
train-3.2.28/lib/train/extras/ 0000775 0000000 0000000 00000000000 13645125472 0016125 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/extras/command_wrapper.rb 0000664 0000000 0000000 00000013074 13645125472 0021635 0 ustar 00root root 0000000 0000000 # encoding: utf-8
# author: Dominik Richter
# author: Christoph Hartmann
require "base64"
require_relative "../errors"
module Train::Extras
# Define the interface of all command wrappers.
class CommandWrapperBase
# Verify that the command wrapper is initialized properly and working.
#
# @return [Any] verification result, nil if all went well, otherwise a message
def verify
raise Train::ClientError, "#{self.class} does not implement #verify()"
end
# Wrap a command and return the augmented command which can be executed.
#
# @param [Strin] command that will be wrapper
# @return [String] result of wrapping the command
def run(_command)
raise Train::ClientError, "#{self.class} does not implement #run(command)"
end
end
# Wrap linux commands and add functionality like sudo.
class LinuxCommand < CommandWrapperBase
Train::Options.attach(self)
option :shell, default: false
option :shell_options, default: nil
option :shell_command, default: nil
option :sudo, default: false
option :sudo_options, default: nil
option :sudo_password, default: nil
option :sudo_command, default: nil
option :user
attr_reader :backend
def initialize(backend, options)
@backend = backend
validate_options(options)
@shell = options[:shell]
@shell_options = options[:shell_options] # e.g. '--login'
@shell_command = options[:shell_command] # e.g. '/bin/sh'
@sudo = options[:sudo]
@sudo_options = options[:sudo_options]
@sudo_password = options[:sudo_password]
@sudo_command = options[:sudo_command]
@user = options[:user]
end
# (see CommandWrapperBase::verify)
def verify
# Do nothing, successfully. Don't use "true", that's not available on Windows.
cmd = if @sudo
# Wrap it up. It needs /dev/null on the outside to disable stdin
"sh -c '(#{run("echo")}) < /dev/null'"
else
run("echo")
end
# rubocop:disable Style/BlockDelimiters
res = @backend.with_sudo_pty {
@backend.run_command(cmd)
}
return nil if res.exit_status == 0
rawerr = "#{res.stdout} #{res.stderr}".strip
case rawerr
when /Sorry, try again/
["Wrong sudo password.", :bad_sudo_password]
when /sudo: no tty present and no askpass program specified/
["Sudo requires a password, please configure it.", :sudo_password_required]
when /sudo: command not found/
["Can't find sudo command. Please either install and "\
"configure it on the target or deactivate sudo.", :sudo_command_not_found]
when /sudo: sorry, you must have a tty to run sudo/
["Sudo requires a TTY. Please see the README on how to configure "\
"sudo to allow for non-interactive usage.", :sudo_no_tty]
else
[rawerr, nil]
end
end
def verify!
msg, reason = verify
return nil unless msg
raise Train::UserError.new("Sudo failed: #{msg}", reason)
end
# (see CommandWrapperBase::run)
def run(command)
shell_wrap(sudo_wrap(command))
end
def self.active?(options)
options.is_a?(Hash) && (
options[:sudo] ||
options[:shell]
)
end
private
# wrap the cmd in a sudo command
def sudo_wrap(cmd)
return cmd unless @sudo
return cmd if @user == "root"
res = (@sudo_command || "sudo") + " "
if @sudo_password
str = safe_string(@sudo_password + "\n")
res = "#{str} | #{res}-S "
end
res << "#{@sudo_options} " if @sudo_options
res + cmd
end
# wrap the cmd in a subshell allowing for options to
# passed to the subshell
def shell_wrap(cmd)
return cmd unless @shell
shell = @shell_command || "$SHELL"
options = " #{@shell_options}" if @shell_options
"#{safe_string(cmd)} | #{shell}#{options}"
end
# encapsulates encoding the string into a safe form, and decoding for use.
# @return [String] A command line snippet that can be used as part of a pipeline.
def safe_string(str)
b64str = Base64.strict_encode64(str)
"echo #{b64str} | base64 --decode"
end
end
# Wrap windows commands.
class WindowsCommand < CommandWrapperBase
Train::Options.attach(self)
option :shell_command, default: nil
def initialize(backend, options)
@backend = backend
validate_options(options)
@shell_command = options[:shell_command] # e.g. 'powershell'
end
# (see CommandWrapperBase::run)
def run(command)
powershell_wrap(command)
end
private
# Wrap the cmd in an encoded command to allow pipes and quotes
def powershell_wrap(cmd)
shell = @shell_command || "powershell"
# Prevent progress stream from leaking into stderr
script = "$ProgressPreference='SilentlyContinue';" + cmd
# Encode script so PowerShell can use it
script = script.encode("UTF-16LE", "UTF-8")
base64_script = Base64.strict_encode64(script)
cmd = "#{shell} -NoProfile -EncodedCommand #{base64_script}"
cmd
end
end
class CommandWrapper
include_options LinuxCommand
include_options WindowsCommand
def self.load(transport, options)
if transport.platform.unix?
return nil unless LinuxCommand.active?(options)
res = LinuxCommand.new(transport, options)
res.verify!
res
elsif transport.platform.windows?
WindowsCommand.new(transport, options)
end
end
end
end
train-3.2.28/lib/train/extras/stat.rb 0000664 0000000 0000000 00000010600 13645125472 0017422 0 ustar 00root root 0000000 0000000 # encoding: utf-8
# author: Dominik Richter
# author: Christoph Hartmann
module Train::Extras
class Stat
TYPES = {
socket: 00140000,
symlink: 00120000,
file: 00100000,
block_device: 00060000,
directory: 00040000,
character_device: 00020000,
pipe: 00010000,
}.freeze
def self.find_type(mode)
res = TYPES.find { |_, mask| mask & mode == mask }
res.nil? ? :unknown : res[0]
end
def self.stat(shell_escaped_path, backend, follow_symlink)
# use perl scripts for aix, solaris 10 and hpux
if backend.os.aix? || (backend.os.solaris? && backend.os[:release].to_i < 11) || backend.os.hpux?
return aix_stat(shell_escaped_path, backend, follow_symlink)
end
return bsd_stat(shell_escaped_path, backend, follow_symlink) if backend.os.bsd?
# linux,solaris 11 and esx will use standard linux stats
return linux_stat(shell_escaped_path, backend, follow_symlink) if backend.os.unix? || backend.os.esx?
# all other cases we don't handle
# TODO: print an error if we get here, as it shouldn't be invoked
# on non-unix
{}
end
def self.linux_stat(shell_escaped_path, backend, follow_symlink)
lstat = follow_symlink ? " -L" : ""
format = (backend.os.esx? || backend.os[:name] == "alpine" || backend.os[:name] == "yocto") ? "-c" : "--printf"
res = backend.run_command("stat#{lstat} #{shell_escaped_path} 2>/dev/null #{format} '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'")
# ignore the exit_code: it is != 0 if selinux labels are not supported
# on the system.
fields = res.stdout.split("\n")
return {} if fields.length != 9
tmask = fields[1].to_i(16)
selinux = fields[8]
## selinux security context string not available on esxi
selinux = nil if (selinux == "?") || (selinux == "(null)") || (selinux == "C")
{
type: find_type(tmask),
mode: tmask & 07777,
owner: fields[2],
uid: fields[3].to_i,
group: fields[4],
gid: fields[5].to_i,
mtime: fields[7].to_i,
size: fields[0].to_i,
selinux_label: selinux,
}
end
def self.bsd_stat(shell_escaped_path, backend, follow_symlink)
# From stat man page on FreeBSD:
# z The size of file in bytes (st_size).
# p File type and permissions (st_mode).
# u, g User ID and group ID of file's owner (st_uid, st_gid).
# a, m, c, B
# The time file was last accessed or modified, or when the
# inode was last changed, or the birth time of the inode
# (st_atime, st_mtime, st_ctime, st_birthtime).
#
# The special output specifier S may be used to indicate that the
# output, if applicable, should be in string format. May be used
# in combination with:
# ...
# gu Display group or user name.
lstat = follow_symlink ? " -L" : ""
res = backend.run_command(
"stat#{lstat} -f '%z\n%p\n%Su\n%u\n%Sg\n%g\n%a\n%m' "\
"#{shell_escaped_path}"
)
return {} if res.exit_status != 0
fields = res.stdout.split("\n")
return {} if fields.length != 8
tmask = fields[1].to_i(8)
{
type: find_type(tmask),
mode: tmask & 07777,
owner: fields[2],
uid: fields[3].to_i,
group: fields[4],
gid: fields[5].to_i,
mtime: fields[7].to_i,
size: fields[0].to_i,
selinux_label: fields[8],
}
end
def self.aix_stat(shell_escaped_path, backend, follow_symlink)
# Perl here b/c it is default on AIX
lstat = follow_symlink ? "lstat" : "stat"
stat_cmd = <<-EOP
perl -e '
@a = #{lstat}(shift) or exit 2;
$u = getpwuid($a[4]);
$g = getgrgid($a[5]);
printf("0%o\\n%s\\n%d\\n%s\\n%d\\n%d\\n%d\\n", $a[2], $u, $a[4], $g, $a[5], $a[9], $a[7])
' #{shell_escaped_path}
EOP
res = backend.run_command(stat_cmd)
return {} if res.exit_status != 0
fields = res.stdout.split("\n")
return {} if fields.length != 7
tmask = fields[0].to_i(8)
{
type: find_type(tmask),
mode: tmask & 07777,
owner: fields[1],
uid: fields[2].to_i,
group: fields[3],
gid: fields[4].to_i,
mtime: fields[5].to_i,
size: fields[6].to_i,
selinux_label: nil,
}
end
end
end
train-3.2.28/lib/train/file.rb 0000664 0000000 0000000 00000013264 13645125472 0016071 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# author: Christoph Hartmann
# author: Dominik Richter
require_relative "file/local"
require_relative "file/remote"
require_relative "extras/stat"
module Train
class File # rubocop:disable Metrics/ClassLength
def initialize(backend, path, follow_symlink = true)
@backend = backend
@path = path || ""
@follow_symlink = follow_symlink
sanitize_filename(path)
end
# This method gets override by particular os class.
def sanitize_filename(_path)
nil
end
# interface methods: these fields should be implemented by every
# backend File
DATA_FIELDS = %w{
exist? mode owner group uid gid content mtime size selinux_label path
}.freeze
DATA_FIELDS.each do |m|
next if m == "path"
define_method m do
raise NotImplementedError, "File must implement the #{m}() method."
end
end
def to_json
res = Hash[DATA_FIELDS.map { |x| [x, method(x).call] }]
# additional fields provided as input
res["type"] = type
res["follow_symlink"] = @follow_symlink
res
end
def type
:unknown
end
def source
if @follow_symlink
self.class.new(@backend, @path, false)
else
self
end
end
def source_path
@path
end
# product_version is primarily used by Windows operating systems only and will be overwritten
# in Windows-related classes. Since this field is returned for all file objects, the acceptable
# default value is nil
def product_version
nil
end
# file_version is primarily used by Windows operating systems only and will be overwritten
# in Windows-related classes. Since this field is returned for all file objects, the acceptable
# default value is nil
def file_version
nil
end
def version?(version)
(product_version == version) ||
(file_version == version)
end
def block_device?
type.to_s == "block_device"
end
def character_device?
type.to_s == "character_device"
end
def pipe?
type.to_s == "pipe"
end
def file?
type.to_s == "file"
end
def socket?
type.to_s == "socket"
end
def directory?
type.to_s == "directory"
end
def symlink?
source.type.to_s == "symlink"
end
def owned_by?(sth)
owner == sth
end
def path
if symlink? && @follow_symlink
link_path
else
@path
end
end
# if the OS-specific file class supports inquirying as to whether the
# file/device is mounted, the #mounted method should return a command
# object whose stdout will not be nil if indeed the device is mounted.
#
# if the OS-specific file class does not support checking for mount
# status, the method should not be implemented and this method will
# return false.
def mounted?
return false unless respond_to?(:mounted)
!mounted.nil? && !mounted.stdout.nil? && !mounted.stdout.empty?
end
def md5sum
# Skip processing rest of method if fallback method is selected
return perform_checksum_ruby(:md5) if defined?(@ruby_checksum_fallback)
checksum = if @backend.os.family == "windows"
perform_checksum_windows(:md5)
else
@md5_command ||= case @backend.os.family
when "darwin"
"md5 -r"
when "solaris"
"digest -a md5"
else
"md5sum"
end
perform_checksum_unix(@md5_command)
end
checksum || perform_checksum_ruby(:md5)
end
def sha256sum
# Skip processing rest of method if fallback method is selected
return perform_checksum_ruby(:sha256) if defined?(@ruby_checksum_fallback)
checksum = if @backend.os.family == "windows"
perform_checksum_windows(:sha256)
else
@sha256_command ||= case @backend.os.family
when "darwin", "hpux", "qnx"
"shasum -a 256"
when "solaris"
"digest -a sha256"
else
"sha256sum"
end
perform_checksum_unix(@sha256_command)
end
checksum || perform_checksum_ruby(:sha256)
end
private
def perform_checksum_unix(cmd)
res = @backend.run_command("#{cmd} #{@path}")
res.stdout.split(" ").first if res.exit_status == 0
end
def perform_checksum_windows(method)
cmd = "CertUtil -hashfile #{@path} #{method.to_s.upcase}"
res = @backend.run_command(cmd)
res.stdout.split("\r\n")[1].tr(" ", "") if res.exit_status == 0
end
# This pulls the content of the file to the machine running Train and uses
# Digest to perform the checksum. This is less efficient than using remote
# system binaries and can lead to incorrect results due to encoding.
def perform_checksum_ruby(method)
# This is used to skip attempting other checksum methods. If this is set
# then we know all other methods have failed.
@ruby_checksum_fallback = true
case method
when :md5
res = Digest::MD5.new
when :sha256
res = Digest::SHA256.new
end
res.update(content)
res.hexdigest
rescue TypeError => _
nil
end
end
end
train-3.2.28/lib/train/file/ 0000775 0000000 0000000 00000000000 13645125472 0015536 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/file/local.rb 0000664 0000000 0000000 00000003262 13645125472 0017160 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train
class File
class Local < Train::File
%w{
exist? file? socket? directory? symlink? pipe? size basename
}.each do |m|
define_method m.to_sym do
::File.method(m.to_sym).call(@path)
end
end
def content
@content ||= ::File.read(@path, encoding: "UTF-8")
rescue StandardError => _
nil
end
def link_path
return nil unless symlink?
begin
@link_path ||= ::File.realpath(@path)
rescue Errno::ELOOP => _
# Leave it blank on symbolic loop, same as readlink
@link_path = ""
end
end
def shallow_link_path
return nil unless symlink?
@link_path ||= ::File.readlink(@path)
end
def block_device?
::File.blockdev?(@path)
end
def character_device?
::File.chardev?(@path)
end
def type
case ::File.ftype(@path)
when "blockSpecial"
:block_device
when "characterSpecial"
:character_device
when "link"
:symlink
when "fifo"
:pipe
else
::File.ftype(@path).to_sym
end
end
%w{
mode owner group uid gid mtime selinux_label
}.each do |field|
define_method field.to_sym do
stat[field.to_sym]
end
end
def mode?(sth)
mode == sth
end
def linked_to?(dst)
link_path == dst
end
end
end
end
# subclass requires are loaded after Train::File::Local is defined
# to avoid superclass mismatch errors
require_relative "local/unix"
require_relative "local/windows"
train-3.2.28/lib/train/file/local/ 0000775 0000000 0000000 00000000000 13645125472 0016630 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/file/local/unix.rb 0000664 0000000 0000000 00000004252 13645125472 0020143 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require "shellwords"
require_relative "../../extras/stat"
module Train
class File
class Local
class Unix < Train::File::Local
def sanitize_filename(path)
@spath = Shellwords.escape(path) || @path
end
def stat
return @stat if defined?(@stat)
begin
file_stat =
if @follow_symlink
::File.stat(@path)
else
::File.lstat(@path)
end
rescue StandardError => _err
return @stat = {}
end
@stat = {
type: Train::Extras::Stat.find_type(file_stat.mode),
mode: file_stat.mode & 07777,
mtime: file_stat.mtime.to_i,
size: file_stat.size,
owner: pw_username(file_stat.uid),
uid: file_stat.uid,
group: pw_groupname(file_stat.gid),
gid: file_stat.gid,
}
lstat = @follow_symlink ? " -L" : ""
res = @backend.run_command("stat#{lstat} #{@spath} 2>/dev/null --printf '%C'")
if res.exit_status == 0 && !res.stdout.empty? && res.stdout != "?"
@stat[:selinux_label] = res.stdout.strip
end
@stat
end
def mounted
@mounted ||=
@backend.run_command("mount | grep -- ' on #{@path} '")
end
def grouped_into?(sth)
group == sth
end
def unix_mode_mask(owner, type)
o = UNIX_MODE_OWNERS[owner.to_sym]
return nil if o.nil?
t = UNIX_MODE_TYPES[type.to_sym]
return nil if t.nil?
t & o
end
private
def pw_username(uid)
Etc.getpwuid(uid).name
rescue ArgumentError => _
nil
end
def pw_groupname(gid)
Etc.getgrgid(gid).name
rescue ArgumentError => _
nil
end
UNIX_MODE_OWNERS = {
all: 00777,
owner: 00700,
group: 00070,
other: 00007,
}.freeze
UNIX_MODE_TYPES = {
r: 00444,
w: 00222,
x: 00111,
}.freeze
end
end
end
end
train-3.2.28/lib/train/file/local/windows.rb 0000664 0000000 0000000 00000003435 13645125472 0020654 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train
class File
class Local
class Windows < Train::File::Local
# Ensures we do not use invalid characters for file names
# @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#naming_conventions
def sanitize_filename(path)
return if path.nil?
# we do not filter :, backslash and forward slash, since they are part of the path
@spath = path.gsub(/[<>"|?*]/, "")
end
def product_version
@product_version ||= @backend.run_command(
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"#{@spath}\").ProductVersion"
).stdout.chomp
end
def file_version
@file_version ||= @backend.run_command(
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"#{@spath}\").FileVersion"
).stdout.chomp
end
def owner
owner = @backend.run_command(
"Get-Acl \"#{@spath}\" | select -expand Owner"
).stdout.strip
return if owner.empty?
owner
end
def stat
return @stat if defined?(@stat)
begin
file_stat =
if @follow_symlink
::File.stat(@path)
else
::File.lstat(@path)
end
rescue StandardError => _err
return @stat = {}
end
@stat = {
type: type,
mode: file_stat.mode,
mtime: file_stat.mtime.to_i,
size: file_stat.size,
owner: owner,
uid: file_stat.uid,
group: nil,
gid: file_stat.gid,
selinux_label: nil,
}
@stat
end
end
end
end
end
train-3.2.28/lib/train/file/remote.rb 0000664 0000000 0000000 00000001732 13645125472 0017361 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train
class File
class Remote < Train::File
def basename(suffix = nil, sep = "/")
raise "Not yet supported: Suffix in file.basename" unless suffix.nil?
@basename ||= detect_filename(path, sep || "/")
end
def stat
return @stat if defined?(@stat)
@stat = Train::Extras::Stat.stat(@spath, @backend, @follow_symlink)
end
# helper methods provided to any implementing class
private
def detect_filename(path, sep)
idx = path.rindex(sep)
return path if idx.nil?
idx += 1
return detect_filename(path[0..-2], sep) if idx == path.length
path[idx..-1]
end
end
end
end
# subclass requires are loaded after Train::File::Remote is defined
# to avoid superclass mismatch errors
require_relative "remote/aix"
require_relative "remote/linux"
require_relative "remote/qnx"
require_relative "remote/unix"
require_relative "remote/windows"
train-3.2.28/lib/train/file/remote/ 0000775 0000000 0000000 00000000000 13645125472 0017031 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/file/remote/aix.rb 0000664 0000000 0000000 00000001210 13645125472 0020131 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require_relative "unix"
module Train
class File
class Remote
class Aix < Train::File::Remote::Unix
def link_path
return nil unless symlink?
@link_path ||=
@backend.run_command("perl -e 'print readlink shift' #{@spath}").stdout.chomp
end
def shallow_link_path
return nil unless symlink?
@shallow_link_path ||=
@backend.run_command("perl -e 'print readlink shift' #{@spath}").stdout.chomp
end
def mounted
@mounted ||= @backend.run_command("lsfs -c #{@spath}")
end
end
end
end
end
train-3.2.28/lib/train/file/remote/linux.rb 0000664 0000000 0000000 00000000706 13645125472 0020520 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require_relative "unix"
module Train
class File
class Remote
class Linux < Train::File::Remote::Unix
def content
return @content if defined?(@content)
@content = @backend.run_command("cat #{@spath} || echo -n").stdout
return @content unless @content.empty?
@content = nil if directory? || size.nil? || (size > 0)
@content
end
end
end
end
end
train-3.2.28/lib/train/file/remote/qnx.rb 0000664 0000000 0000000 00000002001 13645125472 0020155 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# author: Christoph Hartmann
# author: Dominik Richter
require_relative "unix"
module Train
class File
class Remote
class Qnx < Train::File::Remote::Unix
def content
cat = "cat"
cat = "/proc/boot/cat" if @backend.os[:release].to_i >= 7
@content ||= case
when !exist?
nil
else
@backend.run_command("#{cat} #{@spath}").stdout || ""
end
end
def type
if @backend.run_command("file #{@spath}").stdout.include?("directory")
:directory
else
:file
end
end
%w{
mode owner group uid gid mtime size selinux_label link_path mounted stat
}.each do |field|
define_method field.to_sym do
raise NotImplementedError, "QNX does not implement the #{field}() method yet."
end
end
end
end
end
end
train-3.2.28/lib/train/file/remote/unix.rb 0000664 0000000 0000000 00000005055 13645125472 0020346 0 ustar 00root root 0000000 0000000 require "shellwords"
module Train
class File
class Remote
class Unix < Train::File::Remote
def sanitize_filename(path)
@spath = Shellwords.escape(path) || @path
end
def content
@content ||=
if !exist? || directory?
nil
elsif size.nil? || size == 0
""
else
@backend.run_command("cat #{@spath}").stdout || ""
end
end
def exist?
@exist ||= begin
f = @follow_symlink ? "" : " || test -L #{@spath}"
@backend.run_command("test -e #{@spath}" + f)
.exit_status == 0
end
end
def mounted
@mounted ||=
@backend.run_command("mount | grep -- ' on #{@path} '")
end
%w{
type mode owner group uid gid mtime size selinux_label
}.each do |field|
define_method field.to_sym do
stat[field.to_sym]
end
end
def mode?(sth)
mode == sth
end
def grouped_into?(sth)
group == sth
end
def linked_to?(dst)
link_path == dst
end
def link_path
symlink? ? path : nil
end
def shallow_link_path
return nil unless symlink?
@shallow_link_path ||=
@backend.run_command("readlink #{@spath}").stdout.chomp
end
def unix_mode_mask(owner, type)
o = UNIX_MODE_OWNERS[owner.to_sym]
return nil if o.nil?
t = UNIX_MODE_TYPES[type.to_sym]
return nil if t.nil?
t & o
end
def path
return @path unless @follow_symlink && symlink?
@link_path ||= read_target_path
end
private
# Returns full path of a symlink target(real dest) or '' on symlink loop
def read_target_path
full_path = @backend.run_command("readlink -n #{@spath} -f").stdout
# Needed for some OSes like OSX that returns relative path
# when the link and target are in the same directory
if !full_path.start_with?("/") && full_path != ""
full_path = ::File.expand_path("../#{full_path}", @spath)
end
full_path
end
UNIX_MODE_OWNERS = {
all: 00777,
owner: 00700,
group: 00070,
other: 00007,
}.freeze
UNIX_MODE_TYPES = {
r: 00444,
w: 00222,
x: 00111,
}.freeze
end
end
end
end
train-3.2.28/lib/train/file/remote/windows.rb 0000664 0000000 0000000 00000005457 13645125472 0021063 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train
class File
class Remote
class Windows < Train::File::Remote
attr_reader :path
# Ensures we do not use invalid characters for file names
# @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#naming_conventions
def sanitize_filename(path)
return if path.nil?
# we do not filter :, backslash and forward slash, since they are part of the path
@spath = path.gsub(/[<>"|?*]/, "")
end
def basename(suffix = nil, sep = '\\')
super(suffix, sep)
end
def content
return @content if defined?(@content)
@content = @backend.run_command("Get-Content(\"#{@spath}\") | Out-String").stdout
return @content unless @content.empty?
@content = nil if directory? # or size.nil? or size > 0
@content
end
def exist?
return @exist if defined?(@exist)
@exist = @backend.run_command(
"(Test-Path -Path \"#{@spath}\").ToString()"
).stdout.chomp == "True"
end
def owner
owner = @backend.run_command(
"Get-Acl \"#{@spath}\" | select -expand Owner"
).stdout.strip
return if owner.empty?
owner
end
def type
if attributes.include?("Archive") && !attributes.include?("Directory")
return :file
elsif attributes.include?("ReparsePoint")
return :symlink
elsif attributes.include?("Directory")
return :directory
end
:unknown
end
def size
if file?
@backend.run_command("((Get-Item '#{@spath}').Length)").stdout.strip.to_i
end
end
def product_version
@product_version ||= @backend.run_command(
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"#{@spath}\").ProductVersion"
).stdout.chomp
end
def file_version
@file_version ||= @backend.run_command(
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"#{@spath}\").FileVersion"
).stdout.chomp
end
%w{
mode group uid gid mtime selinux_label
}.each do |field|
define_method field.to_sym do
nil
end
end
def link_path
nil
end
def shallow_link_path
nil
end
def mounted
nil
end
private
def attributes
return @attributes if defined?(@attributes)
@attributes = @backend.run_command(
"(Get-ItemProperty -Path \"#{@spath}\").attributes.ToString()"
).stdout.chomp.split(/\s*,\s*/)
end
end
end
end
end
train-3.2.28/lib/train/globals.rb 0000664 0000000 0000000 00000000147 13645125472 0016571 0 ustar 00root root 0000000 0000000 module Train
def self.src_root
File.expand_path(File.join(__FILE__, "..", "..", ".."))
end
end
train-3.2.28/lib/train/options.rb 0000664 0000000 0000000 00000004306 13645125472 0016642 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Dominik Richter ()
# Author:: Christoph Hartmann ()
module Train
module Options
def self.attach(target)
target.class.method(:include).call(ClassOptions)
target.method(:include).call(InstanceOptions)
end
module ClassOptions
def option(name, conf = nil, &block)
d = conf || {}
unless d.is_a? Hash
raise Train::ClientError,
"The transport plugin #{self} declared an option #{name} "\
"and didn't provide a valid configuration hash."
end
if !conf.nil? && !conf[:default].nil? && block_given?
raise Train::ClientError,
"The transport plugin #{self} declared an option #{name} "\
"with both a default value and block. Only use one of these."
end
d[:default] = block if block_given?
default_options[name] = d
end
def default_options
@default_options = {} unless defined? @default_options
@default_options
end
def include_options(other)
unless other.respond_to?(:default_options)
raise "Trying to include options from module #{other.inspect}, "\
"which doesn't seem to support options."
end
default_options.merge!(other.default_options)
end
end
module InstanceOptions
# @return [Hash] options, which created this Transport
attr_reader :options
def default_options
self.class.default_options
end
def merge_options(base, opts)
res = base.merge(opts || {})
default_options.each do |field, hm|
next unless res[field].nil? && hm.key?(:default)
default = hm[:default]
if default.is_a? Proc
res[field] = default.call(res)
else
res[field] = default
end
end
res
end
def validate_options(opts)
default_options.each do |field, hm|
if opts[field].nil? && hm[:required]
raise Train::ClientError,
"You must provide a value for #{field.to_s.inspect}."
end
end
opts
end
end
end
end
train-3.2.28/lib/train/platforms.rb 0000664 0000000 0000000 00000005372 13645125472 0017162 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require_relative "platforms/common"
require_relative "platforms/detect"
require_relative "platforms/detect/scanner"
require_relative "platforms/detect/specifications/os"
require_relative "platforms/detect/specifications/api"
require_relative "platforms/detect/uuid"
require_relative "platforms/family"
require_relative "platforms/platform"
module Train::Platforms
# Retrieve the current platform list
#
# @return [Hash] map with platform names and their objects
def self.list
@list ||= {}
end
# Retrieve the current family list
#
# @return [Hash] map with family names and their objects
def self.families
@families ||= {}
end
# Clear all platform settings. Only used for testing.
def self.__reset
@list = {}
@families = {}
end
# Create or update a platform
#
# @return Train::Platform
def self.name(name, condition = {})
# TODO: refactor this against family. They're stupidly similar
# Check the list to see if one is already created
plat = list[name]
unless plat.nil?
# Pass the condition incase we are adding a family relationship
plat.condition = condition unless condition.nil?
return plat
end
Train::Platforms::Platform.new(name, condition)
end
# Create or update a family
#
# @return Train::Platforms::Family
def self.family(name, condition = {})
# Check the families to see if one is already created
family = families[name]
unless family.nil?
# Pass the condition incase we are adding a family relationship
family.condition = condition unless condition.nil?
return family
end
Train::Platforms::Family.new(name, condition)
end
# Find the families or top level platforms
#
# @return [Hash] with top level family and platforms
def self.top_platforms
empty_list = list.select { |_key, value| value.families.empty? }
empty_fams = families.select { |_key, value| value.families.empty? }
empty_list.merge empty_fams
end
# List all platforms and families in a readable output
def self.list_all
top_platforms = self.top_platforms
top_platforms.each_value do |platform|
puts platform.title
print_children(platform) if defined?(platform.children)
end
end
def self.print_children(parent, pad = 2)
parent.children.each do |key, value|
obj = key
puts "#{" " * pad}-> #{obj.title}#{value unless value.empty?}"
print_children(obj, pad + 2) if defined?(obj.children) && !obj.children.nil?
end
end
def self.export
export = []
list.each do |name, platform|
platform.find_family_hierarchy
export << {
name: name,
families: platform.family_hierarchy,
}
end
export.sort_by { |platform| platform[:name] }
end
end
train-3.2.28/lib/train/platforms/ 0000775 0000000 0000000 00000000000 13645125472 0016626 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/platforms/common.rb 0000664 0000000 0000000 00000001611 13645125472 0020442 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Platforms
module Common
# Add a family connection. This will create a family
# if it does not exist and add a child relationship.
def in_family(family)
if self.class == Train::Platforms::Family && @name == family
raise "Unable to add family #{@name} to itself: '#{@name}.in_family(#{family})'"
end
# add family to the family list
family = Train::Platforms.family(family)
family.children[self] = @condition
@families[family] = @condition
@condition = nil
self
end
def detect(&block)
@detect = block if block
# TODO: detect shouldn't be a setter and getter at the same time
@detect ||= ->(_) { false }
end
def to_s
be = backend ? backend.backend_type : "unknown"
"%s:%s:%s" % [self.class, be, name]
end
def inspect
to_s
end
end
end
train-3.2.28/lib/train/platforms/detect.rb 0000664 0000000 0000000 00000000407 13645125472 0020424 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Platforms
module Detect
# Main detect method to scan all platforms for a match
#
# @return Train::Platform instance or error if none found
def self.scan(backend)
Scanner.new(backend).scan
end
end
end
train-3.2.28/lib/train/platforms/detect/ 0000775 0000000 0000000 00000000000 13645125472 0020076 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/platforms/detect/helpers/ 0000775 0000000 0000000 00000000000 13645125472 0021540 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/platforms/detect/helpers/os_common.rb 0000664 0000000 0000000 00000012217 13645125472 0024061 0 ustar 00root root 0000000 0000000 require_relative "os_linux"
require_relative "os_windows"
require "rbconfig"
module Train::Platforms::Detect::Helpers
module OSCommon
include Train::Platforms::Detect::Helpers::Linux
include Train::Platforms::Detect::Helpers::Windows
def ruby_host_os(regex)
::RbConfig::CONFIG["host_os"] =~ regex
end
def winrm?
backend_name == "TrainPlugins::WinRM::Connection"
end
def backend_name
@backend.class.name
end
def unix_file_contents(path)
# keep a log of files incase multiple checks call the same one
return @files[path] if @files.key?(path)
res = @backend.run_command("test -f #{path} && cat #{path}")
# ignore files that can't be read
@files[path] = res.exit_status == 0 ? res.stdout : nil
@files[path]
end
def unix_file_exist?(path)
@backend.run_command("test -f #{path}").exit_status == 0
end
def command_output(cmd)
res = @backend.run_command(cmd).stdout
# When you try to execute command using ssh connction as root user and you have provided ssh user identity file
# it gives standard output to login as authorised user other than root. To show this standard ouput as an error
# to user we are matching the string of stdout and raising the error here so that user gets exact information.
if @backend.class.to_s == "Train::Transports::SSH::Connection" && res =~ /Please login as the user/
raise Train::UserError, "SSH failed: #{res}"
end
res.strip! unless res.nil?
res
end
def unix_uname_s
return @uname[:s] if @uname.key?(:s)
@uname[:s] = command_output("uname -s")
end
def unix_uname_r
return @uname[:r] if @uname.key?(:r)
@uname[:r] = command_output("uname -r")
end
def unix_uname_m
return @uname[:m] if @uname.key?(:m)
@uname[:m] = command_output("uname -m")
end
def brocade_version
return @cache[:brocade] if @cache.key?(:brocade)
res = command_output("version")
m = res.match(/^Fabric OS:\s+v(\S+)$/)
@cache[:brocade] = m && { version: m[1], type: "fos" }
end
def cisco_show_version
return @cache[:cisco] if @cache.key?(:cisco)
res = command_output("show version")
m = res.match(/Cisco IOS Software, [^,]+? \(([^,]+?)\), Version (\d+\.\d+)/)
unless m.nil?
return @cache[:cisco] = { version: m[2], model: m[1], type: "ios" }
end
m = res.match(/Cisco IOS Software, IOS-XE Software, [^,]+? \(([^,]+?)\), Version (\d+\.\d+\.\d+[A-Z]*)/)
unless m.nil?
return @cache[:cisco] = { version: m[2], model: m[1], type: "ios-xe" }
end
m = res.match(/Cisco Nexus Operating System \(NX-OS\) Software/)
unless m.nil?
v = res[/^\s*system:\s+version (\d+\.\d+)/, 1]
return @cache[:cisco] = { version: v, type: "nexus" }
end
@cache[:cisco] = nil
end
def unix_uuid
(unix_uuid_from_chef ||
unix_uuid_from_machine_file ||
uuid_from_command ||
raise(Train::TransportError, "Cannot find a UUID for your node."))
end
def unix_uuid_from_chef
file = @backend.file("/var/chef/cache/data_collector_metadata.json")
if file.exist? && file.size != 0
json = ::JSON.parse(file.content)
return json["node_uuid"] if json["node_uuid"]
end
end
def unix_uuid_from_machine_file
# require 'pry';binding.pry
%W{
/etc/chef/chef_guid
#{ENV["HOME"]}/.chef/chef_guid
/etc/machine-id
/var/lib/dbus/machine-id
/var/db/dbus/machine-id
}.each do |path|
file = @backend.file(path)
next unless file.exist? && file.size != 0
return file.content.chomp if path =~ /guid/
return uuid_from_string(file.content.chomp)
end
nil
end
# This takes a command from the platform detect block to run.
# We expect the command to return a unique identifier which
# we turn into a UUID.
def uuid_from_command
return unless @platform[:uuid_command]
result = @backend.run_command(@platform[:uuid_command])
uuid_from_string(result.stdout.chomp) if result.exit_status == 0 && !result.stdout.empty?
end
# This hashes the passed string into SHA1.
# Then it downgrades the 160bit SHA1 to a 128bit
# then we format it as a valid UUIDv5.
def uuid_from_string(string)
hash = Digest::SHA1.new
hash.update(string)
ary = hash.digest.unpack("NnnnnN")
ary[2] = (ary[2] & 0x0FFF) | (5 << 12)
ary[3] = (ary[3] & 0x3FFF) | 0x8000
# rubocop:disable Style/FormatString
"%08x-%04x-%04x-%04x-%04x%08x" % ary
end
def json_cmd(cmd)
cmd = @backend.run_command(cmd)
if cmd.exit_status == 0 && !cmd.stdout.empty?
require "json"
eos_ver = JSON.parse(cmd.stdout)
@platform[:release] = eos_ver["version"]
@platform[:arch] = eos_ver["architecture"]
true
end
rescue JSON::ParserError
nil
end
def set_from_uname
@platform[:name] = unix_uname_s.lines.first.chomp
@platform[:release] = unix_uname_r.lines.first.chomp
true
end
end
end
train-3.2.28/lib/train/platforms/detect/helpers/os_linux.rb 0000664 0000000 0000000 00000004541 13645125472 0023731 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Platforms::Detect::Helpers
module Linux
def redhatish_platform(conf)
conf =~ /^red hat/i ? "redhat" : /(\w+)/i.match(conf)[1].downcase
end
def redhatish_version(conf)
case conf
when /rawhide/i
/((\d+) \(Rawhide\))/i.match(conf)[1].downcase
when /Amazon Linux/i
/([\d\.]+)/.match(conf)[1]
when /derived from .*linux|amazon/i
/Linux ((\d+|\.)+)/i.match(conf)[1]
else
/release ([\d\.]+)/.match(conf)[1]
end
end
def redhatish(path)
if (raw = unix_file_contents(path))
@platform[:release] = redhatish_version(raw)
true
end
end
def linux_os_release
data = unix_file_contents("/etc/os-release")
return if data.nil?
os_info = parse_os_release_info(data)
cisco_info_file = os_info["CISCO_RELEASE_INFO"]
if cisco_info_file
os_info.merge!(parse_os_release_info(unix_file_contents(cisco_info_file)))
end
os_info
end
def parse_os_release_info(raw)
return {} if raw.nil?
raw.lines.each_with_object({}) do |line, memo|
line.strip!
next if line.nil? || line.empty?
next if line.start_with?("#")
key, value = line.split("=", 2)
memo[key] = value.gsub(/\A"|"\Z/, "") unless value.nil? || value.empty?
end
end
def lsb_config(content)
id = /^DISTRIB_ID=["']?(.+?)["']?$/.match(content)
release = /^DISTRIB_RELEASE=["']?(.+?)["']?$/.match(content)
codename = /^DISTRIB_CODENAME=["']?(.+?)["']?$/.match(content)
{
id: id.nil? ? nil : id[1],
release: release.nil? ? nil : release[1],
codename: codename.nil? ? nil : codename[1],
}
end
def lsb_release(content)
id = /^Distributor ID:\s+(.+)$/.match(content)
release = /^Release:\s+(.+)$/.match(content)
codename = /^Codename:\s+(.+)$/.match(content)
{
id: id.nil? ? nil : id[1],
release: release.nil? ? nil : release[1],
codename: codename.nil? ? nil : codename[1],
}
end
def read_linux_lsb
return @lsb unless @lsb.empty?
if !(raw = unix_file_contents("/etc/lsb-release")).nil?
@lsb = lsb_config(raw)
elsif !(raw = unix_file_contents("/usr/bin/lsb-release")).nil?
@lsb = lsb_release(raw)
end
end
end
end
train-3.2.28/lib/train/platforms/detect/helpers/os_windows.rb 0000664 0000000 0000000 00000011312 13645125472 0024256 0 ustar 00root root 0000000 0000000 module Train::Platforms::Detect::Helpers
module Windows
def detect_windows
check_cmd || check_powershell
end
def check_cmd
# try to detect windows, use cmd.exe to also support Microsoft OpenSSH
res = @backend.run_command("cmd.exe /c ver")
return false if (res.exit_status != 0) || res.stdout.empty?
# if the ver contains `Windows`, we know its a Windows system
version = res.stdout.strip
return false unless version.downcase =~ /windows/
@platform[:family] = "windows"
# try to extract release from eg. `Microsoft Windows [Version 6.3.9600]`
release = /\[(?.*)\]/.match(version)
if release[:name]
# release is 6.3.9600 now
@platform[:release] = release[:name].downcase.gsub("version", "").strip
# fallback, if we are not able to extract the name from wmic later
@platform[:name] = "Windows #{@platform[:release]}"
end
read_wmic
true
end
def check_powershell
command = @backend.run_command(
"Get-WmiObject Win32_OperatingSystem | Select Caption,Version | ConvertTo-Json"
)
return false if (command.exit_status != 0) || command.stdout.empty?
payload = JSON.parse(command.stdout)
@platform[:family] = "windows"
@platform[:release] = payload["Version"]
@platform[:name] = payload["Caption"]
read_wmic
true
end
def local_windows?
@backend.class.to_s == "Train::Transports::Local::Connection" &&
ruby_host_os(/mswin|mingw32|windows/)
end
# reads os name and version from wmic
# @see https://msdn.microsoft.com/en-us/library/bb742610.aspx#EEAA
# Thanks to Matt Wrock (https://github.com/mwrock) for this hint
def read_wmic
res = @backend.run_command("wmic os get * /format:list")
if res.exit_status == 0
sys_info = {}
res.stdout.lines.each do |line|
m = /^\s*([^=]*?)\s*=\s*(.*?)\s*$/.match(line)
sys_info[m[1].to_sym] = m[2] unless m.nil? || m[1].nil?
end
@platform[:release] = sys_info[:Version]
# additional info on windows
@platform[:build] = sys_info[:BuildNumber]
@platform[:name] = sys_info[:Caption]
@platform[:name] = @platform[:name].gsub("Microsoft", "").strip unless @platform[:name].empty?
@platform[:arch] = read_wmic_cpu
end
end
# `OSArchitecture` from `read_wmic` does not match a normal standard
# For example, `x86_64` shows as `64-bit`
def read_wmic_cpu
res = @backend.run_command("wmic cpu get architecture /format:list")
if res.exit_status == 0
sys_info = {}
res.stdout.lines.each do |line|
m = /^\s*([^=]*?)\s*=\s*(.*?)\s*$/.match(line)
sys_info[m[1].to_sym] = m[2] unless m.nil? || m[1].nil?
end
end
# This converts `wmic os get architecture` output to a normal standard
# https://msdn.microsoft.com/en-us/library/aa394373(VS.85).aspx
arch_map = {
0 => "i386",
1 => "mips",
2 => "alpha",
3 => "powerpc",
5 => "arm",
6 => "ia64",
9 => "x86_64",
}
# The value of `wmic cpu get architecture` is always a number between 0-9
arch_number = sys_info[:Architecture].to_i
arch_map[arch_number]
end
# This method scans the target os for a unique uuid to use
def windows_uuid
uuid = windows_uuid_from_chef
uuid = windows_uuid_from_machine_file if uuid.nil?
uuid = windows_uuid_from_wmic if uuid.nil?
uuid = windows_uuid_from_registry if uuid.nil?
raise Train::TransportError, "Cannot find a UUID for your node." if uuid.nil?
uuid
end
def windows_uuid_from_machine_file
%W{
#{ENV["SYSTEMDRIVE"]}\\chef\\chef_guid
#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}\\.chef\\chef_guid
}.each do |path|
file = @backend.file(path)
return file.content.chomp if file.exist? && file.size != 0
end
nil
end
def windows_uuid_from_chef
file = @backend.file("#{ENV["SYSTEMDRIVE"]}\\chef\\cache\\data_collector_metadata.json")
return if !file.exist? || file.size == 0
json = JSON.parse(file.content)
json["node_uuid"] if json["node_uuid"]
end
def windows_uuid_from_wmic
result = @backend.run_command("wmic csproduct get UUID")
return unless result.exit_status == 0
result.stdout.split("\r\n")[-1].strip
end
def windows_uuid_from_registry
cmd = '(Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" -Name "MachineGuid")."MachineGuid"'
result = @backend.run_command(cmd)
return unless result.exit_status == 0
result.stdout.chomp
end
end
end
train-3.2.28/lib/train/platforms/detect/scanner.rb 0000664 0000000 0000000 00000004407 13645125472 0022061 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require_relative "helpers/os_common"
module Train::Platforms::Detect
class Scanner
include Train::Platforms::Detect::Helpers::OSCommon
def initialize(backend)
@backend = backend
@platform = {}
@family_hierarchy = []
# detect cache variables
@files = {}
@uname = {}
@lsb = {}
@cache = {}
end
# Main detect method to scan all platforms for a match
#
# @return Train::Platform instance or error if none found
def scan
# start with the platform/families who have no families (the top levels)
top = Train::Platforms.top_platforms
top.each do |_name, plat|
# we are doing a instance_eval here to make sure we have the proper
# context with all the detect helper methods
next unless instance_eval(&plat.detect)
# if we have a match start looking at the children
plat_result = scan_children(plat)
next if plat_result.nil?
# return platform to backend
@family_hierarchy << plat.name
return get_platform(plat_result)
end
raise Train::PlatformDetectionFailed, "Sorry, we are unable to detect your platform"
end
def scan_children(parent)
parent.children.each do |plat, condition|
next unless instance_eval(&plat.detect)
if plat.class == Train::Platforms::Platform
return plat if condition.empty? || check_condition(condition)
elsif plat.class == Train::Platforms::Family
plat = scan_family_children(plat)
return plat unless plat.nil?
end
end
nil
end
def scan_family_children(plat)
child_result = scan_children(plat) unless plat.children.nil?
return if child_result.nil?
@family_hierarchy << plat.name
child_result
end
def check_condition(condition)
condition.each do |k, v|
op, expected = v.strip.split(" ")
op = "==" if op == "="
return false if @platform[k].nil? || !instance_eval("'#{@platform[k]}' #{op} '#{expected}'")
end
true
end
def get_platform(plat)
plat.backend = @backend
plat.platform = @platform
plat.add_platform_methods
plat.family_hierarchy = @family_hierarchy
plat
end
end
end
train-3.2.28/lib/train/platforms/detect/specifications/ 0000775 0000000 0000000 00000000000 13645125472 0023101 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/platforms/detect/specifications/api.rb 0000664 0000000 0000000 00000000736 13645125472 0024205 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Platforms::Detect::Specifications
class Api
def self.load
plat = Train::Platforms
plat.family("api")
plat.family("cloud").in_family("api")
plat.name("aws").in_family("cloud")
plat.name("azure").in_family("cloud")
plat.name("gcp").in_family("cloud")
plat.name("vmware").in_family("cloud")
plat.family("iaas").in_family("api")
plat.name("oneview").in_family("iaas")
end
end
end
train-3.2.28/lib/train/platforms/detect/specifications/os.rb 0000664 0000000 0000000 00000040406 13645125472 0024053 0 ustar 00root root 0000000 0000000 # rubocop:disable Style/ParenthesesAroundCondition
module Train::Platforms::Detect::Specifications
class OS
def self.load
load_toplevel
load_windows
load_unix
load_other
end
def self.load_toplevel
plat.family("os").detect { true }
end
def self.load_windows
declare_category("windows", "os") do
(winrm? ||
local_windows? ||
detect_windows) # ssh
end
declare_family("windows", "windows") do
detect_windows
end
end
def self.load_unix
declare_category("unix", "os") do
# we want to catch a special case here where cisco commands
# don't return an exit status and still print to stdout
uname = unix_uname_s
unless (uname.empty? ||
uname.start_with?("Line has invalid autocommand") ||
uname.start_with?("The command you have entered"))
@platform[:arch] = unix_uname_m
true
end
end
load_linux
load_other_unix
load_bsd
end
def self.load_linux
declare_category("linux", "unix") do
unix_uname_s =~ /linux/i
end
declare_category("debian", "linux") do
unix_file_exist?("/etc/debian_version")
end
declare_lsb("ubuntu", "Ubuntu Linux", "debian", /ubuntu/i)
declare_lsb("linuxmint", "LinuxMint", "debian", /linuxmint/i)
declare_instance("kali", "Kali Linux", "debian") do
l_o_r = linux_os_release
if l_o_r && l_o_r["ID"] == "kali"
@platform[:release] = l_o_r["VERSION"]
true
end
end
declare_instance("raspbian", "Raspbian Linux", "debian") do
rel = linux_os_release
if (rel && rel["NAME"] =~ /raspbian/i) ||
unix_file_exist?("/usr/bin/raspi-config")
@platform[:release] = unix_file_contents("/etc/debian_version").chomp
true
end
end
declare_instance("debian", "Debian Linux", "debian") do
# if we get this far we have to be some type of debian
@platform[:release] = unix_file_contents("/etc/debian_version").chomp
true
end
declare_category("fedora", "linux") do
rel = linux_os_release
rel && rel["NAME"] =~ /fedora/i
end
declare_instance("fedora", "Fedora", "fedora") do
@platform[:release] = linux_os_release["VERSION_ID"]
true
end
# must come before redhat as it uses fedora under the hood
plat.family("arista_eos").title("Arista EOS Family").in_family("linux")
.detect do
true
end
declare_instance("arista_eos_bash", "Arista EOS Bash Shell", "arista_eos") do
# this checks for the arista bash shell
if unix_file_exist?("/usr/bin/FastCli")
# TODO: no tests
json_cmd('FastCli -p 15 -c "show version | json"')
end
end
declare_category("redhat", "linux") do
true
end
declare_instance("centos", "Centos Linux", "redhat") do
lsb = read_linux_lsb
if lsb && lsb[:id] =~ /centos/i
# TODO: no tests
@platform[:release] = lsb[:release]
true
elsif (rel = linux_os_release) && rel["NAME"] =~ /centos/i
@platform[:release] = redhatish_version(unix_file_contents("/etc/redhat-release"))
true
end
end
declare_instance("oracle", "Oracle Linux", "redhat") do
(redhatish("/etc/oracle-release") ||
redhatish("/etc/enterprise-release"))
end
declare_lsb("scientific", "Scientific Linux", "redhat", /scientific/i)
declare_lsb("xenserver", "Xenserer Linux", "redhat", /xenserver/i)
declare_instance("parallels-release", "Parallels Linux", "redhat") do
if (raw = unix_file_contents("/etc/parallels-release"))
@platform[:name] = redhatish_platform(raw)
@platform[:release] = raw[/(\d\.\d\.\d)/, 1]
# TODO: no tests
true
end
end
declare_instance("wrlinux", "Wind River Linux", "redhat") do
rel = linux_os_release
if rel && rel["ID_LIKE"] =~ /wrlinux/i
@platform[:release] = rel["VERSION"]
true
end
end
declare_lsb_or_content("amazon", "Amazon Linux", "redhat", "/etc/system-release", /amazon/i)
declare_lsb_or_content("cloudlinux", "CloudLinux", "redhat", "/etc/redhat-release", /cloudlinux/i)
# keep redhat at the end as a fallback for anything with a redhat-release
declare_lsb_or_content("redhat", "Red Hat Linux", "redhat", "/etc/redhat-release", /redhat/i, /./)
declare_category("suse", "linux") do
rel = linux_os_release
if rel && rel["ID_LIKE"] =~ /suse/i
@platform[:release] = rel["VERSION"]
true
elsif (suse = unix_file_contents("/etc/SuSE-release"))
# https://rubular.com/r/UKaYWolCYFMfp1
version = suse.scan(/VERSION = (\d+)\nPATCHLEVEL = (\d+)/).flatten.join(".")
# https://rubular.com/r/b5PN3hZDxa5amV
version = suse[/VERSION\s?=\s?"?([\d\.]{2,})"?/, 1] if version == ""
@platform[:release] = version
true
end
end
declare_os_or_file("opensuse", "OpenSUSE Linux", "suse", "/etc/SuSE-release", /^opensuse/i)
declare_os_or_file("suse", "Suse Linux", "suse", "/etc/SuSE-release", /^sles/i, /suse/i)
declare_path_and_uname("arch", "Arch Linux", "linux", "/etc/arch-release")
declare_file_content("slackware", "Slackware Linux", "linux", "/etc/slackware-version")
declare_file_content("gentoo", "Gentoo Linux", "linux", "/etc/gentoo-release")
declare_path_and_uname("exherbo", "Exherbo Linux", "linux", "/etc/exherbo-release")
# TODO: try using declare_path
declare_instance("alpine", "Alpine Linux", "linux") do
if (raw = unix_file_contents("/etc/alpine-release"))
@platform[:release] = raw.strip
# TODO: no tests
true
end
end
declare_instance("coreos", "CoreOS Linux", "linux") do
if unix_file_exist?("/etc/coreos/update.conf")
lsb = read_linux_lsb
@platform[:release] = lsb[:release]
true
end
end
declare_category("yocto", "linux") do
# /etc/issue isn't specific to yocto, but it's the only way to detect
# the platform because there are no other identifying files
issue = unix_file_contents("/etc/issue")
issue && issue.match?(/Poky|balenaOS/)
end
declare_instance("yocto", "Yocto Project Linux", "yocto") do
issue = unix_file_contents("/etc/issue")
if issue.match?("Poky")
# assuming the Poky version is preferred over the /etc/version build
@platform[:release] = issue[/\d+(\.\d+)+/]
true
end
end
declare_instance("balenaos", "balenaOS Linux", "yocto") do
# balenaOS does have the /etc/os-release file
issue = unix_file_contents("/etc/issue")
if issue.match?("balenaOS") && linux_os_release["NAME"] =~ /balenaos/i
@platform[:release] = linux_os_release["META_BALENA_VERSION"]
true
end
end
# brocade family detected here if device responds to 'uname' command,
# happens when logging in as root
plat.family("brocade").title("Brocade Family").in_family("linux")
.detect do
# TODO: no tests
brocade_version
end
# this should always be last in the linux family list
declare_instance("linux", "Generic Linux", "linux") do
true
end
end
def self.load_other_unix
declare_instance("openvms", "OpenVMS", "unix") do
if unix_uname_s =~ /unrecognized command verb/i
# TODO: no tests
cmd = @backend.run_command("show system/noprocess")
if cmd.exit_status == 0 && !cmd.stdout.empty?
# TODO: no tests
@platform[:name] = cmd.stdout.downcase.split(" ")[0]
cmd = @backend.run_command('write sys$output f$getsyi("VERSION")')
@platform[:release] = cmd.stdout.downcase.split("\n")[1][1..-1]
cmd = @backend.run_command('write sys$output f$getsyi("ARCH_NAME")')
@platform[:arch] = cmd.stdout.downcase.split("\n")[1]
true
end
end
end
declare_category("aix", "unix") do
unix_uname_s =~ /aix/i
end
declare_instance("aix", "Aix", "aix") do
out = @backend.run_command("uname -rvp").stdout
if out =~ /(\d+)\s+(\d+)\s+(.*)/
# TODO: no tests
@platform[:release] = "#{$2}.#{$1}"
@platform[:arch] = "#{$3}"
end
true
end
declare_category("solaris", "unix") do
if unix_uname_s =~ /sunos/i
# TODO: no tests
@platform[:release] = $1 if unix_uname_r =~ /^5\.(\d+)$/
arch = @backend.run_command("uname -p")
@platform[:arch] = arch.stdout.chomp if arch.exit_status == 0
true
end
end
# TODO: these regexps are probably needlessly wasteful
declare_path_regexp("smartos", "SmartOS", "solaris", "/etc/release", /^.*(SmartOS).*$/)
declare_path("omnios", "Omnios", "solaris", "/etc/release", /^\s*OmniOS.*r(\d+).*$/)
declare_path("openindiana", "Openindiana", "solaris", "/etc/release", /^\s*OpenIndiana.*oi_(\d+).*$/)
declare_path("opensolaris", "Open Solaris", "solaris", "/etc/release", /^\s*OpenSolaris.*snv_(\d+).*$/)
# TODO: these regexps are probably needlessly wasteful
declare_path_regexp("nexentacore", "Nexentacore", "solaris", "/etc/release", /^\s*(NexentaCore)\s.*$/)
declare_instance("solaris", "Solaris", "solaris") do
rel = unix_file_contents("/etc/release")
if rel =~ /Oracle Solaris (\d+)/
@platform[:release] = $1
# TODO: no tests
true
elsif rel =~ /^\s*(Solaris)\s.*$/
# TODO: no tests
true
end
# TODO: no tests
# must be some unknown solaris
true
end
declare_category("hpux", "unix") do
unix_uname_s =~ /hp-ux/i
end
declare_instance("hpux", "Hpux", "hpux") do
@platform[:release] = unix_uname_r.lines[0].chomp
# TODO: no tests
true
end
declare_category("qnx", "unix") do
unix_uname_s =~ /qnx/i
end
declare_instance("qnx", "QNX", "qnx") do
# TODO: refactor these uname patterns
@platform[:name] = unix_uname_s.lines[0].chomp.downcase
@platform[:release] = unix_uname_r.lines[0].chomp
@platform[:arch] = unix_uname_m
true
end
end
def self.load_bsd
declare_category("bsd", "unix") do
# we need a better way to determine this family
# for now we are going to just try each platform
true
end
declare_category("darwin", "bsd") do
# rubocop:disable Layout/ExtraSpacing
# rubocop:disable Layout/SpaceAroundOperators
if unix_uname_s =~ /darwin/i
@platform[:release] ||= unix_uname_r.lines[0].chomp
@platform[:arch] = unix_uname_m
cmd = @backend.run_command("sw_vers -buildVersion")
@platform[:build] = cmd.stdout.chomp if cmd.exit_status == 0
true
end
# rubocop:enable Layout/ExtraSpacing
# rubocop:enable Layout/SpaceAroundOperators
end
declare_instance("mac_os_x", "macOS X", "darwin") do
cmd = unix_file_contents("/System/Library/CoreServices/SystemVersion.plist")
@platform[:uuid_command] = "system_profiler SPHardwareDataType | awk '/UUID/ { print $3; }'"
cmd =~ /Mac OS X/i
end
declare_instance("darwin", "Darwin", "darwin") do
# must be some other type of darwin
@platform[:name] = unix_uname_s.lines[0].chomp
true
end
declare_bsd("freebsd", "Freebsd", "bsd", /freebsd/i)
declare_bsd("openbsd", "Openbsd", "bsd", /openbsd/i)
declare_bsd("netbsd", "Netbsd", "bsd", /netbsd/i)
end
def self.load_other
plat.family("arista_eos").title("Arista EOS Family").in_family("os")
.detect do
true
end
declare_instance("arista_eos", "Arista EOS", "arista_eos") do
json_cmd("show version | json")
end
plat.family("esx").title("ESXi Family").in_family("os")
.detect do
unix_uname_s =~ /vmkernel/i
end
plat.name("vmkernel").in_family("esx")
.detect do
# TODO: no tests
set_from_uname
end
plat.family("cisco").title("Cisco Family").in_family("os")
.detect do
cisco_show_version
end
declare_cisco("cisco_ios", "Cisco IOS", "cisco", :cisco_show_version, "ios")
declare_cisco("cisco_ios_xe", "Cisco IOS XE", "cisco", :cisco_show_version, "ios-xe")
declare_cisco("cisco_nexus", "Cisco Nexus", "cisco", :cisco_show_version, "nexus", "show version | include Processor")
plat.family("brocade").title("Brocade Family").in_family("os")
.detect do
brocade_version
end
declare_cisco("brocade_fos", "Brocade FOS", "brocade", :brocade_version, "fos")
end
######################################################################
# Helpers (keep these sorted)
def self.plat
Train::Platforms
end
def self.declare_category(family, parent, &block)
plat.family(family).in_family(parent).detect(&block)
end
def self.declare_family(name, title = nil, family, &block)
thingy = plat.name(name).in_family(family)
thingy.title(title) if title
thingy.detect(&block)
end
def self.declare_instance(name, title, family, &block)
plat.name(name).title(title).in_family(family).detect(&block)
end
def self.declare_bsd(name, title, family, regexp)
declare_instance(name, title, family) do
# TODO: no tests
set_from_uname if unix_uname_s =~ regexp
end
end
def self.declare_cisco(name, title, family, detect, type, uuid = nil)
declare_instance(name, title, family) do
v = send(detect)
next unless v[:type] == type
@platform[:release] = v[:version]
@platform[:arch] = nil
@platform[:uuid_command] = uuid if uuid
true
end
end
def self.declare_file_content(name, title, family, path)
declare_instance(name, title, family) do
if (raw = unix_file_contents(path))
# TODO: no tests
@platform[:release] = raw.scan(/[\d.]+/).join
true
end
end
end
def self.declare_lsb(name, title, family, regexp)
declare_lsb_or_content(name, title, family, nil, regexp)
end
def self.declare_lsb_or_content(name, title, family, path, regexp1, regexp2 = regexp1)
declare_instance(name, title, family) do
lsb = read_linux_lsb
if lsb && lsb[:id] =~ regexp1
@platform[:release] = lsb[:release]
true
elsif path && (raw = unix_file_contents(path)) =~ regexp2
@platform[:name] = redhatish_platform(raw)
@platform[:release] = redhatish_version(raw)
true
end
end
end
def self.declare_os_or_file(name, title, family, path, regexp1, regexp2 = regexp1)
declare_instance(name, title, family) do
rel = linux_os_release
(rel && rel["NAME"] =~ regexp1) ||
unix_file_contents(path) =~ regexp2
end
end
def self.declare_path(name, title, family, path, regexp)
declare_instance(name, title, family) do
rel = unix_file_contents(path)
if rel =~ regexp
# TODO: no tests
@platform[:release] = $1
true
end
end
end
def self.declare_path_and_uname(name, title, family, path)
declare_instance(name, title, family) do
if unix_file_exist?(path)
# Because this is a rolling release distribution,
# use the kernel release, ex. 4.1.6-1-ARCH
# TODO: unix_uname_r.lines[0].chomp ? -- no tests for /etc/exherbo-release or /etc/arch-release
@platform[:release] = unix_uname_r
true
end
end
end
def self.declare_path_regexp(name, title, family, path, regexp)
declare_instance(name, title, family) do
# TODO: no tests
regexp =~ unix_file_contents(path)
end
end
end
end
train-3.2.28/lib/train/platforms/detect/uuid.rb 0000664 0000000 0000000 00000001605 13645125472 0021373 0 ustar 00root root 0000000 0000000 require "digest/sha1"
require "securerandom"
require "json"
module Train::Platforms::Detect
class UUID
include Train::Platforms::Detect::Helpers::OSCommon
def initialize(platform)
@platform = platform
@backend = @platform.backend
end
def find_or_create_uuid
# for api transports uuid is defined on the connection
if defined?(@backend.unique_identifier)
uuid_from_string(@backend.unique_identifier)
elsif @platform.unix?
unix_uuid
elsif @platform.windows?
windows_uuid
else
if @platform[:uuid_command]
result = @backend.run_command(@platform[:uuid_command])
return uuid_from_string(result.stdout.chomp) if result.exit_status == 0 && !result.stdout.empty?
end
raise "Could not find platform uuid! Please set a uuid_command for your platform."
end
end
end
end
train-3.2.28/lib/train/platforms/family.rb 0000664 0000000 0000000 00000001156 13645125472 0020437 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Platforms
class Family
include Train::Platforms::Common
attr_accessor :children, :condition, :families, :name
def initialize(name, condition)
@name = name
@condition = condition
@families = {}
@children = {}
@detect = nil
@title = "#{name.to_s.capitalize} Family"
# add itself to the families list
Train::Platforms.families[@name.to_s] = self
end
def title(title = nil)
return @title if title.nil?
@title = title
self
end
def inspect
"%p[%s]" % [self.class, name]
end
end
end
train-3.2.28/lib/train/platforms/platform.rb 0000664 0000000 0000000 00000004720 13645125472 0021002 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Platforms
class Platform
include Train::Platforms::Common
attr_accessor :backend, :condition, :families, :family_hierarchy, :platform
def initialize(name, condition = {})
@name = name
@condition = condition
@families = {}
@family_hierarchy = []
@platform = {}
@detect = nil
@title = name.to_s.capitalize
# add itself to the platform list
Train::Platforms.list[name] = self
end
def direct_families
@families.collect { |k, _v| k.name }
end
def find_family_hierarchy(platform = self)
families = platform.families.map { |k, v| [k.name, find_family_hierarchy(k)] }
@family_hierarchy = families.flatten
end
def family
@platform[:family] || @family_hierarchy[0]
end
def name
# Override here incase a updated name was set
# during the detect logic
clean_name
end
def clean_name(force: false)
@cleaned_name = nil if force
@cleaned_name ||= (@platform[:name] || @name).downcase.tr(" ", "_")
end
def uuid
@uuid ||= Train::Platforms::Detect::UUID.new(self).find_or_create_uuid.downcase
end
# This is for backwords compatability with
# the current inspec os resource.
def[](name)
if respond_to?(name)
send(name)
else
"unknown"
end
end
def title(title = nil)
return @title if title.nil?
@title = title
self
end
def to_hash
@platform
end
# Add generic family? and platform methods to an existing platform
#
# This is done later to add any custom
# families/properties that were created
def add_platform_methods
# Redo clean name if there is a detect override
clean_name(force: true) unless @platform[:name].nil?
# Add in family methods
family_list = Train::Platforms.families
family_list.each_value do |k|
name = "#{k.name}?"
next if respond_to?(name)
define_singleton_method(name) do
family_hierarchy.include?(k.name)
end
end
# Helper methods for direct platform info
@platform.each_key do |m|
next if respond_to?(m)
define_singleton_method(m) do
@platform[m]
end
end
# Create method for name if its not already true
m = name + "?"
return if respond_to?(m)
define_singleton_method(m) do
true
end
end
end
end
train-3.2.28/lib/train/plugin_test_helper.rb 0000664 0000000 0000000 00000003320 13645125472 0021036 0 ustar 00root root 0000000 0000000 # This file is intended to be 'require'd by plugin authors who are developing a
# plugin outside of the Train source tree.
# Load Train. We certainly need the plugin system, and also several other parts
# that are tightly coupled. Train itself is fairly light, and non-invasive.
require_relative "../train"
# You can select from a number of test harnesses. Since Train is closely related
# to InSpec, and InSpec uses Spec-style controls in profile code, you will
# probably want to use something like minitest/spec, which provides Spec-style
# tests.
require "minitest/spec"
require "minitest/autorun"
# Data formats commonly used in testing
require "json"
require "ostruct"
# Utilities often needed
require "fileutils"
require "tmpdir"
require "pathname"
# You might want to put some debugging tools here. We run tests to find bugs,
# after all.
require "byebug"
# Configure MiniTest to expose things like `let`
class Module
include Minitest::Spec::DSL
end
# Finally, let's make some modules that can help us out.
module TrainPluginBaseHelper
# Sneakily detect the location of the plugin
# source code when they include this Module
def self.included(base)
plugin_test_helper_path = Pathname.new(caller_locations(4, 1).first.absolute_path)
plugin_src_root = plugin_test_helper_path.parent.parent
base.let(:plugin_src_path) { plugin_src_root }
base.let(:plugin_fixtures_path) { File.join(plugin_src_root, "test", "fixtures") }
end
let(:train_src_path) { File.expand_path(File.join(__FILE__, "..", "..")) }
let(:train_fixtures_path) { File.join(train_src_path, "test", "fixtures") }
let(:registry) { Train::Plugins.registry }
end
module TrainPluginFunctionalHelper
include TrainPluginBaseHelper
end
train-3.2.28/lib/train/plugins.rb 0000664 0000000 0000000 00000002327 13645125472 0016631 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Dominik Richter ()
# Author:: Christoph Hartmann ()
require_relative "errors"
module Train
class Plugins
require_relative "plugins/transport"
class << self
# Retrieve the current plugin registry, containing all plugin names
# and their transport handlers.
#
# @return [Hash] map with plugin names and plugins
def registry
@registry ||= {}
end
end
end
# Create a new plugin by inheriting from the class returned by this method.
# Create a versioned plugin by providing the transport layer plugin version
# to this method. It will then select the correct class to inherit from.
#
# The plugin version determins what methods will be available to your plugin.
#
# @param [Int] version = 1 the plugin version to use
# @return [Transport] the versioned transport base class
def self.plugin(version = 1)
if version != 1
raise ClientError,
"Only understand train plugin version 1. You are trying to "\
"initialize a train plugin #{version}, which is not supported "\
"in the current release of train."
end
::Train::Plugins::Transport
end
end
train-3.2.28/lib/train/plugins/ 0000775 0000000 0000000 00000000000 13645125472 0016300 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/plugins/base_connection.rb 0000664 0000000 0000000 00000014646 13645125472 0021771 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require_relative "../errors"
require_relative "../extras"
require_relative "../file"
require "logger"
class Train::Plugins::Transport
# A Connection instance can be generated and re-generated, given new
# connection details such as connection port, hostname, credentials, etc.
# This object is responsible for carrying out the actions on the remote
# host such as executing commands, transferring files, etc.
#
# @author Fletcher Nichol
class BaseConnection
include Train::Extras
# Create a new Connection instance.
#
# @param options [Hash] connection options
# @yield [self] yields itself for block-style invocation
def initialize(options = nil)
@options = options || {}
@logger = @options.delete(:logger) || Logger.new($stdout, level: :fatal)
Train::Platforms::Detect::Specifications::OS.load
Train::Platforms::Detect::Specifications::Api.load
# default caching options
@cache_enabled = {
file: true,
command: false,
api_call: false,
}
@cache = {}
@cache_enabled.each_key do |type|
clear_cache(type)
end
end
def with_sudo_pty
yield
end
# Returns cached client if caching enabled. Otherwise returns whatever is
# given in the block.
#
# @example
#
# def demo_client
# cached_client(:api_call, :demo_connection) do
# DemoClient.new(args)
# end
# end
#
# @param [symbol] type one of [:api_call, :file, :command]
# @param [symbol] key for your cached connection
def cached_client(type, key)
return yield unless cache_enabled?(type)
@cache[type][key] ||= yield
end
def cache_enabled?(type)
@cache_enabled[type.to_sym]
end
# Enable caching types for Train. Currently we support
# :api_call, :file and :command types
def enable_cache(type)
raise Train::UnknownCacheType, "#{type} is not a valid cache type" unless @cache_enabled.keys.include?(type.to_sym)
@cache_enabled[type.to_sym] = true
end
def disable_cache(type)
raise Train::UnknownCacheType, "#{type} is not a valid cache type" unless @cache_enabled.keys.include?(type.to_sym)
@cache_enabled[type.to_sym] = false
clear_cache(type.to_sym)
end
# Closes the session connection, if it is still active.
def close
# this method may be left unimplemented if that is applicable
end
def to_json
{
"files" => Hash[@cache[:file].map { |x, y| [x, y.to_json] }],
}
end
def load_json(j)
require_relative "../transports/mock"
j["files"].each do |path, jf|
@cache[:file][path] = Train::Transports::Mock::Connection::File.from_json(jf)
end
end
def force_platform!(name, platform_details = nil)
plat = Train::Platforms.name(name)
plat.backend = self
plat.platform = platform_details unless platform_details.nil?
plat.find_family_hierarchy
plat.add_platform_methods
plat
end
def backend_type
@options[:backend] || "unknown"
end
def inspect
"%s[%s]" % [self.class, backend_type]
end
alias direct_platform force_platform!
# Get information on the operating system which this transport connects to.
#
# @return [Platform] system information
def platform
@platform ||= Train::Platforms::Detect.scan(self)
end
# we need to keep os as a method for backwards compatibility with inspec
alias os platform
# This is the main command call for all connections. This will call the private
# run_command_via_connection on the connection with optional caching
#
# This command accepts an optional data handler block. When provided,
# inbound data will be published vi `data_handler.call(data)`. This can allow
# callers to receive and render updates from remote command execution.
def run_command(cmd, &data_handler)
return run_command_via_connection(cmd, &data_handler) unless cache_enabled?(:command)
@cache[:command][cmd] ||= run_command_via_connection(cmd, &data_handler)
end
# This is the main file call for all connections. This will call the private
# file_via_connection on the connection with optional caching
def file(path, *args)
return file_via_connection(path, *args) unless cache_enabled?(:file)
@cache[:file][path] ||= file_via_connection(path, *args)
end
# Builds a LoginCommand which can be used to open an interactive
# session on the remote host.
#
# @return [LoginCommand] array of command line tokens
def login_command
raise NotImplementedError, "#{self.class} does not implement #login_command()"
end
# Block and return only when the remote host is prepared and ready to
# execute command and upload files. The semantics and details will
# vary by implementation, but a round trip through the hosted
# service is preferred to simply waiting on a socket to become
# available.
def wait_until_ready
# this method may be left unimplemented if that is applicablelog
end
private
# Execute a command using this connection.
#
# @param command [String] command string to execute
# @param &data_handler(data) [Proc] proc that is called when data arrives from
# the connection. This block is optional. Individual transports will need
# to explicitly invoke the block in their implementation of run_command_via_connection;
# if they do not, the block is ignored and will not be used to report data back to the caller.
#
# @return [CommandResult] contains the result of running the command
def run_command_via_connection(_command, &_data_handler)
raise NotImplementedError, "#{self.class} does not implement #run_command_via_connection()"
end
# Interact with files on the target. Read, write, and get metadata
# from files via the transport.
#
# @param [String] path which is being inspected
# @return [FileCommon] file object that allows for interaction
def file_via_connection(_path, *_args)
raise NotImplementedError, "#{self.class} does not implement #file_via_connection(...)"
end
def clear_cache(type)
@cache[type.to_sym] = {}
end
# @return [Logger] logger for reporting information
# @api private
attr_reader :logger
# @return [Hash] connection options
# @api private
attr_reader :options
end
end
train-3.2.28/lib/train/plugins/transport.rb 0000664 0000000 0000000 00000002610 13645125472 0020660 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Dominik Richter ()
# Author:: Christoph Hartmann ()
require "logger"
require_relative "../errors"
require_relative "../extras"
require_relative "../options"
class Train::Plugins
class Transport
include Train::Extras
Train::Options.attach(self)
require_relative "base_connection"
# Initialize a new Transport object
#
# @param [Hash] config = nil the configuration for this transport
# @return [Transport] the transport object
def initialize(options = {})
@options = merge_options({}, options || {})
@logger = @options[:logger] || Logger.new($stdout, level: :fatal)
end
# Create a connection to the target. Options may be provided
# for additional configuration.
#
# @param [Hash] _options = nil provide optional configuration params
# @return [Connection] the connection for this configuration
def connection(_options = nil)
raise Train::ClientError, "#{self.class} does not implement #connection()"
end
# Register the inheriting class with as a train plugin using the
# provided name.
#
# @param [String] name of the plugin, by which it will be found
def self.name(name)
Train::Plugins.registry[name] = self
end
private
# @return [Logger] logger for reporting information
attr_reader :logger
end
end
train-3.2.28/lib/train/transports/ 0000775 0000000 0000000 00000000000 13645125472 0017036 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/transports/azure.rb 0000664 0000000 0000000 00000014603 13645125472 0020515 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require "train/plugins"
require "ms_rest_azure"
require "azure_mgmt_resources"
require "azure_graph_rbac"
require "azure_mgmt_key_vault"
require "socket"
require "timeout"
require "train/transports/helpers/azure/file_credentials"
require "train/transports/clients/azure/graph_rbac"
require "train/transports/clients/azure/vault"
module Train::Transports
class Azure < Train.plugin(1)
name "azure"
option :tenant_id, default: ENV["AZURE_TENANT_ID"]
option :client_id, default: ENV["AZURE_CLIENT_ID"]
option :client_secret, default: ENV["AZURE_CLIENT_SECRET"]
option :subscription_id, default: ENV["AZURE_SUBSCRIPTION_ID"]
option :msi_port, default: ENV["AZURE_MSI_PORT"] || "50342"
# This can provide the client id and secret
option :credentials_file, default: ENV["AZURE_CRED_FILE"]
def connection(_ = nil)
@connection ||= Connection.new(@options)
end
class Connection < BaseConnection
attr_reader :options
DEFAULT_FILE = ::File.join(Dir.home, ".azure", "credentials")
def initialize(options)
@apis = {}
# Override for any cli options
# azure://subscription_id
options[:subscription_id] = options[:host] || options[:subscription_id]
super(options)
@cache_enabled[:api_call] = true
@cache[:api_call] = {}
if @options[:client_secret].nil? && @options[:client_id].nil?
options[:credentials_file] = DEFAULT_FILE if options[:credentials_file].nil?
@options.merge!(Helpers::Azure::FileCredentials.parse(**@options))
end
@options[:msi_port] = @options[:msi_port].to_i unless @options[:msi_port].nil?
# additional platform details
release = Gem.loaded_specs["azure_mgmt_resources"].version
@platform_details = { release: "azure_mgmt_resources-v#{release}" }
connect
end
def platform
force_platform!("azure", @platform_details)
end
def azure_client(klass = ::Azure::Resources::Profiles::Latest::Mgmt::Client, opts = {})
if cache_enabled?(:api_call)
return @cache[:api_call][klass.to_s.to_sym] unless @cache[:api_call][klass.to_s.to_sym].nil?
end
if klass == ::Azure::Resources::Profiles::Latest::Mgmt::Client
@credentials[:base_url] = MsRestAzure::AzureEnvironments::AzureCloud.resource_manager_endpoint_url
elsif klass == ::Azure::GraphRbac::Profiles::Latest::Client
client = GraphRbac.client(@credentials)
elsif klass == ::Azure::KeyVault::Profiles::Latest::Mgmt::Client
client = Vault.client(opts[:vault_name], @credentials)
end
client ||= klass.new(@credentials)
# Cache if enabled
@cache[:api_call][klass.to_s.to_sym] ||= client if cache_enabled?(:api_call)
client
end
def connect
if msi_auth?
# this needs set for azure cloud to authenticate
ENV["MSI_VM"] = "true"
provider = ::MsRestAzure::MSITokenProvider.new(@options[:msi_port])
else
provider = ::MsRestAzure::ApplicationTokenProvider.new(
@options[:tenant_id],
@options[:client_id],
@options[:client_secret]
)
end
@credentials = {
credentials: ::MsRest::TokenCredentials.new(provider),
subscription_id: @options[:subscription_id],
tenant_id: @options[:tenant_id],
}
@credentials[:client_id] = @options[:client_id] unless @options[:client_id].nil?
@credentials[:client_secret] = @options[:client_secret] unless @options[:client_secret].nil?
end
def uri
"azure://#{@options[:subscription_id]}"
end
# Returns the api version for the specified resource type
#
# If an api version has been specified in the options then the apis version table is updated
# with that value and it is returned
#
# However if it is not specified, or multiple types are being interrogated then this method
# will interrogate Azure for each of the types versions and pick the latest one. This is added
# to the apis table so that it can be retrieved quickly again of another one of those resources
# is encountered again in the resource collection.
#
# @param string resource_type The resource type for which the API is required
# @param hash options Options have that have been passed to the resource during the test.
# @option opts [String] :group_name Resource group name
# @option opts [String] :type Azure resource type
# @option opts [String] :name Name of specific resource to look for
# @option opts [String] :apiversion If looking for a specific item or type specify the api version to use
#
# @return string API Version of the specified resource type
def get_api_version(resource_type, options)
# if an api version has been set in the options, add to the apis hashtable with
# the resource type
if options[:apiversion]
@apis[resource_type] = options[:apiversion]
else
# only attempt to get the api version from Azure if the resource type
# is not present in the apis hashtable
unless @apis.key?(resource_type)
# determine the namespace for the resource type
namespace, type = resource_type.split(%r{/})
client = azure_client(::Azure::Resources::Profiles::Latest::Mgmt::Client)
provider = client.providers.get(namespace)
# get the latest API version for the type
# assuming that this is the first one in the list
api_versions = (provider.resource_types.find { |v| v.resource_type == type }).api_versions
@apis[resource_type] = api_versions[0]
end
end
# return the api version for the type
@apis[resource_type]
end
def unique_identifier
options[:subscription_id] || options[:tenant_id]
end
def msi_auth?
@options[:client_id].nil? && @options[:client_secret].nil? && port_open?(@options[:msi_port])
end
private
def port_open?(port, seconds = 3)
Timeout.timeout(seconds) do
begin
TCPSocket.new("localhost", port).close
true
rescue SystemCallError
false
end
end
rescue Timeout::Error
false
end
end
end
end
train-3.2.28/lib/train/transports/cisco_ios_connection.rb 0000664 0000000 0000000 00000007503 13645125472 0023561 0 ustar 00root root 0000000 0000000 # encoding: utf-8
class Train::Transports::SSH
class CiscoIOSConnection < BaseConnection
class BadEnablePassword < Train::TransportError; end
def initialize(options)
super(options)
# Extract options to avoid passing them in to `Net::SSH.start` later
@host = options.delete(:host)
@user = options.delete(:user)
@port = options.delete(:port)
@enable_password = options.delete(:enable_password)
# Use all options left that are not `nil` for `Net::SSH.start` later
@ssh_options = options.reject { |_key, value| value.nil? }
@prompt = /^\S+[>#]\r\n.*$/
end
def uri
"ssh://#{@user}@#{@host}:#{@port}"
end
def unique_identifier
result = run_command_via_connection("show version | include Processor")
result.stdout.split(" ")[-1]
end
private
def establish_connection
logger.debug("[SSH] opening connection to #{self}")
Net::SSH.start(@host, @user, @ssh_options)
end
def session
return @session unless @session.nil?
@session = open_channel(establish_connection)
# Escalate privilege to enable mode if password is given
if @enable_password
# This verifies we are not in privileged exec mode before running the
# enable command. Otherwise, the password will be in history.
if run_command_via_connection("show privilege").stdout.split[-1] != "15"
# Extra newlines to get back to prompt if incorrect password is used
run_command_via_connection("enable\n#{@enable_password}\n\n\n")
end
end
# Prevent `--MORE--` by removing terminal length limit
run_command_via_connection("terminal length 0")
@session
end
def run_command_via_connection(cmd, &_data_handler)
# Ensure buffer is empty before sending data
@buf = ""
logger.debug("[SSH] Running `#{cmd}` on #{self}")
session.send_data(cmd + "\r\n")
logger.debug("[SSH] waiting for prompt")
until @buf =~ @prompt
if @buf =~ /Bad (secrets|password)|Access denied/
raise BadEnablePassword
end
session.connection.process(0)
end
# Save the buffer and clear it for the next command
output = @buf.dup
@buf = ""
format_result(format_output(output, cmd))
end
ERROR_MATCHERS = [
"Bad IP address",
"Incomplete command",
"Invalid input detected",
"Unrecognized host",
].freeze
# IOS commands do not have an exit code so we must compare the command
# output with partial segments of known errors. Then, we return a
# `CommandResult` with arguments in the correct position based on the
# result.
def format_result(result)
if ERROR_MATCHERS.none? { |e| result.include?(e) }
CommandResult.new(result, "", 0)
else
CommandResult.new("", result, 1)
end
end
# The buffer (@buf) contains all data sent/received on the SSH channel so
# we need to format the data to match what we would expect from Train
def format_output(output, cmd)
leading_prompt = /(\r\n|^)\S+[>#]/
command_string = /#{Regexp.quote(cmd)}\r\n/
trailing_prompt = /\S+[>#](\r\n|$)/
trailing_line_endings = /(\r\n)+$/
output
.sub(leading_prompt, "")
.sub(command_string, "")
.gsub(trailing_prompt, "")
.gsub(trailing_line_endings, "")
end
# Create an SSH channel that writes to @buf when data is received
def open_channel(ssh)
logger.debug("[SSH] opening SSH channel to #{self}")
ssh.open_channel do |ch|
ch.on_data do |_, data|
@buf += data
end
ch.send_channel_request("shell") do |_, success|
raise "Failed to open SSH shell" unless success
logger.debug("[SSH] shell opened")
end
end
end
end
end
train-3.2.28/lib/train/transports/clients/ 0000775 0000000 0000000 00000000000 13645125472 0020477 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/transports/clients/azure/ 0000775 0000000 0000000 00000000000 13645125472 0021625 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/transports/clients/azure/graph_rbac.rb 0000664 0000000 0000000 00000002221 13645125472 0024237 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require "azure_graph_rbac"
# Wrapper class for ::Azure::GraphRbac::Profiles::Latest::Client allowing custom configuration,
# for example, defining additional settings for the ::MsRestAzure::ApplicationTokenProvider.
class GraphRbac
AUTH_ENDPOINT = MsRestAzure::AzureEnvironments::AzureCloud.active_directory_endpoint_url
API_ENDPOINT = MsRestAzure::AzureEnvironments::AzureCloud.active_directory_graph_resource_id
def self.client(credentials)
credentials[:credentials] = ::MsRest::TokenCredentials.new(provider(credentials))
credentials[:base_url] = API_ENDPOINT
::Azure::GraphRbac::Profiles::Latest::Client.new(credentials)
end
def self.provider(credentials)
::MsRestAzure::ApplicationTokenProvider.new(
credentials[:tenant_id],
credentials[:client_id],
credentials[:client_secret],
settings
)
end
def self.settings
client_settings = MsRestAzure::ActiveDirectoryServiceSettings.get_azure_settings
client_settings.authentication_endpoint = AUTH_ENDPOINT
client_settings.token_audience = API_ENDPOINT
client_settings
end
private_class_method :provider, :settings
end
train-3.2.28/lib/train/transports/clients/azure/vault.rb 0000664 0000000 0000000 00000002565 13645125472 0023315 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require "azure_mgmt_key_vault"
# Wrapper class for ::Azure::KeyVault::Profiles::Latest::Mgmt::Client allowing custom configuration,
# for example, defining additional settings for the ::MsRestAzure::ApplicationTokenProvider.
class Vault
AUTH_ENDPOINT = MsRestAzure::AzureEnvironments::AzureCloud.active_directory_endpoint_url
RESOURCE_ENDPOINT = "https://vault.azure.net".freeze
def self.client(vault_name, credentials)
raise ::Train::UserError, "Vault Name cannot be nil" if vault_name.nil?
credentials[:credentials] = ::MsRest::TokenCredentials.new(provider(credentials))
credentials[:base_url] = api_endpoint(vault_name)
::Azure::KeyVault::Profiles::Latest::Mgmt::Client.new(credentials)
end
def self.provider(credentials)
::MsRestAzure::ApplicationTokenProvider.new(
credentials[:tenant_id],
credentials[:client_id],
credentials[:client_secret],
settings
)
end
def self.api_endpoint(vault_name)
"https://#{vault_name}#{MsRestAzure::AzureEnvironments::AzureCloud.key_vault_dns_suffix}"
end
def self.settings
client_settings = MsRestAzure::ActiveDirectoryServiceSettings.get_azure_settings
client_settings.authentication_endpoint = AUTH_ENDPOINT
client_settings.token_audience = RESOURCE_ENDPOINT
client_settings
end
private_class_method :provider, :api_endpoint, :settings
end
train-3.2.28/lib/train/transports/docker.rb 0000664 0000000 0000000 00000004734 13645125472 0020642 0 ustar 00root root 0000000 0000000 require "docker"
module Train::Transports
class Docker < Train.plugin(1)
name "docker"
include_options Train::Extras::CommandWrapper
option :host, required: true
def connection(state = {}, &block)
opts = merge_options(options, state || {})
validate_options(opts)
if @connection && @connection_options == opts
reuse_connection(&block)
else
create_new_connection(opts, &block)
end
end
private
# Creates a new Docker connection instance and save it for potential future
# reuse.
#
# @param options [Hash] connection options
# @return [Docker::Connection] a Docker connection instance
# @api private
def create_new_connection(options, &block)
if @connection
logger.debug("[Docker] shutting previous connection #{@connection}")
@connection.close
end
@connection_options = options
@connection = Connection.new(options, &block)
end
# Return the last saved Docker connection instance.
#
# @return [Docker::Connection] a Docker connection instance
# @api private
def reuse_connection
logger.debug("[Docker] reusing existing connection #{@connection}")
yield @connection if block_given?
@connection
end
end
end
class Train::Transports::Docker
class Connection < BaseConnection
def initialize(conf)
super(conf)
@id = options[:host]
@container = ::Docker::Container.get(@id) ||
raise("Can't find Docker container #{@id}")
@cmd_wrapper = nil
@cmd_wrapper = CommandWrapper.load(self, @options)
end
def close
# nothing to do at the moment
end
def uri
if @container.nil?
"docker://#{@id}"
else
"docker://#{@container.id}"
end
end
private
def file_via_connection(path)
if os.aix?
Train::File::Remote::Aix.new(self, path)
elsif os.solaris?
Train::File::Remote::Unix.new(self, path)
else
Train::File::Remote::Linux.new(self, path)
end
end
def run_command_via_connection(cmd, &_data_handler)
cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil?
stdout, stderr, exit_status = @container.exec(
[
"/bin/sh", "-c", cmd
]
)
CommandResult.new(stdout.join, stderr.join, exit_status)
rescue ::Docker::Error::DockerError => _
raise
rescue => _
# @TODO: differentiate any other error
raise
end
end
end
train-3.2.28/lib/train/transports/gcp.rb 0000664 0000000 0000000 00000010427 13645125472 0020140 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require "train/plugins"
require "google/apis"
require "google/apis/cloudresourcemanager_v1"
require "google/apis/compute_v1"
require "google/apis/storage_v1"
require "google/apis/iam_v1"
require "google/apis/admin_directory_v1"
require "googleauth"
module Train::Transports
class Gcp < Train.plugin(1)
name "gcp"
# GCP will look automatically for the below env var for service accounts etc. :
option :google_application_credentials, required: false do
ENV["GOOGLE_APPLICATION_CREDENTIALS"]
end
# see https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application
# In the absence of this, the client is expected to have already set up local credentials via:
# $ gcloud auth application-default login
# $ gcloud config set project
# GCP projects can have default regions / zones set, see:
# https://cloud.google.com/compute/docs/regions-zones/changing-default-zone-region
# can also specify project via env var:
option :google_cloud_project, required: false do
ENV["GOOGLE_CLOUD_PROJECT"]
end
option :google_super_admin_email, required: false do
ENV["GOOGLE_SUPER_ADMIN_EMAIL"]
end
def connection(_ = nil)
@connection ||= Connection.new(@options)
end
class Connection < BaseConnection
def initialize(options)
super(options)
# additional GCP platform metadata
release = Gem.loaded_specs["google-api-client"].version
@platform_details = { release: "google-api-client-v#{release}" }
# Initialize the client object cache
@cache_enabled[:api_call] = true
@cache[:api_call] = {}
connect
end
def platform
force_platform!("gcp", @platform_details)
end
# Instantiate some named classes for ease of use
def gcp_compute_client
gcp_client(Google::Apis::ComputeV1::ComputeService)
end
def gcp_iam_client
gcp_client(Google::Apis::IamV1::IamService)
end
def gcp_project_client
gcp_client(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService)
end
def gcp_storage_client
gcp_client(Google::Apis::StorageV1::StorageService)
end
def gcp_admin_client
scopes = ["https://www.googleapis.com/auth/admin.directory.user.readonly"]
authorization = Google::Auth.get_application_default(scopes).dup
# Use of the Admin API requires delegation (impersonation). An email address of a Super Admin in
# the G Suite account may be required.
authorization.sub = @options[:google_super_admin_email] if @options[:google_super_admin_email]
Google::Apis::RequestOptions.default.authorization = authorization
gcp_client(Google::Apis::AdminDirectoryV1::DirectoryService)
end
# Let's allow for other clients too
def gcp_client(klass)
return klass.new unless cache_enabled?(:api_call)
@cache[:api_call][klass.to_s.to_sym] ||= klass.new
end
def connect
ENV["GOOGLE_APPLICATION_CREDENTIALS"] = @options[:google_application_credentials] if @options[:google_application_credentials]
ENV["GOOGLE_CLOUD_PROJECT"] = @options[:google_cloud_project] if @options[:google_cloud_project]
# GCP initialization
scopes = ["https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/compute"]
authorization = Google::Auth.get_application_default(scopes)
Google::Apis::ClientOptions.default.application_name = "chef-inspec-train"
Google::Apis::ClientOptions.default.application_version = Train::VERSION
Google::Apis::RequestOptions.default.authorization = authorization
end
def uri
"gcp://#{unique_identifier}"
end
def unique_identifier
unique_id = "default"
# use auth client_id for users (issuer is nil)
unique_id = gcp_iam_client.request_options.authorization.client_id unless gcp_iam_client.request_options.authorization.client_id.nil?
# for service account credentials (client_id is nil)
unique_id = gcp_iam_client.request_options.authorization.issuer unless gcp_iam_client.request_options.authorization.issuer.nil?
unique_id
end
end
end
end
train-3.2.28/lib/train/transports/helpers/ 0000775 0000000 0000000 00000000000 13645125472 0020500 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/transports/helpers/azure/ 0000775 0000000 0000000 00000000000 13645125472 0021626 5 ustar 00root root 0000000 0000000 train-3.2.28/lib/train/transports/helpers/azure/file_credentials.rb 0000664 0000000 0000000 00000002640 13645125472 0025451 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require "inifile"
require_relative "file_parser"
require_relative "subscription_number_file_parser"
require_relative "subscription_id_file_parser"
module Train::Transports
module Helpers
module Azure
class FileCredentials
def self.parse(subscription_id: nil, credentials_file: nil, **_)
return {} if credentials_file.nil?
return {} unless ::File.readable?(credentials_file)
credentials = IniFile.load(::File.expand_path(credentials_file))
subscription_id = parser(subscription_id, ENV["AZURE_SUBSCRIPTION_NUMBER"], credentials).subscription_id
creds(subscription_id, credentials)
end
def self.parser(subscription_id, subscription_number, credentials)
if subscription_id
SubscriptionIdFileParser.new(subscription_id, credentials)
elsif !subscription_number.nil?
SubscriptionNumberFileParser.new(subscription_number.to_i, credentials)
else
FileParser.new(credentials)
end
end
def self.creds(subscription_id, credentials)
{
subscription_id: subscription_id,
tenant_id: credentials[subscription_id]["tenant_id"],
client_id: credentials[subscription_id]["client_id"],
client_secret: credentials[subscription_id]["client_secret"],
}
end
end
end
end
end
train-3.2.28/lib/train/transports/helpers/azure/file_parser.rb 0000664 0000000 0000000 00000001112 13645125472 0024441 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Transports
module Helpers
module Azure
class FileParser
def initialize(credentials)
@credentials = credentials
validate!
end
def validate!
return if @credentials.sections.count == 1
raise "Credentials file must have one entry. Check your credentials file. If you have more than one entry set AZURE_SUBSCRIPTION_ID environment variable."
end
def subscription_id
@subscription_id ||= @credentials.sections[0]
end
end
end
end
end
train-3.2.28/lib/train/transports/helpers/azure/subscription_id_file_parser.rb 0000664 0000000 0000000 00000001070 13645125472 0027724 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Transports
module Helpers
module Azure
class SubscriptionIdFileParser
attr_reader :subscription_id
def initialize(subscription_id, credentials)
@subscription_id = subscription_id
@credentials = credentials
validate!
end
def validate!
if @credentials.sections.empty? || @credentials[subscription_id].empty?
raise "No credentials found for subscription number #{subscription_id}"
end
end
end
end
end
end
train-3.2.28/lib/train/transports/helpers/azure/subscription_number_file_parser.rb 0000664 0000000 0000000 00000001322 13645125472 0030620 0 ustar 00root root 0000000 0000000 # encoding: utf-8
module Train::Transports
module Helpers
module Azure
class SubscriptionNumberFileParser
def initialize(index, credentials)
@index = index
@credentials = credentials
validate!
end
def validate!
if @index == 0
raise "Index must be greater than 0."
end
if @index > @credentials.sections.length
raise "Your credentials file only contains #{@credentials.sections.length} subscriptions. You specified number #{@index}."
end
end
def subscription_id
@subscription_id ||= @credentials.sections[@index - 1]
end
end
end
end
end
train-3.2.28/lib/train/transports/local.rb 0000664 0000000 0000000 00000017050 13645125472 0020460 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# author: Dominik Richter
# author: Christoph Hartmann
require_relative "../plugins"
require_relative "../errors"
require "mixlib/shellout"
module Train::Transports
class Local < Train.plugin(1)
name "local"
class PipeError < Train::TransportError; end
def connection(_ = nil)
@connection ||= Connection.new(@options)
end
class Connection < BaseConnection
def initialize(options)
super(options)
@runner = if options[:command_runner]
force_runner(options[:command_runner])
else
select_runner(options)
end
end
def login_command
nil # none, open your shell
end
def uri
"local://"
end
private
def select_runner(options)
if os.windows?
# Force a 64 bit poweshell if needed
if RUBY_PLATFORM == "i386-mingw32" && os.arch == "x86_64"
powershell_cmd = "#{ENV["SystemRoot"]}\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe"
else
powershell_cmd = "powershell"
end
# Attempt to use a named pipe but fallback to ShellOut if that fails
begin
WindowsPipeRunner.new(powershell_cmd)
rescue PipeError
WindowsShellRunner.new(powershell_cmd)
end
else
GenericRunner.new(self, options)
end
end
def force_runner(command_runner)
case command_runner
when :generic
GenericRunner.new(self, options)
when :windows_pipe
WindowsPipeRunner.new
when :windows_shell
WindowsShellRunner.new
else
raise "Runner type `#{command_runner}` not supported"
end
end
def run_command_via_connection(cmd, &_data_handler)
# Use the runner if it is available
return @runner.run_command(cmd) if defined?(@runner)
# If we don't have a runner, such as at the beginning of setting up the
# transport and performing the first few steps of OS detection, fall
# back to shelling out.
res = Mixlib::ShellOut.new(cmd)
res.run_command
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
rescue Errno::ENOENT => _
CommandResult.new("", "", 1)
end
def file_via_connection(path)
if os.windows?
Train::File::Local::Windows.new(self, path)
else
Train::File::Local::Unix.new(self, path)
end
end
class GenericRunner
include_options Train::Extras::CommandWrapper
def initialize(connection, options)
@cmd_wrapper = Local::CommandWrapper.load(connection, options)
end
def run_command(cmd)
if defined?(@cmd_wrapper) && !@cmd_wrapper.nil?
cmd = @cmd_wrapper.run(cmd)
end
res = Mixlib::ShellOut.new(cmd)
res.run_command
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
end
end
class WindowsShellRunner
require "json"
require "base64"
def initialize(powershell_cmd = "powershell")
@powershell_cmd = powershell_cmd
end
def run_command(script)
# Prevent progress stream from leaking into stderr
script = "$ProgressPreference='SilentlyContinue';" + script
# Encode script so PowerShell can use it
script = script.encode("UTF-16LE", "UTF-8")
base64_script = Base64.strict_encode64(script)
cmd = "#{@powershell_cmd} -NoProfile -EncodedCommand #{base64_script}"
res = Mixlib::ShellOut.new(cmd)
res.run_command
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
end
end
class WindowsPipeRunner
require "json"
require "base64"
require "securerandom"
def initialize(powershell_cmd = "powershell")
@powershell_cmd = powershell_cmd
@pipe = acquire_pipe
raise PipeError if @pipe.nil?
end
# @param cmd The command to execute
# @return Local::ComandResult with stdout, stderr and exitstatus
# Note that exitstatus ($?) in PowerShell is boolean, but we use a numeric exit code.
# A command that succeeds without setting an exit code will have exitstatus 0
# A command that exits with an exit code will have that value as exitstatus
# A command that fails (e.g. throws exception) before setting an exit code will have exitstatus 1
def run_command(cmd)
script = "$ProgressPreference='SilentlyContinue';" + cmd
encoded_script = Base64.strict_encode64(script)
@pipe.puts(encoded_script)
@pipe.flush
res = OpenStruct.new(JSON.parse(Base64.decode64(@pipe.readline)))
Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
end
private
def acquire_pipe
pipe_name = "inspec_#{SecureRandom.hex}"
start_pipe_server(pipe_name)
pipe = nil
# PowerShell needs time to create pipe.
100.times do
begin
pipe = open("//./pipe/#{pipe_name}", "r+")
break
rescue
sleep 0.1
end
end
pipe
end
def start_pipe_server(pipe_name)
require "win32/process"
script = <<-EOF
$ErrorActionPreference = 'Stop'
$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream('#{pipe_name}')
$pipeReader = New-Object System.IO.StreamReader($pipeServer)
$pipeWriter = New-Object System.IO.StreamWriter($pipeServer)
$pipeServer.WaitForConnection()
# Create loop to receive and process user commands/scripts
$clientConnected = $true
while($clientConnected) {
$input = $pipeReader.ReadLine()
$command = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($input))
# Execute user command/script and convert result to JSON
$scriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock($command)
try {
$stdout = & $scriptBlock | Out-String
$exit_code = $LastExitCode
if ($exit_code -eq $null)
{
$exit_code = 0
}
$result = @{ 'stdout' = $stdout ; 'stderr' = ''; 'exitstatus' = $exit_code }
} catch {
$stderr = $_ | Out-String
$exit_code = $LastExitCode
$result = @{ 'stdout' = ''; 'stderr' = $stderr; 'exitstatus' = $exit_code }
}
$resultJSON = $result | ConvertTo-JSON
# Encode JSON in Base64 and write to pipe
$encodedResult = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($resultJSON))
$pipeWriter.WriteLine($encodedResult)
$pipeWriter.Flush()
}
EOF
utf8_script = script.encode("UTF-16LE", "UTF-8")
base64_script = Base64.strict_encode64(utf8_script)
cmd = "#{@powershell_cmd} -NoProfile -ExecutionPolicy bypass -NonInteractive -EncodedCommand #{base64_script}"
server_pid = Process.create(command_line: cmd).process_id
# Ensure process is killed when the Train process exits
at_exit { Process.kill("KILL", server_pid) }
end
end
end
end
end
train-3.2.28/lib/train/transports/mock.rb 0000664 0000000 0000000 00000010502 13645125472 0020312 0 ustar 00root root 0000000 0000000 require_relative "../plugins"
require "digest"
module Train::Transports
class Mock < Train.plugin(1)
name "mock"
def initialize(conf = nil)
@conf = conf || {}
trace_calls if @conf[:trace]
end
def connection
@connection ||= Connection.new(@conf)
end
def to_s
"Mock Transport"
end
private
def trace_calls
interface_methods = {
"Train::Transports::Mock" =>
Train::Transports::Mock.instance_methods(false),
"Train::Transports::Mock::Connection" =>
Connection.instance_methods(false),
"Train::Transports::Mock::Connection::File" =>
Connection::FileCommon.instance_methods(false),
"Train::Transports::Mock::Connection::OS" =>
Train::Platform.instance_methods(false),
}
# rubocop:disable Metrics/ParameterLists
# rubocop:disable Security/Eval
set_trace_func(proc { |event, _file, _line, id, binding, classname|
unless classname.to_s.start_with?("Train::Transports::Mock") &&
(event == "call") &&
interface_methods[classname.to_s].include?(id)
next
end
# kindly borrowed from the wonderful simple-tracer by matugm
arg_names = eval(
"method(__method__).parameters.map { |arg| arg[1].to_s }",
binding
)
args = eval("#{arg_names}.map { |arg| eval(arg) }", binding).join(", ")
prefix = "-" * (classname.to_s.count(":") - 2) + "> "
puts("#{prefix}#{id} #{args}")
})
# rubocop:enable all
end
end
end
class Train::Transports::Mock
class Connection < BaseConnection
attr_reader :options
def initialize(conf = nil)
super(conf)
mock_os
enable_cache(:file)
enable_cache(:command)
end
def uri
"mock://"
end
DEFAULTS = {
name: "mock",
family: "mock",
release: "unknown",
arch: "unknown",
}.freeze
def mock_os(value = {})
value = DEFAULTS
.merge(value || {})
.transform_values { |v| v || "unknown" }
platform = Train::Platforms.name(value[:name])
platform.find_family_hierarchy
platform.platform = value
platform.add_platform_methods
@platform = platform
end
def commands=(commands)
@cache[:command] = commands
end
def commands
@cache[:command]
end
def files=(files)
@cache[:file] = files
end
def files
@cache[:file]
end
def mock_command(cmd, stdout = nil, stderr = nil, exit_status = 0)
@cache[:command][cmd] = Command.new(stdout || "", stderr || "", exit_status)
end
def command_not_found(cmd)
if @options[:verbose]
$stderr.puts("Command not mocked:")
$stderr.puts(" " + cmd.to_s.split("\n").join("\n "))
$stderr.puts(" SHA: " + Digest::SHA256.hexdigest(cmd.to_s))
end
# return a non-zero exit code
mock_command(cmd, nil, nil, 1)
end
def file_not_found(path)
$stderr.puts("File not mocked: " + path.to_s) if @options[:verbose]
File.new(self, path)
end
def to_s
"Mock Connection"
end
private
def run_command_via_connection(cmd, &_data_handler)
@cache[:command][Digest::SHA256.hexdigest cmd.to_s] ||
command_not_found(cmd)
end
def file_via_connection(path)
file_not_found(path)
end
end
end
class Train::Transports::Mock::Connection
Command = Struct.new(:stdout, :stderr, :exit_status)
end
class Train::Transports::Mock::Connection
class File < Train::File
def self.from_json(json)
res = new(json["backend"],
json["path"],
json["follow_symlink"])
res.type = json["type"]
Train::File::DATA_FIELDS.each do |f|
m = (f.tr("?", "") + "=").to_sym
res.method(m).call(json[f])
end
res
end
Train::File::DATA_FIELDS.each do |m|
attr_accessor m.tr("?", "").to_sym
next unless m.include?("?")
define_method m.to_sym do
method(m.tr("?", "").to_sym).call
end
end
attr_accessor :type
def initialize(backend, path, follow_symlink = true)
super(backend, path, follow_symlink)
@type = :unknown
@exist = false
end
def mounted
@mounted ||=
@backend.run_command("mount | grep -- ' on #{@path}'")
end
end
end
train-3.2.28/lib/train/transports/ssh.rb 0000664 0000000 0000000 00000022316 13645125472 0020164 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Fletcher Nichol ()
# Author:: Dominik Richter ()
# Author:: Christoph Hartmann ()
#
# Copyright (C) 2014, Fletcher Nichol
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "net/ssh"
require "net/scp"
require_relative "../errors"
module Train::Transports
# Wrapped exception for any internally raised SSH-related errors.
#
# @author Fletcher Nichol
class SSHFailed < Train::TransportError; end
class SSHPTYFailed < Train::TransportError; end
# A Transport which uses the SSH protocol to execute commands and transfer
# files.
#
# @author Fletcher Nichol
class SSH < Train.plugin(1) # rubocop:disable Metrics/ClassLength
name "ssh"
require_relative "ssh_connection"
require_relative "cisco_ios_connection"
# add options for submodules
include_options Train::Extras::CommandWrapper
# common target configuration
option :host, required: true
option :port, default: 22, required: true
option :user, default: "root", required: true
option :key_files, default: nil
option :password, default: nil
# additional ssh options
option :keepalive, default: true
option :keepalive_interval, default: 60
option :connection_timeout, default: 15
option :connection_retries, default: 5
option :connection_retry_sleep, default: 1
option :max_wait_until_ready, default: 600
option :compression, default: false
option :pty, default: false
option :proxy_command, default: nil
option :bastion_host, default: nil
option :bastion_user, default: "root"
option :bastion_port, default: 22
option :non_interactive, default: false
option :verify_host_key, default: false
option :compression_level do |opts|
# on nil or false: set compression level to 0
opts[:compression] ? 6 : 0
end
# (see Base#connection)
def connection(state = {}, &block)
opts = merge_options(options, state || {})
validate_options(opts)
conn_opts = connection_options(opts)
if defined?(@connection) && @connection_options == conn_opts
reuse_connection(&block)
else
create_new_connection(conn_opts, &block)
end
end
private
def validate_options(options)
super(options)
key_files = Array(options[:key_files])
options[:auth_methods] ||= ["none"]
unless key_files.empty?
options[:auth_methods].push("publickey")
options[:keys_only] = true if options[:password].nil?
options[:key_files] = key_files
end
unless options[:password].nil?
options[:auth_methods].push("password", "keyboard-interactive")
end
if options[:auth_methods] == ["none"]
if ssh_known_identities.empty?
raise Train::ClientError.new(
"Your SSH Agent has no keys added, and you have not specified a password or a key file",
:no_ssh_password_or_key_available
)
else
logger.debug("[SSH] Using Agent keys as no password or key file have been specified")
options[:auth_methods].push("publickey")
end
end
if options[:pty]
logger.warn("[SSH] PTY requested: stderr will be merged into stdout")
end
if [options[:proxy_command], options[:bastion_host]].all? { |type| !type.nil? }
raise Train::ClientError, "Only one of proxy_command or bastion_host needs to be specified"
end
super
self
end
# Creates an SSH Authentication KeyManager instance and saves it for
# potential future reuse.
#
# @return [Hash] hash of SSH Known Identities
# @api private
def ssh_known_identities
# Force KeyManager to load the key(s)
@manager ||= Net::SSH::Authentication::KeyManager.new(nil).each_identity {}
@manager.known_identities
end
# Builds the hash of options needed by the Connection object on
# construction.
#
# @param opts [Hash] merged configuration and mutable state data
# @return [Hash] hash of connection options
# @api private
def connection_options(opts)
connection_options = {
logger: logger,
user_known_hosts_file: "/dev/null",
hostname: opts[:host],
port: opts[:port],
username: opts[:user],
compression: opts[:compression],
compression_level: opts[:compression_level],
keepalive: opts[:keepalive],
keepalive_interval: opts[:keepalive_interval],
timeout: opts[:connection_timeout],
connection_retries: opts[:connection_retries],
connection_retry_sleep: opts[:connection_retry_sleep],
max_wait_until_ready: opts[:max_wait_until_ready],
auth_methods: opts[:auth_methods],
keys_only: opts[:keys_only],
keys: opts[:key_files],
password: opts[:password],
forward_agent: opts[:forward_agent],
proxy_command: opts[:proxy_command],
bastion_host: opts[:bastion_host],
bastion_user: opts[:bastion_user],
bastion_port: opts[:bastion_port],
non_interactive: opts[:non_interactive],
transport_options: opts,
}
# disable host key verification. The hash key and value to use
# depends on the version of net-ssh in use.
connection_options[verify_host_key_option] = verify_host_key_value(opts[:verify_host_key])
connection_options
end
#
# Returns the correct host-key-verification option key to use depending
# on what version of net-ssh is in use. In net-ssh <= 4.1, the supported
# parameter is `paranoid` but in 4.2, it became `verify_host_key`
#
# `verify_host_key` does not work in <= 4.1, and `paranoid` throws
# deprecation warnings in >= 4.2.
#
# While the "right thing" to do would be to pin train's dependency on
# net-ssh to ~> 4.2, this will prevent InSpec from being used in
# Chef v12 because of it pinning to a v3 of net-ssh.
#
def verify_host_key_option
current_net_ssh = Net::SSH::Version::CURRENT
new_option_version = Net::SSH::Version[4, 2, 0]
current_net_ssh >= new_option_version ? :verify_host_key : :paranoid
end
# Likewise, version <5 accepted false; 5+ requires :never or will
# issue a deprecation warning. This method allows a lot of common
# things through.
def verify_host_key_value(given)
current_net_ssh = Net::SSH::Version::CURRENT
new_value_version = Net::SSH::Version[5, 0, 0]
if current_net_ssh >= new_value_version
# 5.0+ style
{
# It's not a boolean anymore.
"true" => :always,
"false" => :never,
true => :always,
false => :never,
# May be correct value, but strings from JSON config
"always" => :always,
"never" => :never,
nil => :never,
}.fetch(given, given)
else
# up to 4.2 style
{
"true" => true,
"false" => false,
nil => false,
}.fetch(given, given)
end
end
# Creates a new SSH Connection instance and save it for potential future
# reuse.
#
# @param options [Hash] conneciton options
# @return [Ssh::Connection] an SSH Connection instance
# @api private
def create_new_connection(options, &block)
if defined?(@connection)
logger.debug("[SSH] shutting previous connection #{@connection}")
@connection.close
end
@connection_options = options
conn = Connection.new(options, &block)
# Cisco IOS requires a special implementation of `Net:SSH`. This uses the
# SSH transport to identify the platform, but then replaces SSHConnection
# with a CiscoIOSConnection in order to behave as expected for the user.
if defined?(conn.platform.cisco_ios?) && conn.platform.cisco_ios?
ios_options = {}
ios_options[:host] = @options[:host]
ios_options[:user] = @options[:user]
# The enable password is used to elevate privileges on Cisco devices
# We will also support the sudo password field for the same purpose
# for the interim. # TODO
ios_options[:enable_password] = @options[:enable_password] || @options[:sudo_password]
ios_options[:logger] = @options[:logger]
ios_options.merge!(@connection_options)
conn = CiscoIOSConnection.new(ios_options)
end
@connection = conn unless conn.nil?
end
# Return the last saved SSH connection instance.
#
# @return [Ssh::Connection] an SSH Connection instance
# @api private
def reuse_connection
logger.debug("[SSH] reusing existing connection #{@connection}")
yield @connection if block_given?
@connection
end
end
end
train-3.2.28/lib/train/transports/ssh_connection.rb 0000664 0000000 0000000 00000026727 13645125472 0022415 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Fletcher Nichol ()
# Author:: Dominik Richter ()
# Author:: Christoph Hartmann ()
#
# Copyright (C) 2014, Fletcher Nichol
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "net/ssh"
require "net/scp"
require "timeout"
class Train::Transports::SSH
# A Connection instance can be generated and re-generated, given new
# connection details such as connection port, hostname, credentials, etc.
# This object is responsible for carrying out the actions on the remote
# host such as executing commands, transferring files, etc.
#
# @author Fletcher Nichol
class Connection < BaseConnection # rubocop:disable Metrics/ClassLength
attr_reader :hostname
attr_reader :transport_options
def initialize(options)
# Track IOS command retries to prevent infinite loop on IOError. This must
# be done before `super()` because the parent runs detection commands.
@ios_cmd_retries = 0
super(options)
@session = nil
@username = @options.delete(:username)
@hostname = @options.delete(:hostname)
@port = @options[:port] # don't delete from options
@connection_retries = @options.delete(:connection_retries)
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
@max_ssh_sessions = @options.delete(:max_ssh_connections) { 9 }
@transport_options = @options.delete(:transport_options)
@proxy_command = @options.delete(:proxy_command)
@bastion_host = @options.delete(:bastion_host)
@bastion_user = @options.delete(:bastion_user)
@bastion_port = @options.delete(:bastion_port)
@cmd_wrapper = CommandWrapper.load(self, @transport_options)
end
# (see Base::Connection#close)
def close
return if @session.nil?
logger.debug("[SSH] closing connection to #{self}")
session.close
ensure
@session = nil
end
def ssh_opts
level = logger.debug? ? "VERBOSE" : "ERROR"
fwd_agent = options[:forward_agent] ? "yes" : "no"
args = %w{ -o UserKnownHostsFile=/dev/null }
args += %w{ -o StrictHostKeyChecking=no }
args += %w{ -o IdentitiesOnly=yes } if options[:keys]
args += %w{ -o BatchMode=yes } if options[:non_interactive]
args += %W{ -o LogLevel=#{level} }
args += %W{ -o ForwardAgent=#{fwd_agent} } if options.key?(:forward_agent)
Array(options[:keys]).each do |ssh_key|
args += %W{ -i #{ssh_key} }
end
args
end
def check_proxy
[@proxy_command, @bastion_host].any? { |type| !type.nil? }
end
def generate_proxy_command
return @proxy_command unless @proxy_command.nil?
args = %w{ ssh }
args += ssh_opts
args += %W{ #{@bastion_user}@#{@bastion_host} }
args += %W{ -p #{@bastion_port} }
args += %w{ -W %h:%p }
args.join(" ")
end
# (see Base::Connection#login_command)
def login_command
args = ssh_opts
args += %W{ -o ProxyCommand='#{generate_proxy_command}' } if check_proxy
args += %W{ -p #{@port} }
args += %W{ #{@username}@#{@hostname} }
LoginCommand.new("ssh", args)
end
# (see Base::Connection#upload)
def upload(locals, remote)
waits = []
Array(locals).each do |local|
opts = File.directory?(local) ? { recursive: true } : {}
waits.push session.scp.upload(local, remote, opts) do |_ch, name, sent, total|
logger.debug("Uploaded #{name} (#{total} bytes)") if sent == total
end
waits.shift.wait while waits.length >= @max_ssh_sessions
end
waits.each(&:wait)
rescue Net::SSH::Exception => ex
raise Train::Transports::SSHFailed, "SCP upload failed (#{ex.message})"
end
def download(remotes, local)
waits = []
Array(remotes).map do |remote|
opts = file(remote).directory? ? { recursive: true } : {}
waits.push session.scp.download(remote, local, opts) do |_ch, name, recv, total|
logger.debug("Downloaded #{name} (#{total} bytes)") if recv == total
end
waits.shift.wait while waits.length >= @max_ssh_sessions
end
waits.each(&:wait)
rescue Net::SSH::Exception => ex
raise Train::Transports::SSHFailed, "SCP download failed (#{ex.message})"
end
# (see Base::Connection#wait_until_ready)
def wait_until_ready
delay = 3
session(
retries: @max_wait_until_ready / delay,
delay: delay,
message: "Waiting for SSH service on #{@hostname}:#{@port}, " \
"retrying in #{delay} seconds"
)
run_command(PING_COMMAND.dup)
end
def uri
"ssh://#{@username}@#{@hostname}:#{@port}"
end
# remote_port_forwarding
def forward_remote(port, host, remote_port, remote_host = "127.0.0.1")
@session.forward.remote(port, host, remote_port, remote_host)
end
def obscured_options
options_to_print = @options.clone
options_to_print[:password] = "" if options_to_print.key?(:password)
options_to_print
end
def with_sudo_pty
old_pty = transport_options[:pty]
transport_options[:pty] = true if @sudo
yield
ensure
transport_options[:pty] = old_pty
end
private
PING_COMMAND = "echo '[SSH] Established'".freeze
RESCUE_EXCEPTIONS_ON_ESTABLISH = [
Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::EPIPE,
Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout,
Timeout::Error
].freeze
# Establish an SSH session on the remote host.
#
# @param opts [Hash] retry options
# @option opts [Integer] :retries the number of times to retry before
# failing
# @option opts [Float] :delay the number of seconds to wait until
# attempting a retry
# @option opts [String] :message an optional message to be logged on
# debug (overriding the default) when a rescuable exception is raised
# @return [Net::SSH::Connection::Session] the SSH connection session
# @api private
def establish_connection(opts)
logger.debug("[SSH] opening connection to #{self}")
logger.debug("[SSH] using options %p" % [obscured_options])
if check_proxy
require "net/ssh/proxy/command"
@options[:proxy] = Net::SSH::Proxy::Command.new(generate_proxy_command)
end
Net::SSH.start(@hostname, @username, @options.clone.delete_if { |_key, value| value.nil? })
rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e
if (opts[:retries] -= 1) <= 0
logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
raise Train::Transports::SSHFailed, "SSH session could not be established"
end
if opts[:message]
logger.debug("[SSH] connection failed (#{e.inspect})")
message = opts[:message]
else
message = "[SSH] connection failed, retrying in #{opts[:delay]}"\
" seconds (#{e.inspect})"
end
logger.info(message)
sleep(opts[:delay])
retry
end
def file_via_connection(path, *args)
if os.aix?
Train::File::Remote::Aix.new(self, path, *args)
elsif os.solaris?
Train::File::Remote::Unix.new(self, path, *args)
elsif os[:name] == "qnx"
Train::File::Remote::Qnx.new(self, path, *args)
elsif os.windows?
Train::File::Remote::Windows.new(self, path, *args)
else
Train::File::Remote::Linux.new(self, path, *args)
end
end
def run_command_via_connection(cmd, &data_handler)
cmd.dup.force_encoding("binary") if cmd.respond_to?(:force_encoding)
reset_session if session.closed?
exit_status, stdout, stderr = execute_on_channel(cmd, &data_handler)
# Since `@session.loop` succeeded, reset the IOS command retry counter
@ios_cmd_retries = 0
CommandResult.new(stdout, stderr, exit_status)
rescue Net::SSH::Exception => ex
raise Train::Transports::SSHFailed, "SSH command failed (#{ex.message})"
rescue IOError
# Cisco IOS occasionally closes the stream prematurely while we are
# running commands to detect if we need to switch to the Cisco IOS
# transport. This retries the command if this is the case.
# See:
# https://github.com/inspec/train/pull/271
logger.debug("[SSH] Possible Cisco IOS race condition, retrying command")
# Only attempt retry up to 5 times to avoid infinite loop
@ios_cmd_retries += 1
raise if @ios_cmd_retries >= 5
retry
end
# Returns a connection session, or establishes one when invoked the
# first time.
#
# @param retry_options [Hash] retry options for the initial connection
# @return [Net::SSH::Connection::Session] the SSH connection session
# @api private
def session(retry_options = {})
@session ||= establish_connection({
retries: @connection_retries.to_i,
delay: @connection_retry_sleep.to_i,
}.merge(retry_options))
end
def reset_session
@session = nil
end
# String representation of object, reporting its connection details and
# configuration.
#
# @api private
def to_s
"#{@username}@#{@hostname}"
end
# Given a channel and a command string, it will execute the command on the channel
# and accumulate results in @stdout/@stderr.
#
# @param channel [Net::SSH::Connection::Channel] an open ssh channel
# @param cmd [String] the command to execute
# @return [Integer] exit status or nil if exit-status/exit-signal requests
# not received.
#
# @api private
def execute_on_channel(cmd)
stdout = ""
stderr = ""
exit_status = nil
session.open_channel do |channel|
# wrap commands if that is configured
cmd = @cmd_wrapper.run(cmd) if @cmd_wrapper
logger.debug("[SSH] #{self} cmd = #{cmd}")
if @transport_options[:pty]
channel.request_pty do |_ch, success|
raise Train::Transports::SSHPTYFailed, "Requesting PTY failed" unless success
end
end
channel.exec(cmd) do |_, success|
abort "Couldn't execute command on SSH." unless success
channel.on_data do |_, data|
yield(data) if block_given?
stdout += data
end
channel.on_extended_data do |_, _type, data|
yield(data) if block_given?
stderr += data
end
channel.on_request("exit-status") do |_, data|
exit_status = data.read_long
end
channel.on_request("exit-signal") do |_, data|
exit_status = data.read_long
end
end
end
session.loop
[exit_status, stdout, stderr]
end
end
end
train-3.2.28/lib/train/transports/vmware.rb 0000664 0000000 0000000 00000012553 13645125472 0020672 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require "train/plugins"
require "open3"
require "ostruct"
require "json"
require "mkmf"
module Train::Transports
class VMware < Train.plugin(1)
name "vmware"
option :viserver, default: proc { ENV["VISERVER"] }
option :username, default: proc { ENV["VISERVER_USERNAME"] }
option :password, default: proc { ENV["VISERVER_PASSWORD"] }
option :insecure, default: false
def connection(_ = nil)
@connection ||= Connection.new(@options)
end
class Connection < BaseConnection # rubocop:disable ClassLength
POWERSHELL_PROMPT_REGEX = /PS\s.*> $/.freeze
def initialize(options)
super(options)
options[:viserver] = options[:viserver] || options[:host]
options[:username] = options[:username] || options[:user]
@username = options[:username]
@viserver = options[:viserver]
@session = nil
@stdout_buffer = ""
@stderr_buffer = ""
@powershell_binary = detect_powershell_binary
if @powershell_binary == :powershell
require_relative "local"
@powershell = Train::Transports::Local::Connection.new(options)
end
if options[:insecure] == true
run_command_via_connection("Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$False")
end
@platform_details = {
release: "vmware-powercli-#{powercli_version}",
}
connect
end
def connect
login_command = "Connect-VIServer #{options[:viserver]} -User #{options[:username]} -Password #{options[:password]} | Out-Null"
result = run_command_via_connection(login_command)
if result.exit_status != 0
message = "Unable to connect to VIServer at #{options[:viserver]}. "
case result.stderr
when /Invalid server certificate/
message += "Certification verification failed. Please use `--insecure` or set `Set-PowerCLIConfiguration -InvalidCertificateAction Ignore` in PowerShell"
when /incorrect user name or password/
message += "Incorrect username or password"
else
message += result.stderr.gsub(/-Password .*\s/, "-Password REDACTED")
end
raise message
end
end
def platform
force_platform!("vmware", @platform_details)
end
def run_command_via_connection(cmd, &_data_handler)
if @powershell_binary == :pwsh
result = parse_pwsh_output(cmd)
# Attach exit status to result
exit_status = parse_pwsh_output("echo $?").stdout.chomp
result.exit_status = exit_status == "True" ? 0 : 1
result
else
@powershell.run_command(cmd)
end
end
def unique_identifier
uuid_command = "(Get-VMHost | Get-View).hardware.systeminfo.uuid"
run_command_via_connection(uuid_command).stdout.chomp
end
def uri
"vmware://#{@username}@#{@viserver}"
end
private
def detect_powershell_binary
if find_executable0("pwsh")
:pwsh
elsif find_executable0("powershell")
:powershell
else
raise "Cannot find PowerShell binary, is `pwsh` installed?"
end
end
# Read from stdout pipe until prompt is received
def flush_stdout(pipe)
@stdout_buffer += pipe.read_nonblock(1) while @stdout_buffer !~ POWERSHELL_PROMPT_REGEX
@stdout_buffer
rescue IO::EAGAINWaitReadable
# We cannot know when the stdout pipe is finished so we keep reading
retry
ensure
@stdout_buffer = ""
end
# This must be called after `flush_stdout` to ensure buffer is full
def flush_stderr(pipe)
loop do
@stderr_buffer += pipe.read_nonblock(1)
end
rescue IO::EAGAINWaitReadable
# If `flush_stderr` is ran after reading stdout we know that all of
# stderr is in the pipe. Thus, we can return the buffer once the pipe
# is unreadable.
@stderr_buffer
ensure
@stderr_buffer = ""
end
def parse_pwsh_output(cmd)
session.stdin.puts(cmd)
stdout = flush_stdout(session.stdout)
# Remove stdin from stdout (including trailing newline)
stdout.slice!(0, cmd.length + 1)
# Remove prompt from stdout
stdout.gsub!(POWERSHELL_PROMPT_REGEX, "")
# Grab stderr
stderr = flush_stderr(session.stderr)
CommandResult.new(
stdout,
stderr,
nil # exit_status is attached in `run_command_via_connection`
)
end
def powercli_version
version_command = "[string](Get-Module -Name VMware.PowerCLI -ListAvailable | Select -ExpandProperty Version)"
result = run_command_via_connection(version_command)
if result.stdout.empty? || result.exit_status != 0
raise "Unable to determine PowerCLI Module version, is it installed?"
end
result.stdout.chomp
end
def session
return @session unless @session.nil?
stdin, stdout, stderr = Open3.popen3("pwsh")
# Remove leading prompt and intro text
flush_stdout(stdout)
@session = OpenStruct.new
@session.stdin = stdin
@session.stdout = stdout
@session.stderr = stderr
@session
end
end
end
end
train-3.2.28/lib/train/version.rb 0000664 0000000 0000000 00000000173 13645125472 0016632 0 ustar 00root root 0000000 0000000 # encoding: utf-8
#
# Author:: Dominik Richter ()
module Train
VERSION = "3.2.28".freeze
end
train-3.2.28/test/ 0000775 0000000 0000000 00000000000 13645125472 0013713 5 ustar 00root root 0000000 0000000 train-3.2.28/test/fixtures/ 0000775 0000000 0000000 00000000000 13645125472 0015564 5 ustar 00root root 0000000 0000000 train-3.2.28/test/fixtures/PowerShell/ 0000775 0000000 0000000 00000000000 13645125472 0017650 5 ustar 00root root 0000000 0000000 train-3.2.28/test/fixtures/PowerShell/exit_fortytwo.ps1 0000664 0000000 0000000 00000000026 13645125472 0023221 0 ustar 00root root 0000000 0000000 echo 'Goodbye'
exit 42 train-3.2.28/test/fixtures/PowerShell/exit_zero.ps1 0000664 0000000 0000000 00000000014 13645125472 0022300 0 ustar 00root root 0000000 0000000 echo 'Hello' train-3.2.28/test/fixtures/PowerShell/throws.ps1 0000664 0000000 0000000 00000000050 13645125472 0021616 0 ustar 00root root 0000000 0000000 echo 'Next line throws'
Throw 'Oh dear.' train-3.2.28/test/fixtures/plugins/ 0000775 0000000 0000000 00000000000 13645125472 0017245 5 ustar 00root root 0000000 0000000 train-3.2.28/test/fixtures/plugins/train-test-fixture/ 0000775 0000000 0000000 00000000000 13645125472 0023023 5 ustar 00root root 0000000 0000000 train-3.2.28/test/fixtures/plugins/train-test-fixture/LICENSE 0000664 0000000 0000000 00000025142 13645125472 0024034 0 ustar 00root root 0000000 0000000 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
train-3.2.28/test/fixtures/plugins/train-test-fixture/README.md 0000664 0000000 0000000 00000000532 13645125472 0024302 0 ustar 00root root 0000000 0000000 This is a very simple train transport plugin. It provided fixed responses to file.content and command.stdout/stderr/exit_status.
It is not a good example to use for learning, nor a good base for starting your own plugin - it's intended for for use during the testing of Train.
For good examples of plugin development, see train/examples/plugin. train-3.2.28/test/fixtures/plugins/train-test-fixture/lib/ 0000775 0000000 0000000 00000000000 13645125472 0023571 5 ustar 00root root 0000000 0000000 train-3.2.28/test/fixtures/plugins/train-test-fixture/lib/train-test-fixture.rb 0000664 0000000 0000000 00000000216 13645125472 0027673 0 ustar 00root root 0000000 0000000 lib = File.expand_path("../../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "train-test-fixture/transport"
train-3.2.28/test/fixtures/plugins/train-test-fixture/lib/train-test-fixture/ 0000775 0000000 0000000 00000000000 13645125472 0027347 5 ustar 00root root 0000000 0000000 train-3.2.28/test/fixtures/plugins/train-test-fixture/lib/train-test-fixture/connection.rb 0000664 0000000 0000000 00000001413 13645125472 0032032 0 ustar 00root root 0000000 0000000 require "train-test-fixture/platform"
require "train/transports/local"
module TrainPlugins
module TestFixture
class Connection < Train::Plugins::Transport::BaseConnection
include TrainPlugins::TestFixture::Platform
def initialize(options)
super(options)
end
private
def run_command_via_connection(cmd)
Train::Transports::Local::CommandResult.new(
"Mock Command Result stdout",
"Mock Command Result stderr",
17
)
end
def file_via_connection(path, *args)
MockFile.new(self, path)
end
class MockFile < Train::File
def content
# Remarkably, the content is always the same.
"Lorem Ipsum"
end
end
end
end
end
train-3.2.28/test/fixtures/plugins/train-test-fixture/lib/train-test-fixture/platform.rb 0000664 0000000 0000000 00000001023 13645125472 0031514 0 ustar 00root root 0000000 0000000 require "train-test-fixture/version"
module TrainPlugins
module TestFixture
module Platform
def platform
# Build this platform's family declarations.
# You'll need at least unix and windows to make the file() resource work.
Train::Platforms.name("test-fixture").in_family("unix")
Train::Platforms.name("test-fixture").in_family("windows")
force_platform!("test-fixture",
release: TrainPlugins::TestFixture::VERSION,
arch: "mock")
end
end
end
end
train-3.2.28/test/fixtures/plugins/train-test-fixture/lib/train-test-fixture/transport.rb 0000664 0000000 0000000 00000000432 13645125472 0031727 0 ustar 00root root 0000000 0000000 require "train-test-fixture/connection"
module TrainPlugins
module TestFixture
class Transport < Train.plugin(1)
name "test-fixture"
def connection(_ = nil)
@connection ||= TrainPlugins::TestFixture::Connection.new(@options)
end
end
end
end
train-3.2.28/test/fixtures/plugins/train-test-fixture/lib/train-test-fixture/version.rb 0000664 0000000 0000000 00000000120 13645125472 0031352 0 ustar 00root root 0000000 0000000 module TrainPlugins
module TestFixture
VERSION = "0.1.0".freeze
end
end
train-3.2.28/test/fixtures/plugins/train-test-fixture/pkg/ 0000775 0000000 0000000 00000000000 13645125472 0023604 5 ustar 00root root 0000000 0000000 train-3.2.28/test/fixtures/plugins/train-test-fixture/pkg/train-test-fixture-0.1.0.gem 0000664 0000000 0000000 00000023000 13645125472 0030501 0 ustar 00root root 0000000 0000000 metadata.gz 0000444 0000000 0000000 00000001240 13352730225 013435 0 ustar 00wheel wheel 0000000 0000000 [TMo0WB>
"*Am"3b;ت;mQr$JdY.*ޢ^k%I;+4XymF([
w (/KZ;o*ks>T"38-(ж"zm[ &xۨ="V6WOS:.OE#YV>PU|GA4i@ҟ\U.ȅ~ǒR0=H&v!#l瑱ӻa +>$ˢ~}Ó8UfhL̇i4O
N[y&skv2Ĝ99"uDCԍ\9St"E:y#FB_lSbJx{:\ ܠIʸ!
W/k8UFLrɣenzUܷbWY{a3xEq0TۢʊտV$ Yld$;^VpK.PͫH}m0,.Y{RxG Kyc[{:bĮoLƗ}x%eL\I/^hY1%.܄WfX!>a4Fz\/&l5sujRO]?|.4o+z data.tar.gz 0000444 0000000 0000000 00000012460 13352730225 013361 0 ustar 00wheel wheel 0000000 0000000 [\{s7XD]JRŒɒKחJTHb=3`3~ 0=D"F?
t?ɓGZtvGbgx5)3CWy{_GsΔ8թ*5/ڔbe#}yƬ;;b(i;9obwzWX8?;].$'/Ώu<}?XMtk{HD/L(,E
GUUX!LxJ4V%RdM'̴+=nC!p3R\W+LgOLq&m
ULFRjJOg0RUY^
3SfȦLVfSVSZtmsJȔa,aK+˛䉐or5CM)q7J,t=Exxi*`TsjѲ1Ȗ$wKV63U%
dODmD*A8ԕ(d)
;&9.N
M%-Ш2@
$;s\f'R\wtm KӴJS32 JY7V%< N?/0۱Fg
.TXp"EB[KL:NB%쓂m4DU̥b{\0I.Ӽu"ׅ}AdLꐥ@kěDOr ueπMN?Lә,XR~}iqZCDH,p=5Z!Ѧ q >2!8
;^E*Ry8[S_3|HGA]j[zVn;J!3
7Rr{{