opsworks-cli 0.4.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|