opsworks-cli 0.4.5 → 0.5.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 +4 -4
- data/.travis.yml +5 -2
- data/Gemfile +3 -0
- data/README.md +4 -2
- data/lib/opsworks/app.rb +15 -5
- data/lib/opsworks/cli/agent.rb +2 -3
- data/lib/opsworks/cli/subcommands/apps.rb +34 -22
- data/lib/opsworks/cli/subcommands/chef.rb +15 -16
- data/lib/opsworks/cli/subcommands/config.rb +1 -9
- data/lib/opsworks/cli/subcommands/deployments.rb +82 -0
- data/lib/opsworks/cli/subcommands/iam.rb +0 -6
- data/lib/opsworks/cli/subcommands/recipes.rb +1 -8
- data/lib/opsworks/cli/version.rb +1 -1
- data/lib/opsworks/deployment.rb +20 -22
- data/lib/opsworks/instance.rb +2 -1
- data/lib/opsworks/layer.rb +6 -6
- data/lib/opsworks/permission.rb +5 -3
- data/lib/opsworks/resource.rb +5 -7
- data/lib/opsworks/stack.rb +72 -35
- data/opsworks-cli.gemspec +2 -3
- data/spec/fabricators/opsworks/app_fabricator.rb +1 -0
- data/spec/fabricators/opsworks/deployment_fabricator.rb +1 -0
- data/spec/fabricators/opsworks/permission_fabricator.rb +1 -0
- data/spec/fabricators/opsworks/stack_fabricator.rb +1 -0
- data/spec/opsworks/cli/subcommands/apps_spec.rb +43 -5
- data/spec/opsworks/cli/subcommands/chef_spec.rb +1 -1
- data/spec/opsworks/cli/subcommands/config_spec.rb +11 -13
- data/spec/opsworks/cli/subcommands/iam_spec.rb +2 -2
- data/spec/opsworks/cli/subcommands/recipes_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -7
- metadata +11 -25
- data/lib/opsworks/cli/helpers/credentials.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f9314cdf3a4f3ca9c386e2dc2f0e537cfa498f9
|
4
|
+
data.tar.gz: cfb55e0c88b9efb9188c0fece32de6edbf83494e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c63763cc141a5bc68fc7a32632cf6b73da5ba4e316dfbc2b96c51b7b29aded201b45eb857b5cc79fc42bacbb31a9b630081ad93c8755577329297963d47f4ab9
|
7
|
+
data.tar.gz: 221fe534dcb3affcc3de82ec1038fe5524c48af2a9a5ea79f69a446e9ec6b418b893cf80538de625f3dc7d91cb29f71fd5f277d1656081e018eb398ba39746ab
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -14,12 +14,14 @@ Install the gem:
|
|
14
14
|
|
15
15
|
## Configuration
|
16
16
|
|
17
|
-
The gem expects to have access to your AWS access key ID and secret access key. You
|
17
|
+
The gem expects to have access to your AWS access key ID and secret access key. You may set the following environment variables:
|
18
18
|
|
19
19
|
export AWS_ACCESS_KEY_ID=...
|
20
20
|
export AWS_SECRET_ACCESS_KEY=...
|
21
21
|
|
22
|
-
|
22
|
+
To avoid manually setting these ENV variables each time, you may use the [Omnivault](https://github.com/aptible/omnivault) gem to store and configure your credentials once, then access them later by running e.g.
|
23
|
+
|
24
|
+
omnivault exec opsworks [...]
|
23
25
|
|
24
26
|
## Usage
|
25
27
|
|
data/lib/opsworks/app.rb
CHANGED
@@ -5,10 +5,11 @@ module OpsWorks
|
|
5
5
|
class App < Resource
|
6
6
|
attr_accessor :id, :name, :revision
|
7
7
|
|
8
|
-
def self.from_collection_response(response)
|
9
|
-
response.data[:apps].map do |
|
8
|
+
def self.from_collection_response(client, response)
|
9
|
+
response.data[:apps].map do |app|
|
10
|
+
hash = app.to_h
|
10
11
|
revision = hash[:app_source][:revision] if hash[:app_source]
|
11
|
-
new(id: hash[:app_id], name: hash[:name], revision: revision)
|
12
|
+
new(client, id: hash[:app_id], name: hash[:name], revision: revision)
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -20,12 +21,21 @@ module OpsWorks
|
|
20
21
|
deployments.find(&:success?)
|
21
22
|
end
|
22
23
|
|
24
|
+
def update_revision(revision)
|
25
|
+
client.update_app(
|
26
|
+
app_id: id,
|
27
|
+
app_source: { revision: revision }
|
28
|
+
)
|
29
|
+
|
30
|
+
self.revision = revision
|
31
|
+
end
|
32
|
+
|
23
33
|
private
|
24
34
|
|
25
35
|
def initialize_deployments
|
26
36
|
return [] unless id
|
27
|
-
response =
|
28
|
-
Deployment.from_collection_response(response)
|
37
|
+
response = client.describe_deployments(app_id: id)
|
38
|
+
Deployment.from_collection_response(client, response)
|
29
39
|
end
|
30
40
|
end
|
31
41
|
end
|
data/lib/opsworks/cli/agent.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'thor'
|
2
|
-
require 'aws'
|
3
2
|
|
4
|
-
require_relative 'helpers/credentials'
|
5
3
|
require_relative 'helpers/options'
|
6
4
|
require_relative 'helpers/typecasts'
|
7
5
|
|
@@ -10,13 +8,13 @@ require_relative 'subcommands/recipes'
|
|
10
8
|
require_relative 'subcommands/apps'
|
11
9
|
require_relative 'subcommands/iam'
|
12
10
|
require_relative 'subcommands/config'
|
11
|
+
require_relative 'subcommands/deployments'
|
13
12
|
|
14
13
|
module OpsWorks
|
15
14
|
module CLI
|
16
15
|
class Agent < Thor
|
17
16
|
include Thor::Actions
|
18
17
|
|
19
|
-
include Helpers::Credentials
|
20
18
|
include Helpers::Options
|
21
19
|
include Helpers::Typecasts
|
22
20
|
|
@@ -25,6 +23,7 @@ module OpsWorks
|
|
25
23
|
include Subcommands::Apps
|
26
24
|
include Subcommands::IAM
|
27
25
|
include Subcommands::Config
|
26
|
+
include Subcommands::Deployments
|
28
27
|
|
29
28
|
desc 'version', 'Print OpsWorks CLI version'
|
30
29
|
def version
|
@@ -4,31 +4,37 @@ module OpsWorks
|
|
4
4
|
module CLI
|
5
5
|
module Subcommands
|
6
6
|
module Apps
|
7
|
-
# rubocop:disable MethodLength
|
8
|
-
# rubocop:disable CyclomaticComplexity
|
9
|
-
# rubocop:disable PerceivedComplexity
|
10
7
|
def self.included(thor)
|
11
8
|
thor.class_eval do
|
12
9
|
desc 'apps:deploy APP [--stack STACK]', 'Deploy an OpsWorks app'
|
13
10
|
option :stack, type: :array
|
14
11
|
option :timeout, type: :numeric, default: 300
|
15
12
|
option :migrate, type: :boolean, default: false
|
13
|
+
option :layer, type: :string
|
16
14
|
define_method 'apps:deploy' do |name|
|
17
|
-
fetch_credentials unless env_credentials?
|
18
15
|
stacks = parse_stacks(options.merge(active: true))
|
19
16
|
deployments = stacks.map do |stack|
|
20
17
|
next unless (app = stack.find_app_by_name(name))
|
21
18
|
say "Deploying to #{stack.name}..."
|
22
|
-
stack.deploy_app(
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
dpl = stack.deploy_app(
|
20
|
+
app,
|
21
|
+
layer: options[:layer],
|
22
|
+
args: { 'migrate' => [options[:migrate].to_s] }
|
23
|
+
)
|
24
|
+
next unless dpl
|
25
|
+
[stack, dpl]
|
26
|
+
end.compact
|
27
|
+
|
28
|
+
OpsWorks::Deployment.wait(deployments.map(&:last),
|
29
|
+
options[:timeout])
|
30
|
+
|
31
|
+
failures = deployments.map do |stack, deployment|
|
32
|
+
next if deployment.success?
|
33
|
+
stack
|
34
|
+
end.compact
|
35
|
+
|
36
|
+
unless failures.empty?
|
37
|
+
raise "Deploy failed on #{failures.map(&:name).join(' ')}"
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
@@ -36,8 +42,6 @@ module OpsWorks
|
|
36
42
|
'Display the most recent deployment of an app'
|
37
43
|
option :stack, type: :array
|
38
44
|
define_method 'apps:status' do |name|
|
39
|
-
fetch_credentials unless env_credentials?
|
40
|
-
|
41
45
|
table = parse_stacks(options).map do |stack|
|
42
46
|
next unless (app = stack.find_app_by_name(name))
|
43
47
|
if (deployment = app.last_deployment)
|
@@ -60,12 +64,11 @@ module OpsWorks
|
|
60
64
|
option :shortname
|
61
65
|
define_method 'apps:create' do |name|
|
62
66
|
unless %w(other).include?(options[:type])
|
63
|
-
|
67
|
+
raise "Unsupported type: #{options[:type]}"
|
64
68
|
end
|
65
69
|
|
66
|
-
|
70
|
+
raise 'Git URL not yet supported' if options[:git_url]
|
67
71
|
|
68
|
-
fetch_credentials unless env_credentials?
|
69
72
|
stacks = parse_stacks(options)
|
70
73
|
|
71
74
|
stacks.each do |stack|
|
@@ -75,6 +78,18 @@ module OpsWorks
|
|
75
78
|
end
|
76
79
|
end
|
77
80
|
|
81
|
+
desc 'apps:revision:update APP REVISION [--stack STACK]',
|
82
|
+
'Set the revision for an app'
|
83
|
+
option :stack, type: :array
|
84
|
+
define_method 'apps:revision:update' do |app_name, revision|
|
85
|
+
stacks = parse_stacks(options.merge(active: true))
|
86
|
+
stacks.each do |stack|
|
87
|
+
next unless (app = stack.find_app_by_name(app_name))
|
88
|
+
say "Updating #{stack.name} (from: #{app.revision})..."
|
89
|
+
app.update_revision(revision)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
78
93
|
private
|
79
94
|
|
80
95
|
def formatted_time(timestamp)
|
@@ -82,9 +97,6 @@ module OpsWorks
|
|
82
97
|
end
|
83
98
|
end
|
84
99
|
end
|
85
|
-
# rubocop:enable PerceivedComplexity
|
86
|
-
# rubocop:enable CyclomaticComplexity
|
87
|
-
# rubocop:enable MethodLength
|
88
100
|
end
|
89
101
|
end
|
90
102
|
end
|
@@ -1,12 +1,9 @@
|
|
1
|
-
require 'aws'
|
2
1
|
require 'opsworks/stack'
|
3
2
|
|
4
3
|
module OpsWorks
|
5
4
|
module CLI
|
6
5
|
module Subcommands
|
7
6
|
module Chef
|
8
|
-
# rubocop:disable MethodLength
|
9
|
-
# rubocop:disable CyclomaticComplexity
|
10
7
|
def self.included(thor)
|
11
8
|
thor.class_eval do
|
12
9
|
desc 'chef:configure [--stack STACK]', 'Configure Chef/Berkshelf'
|
@@ -20,7 +17,6 @@ module OpsWorks
|
|
20
17
|
option :cookbook_username
|
21
18
|
option :cookbook_password
|
22
19
|
define_method 'chef:configure' do
|
23
|
-
fetch_credentials unless env_credentials?
|
24
20
|
stacks = parse_stacks(options.merge(active: true))
|
25
21
|
stacks.each do |stack|
|
26
22
|
say "Configuring Chef #{options[:version]} on #{stack.name}..."
|
@@ -32,25 +28,28 @@ module OpsWorks
|
|
32
28
|
option :stack, type: :array
|
33
29
|
option :timeout, type: :numeric, default: 300
|
34
30
|
define_method 'chef:sync' do
|
35
|
-
fetch_credentials unless env_credentials?
|
36
31
|
stacks = parse_stacks(options.merge(active: true))
|
37
32
|
deployments = stacks.map do |stack|
|
38
33
|
say "Syncing #{stack.name}..."
|
39
|
-
stack.update_custom_cookbooks
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
34
|
+
dpl = stack.update_custom_cookbooks
|
35
|
+
next unless dpl
|
36
|
+
[stack, dpl]
|
37
|
+
end.compact
|
38
|
+
|
39
|
+
OpsWorks::Deployment.wait(deployments.map(&:last),
|
40
|
+
options[:timeout])
|
41
|
+
|
42
|
+
failures = deployments.map do |stack, deployment|
|
43
|
+
next if deployment.success?
|
44
|
+
stack
|
45
|
+
end.compact
|
46
|
+
|
47
|
+
unless failures.empty?
|
48
|
+
raise "Deploy failed on #{failures.map(&:name).join(', ')}"
|
48
49
|
end
|
49
50
|
end
|
50
51
|
end
|
51
52
|
end
|
52
|
-
# rubocop:enable CyclomaticComplexity
|
53
|
-
# rubocop:enable MethodLength
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|
@@ -1,21 +1,17 @@
|
|
1
|
-
require 'aws'
|
2
1
|
require 'opsworks/stack'
|
3
2
|
|
4
3
|
module OpsWorks
|
5
4
|
module CLI
|
6
5
|
module Subcommands
|
7
6
|
module Config
|
8
|
-
# rubocop:disable MethodLength
|
9
|
-
# rubocop:disable CyclomaticComplexity
|
10
7
|
def self.included(thor)
|
11
8
|
thor.class_eval do
|
12
9
|
desc 'config:get KEY [--stack STACK]', 'Get a single config value'
|
13
10
|
option :stack, type: :array
|
14
11
|
define_method 'config:get' do |key|
|
15
|
-
fetch_credentials unless env_credentials?
|
16
12
|
table = parse_stacks(options).map do |stack|
|
17
13
|
value = stack.custom_json_at(key)
|
18
|
-
[stack.name, value
|
14
|
+
[stack.name, value.nil? ? '(null)' : value]
|
19
15
|
end
|
20
16
|
table.compact!
|
21
17
|
table.sort! { |x, y| x.first <=> y.first }
|
@@ -25,7 +21,6 @@ module OpsWorks
|
|
25
21
|
desc 'config:set KEY VALUE [--stack STACK]', 'Set a config value'
|
26
22
|
option :stack, type: :array
|
27
23
|
define_method 'config:set' do |key, value|
|
28
|
-
fetch_credentials unless env_credentials?
|
29
24
|
parse_stacks(options).each do |stack|
|
30
25
|
say "Updating #{stack.name}..."
|
31
26
|
stack.set_custom_json_at(key, typecast_string_argument(value))
|
@@ -35,7 +30,6 @@ module OpsWorks
|
|
35
30
|
desc 'config:unset KEY [--stack STACK]', 'Unset a config value'
|
36
31
|
option :stack, type: :array
|
37
32
|
define_method 'config:unset' do |key|
|
38
|
-
fetch_credentials unless env_credentials?
|
39
33
|
parse_stacks(options).map do |stack|
|
40
34
|
say "Updating #{stack.name}..."
|
41
35
|
stack.set_custom_json_at(key, nil)
|
@@ -43,8 +37,6 @@ module OpsWorks
|
|
43
37
|
end
|
44
38
|
end
|
45
39
|
end
|
46
|
-
# rubocop:enable CyclomaticComplexity
|
47
|
-
# rubocop:enable MethodLength
|
48
40
|
end
|
49
41
|
end
|
50
42
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module OpsWorks
|
2
|
+
module CLI
|
3
|
+
module Subcommands
|
4
|
+
module Deployments
|
5
|
+
def self.included(thor)
|
6
|
+
thor.class_eval do
|
7
|
+
desc 'deployments:retry [--stack STACK]', 'Retry last deployment'
|
8
|
+
option :stack, type: :array
|
9
|
+
option :timeout, type: :numeric, default: 300
|
10
|
+
define_method 'deployments:retry' do
|
11
|
+
stacks = parse_stacks(options.merge(active: true))
|
12
|
+
|
13
|
+
last_deployment = stacks.map do |stack|
|
14
|
+
say "Loading last deployment for #{stack.name}"
|
15
|
+
[stack, stack.deployments.max_by(&:created_at)]
|
16
|
+
end
|
17
|
+
|
18
|
+
will_retry = last_deployment.map do |stack, deployment|
|
19
|
+
if deployment.status == 'successful'
|
20
|
+
say "Skipping stack #{stack.name}: last deployment is " \
|
21
|
+
"#{deployment.status}"
|
22
|
+
next
|
23
|
+
end
|
24
|
+
|
25
|
+
say "Loading instance status for #{stack.name}"
|
26
|
+
|
27
|
+
res = deployment.client.describe_commands(
|
28
|
+
deployment_id: deployment.id
|
29
|
+
)
|
30
|
+
|
31
|
+
retry_commands = res.commands.select do |command|
|
32
|
+
command.status != 'successful'
|
33
|
+
end
|
34
|
+
|
35
|
+
instance_ids = retry_commands.map(&:instance_id).to_a
|
36
|
+
|
37
|
+
if instance_ids.empty?
|
38
|
+
say "Skipping #{stack.name}: no instances failed"
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
[stack, deployment, instance_ids]
|
43
|
+
end.compact
|
44
|
+
|
45
|
+
deployments = will_retry.map do |stack, dep, instance_ids|
|
46
|
+
say "Retrying #{dep.command[:name]} in #{stack.name} " \
|
47
|
+
"on #{instance_ids.join ' '}"
|
48
|
+
|
49
|
+
opts = {
|
50
|
+
command: dep.command,
|
51
|
+
instance_ids: instance_ids
|
52
|
+
}
|
53
|
+
opts[:app_id] = dep.app_id if dep.app_id
|
54
|
+
opts[:custom_json] = dep.custom_json if dep.custom_json
|
55
|
+
|
56
|
+
new_deployment = stack.send(:create_deployment, opts)
|
57
|
+
|
58
|
+
[stack, new_deployment]
|
59
|
+
end
|
60
|
+
|
61
|
+
say "Waiting #{options[:timeout]}s for deployments to finish"
|
62
|
+
|
63
|
+
OpsWorks::Deployment.wait(deployments.map(&:last),
|
64
|
+
options[:timeout])
|
65
|
+
|
66
|
+
failures = deployments.map do |stack, deployment|
|
67
|
+
next if deployment.success?
|
68
|
+
stack
|
69
|
+
end.compact
|
70
|
+
|
71
|
+
unless failures.empty?
|
72
|
+
raise "Deploy failed on #{failures.map(&:name).join(' ')}"
|
73
|
+
end
|
74
|
+
|
75
|
+
say "All #{deployments.size} deployments suceeded"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -4,8 +4,6 @@ module OpsWorks
|
|
4
4
|
module CLI
|
5
5
|
module Subcommands
|
6
6
|
module IAM
|
7
|
-
# rubocop:disable MethodLength
|
8
|
-
# rubocop:disable CyclomaticComplexity
|
9
7
|
def self.included(thor)
|
10
8
|
thor.class_eval do
|
11
9
|
desc 'iam:allow USER [--stack STACK]',
|
@@ -14,7 +12,6 @@ module OpsWorks
|
|
14
12
|
option :ssh, type: :boolean, default: true
|
15
13
|
option :sudo, type: :boolean, default: true
|
16
14
|
define_method 'iam:allow' do |user|
|
17
|
-
fetch_credentials unless env_credentials?
|
18
15
|
stacks = parse_stacks(options.merge(active: true))
|
19
16
|
stacks.each do |stack|
|
20
17
|
permission = stack.find_permission_by_user(user)
|
@@ -27,7 +24,6 @@ module OpsWorks
|
|
27
24
|
desc 'iam:lockdown [--stack STACK]', 'Remove all stack permissions'
|
28
25
|
option :stack, type: :array
|
29
26
|
define_method 'iam:lockdown' do
|
30
|
-
fetch_credentials unless env_credentials?
|
31
27
|
stacks = parse_stacks(options.merge(active: true))
|
32
28
|
stacks.each do |stack|
|
33
29
|
say "Locking down #{stack.name}..."
|
@@ -38,8 +34,6 @@ module OpsWorks
|
|
38
34
|
end
|
39
35
|
end
|
40
36
|
end
|
41
|
-
# rubocop:enable CyclomaticComplexity
|
42
|
-
# rubocop:enable MethodLength
|
43
37
|
end
|
44
38
|
end
|
45
39
|
end
|
@@ -4,15 +4,12 @@ module OpsWorks
|
|
4
4
|
module CLI
|
5
5
|
module Subcommands
|
6
6
|
module Recipes
|
7
|
-
# rubocop:disable MethodLength
|
8
|
-
# rubocop:disable CyclomaticComplexity
|
9
7
|
def self.included(thor)
|
10
8
|
thor.class_eval do
|
11
9
|
desc 'recipes:run RECIPE [--stack STACK]', 'Execute a Chef recipe'
|
12
10
|
option :stack, type: :array
|
13
11
|
option :timeout, type: :numeric, default: 300
|
14
12
|
define_method 'recipes:run' do |recipe|
|
15
|
-
fetch_credentials unless env_credentials?
|
16
13
|
stacks = parse_stacks(options.merge(active: true))
|
17
14
|
deployments = stacks.map do |stack|
|
18
15
|
say "Executing recipe on #{stack.name}..."
|
@@ -24,7 +21,7 @@ module OpsWorks
|
|
24
21
|
deployments.each_with_index do |deployment, i|
|
25
22
|
failures << stacks[i].name unless deployment.success?
|
26
23
|
end
|
27
|
-
|
24
|
+
raise "Command failed on #{failures.join(', ')}"
|
28
25
|
end
|
29
26
|
end
|
30
27
|
|
@@ -32,7 +29,6 @@ module OpsWorks
|
|
32
29
|
'Add a recipe to a given layer and lifecycle event'
|
33
30
|
option :stack, type: :array
|
34
31
|
define_method 'recipes:add' do |layername, event, recipe|
|
35
|
-
fetch_credentials unless env_credentials?
|
36
32
|
stacks = parse_stacks(options)
|
37
33
|
stacks.each do |stack|
|
38
34
|
layer = stack.layers.find { |l| l.shortname == layername }
|
@@ -48,7 +44,6 @@ module OpsWorks
|
|
48
44
|
'Remove a recipe from a given layer and lifecycle event'
|
49
45
|
option :stack, type: :array
|
50
46
|
define_method 'recipes:rm' do |layername, event, recipe|
|
51
|
-
fetch_credentials unless env_credentials?
|
52
47
|
stacks = parse_stacks(options)
|
53
48
|
stacks.each do |stack|
|
54
49
|
layer = stack.layers.find { |l| l.shortname == layername }
|
@@ -61,8 +56,6 @@ module OpsWorks
|
|
61
56
|
end
|
62
57
|
end
|
63
58
|
end
|
64
|
-
# rubocop:enable CyclomaticComplexity
|
65
|
-
# rubocop:enable MethodLength
|
66
59
|
end
|
67
60
|
end
|
68
61
|
end
|
data/lib/opsworks/cli/version.rb
CHANGED
data/lib/opsworks/deployment.rb
CHANGED
@@ -2,13 +2,12 @@ require 'opsworks/resource'
|
|
2
2
|
|
3
3
|
module OpsWorks
|
4
4
|
class Deployment < Resource
|
5
|
-
attr_accessor :id, :status, :created_at
|
5
|
+
attr_accessor :id, :command, :status, :created_at, :custom_json, :app_id
|
6
6
|
|
7
7
|
TIMEOUT = 300
|
8
8
|
POLL_INTERVAL = 5
|
9
9
|
API_LIMIT = 25
|
10
10
|
|
11
|
-
# rubocop:disable MethodLength
|
12
11
|
def self.wait(deployments, timeout = TIMEOUT)
|
13
12
|
start_time = Time.now
|
14
13
|
timeout ||= TIMEOUT
|
@@ -17,41 +16,40 @@ module OpsWorks
|
|
17
16
|
sleep POLL_INTERVAL
|
18
17
|
updates = []
|
19
18
|
running_deployments = deployments.select(&:running?)
|
20
|
-
running_deployments.
|
21
|
-
|
22
|
-
|
23
|
-
)
|
24
|
-
|
19
|
+
groups = running_deployments.group_by { |d| d.client.config.region }
|
20
|
+
|
21
|
+
groups.each do |region, region_deployments|
|
22
|
+
client = Aws::OpsWorks::Client.new(region: region)
|
23
|
+
region_deployments.map(&:id).each_slice(API_LIMIT) do |slice|
|
24
|
+
response = client.describe_deployments(deployment_ids: slice)
|
25
|
+
updates += from_collection_response(client, response)
|
26
|
+
end
|
25
27
|
end
|
28
|
+
|
26
29
|
running_deployments.each do |deployment|
|
27
30
|
update = updates.find { |u| u.id == deployment.id }
|
28
31
|
deployment.status = update.status
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
32
|
-
# rubocop:enble MethodLength
|
33
35
|
|
34
|
-
def self.from_collection_response(response)
|
35
|
-
response.data[:deployments].map do |
|
36
|
+
def self.from_collection_response(client, response)
|
37
|
+
response.data[:deployments].map do |deployment|
|
38
|
+
hash = deployment.to_h
|
36
39
|
new(
|
40
|
+
client,
|
37
41
|
id: hash[:deployment_id],
|
42
|
+
command: hash[:command],
|
38
43
|
created_at: hash[:created_at],
|
39
|
-
status: hash[:status]
|
44
|
+
status: hash[:status],
|
45
|
+
custom_json: hash[:custom_json],
|
46
|
+
app_id: hash[:app_id]
|
40
47
|
)
|
41
48
|
end
|
42
49
|
end
|
43
50
|
|
44
|
-
def self.from_response(response)
|
45
|
-
new(id: response[:deployment_id])
|
46
|
-
end
|
47
|
-
|
48
|
-
def wait
|
49
|
-
while deployment.running?
|
50
|
-
sleep POLL_INTERVAL
|
51
|
-
response = client.describe_deployments(deployment_ids: [id])
|
52
|
-
update = from_collection_response(response).first
|
53
|
-
deployment.status = update.status
|
54
|
-
end
|
51
|
+
def self.from_response(client, response)
|
52
|
+
new(client, id: response[:deployment_id])
|
55
53
|
end
|
56
54
|
|
57
55
|
def running?
|
data/lib/opsworks/instance.rb
CHANGED
@@ -4,9 +4,10 @@ module OpsWorks
|
|
4
4
|
class Instance < Resource
|
5
5
|
attr_accessor :id, :hostname, :ec2_instance_id, :instance_type, :status
|
6
6
|
|
7
|
-
def self.from_collection_response(response)
|
7
|
+
def self.from_collection_response(client, response)
|
8
8
|
response.data[:instances].map do |hash|
|
9
9
|
new(
|
10
|
+
client,
|
10
11
|
id: hash[:instance_id],
|
11
12
|
hostname: hash[:hostname],
|
12
13
|
ec2_instance_id: hash[:ec2_instance_id],
|
data/lib/opsworks/layer.rb
CHANGED
@@ -5,14 +5,15 @@ module OpsWorks
|
|
5
5
|
class Layer < Resource
|
6
6
|
attr_accessor :id, :name, :shortname, :custom_recipes
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def self.from_collection_response(client, response)
|
9
|
+
response.data[:layers].map do |layer|
|
10
|
+
hash = layer.to_h
|
11
11
|
# Make custom_recipes accessible by string or symbol
|
12
12
|
custom_recipes = Thor::CoreExt::HashWithIndifferentAccess.new(
|
13
13
|
hash[:custom_recipes]
|
14
14
|
)
|
15
15
|
new(
|
16
|
+
client,
|
16
17
|
id: hash[:layer_id],
|
17
18
|
name: hash[:name],
|
18
19
|
shortname: hash[:shortname],
|
@@ -20,14 +21,13 @@ module OpsWorks
|
|
20
21
|
)
|
21
22
|
end
|
22
23
|
end
|
23
|
-
# rubocop:enable MethodLength
|
24
24
|
|
25
25
|
def add_custom_recipe(event, recipe)
|
26
26
|
return if custom_recipes[event].include?(recipe)
|
27
27
|
|
28
28
|
custom_recipes[event] ||= []
|
29
29
|
custom_recipes[event].push recipe
|
30
|
-
|
30
|
+
client.update_layer(
|
31
31
|
layer_id: id,
|
32
32
|
custom_recipes: custom_recipes
|
33
33
|
)
|
@@ -37,7 +37,7 @@ module OpsWorks
|
|
37
37
|
return unless custom_recipes[event].include?(recipe)
|
38
38
|
|
39
39
|
custom_recipes[event].delete recipe
|
40
|
-
|
40
|
+
client.update_layer(
|
41
41
|
layer_id: id,
|
42
42
|
custom_recipes: custom_recipes
|
43
43
|
)
|
data/lib/opsworks/permission.rb
CHANGED
@@ -4,9 +4,11 @@ module OpsWorks
|
|
4
4
|
class Permission < Resource
|
5
5
|
attr_accessor :id, :stack_id, :iam_user_arn, :ssh, :sudo
|
6
6
|
|
7
|
-
def self.from_collection_response(response)
|
8
|
-
response.data[:permissions].map do |
|
7
|
+
def self.from_collection_response(client, response)
|
8
|
+
response.data[:permissions].map do |permission|
|
9
|
+
hash = permission.to_h
|
9
10
|
new(
|
11
|
+
client,
|
10
12
|
id: hash[:permission_id],
|
11
13
|
stack_id: hash[:stack_id],
|
12
14
|
iam_user_arn: hash[:iam_user_arn],
|
@@ -32,7 +34,7 @@ module OpsWorks
|
|
32
34
|
options[:ssh] = ssh if options[:ssh].nil?
|
33
35
|
options[:sudo] = sudo if options[:sudo].nil?
|
34
36
|
|
35
|
-
|
37
|
+
client.set_permission(
|
36
38
|
stack_id: stack_id,
|
37
39
|
iam_user_arn: iam_user_arn,
|
38
40
|
allow_ssh: options[:ssh],
|
data/lib/opsworks/resource.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
|
-
require 'aws'
|
2
|
-
|
3
1
|
module OpsWorks
|
4
2
|
class Resource
|
5
|
-
|
3
|
+
attr_reader :client
|
4
|
+
|
5
|
+
def initialize(client, options = {})
|
6
|
+
@client = client
|
7
|
+
|
6
8
|
options.each do |key, value|
|
7
9
|
send("#{key}=", value) if respond_to?("#{key}=")
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
11
|
-
def self.client
|
12
|
-
@client ||= AWS::OpsWorks::Client.new
|
13
|
-
end
|
14
|
-
|
15
13
|
def self.account
|
16
14
|
ENV['ACCOUNT'] || @account || 'opsworks-cli'
|
17
15
|
end
|
data/lib/opsworks/stack.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'jsonpath'
|
2
|
+
require 'aws-sdk'
|
2
3
|
require 'active_support/core_ext/hash/slice'
|
3
4
|
|
4
5
|
require 'opsworks/resource'
|
@@ -8,20 +9,38 @@ require 'opsworks/permission'
|
|
8
9
|
require 'opsworks/layer'
|
9
10
|
|
10
11
|
module OpsWorks
|
11
|
-
# rubocop:disable ClassLength
|
12
12
|
class Stack < Resource
|
13
13
|
attr_accessor :id, :name, :custom_json
|
14
14
|
|
15
|
-
AVAILABLE_CHEF_VERSIONS = %w(0.9 11.4 11.10)
|
15
|
+
AVAILABLE_CHEF_VERSIONS = %w(0.9 11.4 11.10).freeze
|
16
|
+
DEPLOY_NO_INSTANCES_ERROR = 'Please provide at least an instance ID of ' \
|
17
|
+
'one running instance'.freeze
|
16
18
|
|
17
19
|
def self.all
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
regions = Aws.partition('aws').regions.select do |region|
|
21
|
+
region.services.include?('OpsWorks')
|
22
|
+
end.map(&:name)
|
23
|
+
|
24
|
+
stack_queue = Queue.new
|
25
|
+
|
26
|
+
threads = regions.map do |region|
|
27
|
+
Thread.new do
|
28
|
+
client = Aws::OpsWorks::Client.new(region: region)
|
29
|
+
client.describe_stacks.stacks.each do |stack|
|
30
|
+
stack_queue << new(
|
31
|
+
client,
|
32
|
+
id: stack.stack_id,
|
33
|
+
name: stack.name,
|
34
|
+
custom_json: JSON.parse(stack.custom_json || '{}')
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
24
38
|
end
|
39
|
+
threads.each(&:join)
|
40
|
+
|
41
|
+
stacks = []
|
42
|
+
stacks << stack_queue.pop until stack_queue.empty?
|
43
|
+
stacks
|
25
44
|
end
|
26
45
|
|
27
46
|
def self.active
|
@@ -60,6 +79,10 @@ module OpsWorks
|
|
60
79
|
@layers ||= initialize_layers
|
61
80
|
end
|
62
81
|
|
82
|
+
def deployments
|
83
|
+
@deployments ||= initialize_deployments
|
84
|
+
end
|
85
|
+
|
63
86
|
def update_chef(options)
|
64
87
|
params = {
|
65
88
|
stack_id: id,
|
@@ -83,7 +106,7 @@ module OpsWorks
|
|
83
106
|
password: options[:cookbook_password]
|
84
107
|
}
|
85
108
|
end
|
86
|
-
|
109
|
+
client.update_stack(params)
|
87
110
|
end
|
88
111
|
|
89
112
|
def update_custom_cookbooks
|
@@ -99,15 +122,24 @@ module OpsWorks
|
|
99
122
|
)
|
100
123
|
end
|
101
124
|
|
102
|
-
def deploy_app(app, args
|
103
|
-
|
104
|
-
|
125
|
+
def deploy_app(app, layer: nil, args: {})
|
126
|
+
raise 'App not found' unless app && app.id
|
127
|
+
|
128
|
+
deploy_args = {
|
105
129
|
app_id: app.id,
|
106
130
|
command: {
|
107
131
|
name: 'deploy',
|
108
132
|
args: args
|
109
133
|
}
|
110
|
-
|
134
|
+
}
|
135
|
+
|
136
|
+
if layer
|
137
|
+
layer = layers.find { |l| l.shortname == layer }
|
138
|
+
raise "Layer #{layer} not found" unless layer
|
139
|
+
deploy_args[:layer_ids] = [layer.id]
|
140
|
+
end
|
141
|
+
|
142
|
+
create_deployment(**deploy_args)
|
111
143
|
end
|
112
144
|
|
113
145
|
def active?
|
@@ -121,31 +153,23 @@ module OpsWorks
|
|
121
153
|
def set_custom_json_at(key, value)
|
122
154
|
self.custom_json = replace_hash_at_path(custom_json, key, value)
|
123
155
|
|
124
|
-
|
156
|
+
client.update_stack(
|
125
157
|
stack_id: id,
|
126
158
|
custom_json: JSON.pretty_generate(custom_json)
|
127
159
|
)
|
128
160
|
end
|
129
161
|
|
130
162
|
def create_app(name, options = {})
|
131
|
-
options = options.slice(:type, :shortname)
|
132
|
-
|
133
|
-
self.class.client.create_app(options)
|
163
|
+
options = options.slice(:type, :shortname).merge(stack_id: id, name: name)
|
164
|
+
client.create_app(options)
|
134
165
|
end
|
135
166
|
|
136
167
|
private
|
137
168
|
|
138
|
-
def initialize_apps
|
139
|
-
return [] unless id
|
140
|
-
response = self.class.client.describe_apps(stack_id: id)
|
141
|
-
App.from_collection_response(response)
|
142
|
-
end
|
143
|
-
|
144
169
|
# rubocop:disable Eval
|
145
|
-
# rubocop:disable MethodLength
|
146
170
|
def replace_hash_at_path(hash, key, value)
|
147
171
|
path = JsonPath.new(key).path
|
148
|
-
if value
|
172
|
+
if !value.nil?
|
149
173
|
# REVIEW: Is there a better way to parse the JSON Path and ensure
|
150
174
|
# a value at the location?
|
151
175
|
(0...(path.length - 1)).each do |i|
|
@@ -160,33 +184,46 @@ module OpsWorks
|
|
160
184
|
|
161
185
|
hash
|
162
186
|
end
|
163
|
-
# rubocop:enable MethodLength
|
164
187
|
# rubocop:enable Eval
|
165
188
|
|
189
|
+
def initialize_apps
|
190
|
+
return [] unless id
|
191
|
+
response = client.describe_apps(stack_id: id)
|
192
|
+
App.from_collection_response(client, response)
|
193
|
+
end
|
194
|
+
|
166
195
|
def initialize_permissions
|
167
196
|
return [] unless id
|
168
|
-
response =
|
169
|
-
Permission.from_collection_response(response)
|
197
|
+
response = client.describe_permissions(stack_id: id)
|
198
|
+
Permission.from_collection_response(client, response)
|
170
199
|
end
|
171
200
|
|
172
201
|
def initialize_instances
|
173
202
|
return [] unless id
|
174
|
-
response =
|
175
|
-
Instance.from_collection_response(response)
|
203
|
+
response = client.describe_instances(stack_id: id)
|
204
|
+
Instance.from_collection_response(client, response)
|
176
205
|
end
|
177
206
|
|
178
207
|
def initialize_layers
|
179
208
|
return [] unless id
|
180
|
-
response =
|
181
|
-
Layer.from_collection_response(response)
|
209
|
+
response = client.describe_layers(stack_id: id)
|
210
|
+
Layer.from_collection_response(client, response)
|
211
|
+
end
|
212
|
+
|
213
|
+
def initialize_deployments
|
214
|
+
return [] unless id
|
215
|
+
response = client.describe_deployments(stack_id: id)
|
216
|
+
Deployment.from_collection_response(client, response)
|
182
217
|
end
|
183
218
|
|
184
219
|
def create_deployment(options = {})
|
185
|
-
response =
|
220
|
+
response = client.create_deployment(
|
186
221
|
options.merge(stack_id: id)
|
187
222
|
)
|
188
|
-
|
223
|
+
rescue Aws::OpsWorks::Errors::ValidationException => e
|
224
|
+
raise unless e.message == DEPLOY_NO_INSTANCES_ERROR
|
225
|
+
else
|
226
|
+
Deployment.from_response(client, response)
|
189
227
|
end
|
190
228
|
end
|
191
|
-
# rubocop:enable ClassLength
|
192
229
|
end
|
data/opsworks-cli.gemspec
CHANGED
@@ -21,15 +21,14 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.require_paths = ['lib']
|
22
22
|
|
23
23
|
spec.add_dependency 'thor'
|
24
|
-
spec.add_dependency 'aws-sdk
|
24
|
+
spec.add_dependency 'aws-sdk', '~> 2.9.6'
|
25
25
|
spec.add_dependency 'jsonpath'
|
26
26
|
spec.add_dependency 'activesupport'
|
27
|
-
spec.add_dependency 'omnivault'
|
28
27
|
|
29
28
|
spec.add_development_dependency 'bundler', '~> 1.5'
|
30
29
|
spec.add_development_dependency 'aptible-tasks'
|
31
30
|
spec.add_development_dependency 'rake'
|
32
31
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
33
|
-
spec.add_development_dependency 'fabrication'
|
32
|
+
spec.add_development_dependency 'fabrication', '~> 2.16.0'
|
34
33
|
spec.add_development_dependency 'pry'
|
35
34
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe OpsWorks::CLI::Agent do
|
4
4
|
context 'apps' do
|
5
5
|
let(:app_name) { 'aptible' }
|
6
|
-
let(:stacks) { 2
|
6
|
+
let(:stacks) { Array.new(2) { Fabricate(:stack) } }
|
7
7
|
|
8
8
|
before { allow(subject).to receive(:say) }
|
9
9
|
before { allow(OpsWorks::Deployment).to receive(:wait) }
|
@@ -46,9 +46,9 @@ describe OpsWorks::CLI::Agent do
|
|
46
46
|
|
47
47
|
it 'should optionally run migrations' do
|
48
48
|
expect(stacks[0]).to receive(:deploy_app)
|
49
|
-
.with(app, 'migrate' => ['true']) { success }
|
49
|
+
.with(app, args: { 'migrate' => ['true'] }, layer: nil) { success }
|
50
50
|
expect(stacks[1]).to receive(:deploy_app)
|
51
|
-
.with(app, 'migrate' => ['true']) { success }
|
51
|
+
.with(app, args: { 'migrate' => ['true'] }, layer: nil) { success }
|
52
52
|
|
53
53
|
allow(subject).to receive(:options) { { migrate: true } }
|
54
54
|
subject.send('apps:deploy', app_name)
|
@@ -80,7 +80,7 @@ describe OpsWorks::CLI::Agent do
|
|
80
80
|
end
|
81
81
|
|
82
82
|
it 'should fail with a helpful error on unsupported type' do
|
83
|
-
options
|
83
|
+
options[:type] = 'foobar'
|
84
84
|
allow(subject).to receive(:options) { options }
|
85
85
|
expect { subject.send('apps:create', app_name) }.to raise_error
|
86
86
|
end
|
@@ -95,12 +95,50 @@ describe OpsWorks::CLI::Agent do
|
|
95
95
|
end
|
96
96
|
|
97
97
|
it 'should accept a different shortname' do
|
98
|
-
options
|
98
|
+
options[:shortname] = 'foobar'
|
99
99
|
allow(subject).to receive(:options) { options }
|
100
100
|
expect(stacks[0]).to receive(:create_app).with(app_name, options)
|
101
101
|
expect(stacks[1]).to receive(:create_app).with(app_name, options)
|
102
102
|
subject.send('apps:create', app_name)
|
103
103
|
end
|
104
104
|
end
|
105
|
+
|
106
|
+
describe 'apps:revision:update' do
|
107
|
+
let(:revision) { '123' }
|
108
|
+
|
109
|
+
before do
|
110
|
+
stacks.each do |stack|
|
111
|
+
app = Fabricate(:app, name: app_name)
|
112
|
+
allow(stack).to receive(:apps) { [app] }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should update the app revision on all stacks' do
|
117
|
+
expect(stacks[0].apps[0]).to receive(:update_revision).with(revision)
|
118
|
+
expect(stacks[1].apps[0]).to receive(:update_revision).with(revision)
|
119
|
+
subject.send('apps:revision:update', app_name, revision)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should only run on active stacks' do
|
123
|
+
allow(OpsWorks::Stack).to receive(:active) { [stacks[0]] }
|
124
|
+
expect(stacks[0].apps[0]).to receive(:update_revision).with(revision)
|
125
|
+
expect(stacks[1].apps[0]).not_to receive(:update_revision)
|
126
|
+
subject.send('apps:revision:update', app_name, revision)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should optionally run on a subset of stacks' do
|
130
|
+
expect(stacks[0].apps[0]).to receive(:update_revision).with(revision)
|
131
|
+
expect(stacks[1].apps[0]).not_to receive(:update_revision)
|
132
|
+
|
133
|
+
allow(subject).to receive(:options) { { stack: [stacks[0].name] } }
|
134
|
+
subject.send('apps:revision:update', app_name, revision)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should not fail if a stack does not have the app' do
|
138
|
+
allow(stacks[0]).to receive(:apps) { [] }
|
139
|
+
expect(stacks[1].apps[0]).to receive(:update_revision).with(revision)
|
140
|
+
subject.send('apps:revision:update', app_name, revision)
|
141
|
+
end
|
142
|
+
end
|
105
143
|
end
|
106
144
|
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe OpsWorks::CLI::Agent do
|
4
4
|
describe 'chef:sync' do
|
5
|
-
let(:stacks) { 2
|
5
|
+
let(:stacks) { Array.new(2) { Fabricate(:stack) } }
|
6
6
|
let(:deployment) { Fabricate(:deployment, status: 'successful') }
|
7
7
|
|
8
8
|
before { allow(subject).to receive(:say) }
|
@@ -5,11 +5,9 @@ describe OpsWorks::CLI::Agent do
|
|
5
5
|
let(:custom_json) { { 'env' => { 'FOO' => 'bar' } } }
|
6
6
|
let(:json_path) { 'env.FOO' }
|
7
7
|
let(:stack) { Fabricate(:stack, custom_json: custom_json) }
|
8
|
-
let(:client) { double.as_null_object }
|
9
8
|
|
10
9
|
before { allow(subject).to receive(:say) }
|
11
|
-
before { allow(
|
12
|
-
before { allow(OpsWorks::Stack).to receive(:client) { client } }
|
10
|
+
before { allow(subject).to receive(:parse_stacks).and_return([stack]) }
|
13
11
|
|
14
12
|
describe 'config:get' do
|
15
13
|
it 'should print the variable from the stack custom JSON' do
|
@@ -30,7 +28,7 @@ describe OpsWorks::CLI::Agent do
|
|
30
28
|
|
31
29
|
describe 'config:set' do
|
32
30
|
it 'should reset the variable, if it is already set' do
|
33
|
-
expect(client).to receive(:update_stack) do |hash|
|
31
|
+
expect(stack.client).to receive(:update_stack) do |hash|
|
34
32
|
json = JSON.parse(hash[:custom_json])
|
35
33
|
expect(json['env']['FOO']).to eq 'baz'
|
36
34
|
end
|
@@ -40,7 +38,7 @@ describe OpsWorks::CLI::Agent do
|
|
40
38
|
|
41
39
|
it 'should work with deep nested hashes' do
|
42
40
|
stack.custom_json = { 'app' => { 'var' => 'value' } }
|
43
|
-
expect(client).to receive(:update_stack) do |hash|
|
41
|
+
expect(stack.client).to receive(:update_stack) do |hash|
|
44
42
|
json = JSON.parse(hash[:custom_json])
|
45
43
|
expect(json['app']['env']['FOO']).to eq 'baz'
|
46
44
|
end
|
@@ -50,7 +48,7 @@ describe OpsWorks::CLI::Agent do
|
|
50
48
|
|
51
49
|
it 'should set the variable, if it is unset' do
|
52
50
|
stack.custom_json = {}
|
53
|
-
expect(client).to receive(:update_stack) do |hash|
|
51
|
+
expect(stack.client).to receive(:update_stack) do |hash|
|
54
52
|
json = JSON.parse(hash[:custom_json])
|
55
53
|
expect(json['env']['FOO']).to eq 'baz'
|
56
54
|
end
|
@@ -59,8 +57,8 @@ describe OpsWorks::CLI::Agent do
|
|
59
57
|
end
|
60
58
|
|
61
59
|
it 'should leave other variables alone' do
|
62
|
-
stack.custom_json
|
63
|
-
expect(client).to receive(:update_stack) do |hash|
|
60
|
+
stack.custom_json['other'] = 'something'
|
61
|
+
expect(stack.client).to receive(:update_stack) do |hash|
|
64
62
|
json = JSON.parse(hash[:custom_json])
|
65
63
|
expect(json['env']['FOO']).to eq 'baz'
|
66
64
|
expect(json['other']).to eq 'something'
|
@@ -72,7 +70,7 @@ describe OpsWorks::CLI::Agent do
|
|
72
70
|
|
73
71
|
it 'should typecast Boolean values' do
|
74
72
|
stack.custom_json = {}
|
75
|
-
expect(client).to receive(:update_stack) do |hash|
|
73
|
+
expect(stack.client).to receive(:update_stack) do |hash|
|
76
74
|
json = JSON.parse(hash[:custom_json])
|
77
75
|
expect(json['env']['FOO']).to eq true
|
78
76
|
end
|
@@ -83,7 +81,7 @@ describe OpsWorks::CLI::Agent do
|
|
83
81
|
|
84
82
|
describe 'config:unset' do
|
85
83
|
it 'should unset the variable' do
|
86
|
-
expect(client).to receive(:update_stack) do |hash|
|
84
|
+
expect(stack.client).to receive(:update_stack) do |hash|
|
87
85
|
json = JSON.parse(hash[:custom_json])
|
88
86
|
expect(json['env'].keys).not_to include('FOO')
|
89
87
|
end
|
@@ -92,8 +90,8 @@ describe OpsWorks::CLI::Agent do
|
|
92
90
|
end
|
93
91
|
|
94
92
|
it 'should leave other variables alone' do
|
95
|
-
stack.custom_json['env']
|
96
|
-
expect(client).to receive(:update_stack) do |hash|
|
93
|
+
stack.custom_json['env']['OTHER'] = 'something'
|
94
|
+
expect(stack.client).to receive(:update_stack) do |hash|
|
97
95
|
json = JSON.parse(hash[:custom_json])
|
98
96
|
expect(json['env'].keys).not_to include('FOO')
|
99
97
|
end
|
@@ -103,7 +101,7 @@ describe OpsWorks::CLI::Agent do
|
|
103
101
|
|
104
102
|
it 'should work even with nil values' do
|
105
103
|
stack.custom_json['env'] = { 'FOO' => nil }
|
106
|
-
expect(client).to receive(:update_stack) do |hash|
|
104
|
+
expect(stack.client).to receive(:update_stack) do |hash|
|
107
105
|
json = JSON.parse(hash[:custom_json])
|
108
106
|
expect(json['env'].keys).not_to include('FOO')
|
109
107
|
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe OpsWorks::CLI::Agent do
|
4
4
|
context 'iam' do
|
5
|
-
let(:permissions) { 2
|
5
|
+
let(:permissions) { Array.new(2) { Fabricate(:permission) } }
|
6
6
|
let(:user) { permissions[0].user }
|
7
7
|
|
8
8
|
before { allow(subject).to receive(:say) }
|
@@ -11,7 +11,7 @@ describe OpsWorks::CLI::Agent do
|
|
11
11
|
|
12
12
|
describe 'iam:allow' do
|
13
13
|
let(:stacks) do
|
14
|
-
2
|
14
|
+
Array.new(2) do |i|
|
15
15
|
Fabricate(:stack).tap do |stack|
|
16
16
|
allow(stack).to receive(:find_permission_by_user) { permissions[i] }
|
17
17
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe OpsWorks::CLI::Agent do
|
4
4
|
context 'recipes' do
|
5
5
|
let(:recipe) { 'hotpockets::install' }
|
6
|
-
let(:stacks) { 2
|
6
|
+
let(:stacks) { Array.new(2) { Fabricate(:stack) } }
|
7
7
|
|
8
8
|
before { allow(subject).to receive(:say) }
|
9
9
|
before { allow(OpsWorks::Deployment).to receive(:wait) }
|
data/spec/spec_helper.rb
CHANGED
@@ -4,14 +4,12 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
4
4
|
# Require library up front
|
5
5
|
require 'opsworks/cli'
|
6
6
|
|
7
|
+
require 'securerandom'
|
7
8
|
require 'fabrication'
|
8
|
-
require 'omnivault'
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
allow(AWS).to receive(:config)
|
10
|
+
def opsworks_stub
|
11
|
+
Aws::OpsWorks::Client.new(stub_responses: true)
|
12
|
+
end
|
14
13
|
|
15
|
-
|
16
|
-
end
|
14
|
+
RSpec.configure do |_config|
|
17
15
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opsworks-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Macreery
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -25,19 +25,19 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: aws-sdk
|
28
|
+
name: aws-sdk
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 2.9.6
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 2.9.6
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: jsonpath
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: omnivault
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: bundler
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -140,16 +126,16 @@ dependencies:
|
|
140
126
|
name: fabrication
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
142
128
|
requirements:
|
143
|
-
- - "
|
129
|
+
- - "~>"
|
144
130
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
131
|
+
version: 2.16.0
|
146
132
|
type: :development
|
147
133
|
prerelease: false
|
148
134
|
version_requirements: !ruby/object:Gem::Requirement
|
149
135
|
requirements:
|
150
|
-
- - "
|
136
|
+
- - "~>"
|
151
137
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
138
|
+
version: 2.16.0
|
153
139
|
- !ruby/object:Gem::Dependency
|
154
140
|
name: pry
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -183,12 +169,12 @@ files:
|
|
183
169
|
- lib/opsworks/app.rb
|
184
170
|
- lib/opsworks/cli.rb
|
185
171
|
- lib/opsworks/cli/agent.rb
|
186
|
-
- lib/opsworks/cli/helpers/credentials.rb
|
187
172
|
- lib/opsworks/cli/helpers/options.rb
|
188
173
|
- lib/opsworks/cli/helpers/typecasts.rb
|
189
174
|
- lib/opsworks/cli/subcommands/apps.rb
|
190
175
|
- lib/opsworks/cli/subcommands/chef.rb
|
191
176
|
- lib/opsworks/cli/subcommands/config.rb
|
177
|
+
- lib/opsworks/cli/subcommands/deployments.rb
|
192
178
|
- lib/opsworks/cli/subcommands/iam.rb
|
193
179
|
- lib/opsworks/cli/subcommands/recipes.rb
|
194
180
|
- lib/opsworks/cli/version.rb
|
@@ -230,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
216
|
version: '0'
|
231
217
|
requirements: []
|
232
218
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.4.5
|
219
|
+
rubygems_version: 2.4.5.1
|
234
220
|
signing_key:
|
235
221
|
specification_version: 4
|
236
222
|
summary: Alternative CLI for Amazon OpsWorks
|
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'omnivault'
|
2
|
-
|
3
|
-
module OpsWorks
|
4
|
-
module CLI
|
5
|
-
module Helpers
|
6
|
-
module Credentials
|
7
|
-
def fetch_credentials
|
8
|
-
vault = Omnivault.autodetect
|
9
|
-
vault.configure_aws!
|
10
|
-
end
|
11
|
-
|
12
|
-
def env_credentials?
|
13
|
-
!!(ENV['AWS_ACCESS_KEY_ID'] && ENV['AWS_SECRET_ACCESS_KEY'])
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|