opsworks-cli 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 18ef86da686c271e0ca39400b1fdfd653933f038
4
- data.tar.gz: 49fdede59882e9141496a5421857b5e25c052c15
3
+ metadata.gz: 24ea648dce531f41a4ff17b6d4cb9ab1991f708a
4
+ data.tar.gz: 4b897b4a05110a1de06a9196da2ac84d8a9fc62a
5
5
  SHA512:
6
- metadata.gz: ee72de4769bfa43179a859bbc512cfadc4ba5342103ea9de8cbd5f653a21d988b7810f589753e2cf1b6dcb9406df4e9f42585ec1dee35ad57b9d6274da2c3faa
7
- data.tar.gz: a58f99d1895c9f4bf4f673c739387717c464ad1b45064766c77e5d53283f7eafbb285d9515e96cf8a0093ce264e3104eefc8cffb36583bfd8bd44f5331826493
6
+ metadata.gz: 899b768a457184a2e69014c9de72e385fa35601a7c4229c6df828793624532d8002967a7c3a9a0386fe9aa1fc5bf6f6f0c3db011180b53da6613692a1c7faa75
7
+ data.tar.gz: fbbe850475cfa8625531a78ce76df586255f85fa41cebab7ee94cccda743da2ae074e0794dcdc57abe7b7e82838becbda8f72d2f9e64fc6bf3d564f72dd3e367
data/lib/opsworks/app.rb CHANGED
@@ -1,10 +1,17 @@
1
- require_relative 'resource'
2
- require_relative 'deployment'
1
+ require 'opsworks/resource'
2
+ require 'opsworks/deployment'
3
3
 
4
4
  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 |hash|
10
+ revision = hash[:app_source][:revision] if hash[:app_source]
11
+ new(id: hash[:app_id], name: hash[:name], revision: revision)
12
+ end
13
+ end
14
+
8
15
  def deployments
9
16
  @deployments ||= initialize_deployments
10
17
  end
@@ -9,10 +9,6 @@ require_relative 'subcommands/exec'
9
9
  require_relative 'subcommands/deploy'
10
10
  require_relative 'subcommands/status'
11
11
 
12
- require 'opsworks/stack'
13
- require 'opsworks/app'
14
- require 'opsworks/deployment'
15
-
16
12
  module OpsWorks
17
13
  module CLI
18
14
  class Agent < Thor
@@ -5,11 +5,13 @@ module OpsWorks
5
5
  module Helpers
6
6
  module Options
7
7
  def parse_stacks(options = {})
8
- stacks = OpsWorks::Stack.all
9
8
  if options[:stack]
10
- stacks.select! { |stack| options[:stack].include?(stack.name) }
9
+ OpsWorks::Stack.all.select! do |stack|
10
+ options[:stack].include?(stack.name)
11
+ end
12
+ else
13
+ options[:active] ? OpsWorks::Stack.active : OpsWorks::Stack.all
11
14
  end
12
- stacks
13
15
  end
14
16
  end
15
17
  end
@@ -1,3 +1,5 @@
1
+ require 'opsworks/deployment'
2
+
1
3
  module OpsWorks
2
4
  module CLI
3
5
  module Subcommands
@@ -10,17 +12,18 @@ module OpsWorks
10
12
  option :stack, type: :array
11
13
  def deploy(name)
12
14
  fetch_keychain_credentials unless env_credentials?
13
- stacks = parse_stacks(options)
15
+ stacks = parse_stacks(options.merge(active: true))
14
16
  deployments = stacks.map do |stack|
15
17
  next unless (app = stack.find_app_by_name(name))
16
18
  say "Deploying to #{stack.name}..."
17
19
  stack.deploy_app(app)
18
20
  end
19
- Deployment.wait(deployments)
21
+ deployments.compact!
22
+ OpsWorks::Deployment.wait(deployments)
20
23
  unless deployments.all?(&:success?)
21
24
  failures = []
22
25
  deployments.each_with_index do |deployment, i|
23
- failures << stacks[i].name if deployment.failed?
26
+ failures << stacks[i].name unless deployment.success?
24
27
  end
25
28
  fail "Deploy failed on #{failures.join(', ')}"
26
29
  end
@@ -1,3 +1,5 @@
1
+ require 'opsworks/deployment'
2
+
1
3
  module OpsWorks
2
4
  module CLI
3
5
  module Subcommands
@@ -10,16 +12,16 @@ module OpsWorks
10
12
  option :stack, type: :array
11
13
  def exec(recipe)
12
14
  fetch_keychain_credentials unless env_credentials?
13
- stacks = parse_stacks(options)
15
+ stacks = parse_stacks(options.merge(active: true))
14
16
  deployments = stacks.map do |stack|
15
17
  say "Executing recipe on #{stack.name}..."
16
18
  stack.execute_recipe(recipe)
17
19
  end
18
- Deployment.wait(deployments)
20
+ OpsWorks::Deployment.wait(deployments)
19
21
  unless deployments.all?(&:success?)
20
22
  failures = []
21
23
  deployments.each_with_index do |deployment, i|
22
- failures << stacks[i].name if deployment.failed?
24
+ failures << stacks[i].name unless deployment.success?
23
25
  end
24
26
  fail "Command failed on #{failures.join(', ')}"
25
27
  end
@@ -25,8 +25,8 @@ module OpsWorks
25
25
  [stack.name, name, "(#{app.revision})", deployed_at]
26
26
  end
27
27
  # Sort output in descending date order
28
- table.sort! { |x, y| y.last <=> x.last }
29
28
  table.compact!
29
+ table.sort! { |x, y| y.last <=> x.last }
30
30
  print_table table
31
31
  end
32
32
 
@@ -1,4 +1,5 @@
1
1
  require 'aws'
2
+ require 'opsworks/deployment'
2
3
 
3
4
  module OpsWorks
4
5
  module CLI
@@ -15,16 +16,16 @@ module OpsWorks
15
16
  option :stack, type: :array
16
17
  def update
17
18
  fetch_keychain_credentials unless env_credentials?
18
- stacks = parse_stacks(options)
19
+ stacks = parse_stacks(options.merge(active: true))
19
20
  deployments = stacks.map do |stack|
20
21
  say "Updating #{stack.name}..."
21
22
  stack.update_custom_cookbooks
22
23
  end
23
- Deployment.wait(deployments)
24
+ OpsWorks::Deployment.wait(deployments)
24
25
  unless deployments.all?(&:success?)
25
26
  failures = []
26
27
  deployments.each_with_index do |deployment, i|
27
- failures << stacks[i].name if deployment.failed?
28
+ failures << stacks[i].name unless deployment.success?
28
29
  end
29
30
  fail "Update failed on #{failures.join(', ')}"
30
31
  end
@@ -1,5 +1,5 @@
1
1
  module OpsWorks
2
2
  module CLI
3
- VERSION = '0.2.0'
3
+ VERSION = '0.2.1'
4
4
  end
5
5
  end
@@ -1,20 +1,28 @@
1
- require_relative 'resource'
1
+ require 'opsworks/resource'
2
2
 
3
3
  module OpsWorks
4
4
  class Deployment < Resource
5
5
  attr_accessor :id, :status, :created_at
6
6
 
7
+ TIMEOUT = 300
7
8
  POLL_INTERVAL = 5
9
+ API_LIMIT = 25
8
10
 
9
11
  # rubocop:disable MethodLength
10
- def self.wait(deployments)
12
+ def self.wait(deployments, timeout = TIMEOUT)
13
+ start_time = Time.now
11
14
  while deployments.any?(&:running?)
15
+ return if Time.now - start_time > timeout
12
16
  sleep POLL_INTERVAL
13
- response = client.describe_deployments(
14
- deployment_ids: deployments.map(&:id)
15
- )
16
- updates = from_collection_response(response)
17
- deployments.each do |deployment|
17
+ updates = []
18
+ running_deployments = deployments.select(&:running?)
19
+ running_deployments.map(&:id).each_slice(API_LIMIT) do |slice|
20
+ response = client.describe_deployments(
21
+ deployment_ids: slice
22
+ )
23
+ updates += from_collection_response(response)
24
+ end
25
+ running_deployments.each do |deployment|
18
26
  update = updates.find { |u| u.id == deployment.id }
19
27
  deployment.status = update.status
20
28
  end
@@ -0,0 +1,23 @@
1
+ require 'opsworks/resource'
2
+
3
+ module OpsWorks
4
+ class Instance < Resource
5
+ attr_accessor :id, :hostname, :ec2_instance_id, :instance_type, :status
6
+
7
+ def self.from_collection_response(response)
8
+ response.data[:instances].map do |hash|
9
+ new(
10
+ id: hash[:instance_id],
11
+ hostname: hash[:hostname],
12
+ ec2_instance_id: hash[:ec2_instance_id],
13
+ instance_type: hash[:instance_type],
14
+ status: hash[:status]
15
+ )
16
+ end
17
+ end
18
+
19
+ def online?
20
+ status == 'online'
21
+ end
22
+ end
23
+ end
@@ -1,3 +1,5 @@
1
+ require 'aws'
2
+
1
3
  module OpsWorks
2
4
  class Resource
3
5
  def initialize(options = {})
@@ -1,5 +1,6 @@
1
- require_relative 'resource'
2
- require_relative 'app'
1
+ require 'opsworks/resource'
2
+ require 'opsworks/app'
3
+ require 'opsworks/instance'
3
4
 
4
5
  module OpsWorks
5
6
  class Stack < Resource
@@ -11,6 +12,10 @@ module OpsWorks
11
12
  end
12
13
  end
13
14
 
15
+ def self.active
16
+ all.select(&:active?)
17
+ end
18
+
14
19
  def self.find_by_name(name)
15
20
  all.find { |stack| stack.name == name }
16
21
  end
@@ -23,6 +28,10 @@ module OpsWorks
23
28
  apps.find { |app| app.name == name }
24
29
  end
25
30
 
31
+ def instances
32
+ @instances ||= initialize_instances
33
+ end
34
+
26
35
  def update_custom_cookbooks
27
36
  create_deployment(command: { name: 'update_custom_cookbooks' })
28
37
  end
@@ -41,14 +50,22 @@ module OpsWorks
41
50
  create_deployment(app_id: app.id, command: { name: 'deploy' })
42
51
  end
43
52
 
53
+ def active?
54
+ instances.any?(&:online?)
55
+ end
56
+
44
57
  private
45
58
 
46
59
  def initialize_apps
47
60
  return [] unless id
48
- self.class.client.describe_apps(stack_id: id).data[:apps].map do |hash|
49
- revision = hash[:app_source][:revision] if hash[:app_source]
50
- App.new(id: hash[:app_id], name: hash[:name], revision: revision)
51
- end
61
+ response = self.class.client.describe_apps(stack_id: id)
62
+ App.from_collection_response(response)
63
+ end
64
+
65
+ def initialize_instances
66
+ return [] unless id
67
+ response = self.class.client.describe_instances(stack_id: id)
68
+ Instance.from_collection_response(response)
52
69
  end
53
70
 
54
71
  def create_deployment(options = {})
data/opsworks-cli.gemspec CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency 'bundler', '~> 1.5'
28
28
  spec.add_development_dependency 'aptible-tasks'
29
29
  spec.add_development_dependency 'rake'
30
- spec.add_development_dependency 'rspec', '~> 2.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency 'fabrication'
31
32
  spec.add_development_dependency 'pry'
32
33
  end
@@ -0,0 +1,5 @@
1
+ Fabricator(:app, from: OpsWorks::App) do
2
+ id { SecureRandom.uuid }
3
+ name { Fabricate.sequence(:name) { |i| "app#{i}" } }
4
+ revision 'master'
5
+ end
@@ -0,0 +1,5 @@
1
+ Fabricator(:deployment, from: OpsWorks::Deployment) do
2
+ id { SecureRandom.uuid }
3
+ status 'running'
4
+ created_at { Time.now }
5
+ end
@@ -0,0 +1,4 @@
1
+ Fabricator(:stack, from: OpsWorks::Stack) do
2
+ id { SecureRandom.uuid }
3
+ name { Fabricate.sequence(:name) { |i| "test-stack#{i}" } }
4
+ end
@@ -1,8 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe OpsWorks::CLI::Agent do
4
- before { subject.stub(:ask) }
5
-
6
4
  describe '#version' do
7
5
  it 'should print the version' do
8
6
  version = OpsWorks::CLI::VERSION
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpsWorks::CLI::Agent do
4
+ describe '#deploy' do
5
+ let(:app_name) { 'aptible' }
6
+ let(:app) { Fabricate(:app, name: app_name) }
7
+
8
+ let(:stacks) { 2.times.map { Fabricate(:stack) } }
9
+ let(:deployment) { Fabricate(:deployment, status: 'successful') }
10
+
11
+ before { allow(subject).to receive(:say) }
12
+ before { allow(OpsWorks::Deployment).to receive(:wait) }
13
+ before { allow(OpsWorks::Stack).to receive(:all) { stacks } }
14
+ before { allow(OpsWorks::Stack).to receive(:active) { stacks } }
15
+
16
+ before { stacks.each { |stack| allow(stack).to receive(:apps) { [app] } } }
17
+
18
+ it 'should update custom cookbooks on all stacks' do
19
+ expect(stacks[0]).to receive(:deploy_app).with(app) { deployment }
20
+ expect(stacks[1]).to receive(:deploy_app).with(app) { deployment }
21
+ subject.deploy(app_name)
22
+ end
23
+
24
+ it 'should not fail if some stacks are inactive' do
25
+ allow(OpsWorks::Stack).to receive(:active) { [stacks[0]] }
26
+ expect(stacks[0]).to receive(:deploy_app).with(app) { deployment }
27
+ expect(stacks[1]).not_to receive(:deploy_app)
28
+ subject.deploy(app_name)
29
+ end
30
+
31
+ it 'should optionally run on a subset of stacks' do
32
+ expect(stacks[0]).to receive(:deploy_app).with(app) { deployment }
33
+ expect(stacks[1]).not_to receive(:deploy_app)
34
+
35
+ allow(subject).to receive(:options) { { stack: [stacks[0].name] } }
36
+ subject.deploy(app_name)
37
+ end
38
+
39
+ it 'should not fail if a stack does not have the app' do
40
+ allow(stacks[0]).to receive(:apps) { [] }
41
+ expect(stacks[1]).to receive(:deploy_app).with(app) { deployment }
42
+ expect { subject.deploy(app_name) }.not_to raise_error
43
+ end
44
+
45
+ it 'should fail if any update fails' do
46
+ failure = Fabricate(:deployment, status: 'failed')
47
+ expect(stacks[0]).to receive(:deploy_app).with(app) { failure }
48
+
49
+ allow(subject).to receive(:options) { { stack: [stacks[0].name] } }
50
+ expect { subject.deploy(app_name) }.to raise_error
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpsWorks::CLI::Agent do
4
+ describe '#exec' do
5
+ let(:recipe) { 'hotpockets::install' }
6
+
7
+ let(:stacks) { 2.times.map { Fabricate(:stack) } }
8
+ let(:deployment) { Fabricate(:deployment, status: 'successful') }
9
+
10
+ before { allow(subject).to receive(:say) }
11
+ before { allow(OpsWorks::Deployment).to receive(:wait) }
12
+ before { allow(OpsWorks::Stack).to receive(:all) { stacks } }
13
+ before { allow(OpsWorks::Stack).to receive(:active) { stacks } }
14
+
15
+ it 'should update custom cookbooks on all stacks' do
16
+ expect(stacks[0]).to receive(:execute_recipe).with(recipe) { deployment }
17
+ expect(stacks[1]).to receive(:execute_recipe).with(recipe) { deployment }
18
+ subject.exec(recipe)
19
+ end
20
+
21
+ it 'should optionally run on a subset of stacks' do
22
+ expect(stacks[0]).to receive(:execute_recipe).with(recipe) { deployment }
23
+ expect(stacks[1]).not_to receive(:execute_recipe)
24
+
25
+ allow(subject).to receive(:options) { { stack: [stacks[0].name] } }
26
+ subject.exec(recipe)
27
+ end
28
+
29
+ it 'should fail if any update fails' do
30
+ failure = Fabricate(:deployment, status: 'failed')
31
+ expect(stacks[0]).to receive(:execute_recipe).with(recipe) { failure }
32
+
33
+ allow(subject).to receive(:options) { { stack: [stacks[0].name] } }
34
+ expect { subject.exec(recipe) }.to raise_error
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpsWorks::CLI::Agent do
4
+ describe '#update' do
5
+ let(:stacks) { 2.times.map { Fabricate(:stack) } }
6
+ let(:deployment) { Fabricate(:deployment, status: 'successful') }
7
+
8
+ before { allow(subject).to receive(:say) }
9
+ before { allow(OpsWorks::Deployment).to receive(:wait) }
10
+ before { allow(OpsWorks::Stack).to receive(:all) { stacks } }
11
+ before { allow(OpsWorks::Stack).to receive(:active) { stacks } }
12
+
13
+ it 'should update custom cookbooks on all stacks' do
14
+ expect(stacks[0]).to receive(:update_custom_cookbooks) { deployment }
15
+ expect(stacks[1]).to receive(:update_custom_cookbooks) { deployment }
16
+ subject.update
17
+ end
18
+
19
+ it 'should optionally run on a subset of stacks' do
20
+ expect(stacks[0]).to receive(:update_custom_cookbooks) { deployment }
21
+ expect(stacks[1]).not_to receive(:update_custom_cookbooks)
22
+
23
+ allow(subject).to receive(:options) { { stack: [stacks[0].name] } }
24
+ subject.update
25
+ end
26
+
27
+ it 'should fail if any update fails' do
28
+ failure = Fabricate(:deployment, status: 'failed')
29
+ expect(stacks[0]).to receive(:update_custom_cookbooks) { failure }
30
+
31
+ allow(subject).to receive(:options) { { stack: [stacks[0].name] } }
32
+ expect { subject.update }.to raise_error
33
+ end
34
+ end
35
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,23 @@
1
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
3
 
4
- # Load shared spec files
5
- Dir["#{File.dirname(__FILE__)}/shared/**/*.rb"].each do |file|
6
- require file
7
- end
8
-
9
4
  # Require library up front
10
5
  require 'opsworks/cli'
6
+
7
+ require 'fabrication'
8
+
9
+ RSpec.configure do |config|
10
+ config.before do
11
+ allow(AWS::OpsWorks::Client).to receive(:new) { double.as_null_object }
12
+ allow(AWS).to receive(:config)
13
+
14
+ begin
15
+ require 'aws-keychain-util/credential_provider'
16
+ allow(AwsKeychainUtil::CredentialProvider).to receive(:new) do
17
+ double.as_null_object
18
+ end
19
+ rescue LoadError
20
+ nil
21
+ end
22
+ end
23
+ 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.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Macreery
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-27 00:00:00.000000000 Z
11
+ date: 2014-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -100,14 +100,28 @@ dependencies:
100
100
  requirements:
101
101
  - - ~>
102
102
  - !ruby/object:Gem::Version
103
- version: '2.0'
103
+ version: '3.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ~>
109
109
  - !ruby/object:Gem::Version
110
- version: '2.0'
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: fabrication
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: pry
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -149,10 +163,17 @@ files:
149
163
  - lib/opsworks/cli/subcommands/update.rb
150
164
  - lib/opsworks/cli/version.rb
151
165
  - lib/opsworks/deployment.rb
166
+ - lib/opsworks/instance.rb
152
167
  - lib/opsworks/resource.rb
153
168
  - lib/opsworks/stack.rb
154
169
  - opsworks-cli.gemspec
170
+ - spec/fabricators/opsworks/app_fabricator.rb
171
+ - spec/fabricators/opsworks/deployment_fabricator.rb
172
+ - spec/fabricators/opsworks/stack_fabricator.rb
155
173
  - spec/opsworks/cli/agent_spec.rb
174
+ - spec/opsworks/cli/subcommands/deploy_spec.rb
175
+ - spec/opsworks/cli/subcommands/exec_spec.rb
176
+ - spec/opsworks/cli/subcommands/update_spec.rb
156
177
  - spec/spec_helper.rb
157
178
  homepage: https://github.com/aptible/opsworks-cli
158
179
  licenses:
@@ -179,5 +200,11 @@ signing_key:
179
200
  specification_version: 4
180
201
  summary: Alternative CLI for Amazon OpsWorks
181
202
  test_files:
203
+ - spec/fabricators/opsworks/app_fabricator.rb
204
+ - spec/fabricators/opsworks/deployment_fabricator.rb
205
+ - spec/fabricators/opsworks/stack_fabricator.rb
182
206
  - spec/opsworks/cli/agent_spec.rb
207
+ - spec/opsworks/cli/subcommands/deploy_spec.rb
208
+ - spec/opsworks/cli/subcommands/exec_spec.rb
209
+ - spec/opsworks/cli/subcommands/update_spec.rb
183
210
  - spec/spec_helper.rb