comply-cli 0.0.1
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 +7 -0
- data/.github/CODEOWNERS +1 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.md +22 -0
- data/README.md +54 -0
- data/Rakefile +4 -0
- data/bin/comply +20 -0
- data/comply-cli.gemspec +34 -0
- data/lib/comply/cli.rb +9 -0
- data/lib/comply/cli/agent.rb +122 -0
- data/lib/comply/cli/helpers/integration.rb +55 -0
- data/lib/comply/cli/helpers/program.rb +81 -0
- data/lib/comply/cli/helpers/token.rb +54 -0
- data/lib/comply/cli/subcommands/integrations.rb +56 -0
- data/lib/comply/cli/version.rb +5 -0
- data/lib/comply/ext/string.rb +5 -0
- data/script/sync-readme-usage +34 -0
- data/spec/comply/cli/agent_spec.rb +196 -0
- data/spec/comply/cli/helpers/program_spec.rb +41 -0
- data/spec/comply/cli/helpers/token_spec.rb +41 -0
- data/spec/comply/cli/subcommands/groups_spec.rb +4 -0
- data/spec/comply/cli/subcommands/integrations_spec.rb +4 -0
- data/spec/comply/cli_spec.rb +4 -0
- data/spec/fabricators/organization_fabricator.rb +8 -0
- data/spec/fabricators/program_fabricator.rb +11 -0
- data/spec/spec_helper.rb +12 -0
- metadata +235 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fcf980a7d9d589a6a1cfe1da71914a000bbe0449847e87ea3045eb0712322d8e
|
4
|
+
data.tar.gz: f2842dbe4f9e4e2d2396a3b8ac6c14a722c09d48d6ad7240b6ef8bcd73b1c5e6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f78cb51dd7da44ad62c11d12e7465e06f8f4c3510be00851ce6e860647f4c33fa8c769adf70c6e18f39416f45b207e86b7b359c1a60386ffdb172da2841edd30
|
7
|
+
data.tar.gz: 2adb450ccc5370b987d933721d3b2c82dd734e1dfe71cbef8a4349cc06a5b800c6eca041516e38c3612558f00da603c12aff69735453aaeac0443af0848ff164
|
data/.github/CODEOWNERS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* @wongal5
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Aptible, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Comply CLI
|
2
|
+
|
3
|
+
Command-line interface for Aptible Comply.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
_Note: while this gem is in development, it is (a) private, and (b) built against API endpoints that are still in development. As a result, the instructions here are both temporary (subject to change) and more complicated than they will eventually be._
|
8
|
+
|
9
|
+
1. Clone this repo: `git clone git@github.com:aptible/comply-cli.git`
|
10
|
+
2. From the repo directory, install the gem:
|
11
|
+
|
12
|
+
pushd comply-cli/
|
13
|
+
bundle install
|
14
|
+
bundle exec rake install
|
15
|
+
popd
|
16
|
+
|
17
|
+
When using the gem, you will need to configure it to point at a version of the Comply CLI that supports the endpoints required by the CLI. At this moment, the "comply-api-cli" app in the "aptible-staging" environment on Deploy is the deployment kept mos up to date. To use this app with comply-cli, set `APTIBLE_AUTH_ROOT_URL` and `APTIBLE_COMPLY_ROOT_URL` each time you open a new shell (terminal) to use the CLI:
|
18
|
+
|
19
|
+
```
|
20
|
+
export APTIBLE_AUTH_ROOT_URL=https://auth-api-master.aptible-staging.com APTIBLE_COMPLY_ROOT_URL=https://comply-api-cli.aptible-staging.com
|
21
|
+
```
|
22
|
+
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
From `comply help`:
|
27
|
+
|
28
|
+
<!-- BEGIN USAGE -->
|
29
|
+
```
|
30
|
+
Commands:
|
31
|
+
comply help [COMMAND] # Describe available commands or one specific command
|
32
|
+
comply integrations:enable INTEGRATION_ID # Enable an integration
|
33
|
+
comply integrations:list # List integrations
|
34
|
+
comply integrations:sync INTEGRATION_ID # Sync an integration
|
35
|
+
comply integrations:update INTEGRATION_ID # Enable an integration
|
36
|
+
comply login # Log in to Aptible
|
37
|
+
comply programs:select # Select a program for CLI context
|
38
|
+
comply version # Print Aptible CLI version
|
39
|
+
```
|
40
|
+
<!-- END USAGE -->
|
41
|
+
|
42
|
+
## Contributing
|
43
|
+
|
44
|
+
1. Fork the project.
|
45
|
+
1. Commit your changes, with specs.
|
46
|
+
1. Ensure that your code passes specs (`rake spec`) and meets Aptible's Ruby style guide (`rake rubocop`).
|
47
|
+
1. If you add a command, sync this README (`bundle exec script/sync-readme-usage`).
|
48
|
+
1. Create a new pull request on GitHub.
|
49
|
+
|
50
|
+
## Copyright and License
|
51
|
+
|
52
|
+
MIT License, see [LICENSE](LICENSE.md) for details.
|
53
|
+
|
54
|
+
Copyright (c) 2019 [Aptible](https://www.aptible.com) and contributors.
|
data/Rakefile
ADDED
data/bin/comply
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'comply/cli'
|
5
|
+
rescue
|
6
|
+
require 'rubygems'
|
7
|
+
require 'comply/cli'
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
Comply::CLI::Agent.start
|
12
|
+
rescue HyperResource::ClientError => e
|
13
|
+
m = if e.body['error'] == 'invalid_token'
|
14
|
+
'API authentication error: please run comply login'
|
15
|
+
else
|
16
|
+
"An error occurred: #{e.body['message']}"
|
17
|
+
end
|
18
|
+
puts m
|
19
|
+
exit 1
|
20
|
+
end
|
data/comply-cli.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'English'
|
6
|
+
require 'comply/cli/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'comply-cli'
|
10
|
+
spec.version = Comply::CLI::VERSION
|
11
|
+
spec.authors = ['Frank Macreery']
|
12
|
+
spec.email = ['frank@macreery.com']
|
13
|
+
spec.description = 'Comply CLI'
|
14
|
+
spec.summary = 'Command-line interface for Aptible Comply'
|
15
|
+
spec.homepage = 'https://github.com/aptible/comply-cli'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($RS)
|
19
|
+
spec.executables = spec.files.grep(%r{bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{spec/})
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'aptible-resource', '~> 1.1'
|
24
|
+
spec.add_dependency 'aptible-auth', '~> 1.0'
|
25
|
+
spec.add_dependency 'aptible-comply', '>= 0.1.0'
|
26
|
+
spec.add_dependency 'thor', '~> 0.20.0'
|
27
|
+
spec.add_dependency 'chronic_duration', '~> 0.10.6'
|
28
|
+
spec.add_dependency 'highline', '~> 1.7'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
30
|
+
spec.add_development_dependency 'fabrication', '~> 2.15.2'
|
31
|
+
spec.add_development_dependency 'climate_control', '= 0.0.3'
|
32
|
+
spec.add_development_dependency 'aptible-tasks', '~> 0.5.8'
|
33
|
+
spec.add_development_dependency 'pry'
|
34
|
+
end
|
data/lib/comply/cli.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'aptible/auth'
|
2
|
+
require 'thor'
|
3
|
+
require 'chronic_duration'
|
4
|
+
require 'highline/import'
|
5
|
+
|
6
|
+
Dir[File.join(__dir__, 'helpers', '*.rb')].each { |file| require file }
|
7
|
+
Dir[File.join(__dir__, 'subcommands', '*.rb')].each { |file| require file }
|
8
|
+
|
9
|
+
module Comply
|
10
|
+
module CLI
|
11
|
+
class Agent < Thor
|
12
|
+
include Thor::Actions
|
13
|
+
|
14
|
+
include Helpers::Token
|
15
|
+
include Helpers::Program
|
16
|
+
|
17
|
+
include Subcommands::Integrations
|
18
|
+
|
19
|
+
# Forward return codes on failures.
|
20
|
+
def self.exit_on_failure?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(*)
|
25
|
+
Aptible::Resource.configure { |conf| conf.user_agent = version_string }
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'version', 'Print Aptible CLI version'
|
30
|
+
def version
|
31
|
+
puts version_string
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'login', 'Log in to Aptible'
|
35
|
+
option :email
|
36
|
+
option :password
|
37
|
+
option :lifetime, desc: 'The duration the token should be valid for ' \
|
38
|
+
'(example usage: 24h, 1d, 600s, etc.)'
|
39
|
+
option :otp_token, desc: 'A token generated by your second-factor app'
|
40
|
+
def login
|
41
|
+
email = options[:email] || ask('Email: ')
|
42
|
+
password = options[:password] || ask('Password: ', echo: false)
|
43
|
+
puts ''
|
44
|
+
|
45
|
+
token_options = { email: email, password: password }
|
46
|
+
|
47
|
+
otp_token = options[:otp_token]
|
48
|
+
token_options[:otp_token] = otp_token if otp_token
|
49
|
+
|
50
|
+
begin
|
51
|
+
lifetime = '1w'
|
52
|
+
lifetime = '12h' if token_options[:otp_token]
|
53
|
+
lifetime = options[:lifetime] if options[:lifetime]
|
54
|
+
|
55
|
+
duration = ChronicDuration.parse(lifetime)
|
56
|
+
if duration.nil?
|
57
|
+
raise Thor::Error, "Invalid token lifetime requested: #{lifetime}"
|
58
|
+
end
|
59
|
+
|
60
|
+
token_options[:expires_in] = duration
|
61
|
+
token = Aptible::Auth::Token.create(token_options)
|
62
|
+
rescue OAuth2::Error => e
|
63
|
+
if e.code == 'otp_token_required'
|
64
|
+
token_options[:otp_token] = options[:otp_token] ||
|
65
|
+
ask('2FA Token: ')
|
66
|
+
retry
|
67
|
+
end
|
68
|
+
|
69
|
+
raise Thor::Error, 'Could not authenticate with given credentials: ' \
|
70
|
+
"#{e.code}"
|
71
|
+
end
|
72
|
+
|
73
|
+
save_token(token.access_token)
|
74
|
+
puts "Token written to #{token_file}"
|
75
|
+
|
76
|
+
lifetime_format = { units: 2, joiner: ', ' }
|
77
|
+
token_lifetime = (token.expires_at - token.created_at).round
|
78
|
+
expires_in = ChronicDuration.output(token_lifetime, lifetime_format)
|
79
|
+
puts "This token will expire after #{expires_in} " \
|
80
|
+
'(use --lifetime to customize)'
|
81
|
+
|
82
|
+
# Select a default program
|
83
|
+
set_default_program
|
84
|
+
end
|
85
|
+
|
86
|
+
desc 'programs:select', 'Select a program for CLI context'
|
87
|
+
define_method 'programs:select' do
|
88
|
+
candidates = accessible_programs
|
89
|
+
current_program_id = begin
|
90
|
+
fetch_program_id
|
91
|
+
rescue Thor::Error
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
choose do |menu|
|
96
|
+
menu.prompt = 'Choose a program (* is current default):'
|
97
|
+
candidates.each do |program|
|
98
|
+
pretty = pretty_print_program(program)
|
99
|
+
if program.id == current_program_id
|
100
|
+
pretty = "* #{pretty}"
|
101
|
+
menu.default = pretty
|
102
|
+
end
|
103
|
+
|
104
|
+
menu.choice pretty do
|
105
|
+
save_program_id program.id
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def version_string
|
114
|
+
bits = [
|
115
|
+
'comply-cli',
|
116
|
+
"v#{Comply::CLI::VERSION}"
|
117
|
+
]
|
118
|
+
bits.join ' '
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'aptible/comply'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Comply
|
5
|
+
module CLI
|
6
|
+
module Helpers
|
7
|
+
module Integration
|
8
|
+
def prettify_integration(integration)
|
9
|
+
integration.integration_type
|
10
|
+
end
|
11
|
+
|
12
|
+
def pretty_print_integration(integration)
|
13
|
+
"#{prettify_integration(integration)} (#{integration.id})"
|
14
|
+
end
|
15
|
+
|
16
|
+
def prompt_and_create_integration(type)
|
17
|
+
env = prompt_for_env(type)
|
18
|
+
default_program.create_integration(integration_type: type, env: env)
|
19
|
+
end
|
20
|
+
|
21
|
+
def prompt_and_update_integration(integration)
|
22
|
+
env = prompt_for_env(integration.integration_type)
|
23
|
+
integration.update(env: env)
|
24
|
+
end
|
25
|
+
|
26
|
+
def prompt_for_env(type)
|
27
|
+
env = {}
|
28
|
+
env_vars_by_prompt(type).each do |human, key|
|
29
|
+
env[key] = ask("#{human}:", echo: false)
|
30
|
+
puts
|
31
|
+
end
|
32
|
+
|
33
|
+
env
|
34
|
+
end
|
35
|
+
|
36
|
+
def env_vars_by_prompt(type)
|
37
|
+
case type
|
38
|
+
when 'gsuite'
|
39
|
+
{
|
40
|
+
'Client ID' => 'GOOGLE_CLIENT_ID',
|
41
|
+
'Client Secret' => 'GOOGLE_CLIENT_SECRET',
|
42
|
+
'Access Token' => 'GOOGLE_ACCESS_TOKEN',
|
43
|
+
'Refresh Token' => 'GOOGLE_REFRESH_TOKEN'
|
44
|
+
}
|
45
|
+
when 'okta'
|
46
|
+
{
|
47
|
+
'Org Name' => 'OKTA_ORG_NAME',
|
48
|
+
'API Key' => 'OKTA_API_KEY'
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'aptible/comply'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require_relative 'token'
|
5
|
+
|
6
|
+
module Comply
|
7
|
+
module CLI
|
8
|
+
module Helpers
|
9
|
+
module Program
|
10
|
+
include Helpers::Token
|
11
|
+
|
12
|
+
PROGRAM_ENV_VAR = 'APTIBLE_PROGRAM_ID'.freeze
|
13
|
+
|
14
|
+
def default_program
|
15
|
+
return nil unless (id = fetch_program_id)
|
16
|
+
@default_program ||= Aptible::Comply::Program.find(
|
17
|
+
id, token: fetch_token
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_default_program
|
22
|
+
default_program = accessible_programs.first
|
23
|
+
save_program_id(default_program.id) if default_program
|
24
|
+
end
|
25
|
+
|
26
|
+
def pretty_print_program(program)
|
27
|
+
"#{program.organization.name} (#{program.id})"
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_program_id
|
31
|
+
@program_id ||=
|
32
|
+
ENV[PROGRAM_ENV_VAR] ||
|
33
|
+
current_program_id_hash[Aptible::Comply.configuration.root_url]
|
34
|
+
return @program_id if @program_id
|
35
|
+
raise Thor::Error, 'Could not read program: please run comply ' \
|
36
|
+
"programs:select or set #{PROGRAM_ENV_VAR}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def save_program_id(program_id)
|
40
|
+
hash = current_program_id_hash.merge(
|
41
|
+
Aptible::Comply.configuration.root_url => program_id
|
42
|
+
)
|
43
|
+
|
44
|
+
FileUtils.mkdir_p(File.dirname(program_id_file))
|
45
|
+
|
46
|
+
File.open(program_id_file, 'w', 0o600) do |file|
|
47
|
+
file.puts hash.to_json
|
48
|
+
end
|
49
|
+
rescue StandardError => e
|
50
|
+
m = "Could not write program to #{program_id_file}: #{e}. " \
|
51
|
+
'Check filesystem permissions.'
|
52
|
+
raise Thor::Error, m
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_program_id_hash
|
56
|
+
JSON.parse(File.read(program_id_file))
|
57
|
+
rescue
|
58
|
+
{}
|
59
|
+
end
|
60
|
+
|
61
|
+
def program_id_file
|
62
|
+
File.join ENV['HOME'], '.aptible', 'programs.json'
|
63
|
+
end
|
64
|
+
|
65
|
+
def accessible_programs
|
66
|
+
# If a user is a member of a role in ACCOUNT_MANAGEMENT_ROLE_IDS
|
67
|
+
# in Comply, they have read access to ALL programs. As a result,
|
68
|
+
# when offering programs for customers to access, we select just
|
69
|
+
# those which actually belong to their organization(s).
|
70
|
+
|
71
|
+
programs = Aptible::Comply::Program.all(token: fetch_token)
|
72
|
+
orgs = Aptible::Auth::Organization.all(token: fetch_token)
|
73
|
+
|
74
|
+
programs.select do |program|
|
75
|
+
orgs.map(&:href).include?(program.organization_url)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'aptible/auth'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Comply
|
5
|
+
module CLI
|
6
|
+
module Helpers
|
7
|
+
module Token
|
8
|
+
TOKEN_ENV_VAR = 'APTIBLE_ACCESS_TOKEN'.freeze
|
9
|
+
|
10
|
+
def current_user_email
|
11
|
+
@current_user_email ||=
|
12
|
+
Aptible::Auth::Agent.new(token: fetch_token).me.email
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_token
|
16
|
+
@token ||= ENV[TOKEN_ENV_VAR] ||
|
17
|
+
current_token_hash[Aptible::Auth.configuration.root_url]
|
18
|
+
return @token if @token
|
19
|
+
raise Thor::Error, 'Could not read token: please run comply login ' \
|
20
|
+
"or set #{TOKEN_ENV_VAR}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def save_token(token)
|
24
|
+
hash = current_token_hash.merge(
|
25
|
+
Aptible::Auth.configuration.root_url => token
|
26
|
+
)
|
27
|
+
|
28
|
+
FileUtils.mkdir_p(File.dirname(token_file))
|
29
|
+
|
30
|
+
File.open(token_file, 'w', 0o600) do |file|
|
31
|
+
file.puts hash.to_json
|
32
|
+
end
|
33
|
+
rescue StandardError => e
|
34
|
+
m = "Could not write token to #{token_file}: #{e}. " \
|
35
|
+
'Check filesystem permissions.'
|
36
|
+
raise Thor::Error, m
|
37
|
+
end
|
38
|
+
|
39
|
+
def current_token_hash
|
40
|
+
# NOTE: older versions of the CLI did not properly create the
|
41
|
+
# token_file with mode 600, which is why we update it when reading.
|
42
|
+
File.chmod(0o600, token_file)
|
43
|
+
JSON.parse(File.read(token_file))
|
44
|
+
rescue
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
|
48
|
+
def token_file
|
49
|
+
File.join ENV['HOME'], '.aptible', 'tokens.json'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Comply
|
2
|
+
module CLI
|
3
|
+
module Subcommands
|
4
|
+
module Integrations
|
5
|
+
include Helpers::Integration
|
6
|
+
|
7
|
+
def self.included(thor)
|
8
|
+
thor.class_eval do
|
9
|
+
desc 'integrations:list', 'List integrations'
|
10
|
+
define_method 'integrations:list' do
|
11
|
+
integrations = default_program.integrations
|
12
|
+
if integrations.empty?
|
13
|
+
say 'No integrations found.'
|
14
|
+
else
|
15
|
+
integrations.each do |integration|
|
16
|
+
say pretty_print_integration(integration)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'integrations:enable INTEGRATION_ID', 'Enable an integration'
|
22
|
+
define_method 'integrations:enable' do |integration_type|
|
23
|
+
integration = default_program.integrations.find do |i|
|
24
|
+
i.integration_type == integration_type
|
25
|
+
end
|
26
|
+
raise Thor::Error, 'Integration already enabled' if integration
|
27
|
+
|
28
|
+
prompt_and_create_integration(integration_type)
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'integrations:update INTEGRATION_ID', 'Enable an integration'
|
32
|
+
define_method 'integrations:update' do |integration_type|
|
33
|
+
integration = default_program.integrations.find do |i|
|
34
|
+
i.integration_type == integration_type
|
35
|
+
end
|
36
|
+
raise Thor::Error, 'Integration not found' unless integration
|
37
|
+
|
38
|
+
prompt_and_update_integration(integration)
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'integrations:sync INTEGRATION_ID', 'Sync an integration'
|
42
|
+
define_method 'integrations:sync' do |integration_type|
|
43
|
+
integration = default_program.integrations.find do |i|
|
44
|
+
i.integration_type == integration_type
|
45
|
+
end
|
46
|
+
raise Thor::Error, 'Integration not found' unless integration
|
47
|
+
|
48
|
+
integration.links['sync'].post
|
49
|
+
say 'Integration synced'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
USAGE = ARGV.fetch(0, 'README.md')
|
5
|
+
|
6
|
+
puts "Sync CLI usage in #{USAGE}"
|
7
|
+
|
8
|
+
txt, err, status = Open3.capture3(
|
9
|
+
{ 'THOR_COLUMNS' => '1000' },
|
10
|
+
'bundle', 'exec', 'bin/comply', 'help'
|
11
|
+
)
|
12
|
+
|
13
|
+
raise "Failed to extract usage: #{err}" unless status.success?
|
14
|
+
|
15
|
+
usage = "```\n#{txt.gsub(/^$\n/, '')}```\n"
|
16
|
+
|
17
|
+
bits = []
|
18
|
+
|
19
|
+
File.open(USAGE) do |f|
|
20
|
+
in_usage = false
|
21
|
+
|
22
|
+
f.each_line do |l|
|
23
|
+
in_usage = false if l.include?('END USAGE')
|
24
|
+
|
25
|
+
bits << l unless in_usage
|
26
|
+
|
27
|
+
if l.include?('BEGIN USAGE')
|
28
|
+
in_usage = true
|
29
|
+
bits << usage
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
File.write(USAGE, bits.join(''))
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Comply::CLI::Agent do
|
4
|
+
before do
|
5
|
+
allow(subject).to receive(:ask)
|
6
|
+
allow(subject).to receive(:save_token)
|
7
|
+
allow(subject).to receive(:token_file).and_return 'some.json'
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'version' do
|
11
|
+
it 'should print the version' do
|
12
|
+
version = Comply::CLI::VERSION
|
13
|
+
|
14
|
+
expect do
|
15
|
+
subject.version
|
16
|
+
end.to output("comply-cli v#{version}\n").to_stdout
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'login' do
|
21
|
+
let(:token) { double('Aptible::Auth::Token') }
|
22
|
+
let(:created_at) { Time.now }
|
23
|
+
let(:expires_at) { created_at + 1.week }
|
24
|
+
|
25
|
+
before do
|
26
|
+
m = -> (code) { @code = code }
|
27
|
+
OAuth2::Error.send :define_method, :initialize, m
|
28
|
+
|
29
|
+
allow(token).to receive(:access_token).and_return 'access_token'
|
30
|
+
allow(token).to receive(:created_at).and_return created_at
|
31
|
+
allow(token).to receive(:expires_at).and_return expires_at
|
32
|
+
allow(subject).to receive(:puts) {}
|
33
|
+
|
34
|
+
allow(subject).to receive(:set_default_program) {}
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should save a token to ~/.aptible/tokens' do
|
38
|
+
allow(Aptible::Auth::Token).to receive(:create).and_return token
|
39
|
+
expect(subject).to receive(:save_token).with('access_token')
|
40
|
+
subject.login
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should output the token location and token lifetime' do
|
44
|
+
allow(Aptible::Auth::Token).to receive(:create).and_return token
|
45
|
+
allow(subject).to receive(:puts).and_call_original
|
46
|
+
|
47
|
+
expect { subject.login }.to output(/token written to.*json/i).to_stdout
|
48
|
+
expect { subject.login }.to output(/expire after 7 days/i).to_stdout
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should raise an error if authentication fails' do
|
52
|
+
allow(Aptible::Auth::Token).to receive(:create)
|
53
|
+
.and_raise(OAuth2::Error, 'foo')
|
54
|
+
expect do
|
55
|
+
subject.login
|
56
|
+
end.to raise_error 'Could not authenticate with given credentials: foo'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should use command line arguments if passed' do
|
60
|
+
options = { email: 'test@example.com', password: 'password',
|
61
|
+
lifetime: '30 minutes' }
|
62
|
+
allow(subject).to receive(:options).and_return options
|
63
|
+
args = { email: options[:email], password: options[:password],
|
64
|
+
expires_in: 30.minutes.seconds }
|
65
|
+
expect(Aptible::Auth::Token).to receive(:create).with(args) { token }
|
66
|
+
subject.login
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should default to 1 week expiry when OTP is disabled' do
|
70
|
+
options = { email: 'test@example.com', password: 'password' }
|
71
|
+
allow(subject).to receive(:options).and_return options
|
72
|
+
args = options.dup.merge(expires_in: 1.week.seconds)
|
73
|
+
expect(Aptible::Auth::Token).to receive(:create).with(args) { token }
|
74
|
+
subject.login
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should fail if the lifetime is invalid' do
|
78
|
+
options = { email: 'test@example.com', password: 'password',
|
79
|
+
lifetime: 'this is sparta' }
|
80
|
+
allow(subject).to receive(:options).and_return options
|
81
|
+
|
82
|
+
expect { subject.login }.to raise_error(/Invalid token lifetime/)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should set a default program' do
|
86
|
+
allow(Aptible::Auth::Token).to receive(:create).and_return token
|
87
|
+
expect(subject).to receive(:set_default_program)
|
88
|
+
subject.login
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with OTP' do
|
92
|
+
let(:email) { 'foo@example.org' }
|
93
|
+
let(:password) { 'bar' }
|
94
|
+
let(:token) { '123456' }
|
95
|
+
|
96
|
+
context 'with options' do
|
97
|
+
before do
|
98
|
+
allow(subject).to receive(:options)
|
99
|
+
.and_return(email: email, password: password, otp_token: token)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should authenticate without otp_token_required feedback' do
|
103
|
+
expect(Aptible::Auth::Token).to receive(:create)
|
104
|
+
.with(email: email, password: password, otp_token: token,
|
105
|
+
expires_in: 12.hours.seconds)
|
106
|
+
.once
|
107
|
+
.and_return(token)
|
108
|
+
|
109
|
+
subject.login
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'with prompts' do
|
114
|
+
before do
|
115
|
+
[
|
116
|
+
[['Email: '], email],
|
117
|
+
[['Password: ', echo: false], password],
|
118
|
+
[['2FA Token: '], token]
|
119
|
+
].each do |prompt, val|
|
120
|
+
expect(subject).to receive(:ask).with(*prompt).once.and_return(val)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should prompt for an OTP token and use it' do
|
125
|
+
expect(Aptible::Auth::Token).to receive(:create)
|
126
|
+
.with(email: email, password: password, expires_in: 1.week.seconds)
|
127
|
+
.once
|
128
|
+
.and_raise(OAuth2::Error, 'otp_token_required')
|
129
|
+
|
130
|
+
expect(Aptible::Auth::Token).to receive(:create)
|
131
|
+
.with(email: email, password: password, otp_token: token,
|
132
|
+
expires_in: 12.hours.seconds)
|
133
|
+
.once
|
134
|
+
.and_return(token)
|
135
|
+
|
136
|
+
subject.login
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should let the user override the default lifetime' do
|
140
|
+
expect(Aptible::Auth::Token).to receive(:create)
|
141
|
+
.with(email: email, password: password, expires_in: 1.day.seconds)
|
142
|
+
.once
|
143
|
+
.and_raise(OAuth2::Error, 'otp_token_required')
|
144
|
+
|
145
|
+
expect(Aptible::Auth::Token).to receive(:create)
|
146
|
+
.with(email: email, password: password, otp_token: token,
|
147
|
+
expires_in: 1.day.seconds)
|
148
|
+
.once
|
149
|
+
.and_return(token)
|
150
|
+
|
151
|
+
allow(subject).to receive(:options).and_return(lifetime: '1d')
|
152
|
+
subject.login
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should not retry non-OTP errors.' do
|
156
|
+
expect(Aptible::Auth::Token).to receive(:create)
|
157
|
+
.with(email: email, password: password, expires_in: 1.week.seconds)
|
158
|
+
.once
|
159
|
+
.and_raise(OAuth2::Error, 'otp_token_required')
|
160
|
+
|
161
|
+
expect(Aptible::Auth::Token).to receive(:create)
|
162
|
+
.with(email: email, password: password, otp_token: token,
|
163
|
+
expires_in: 12.hours.seconds)
|
164
|
+
.once
|
165
|
+
.and_raise(OAuth2::Error, 'foo')
|
166
|
+
|
167
|
+
expect { subject.login }.to raise_error(/Could not authenticate/)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe 'programs:select' do
|
174
|
+
# TODO: Try to get this working
|
175
|
+
# https://github.com/JEG2/highline/issues/176 might be helpful
|
176
|
+
|
177
|
+
let(:p1) { Fabricate(:program) }
|
178
|
+
let(:p2) { Fabricate(:program) }
|
179
|
+
|
180
|
+
before do
|
181
|
+
allow(subject).to receive(:accessible_programs) { [p1, p2] }
|
182
|
+
allow(subject).to receive(:fetch_program_id) { p1.id }
|
183
|
+
allow_any_instance_of(HighLine).to receive(:get_line) { '1' }
|
184
|
+
end
|
185
|
+
|
186
|
+
skip 'displays a selection of available programs' do
|
187
|
+
lines = ["* #{subject.pretty_print_program(p1)}",
|
188
|
+
subject.pretty_print_program(p2),
|
189
|
+
'Choose a program']
|
190
|
+
|
191
|
+
lines.each do |line|
|
192
|
+
expect { subject.send('programs:select') }.to output(line).to_stdout
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
describe Comply::CLI::Helpers::Program do
|
5
|
+
around do |example|
|
6
|
+
Dir.mktmpdir { |home| ClimateControl.modify(HOME: home) { example.run } }
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { Class.new.send(:include, described_class).new }
|
10
|
+
|
11
|
+
let(:program_id) { SecureRandom.uuid }
|
12
|
+
let(:token) { double('token') }
|
13
|
+
|
14
|
+
before do
|
15
|
+
allow(subject).to receive(:fetch_token) { token }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#save_program / #fetch_program' do
|
19
|
+
it 'reads back a program ID it saved' do
|
20
|
+
subject.save_program_id(program_id)
|
21
|
+
expect(subject.fetch_program_id).to eq(program_id)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'accessible_programs' do
|
26
|
+
it 'filters programs' do
|
27
|
+
o1 = Fabricate(:organization)
|
28
|
+
o2 = Fabricate(:organization)
|
29
|
+
|
30
|
+
p1 = Fabricate(:program, organization: o1)
|
31
|
+
p2 = Fabricate(:program, organization: o2)
|
32
|
+
p3 = Fabricate(:program)
|
33
|
+
|
34
|
+
allow(Aptible::Comply::Program).to receive(:all) { [p1, p2, p3] }
|
35
|
+
allow(Aptible::Auth::Organization).to receive(:all) { [o1, o2] }
|
36
|
+
|
37
|
+
programs = subject.accessible_programs
|
38
|
+
expect(programs.map(&:id).sort).to eq [p1.id, p2.id].sort
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Comply::CLI::Helpers::Token do
|
4
|
+
around do |example|
|
5
|
+
Dir.mktmpdir { |home| ClimateControl.modify(HOME: home) { example.run } }
|
6
|
+
end
|
7
|
+
|
8
|
+
subject { Class.new.send(:include, described_class).new }
|
9
|
+
|
10
|
+
describe '#save_token / #fetch_token' do
|
11
|
+
it 'reads back a token it saved' do
|
12
|
+
subject.save_token('foo')
|
13
|
+
expect(subject.fetch_token).to eq('foo')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'permissions' do
|
18
|
+
before { skip 'Windows' if Gem.win_platform? }
|
19
|
+
|
20
|
+
describe '#save_token' do
|
21
|
+
it 'creates the token_file with mode 600' do
|
22
|
+
subject.save_token('foo')
|
23
|
+
expect(format('%o', File.stat(subject.token_file).mode))
|
24
|
+
.to eq('100600')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#current_token_hash' do
|
29
|
+
it 'updates the token_file to mode 600' do
|
30
|
+
subject.save_token('foo')
|
31
|
+
File.chmod(0o644, subject.token_file)
|
32
|
+
expect(format('%o', File.stat(subject.token_file).mode))
|
33
|
+
.to eq('100644')
|
34
|
+
|
35
|
+
subject.current_token_hash
|
36
|
+
expect(format('%o', File.stat(subject.token_file).mode))
|
37
|
+
.to eq('100600')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class StubProgram < OpenStruct
|
2
|
+
end
|
3
|
+
|
4
|
+
Fabricator(:program, from: :stub_program) do
|
5
|
+
id { Fabricate.sequence(:program_id) { |i| i } }
|
6
|
+
|
7
|
+
organization
|
8
|
+
organization_url { |attrs| attrs[:organization].href }
|
9
|
+
|
10
|
+
errors { Aptible::Resource::Errors.new }
|
11
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
Bundler.require :development
|
5
|
+
|
6
|
+
# Load shared spec files
|
7
|
+
Dir["#{File.dirname(__FILE__)}/shared/**/*.rb"].each do |file|
|
8
|
+
require file
|
9
|
+
end
|
10
|
+
|
11
|
+
# Require library up front
|
12
|
+
require 'comply/cli'
|
metadata
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: comply-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Frank Macreery
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-12-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aptible-resource
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aptible-auth
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: aptible-comply
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: thor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.20.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.20.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: chronic_duration
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.10.6
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.10.6
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: highline
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.7'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.7'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.2'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.2'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: fabrication
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.15.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 2.15.2
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: climate_control
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.0.3
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.0.3
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: aptible-tasks
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.5.8
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.5.8
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: pry
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: Comply CLI
|
168
|
+
email:
|
169
|
+
- frank@macreery.com
|
170
|
+
executables:
|
171
|
+
- comply
|
172
|
+
extensions: []
|
173
|
+
extra_rdoc_files: []
|
174
|
+
files:
|
175
|
+
- ".github/CODEOWNERS"
|
176
|
+
- ".gitignore"
|
177
|
+
- ".rspec"
|
178
|
+
- ".travis.yml"
|
179
|
+
- Gemfile
|
180
|
+
- LICENSE.md
|
181
|
+
- README.md
|
182
|
+
- Rakefile
|
183
|
+
- bin/comply
|
184
|
+
- comply-cli.gemspec
|
185
|
+
- lib/comply/cli.rb
|
186
|
+
- lib/comply/cli/agent.rb
|
187
|
+
- lib/comply/cli/helpers/integration.rb
|
188
|
+
- lib/comply/cli/helpers/program.rb
|
189
|
+
- lib/comply/cli/helpers/token.rb
|
190
|
+
- lib/comply/cli/subcommands/integrations.rb
|
191
|
+
- lib/comply/cli/version.rb
|
192
|
+
- lib/comply/ext/string.rb
|
193
|
+
- script/sync-readme-usage
|
194
|
+
- spec/comply/cli/agent_spec.rb
|
195
|
+
- spec/comply/cli/helpers/program_spec.rb
|
196
|
+
- spec/comply/cli/helpers/token_spec.rb
|
197
|
+
- spec/comply/cli/subcommands/groups_spec.rb
|
198
|
+
- spec/comply/cli/subcommands/integrations_spec.rb
|
199
|
+
- spec/comply/cli_spec.rb
|
200
|
+
- spec/fabricators/organization_fabricator.rb
|
201
|
+
- spec/fabricators/program_fabricator.rb
|
202
|
+
- spec/spec_helper.rb
|
203
|
+
homepage: https://github.com/aptible/comply-cli
|
204
|
+
licenses:
|
205
|
+
- MIT
|
206
|
+
metadata: {}
|
207
|
+
post_install_message:
|
208
|
+
rdoc_options: []
|
209
|
+
require_paths:
|
210
|
+
- lib
|
211
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
217
|
+
requirements:
|
218
|
+
- - ">="
|
219
|
+
- !ruby/object:Gem::Version
|
220
|
+
version: '0'
|
221
|
+
requirements: []
|
222
|
+
rubygems_version: 3.0.3
|
223
|
+
signing_key:
|
224
|
+
specification_version: 4
|
225
|
+
summary: Command-line interface for Aptible Comply
|
226
|
+
test_files:
|
227
|
+
- spec/comply/cli/agent_spec.rb
|
228
|
+
- spec/comply/cli/helpers/program_spec.rb
|
229
|
+
- spec/comply/cli/helpers/token_spec.rb
|
230
|
+
- spec/comply/cli/subcommands/groups_spec.rb
|
231
|
+
- spec/comply/cli/subcommands/integrations_spec.rb
|
232
|
+
- spec/comply/cli_spec.rb
|
233
|
+
- spec/fabricators/organization_fabricator.rb
|
234
|
+
- spec/fabricators/program_fabricator.rb
|
235
|
+
- spec/spec_helper.rb
|