opsworks-cli 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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