train-habitat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: eac163fde458b35d8ca76275c7855ffa74a85cdc
4
+ data.tar.gz: be696afb0d1f2bbb6562c030fe88b566e3004ae9
5
+ SHA512:
6
+ metadata.gz: fa6c53f04316c25a25a0a393a789abe030f11dbb6097a240a630fe0667b5ed44be447f29306e98e60e03bc9ff41bdca50278712c8829ad53083f43e5e32cc2ed
7
+ data.tar.gz: a4e3061e4a97b64e57e60408327f09707f7630a02b639c6e6ea4f943d01fdb72563770afd0e37c0eff921e7430edd24372c09ae4e902e72c5804c6d053247ff8
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development do
8
+ gem 'byebug', '~> 11.0'
9
+ gem 'm', '~> 1.5'
10
+ gem 'minitest', '~> 5.11'
11
+ gem 'mocha', '~> 1.8'
12
+ gem 'pry', '~> 0.11'
13
+ gem 'rake', '~> 12.3'
14
+ gem 'rubocop', '~> 0.59'
15
+ end
data/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # Train-Habitat
2
+
3
+ `train-habitat` is a Train plugin and is used as a Train Transport to connect to Habitat installations.
4
+
5
+ ## To Install this as a User
6
+
7
+ You will need InSpec v2.3 or later.
8
+
9
+ Simply run:
10
+
11
+ ```
12
+ $ inspec plugin install train-habitat
13
+ ```
14
+
15
+ ## Using train-habitat from InSpec
16
+
17
+ As `train-habitat` takes potentially many options, it is simplest to list the options in your `~/.inspec/config.json` file, then used the named set of options with `-t`.
18
+
19
+ For example, if your config file contains:
20
+
21
+ ```
22
+ {
23
+ "file_version": "1.1",
24
+ "credentials": {
25
+ "habitat": {
26
+ "dev-hab": {
27
+ "api_url": "http://dev-hab.my-corp.io",
28
+ "cli_ssh_host": "dev-hab.my-corp.io"
29
+ },
30
+ "prod-hab": {
31
+ "api_url": "https://prod-hab.my-corp.io",
32
+ "api_auth_token": "opensesame"
33
+ },
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ Using this configuration, you could execute:
40
+
41
+ ```
42
+ $ inspec exec some-profile -t habitat://dev-hab
43
+ # Or
44
+ $ inspec exec some-profile -t habitat://prod-hab
45
+ ```
46
+
47
+ You may also pass `--config some-file.json` to use a config file at a different location.
48
+
49
+ See the next section for the full list of options you may use with a `habitat` credential set in your configuration.
50
+
51
+ ## Using train-habitat from Ruby
52
+
53
+ The options that may be passed to `Train.create` are listed below.
54
+
55
+ ### Dual-mode transport
56
+
57
+ Because Habitat exposes some facts by its HTTP Gateway API, and some facts by its CLI tool `hab`, this Train Transport has three modes of operation:
58
+
59
+ * Using only the HTTP API (no ability to query packages, but rich ability to query rings)
60
+ * Using only the `hab` CLI command (limitations TBD)
61
+ * Using both (full capabilities)
62
+
63
+ When creating a `train-habitat` Connection, there are thus two sets of options, prefixed with `api_` and `cli_` respectively. You must provide at least one set.
64
+
65
+ ### API-Mode options
66
+
67
+ API-mode options are used to connect to a Habitat Supervisor running with an exposed HTTP Gateway. They are prefixed with `api_`.
68
+
69
+ ```ruby
70
+ Train.create(:habitat, api_url: 'http://my-hab.my-company.io:9631')
71
+ ```
72
+
73
+ #### api_url
74
+
75
+ Required for API-mode use. This is an HTTP or HTTPS URL that identifies a Supervisor HTTP Gateway. If the port is omitted from the URL, the API standard port of 9631 is assumed; to use port 80, specify it explicitly.
76
+
77
+ #### api_auth_token
78
+
79
+ The supervisor may be configured to require a [Bearer Token Authorization](https://www.habitat.sh/docs/using-habitat/#monitor-services-through-the-http-api), in which the client and the gateway use a pre-shared secret. Use this option to specify the secret.
80
+
81
+ ### CLI Mode options
82
+
83
+ CLI options are more varied, and are entirely dependent on the underlying transport chosen to reach the CLI. For example, if there were a supported transport named 'radio' that took options 'channel' and 'band', specify them to train-habitat like this:
84
+
85
+ ```ruby
86
+ Train.create(:habitat, {cli_radio_band: 'VHF', cli_radio_channel: 23})
87
+ ```
88
+
89
+ `train-habitat` identifies the underlying "sub-transport" using the prefixes of the provided options. For example, if you pass an option named `cli_ssh_host`, `train-habitat` will recognize that you intend to use the SSH transport to connect to a location that has access to the `hab` CLI tool.
90
+
91
+ You may specify many options referring to the same sub-transport (such as credentials), but it is an error to specify more than one CLI sub-transport.
92
+
93
+ Currently supported CLI transports include:
94
+ * SSH
95
+
96
+ Plans for future support include (in approximate order):
97
+ * WinRM
98
+ * Local
99
+ * Docker
100
+
101
+ #### General Options
102
+
103
+ Any options not prefixed with `cli_` or `api_` are also passed to the CLI transport. This means you can use generic Train connection options such as the `sudo` and `shell` sets of options (see [train source code](https://github.com/inspec/train/blob/71679307903fc8853e09abd93f3901c83800e019/lib/train/extras/command_wrapper.rb#L31)), as well as `logger`.
104
+
105
+ #### SSH Options
106
+
107
+ `train-habitat` can accept any option that the Train SSH Transport accepts if the prefix `cli_ssh_` is added. This includes:
108
+
109
+ * `cli_ssh_host` - String hostname or IP address
110
+ * `cli_ssh_user` - String user to connect as
111
+ * `cli_ssh_key_files` - Array of paths to private key files to use
112
+
113
+ Other options are available; see [train source code](https://github.com/inspec/train/blob/71679307903fc8853e09abd93f3901c83800e019/lib/train/transports/ssh.rb#L45) for details.
114
+
115
+ ## Development
116
+
117
+ ### Testing
118
+ ```
119
+ # Install development tools
120
+ $ gem install bundler
121
+ $ bundle install
122
+
123
+ # Running style checker
124
+ bundle exec rake lint
125
+
126
+ # Running unit tests
127
+ bundle exec rake test:unit
128
+
129
+ # Running integration tests (requires Vagrant and VirtualBox)
130
+ bundle exec rake test:integration
131
+ ```
132
+
133
+ ## Contributing
134
+
135
+ 1. Fork it
136
+ 1. Create your feature branch (git checkout -b my-new-feature)
137
+ 1. Commit your changes (git commit -sam 'Add some feature')
138
+ 1. Push to the branch (git push origin my-new-feature)
139
+ 1. Create new Pull Request
140
+
141
+ ## License
142
+
143
+ | **Author:** | Paul Welch
144
+
145
+ | **Author:** | David McCown
146
+
147
+ | **Author:** | Clinton Wolfe
148
+
149
+ | **Copyright:** | Copyright (c) 2018-2019 Chef Software Inc.
150
+
151
+ | **License:** | Apache License, Version 2.0
152
+
153
+ Licensed under the Apache License, Version 2.0 (the "License");
154
+ you may not use this file except in compliance with the License.
155
+ You may obtain a copy of the License at
156
+
157
+ http://www.apache.org/licenses/LICENSE-2.0
158
+
159
+ Unless required by applicable law or agreed to in writing, software
160
+ distributed under the License is distributed on an "AS IS" BASIS,
161
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
162
+ See the License for the specific language governing permissions and
163
+ limitations under the License.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ libdir = File.dirname(__FILE__)
4
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
5
+
6
+ require 'train-habitat/version'
7
+ require 'train-habitat/transport'
8
+ require 'train-habitat/platform'
9
+ require 'train-habitat/connection'
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'train-habitat/httpgateway'
6
+ require 'train-habitat/platform'
7
+ require 'train-habitat/transport'
8
+
9
+ module TrainPlugins
10
+ module Habitat
11
+ class Connection < Train::Plugins::Transport::BaseConnection
12
+ include TrainPlugins::Habitat::Platform
13
+
14
+ attr_reader :cli_transport_name, :cli_connection, :cli_transport, :transport_options
15
+
16
+ def initialize(options = {})
17
+ @transport_options = options
18
+ validate_transport_options!
19
+
20
+ super(transport_options)
21
+ initialize_cli_connection! if cli_options_provided?
22
+ enable_cache :api_call
23
+ end
24
+
25
+ def uri
26
+ "habitat://#{@options[:host]}"
27
+ end
28
+
29
+ def api_options_provided?
30
+ have_transport_options_with_prefix?('api')
31
+ end
32
+
33
+ def cli_options_provided?
34
+ TrainPlugins::Habitat::Transport.cli_transport_prefixes.keys.map(&:to_s).any? do |prefix|
35
+ have_transport_options_with_prefix?(prefix)
36
+ end
37
+ end
38
+
39
+ # Use this to execute a `hab` command. Do not include the `hab` executable in the invocation.
40
+ def run_hab_cli(command, _exec_options = {})
41
+ raise CliNotAvailableError(cli_tranport_names) unless cli_options_provided?
42
+
43
+ # TODO: - leverage exec_options to add things like JSON parsing, ENV setting, etc.
44
+ cli_connection.run_command(hab_path + ' ' + command)
45
+ end
46
+
47
+ def hab_path
48
+ '/bin/hab'
49
+ end
50
+
51
+ def habitat_api_client
52
+ cached_client(:api_call, :HTTPGateway) do
53
+ # Send all options beginning with api_ to the HTTPGateway, stripping the prefix
54
+ api_options = {}
55
+ transport_options.each do |option_name, option_value|
56
+ next unless option_name.to_s.start_with? 'api_'
57
+
58
+ api_options[option_name.to_s.sub(/^api_/, '').to_sym] = option_value
59
+ end
60
+ HTTPGateway.new(api_options)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def validate_transport_options!
67
+ unless api_options_provided? || cli_options_provided?
68
+ raise Train::TransportError, "Habitat connection options that begin with either 'cli' or 'api' required"
69
+ end
70
+
71
+ valid_cli_prefixes = TrainPlugins::Habitat::Transport.cli_transport_prefixes.keys.map(&:to_s)
72
+ seen_cli_options = transport_options.keys.map(&:to_s).select { |n| n.start_with?('cli_') }
73
+
74
+ # All seen CLI options must start with a recognized prefix
75
+ options_by_prefix = {}
76
+ seen_cli_options.each do |option|
77
+ prefix = valid_cli_prefixes.detect { |p| option.start_with?(p) }
78
+ unless prefix
79
+ raise Train::TransportError, "All Habitat CLI connection options must begin with a recognized prefix (#{valid_cli_prefixes.join(', ')}) - saw #{option}"
80
+ end
81
+
82
+ options_by_prefix[prefix] ||= []
83
+ options_by_prefix[prefix] << option
84
+ end
85
+
86
+ # Only one prefix may be used (don't mix and match)
87
+ if options_by_prefix.count > 1
88
+ raise Train::TransportError, "Only one set of Habitat CLI connection options may be used - saw #{options_by_prefix.keys.join(', ')}"
89
+ end
90
+ end
91
+
92
+ def cli_transport_names
93
+ TrainPlugins::Habitat::Transport.cli_transport_prefixes.values
94
+ end
95
+
96
+ def have_transport_options_with_prefix?(prefix)
97
+ transport_options.keys.map(&:to_s).any? { |option_name| option_name.start_with? prefix }
98
+ end
99
+
100
+ def initialize_cli_connection! # rubocop:disable Metrics/AbcSize
101
+ return if @cli_connection
102
+ raise CliNotAvailableError(cli_tranport_names) unless cli_options_provided?
103
+
104
+ # group cli connection options by prefix
105
+ # Our incoming connection options have prefixes, so we see things like 'cli_ssh_host'.
106
+ # The ssh transport wants just 'host'. So identify which transports we have
107
+ # options for, and trim them down.
108
+ known_prefixes = TrainPlugins::Habitat::Transport.cli_transport_prefixes.keys.map(&:to_s)
109
+
110
+ seen_cli_transports = {}
111
+ non_specific_options = {} # Things like :logger, :sudo, etc
112
+ transport_options.each do |xport_option_name, xport_option_value|
113
+ unless xport_option_name.to_s.start_with?('cli_')
114
+ non_specific_options[xport_option_name] = xport_option_value
115
+ next
116
+ end
117
+ known_prefixes.each do |prefix|
118
+ next unless xport_option_name.to_s.start_with?(prefix)
119
+
120
+ xport_name = TrainPlugins::Habitat::Transport.cli_transport_prefixes[prefix.to_sym]
121
+
122
+ seen_cli_transports[xport_name] ||= {}
123
+ # Remove the prefix from the option and store under transport name
124
+ seen_cli_transports[xport_name][xport_option_name.to_s.sub(/^#{prefix}_/, '').to_sym] = xport_option_value
125
+ end
126
+ end
127
+
128
+ raise MultipleCliTransportsError, seen_cli_tranports.keys if seen_cli_transports.count > 1
129
+
130
+ # If no specific logger was passed in, re-use ours.
131
+ non_specific_options[:logger] = logger
132
+
133
+ @cli_transport_name = seen_cli_transports.keys.first
134
+ final_train_options = seen_cli_transports[cli_transport_name].merge(non_specific_options)
135
+ @cli_transport = Train.create(cli_transport_name, final_train_options)
136
+ @cli_connection = cli_transport.connection
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,46 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+
4
+ module TrainPlugins
5
+ module Habitat
6
+ class HTTPGateway
7
+ Response = Struct.new(:code, :body, :raw_response)
8
+
9
+ attr_reader :base_uri
10
+
11
+ def initialize(opts)
12
+ @base_uri = URI(opts[:url])
13
+ # check for provided port and default if not provided
14
+ if base_uri.port == 80 && opts[:url] !~ %r{\w+:\d+(\/|$)}
15
+ base_uri.port = 9631
16
+ end
17
+
18
+ @auth_token = opts[:auth_token]
19
+ end
20
+
21
+ def get_path(path)
22
+ uri = base_uri.dup
23
+ uri.path = path
24
+ headers = {}
25
+ unless auth_token.nil?
26
+ headers['Authorization'] = 'Bearer ' + auth_token # Set bearer token, see https://www.habitat.sh/docs/using-habitat/#authentication
27
+ end
28
+
29
+ conn = Net::HTTP.start(uri.host, uri.port)
30
+ conn.read_timeout = 5
31
+
32
+ resp = Response.new
33
+ resp.raw_response = conn.get(uri, headers)
34
+
35
+ resp.code = resp.raw_response.code.to_i
36
+ if resp.code == 200
37
+ resp.body = JSON.parse(resp.raw_response.body, symbolize_names: true)
38
+ end
39
+ resp
40
+ end
41
+
42
+ # Private accessor
43
+ attr_reader :auth_token
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IllegalStateError < StandardError
4
+ def initialize(msg = nil)
5
+ super
6
+ end
7
+ end
8
+
9
+ class NoServicesFoundError < IllegalStateError
10
+ def initialize(origin, name)
11
+ super("Expected one service '#{origin}/#{name}', but found none.")
12
+ end
13
+ end
14
+
15
+ class MultipleServicesFoundError < IllegalStateError
16
+ def initialize(origin, name)
17
+ super("Expected one service '#{origin}/#{name}', but found multiple.")
18
+ end
19
+ end
20
+
21
+ class CliNotAvailableError < IllegalStateError
22
+ def initialize(_cli_tranport_names)
23
+ super("No CLI-capable connection options passed - use one of #{cli_transport_names.join(', ')} option sets")
24
+ end
25
+ end
26
+
27
+ class MultipleCliTransportsError < IllegalStateError
28
+ def initialize(_cli_tranport_names)
29
+ super("Too many CLI-capable transports specified - only one may be used at a time. Saw: #{cli_transport_names.join(', ')}.")
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrainPlugins
4
+ module Habitat
5
+ module Platform
6
+ def platform
7
+ Train::Platforms.name('habitat').in_family('api')
8
+ force_platform!('habitat', release: TrainPlugins::Habitat::VERSION)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ require 'train'
2
+ require 'train/plugins'
3
+ require 'train-habitat/connection'
4
+
5
+ module TrainPlugins
6
+ module Habitat
7
+ class Transport < Train.plugin(1)
8
+ name 'habitat'
9
+
10
+ # The train-habitat plugins is a chimeric plugin, meaning it uses
11
+ # multiple ways of connecting to its target. A user must specifiy
12
+ # at least one set, but they can use multiple sets of connecting
13
+ # information to get a full experience.
14
+
15
+ # For service listings and health, specify supervisor api options.
16
+ # https://www.habitat.sh/docs/using-habitat/#monitor-services-through-the-http-api
17
+ option :api_url, required: false, desc: 'The url at which a Habitat Supervisor exposes its HTTP Gateway API'
18
+ option :api_auth_token, required: false, desc: 'A bearer token which may be used to authenticate to the Supervisor HTTP Gateway'
19
+
20
+ def self.cli_transport_prefixes
21
+ {
22
+ # add transports here, prefix => transport name
23
+ cli_ssh: :ssh,
24
+ }
25
+ end
26
+
27
+ # inspec -t habitat://credset
28
+ def connection(_instance_opts = nil)
29
+ @connection ||= TrainPlugins::Habitat::Connection.new(@options)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrainPlugins
4
+ module Habitat
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'train-habitat/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'train-habitat'
9
+ spec.version = TrainPlugins::Habitat::VERSION
10
+ spec.authors = ['Chef InSpec Team']
11
+ spec.email = ['inspec@chef.io']
12
+ spec.summary = 'Habitat API Transport for Train'
13
+ spec.description = 'Allows applications using Train to speak to Habitat.'
14
+ spec.homepage = 'https://github.com/inspec/train-habitat'
15
+ spec.license = 'Apache-2.0'
16
+
17
+ spec.files = %w{
18
+ README.md train-habitat.gemspec Gemfile
19
+ } + Dir.glob(
20
+ 'lib/**/*', File::FNM_DOTMATCH
21
+ ).reject { |f| File.directory?(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ # All plugins should mention train, > 1.4
25
+ spec.add_dependency 'train', '>= 1.7.8', '< 3.0'
26
+
27
+ spec.add_development_dependency 'minitest', '~> 5.0'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: train-habitat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chef InSpec Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: train
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.7.8
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.7.8
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: minitest
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '10.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '10.0'
61
+ description: Allows applications using Train to speak to Habitat.
62
+ email:
63
+ - inspec@chef.io
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - Gemfile
69
+ - README.md
70
+ - lib/train-habitat.rb
71
+ - lib/train-habitat/connection.rb
72
+ - lib/train-habitat/httpgateway.rb
73
+ - lib/train-habitat/illegal_state_error.rb
74
+ - lib/train-habitat/platform.rb
75
+ - lib/train-habitat/transport.rb
76
+ - lib/train-habitat/version.rb
77
+ - train-habitat.gemspec
78
+ homepage: https://github.com/inspec/train-habitat
79
+ licenses:
80
+ - Apache-2.0
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.6.14.3
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Habitat API Transport for Train
102
+ test_files: []