train-habitat 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []