dream-ops 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8b8aa9f131724802c240a24e1d23dbf37eb8c70e
4
- data.tar.gz: 513e26443ce943be5ec10c4251a90794364ce536
3
+ metadata.gz: fe5971e39855c322c3680d7495452950f6e129ac
4
+ data.tar.gz: 6d7b6cd6b14ae02429694879aa34d7656ecd2228
5
5
  SHA512:
6
- metadata.gz: d95f1f526da0bcc544dfcb06af87576571b46cf4ad75e14d1b68b36085084121ec5f14a1c561a9da5197fad733a9c2ad934d0ea5a6e3f2d73e4ad98c928f0341
7
- data.tar.gz: 42e9083c9b3d6038548bd5b4897a91c861eb673a84bb2e46d0f675eef5166e74802bc17d7a0fe674082b5e067a7fc08509beed271cb178e59ad70b9644a5465d
6
+ metadata.gz: 04e7efd3f5108eb2f547265bee1b326a1523d4e19b8cd49eea62b0d45c7331509ec380d5a7d4e996312bf6d90fcd3665226d4fa220fb8f81644e93d24be31ac5
7
+ data.tar.gz: 642468eaabb2ec607aa1c3415dc21052884927d6e8835bca5849a9d08eee1edf966cc66db02993962a34438b73f0649f776ca786c077f23e89ee43b03cda8d22
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # DreamOps
2
+ [![Gem Version](https://img.shields.io/gem/v/dream-ops.svg)][gem]
2
3
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dream-ops`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+ [gem]: https://rubygems.org/gems/dream-ops
4
5
 
5
- TODO: Delete this and the text above, and describe your gem
6
+ CLI for Dream projects based on the [berkshelf](https://github.com/berkshelf/berkshelf) project.
6
7
 
7
8
  ## Installation
8
9
 
@@ -22,7 +23,23 @@ Or install it yourself as:
22
23
 
23
24
  ## Usage
24
25
 
25
- TODO: Write usage instructions here
26
+ ```bash
27
+ dream help
28
+ ```
29
+
30
+ ### Deploying to OpsWorks
31
+
32
+ ```bash
33
+ dream deploy opsworks --stacks 08137c03-1e85-4787-b82c-cb825638cdfa
34
+ Stack: nodeapp
35
+ --- Cookbook: chef-nodeapp
36
+ --- Apps: ["nodeapp"]
37
+ ...Building cookbook [chef-nodeapp]
38
+ ...Deploying cookbook [chef-nodeapp]
39
+ ...Updating custom cookbooks [stack="nodeapp"]
40
+ ...Running setup command [stack="nodeapp"]
41
+ ...Deploying [stack="nodeapp"] [app="nodeapp"]
42
+ ```
26
43
 
27
44
  ## Development
28
45
 
@@ -32,8 +49,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
49
 
33
50
  ## Contributing
34
51
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dream-ops.
36
-
37
- ## License
38
-
39
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
52
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dream-ops.
@@ -11,24 +11,28 @@ end
11
11
  require "berkshelf"
12
12
  require "thor"
13
13
 
14
- Berkshelf.ui.mute!
15
-
16
14
  module DreamOps
17
15
 
18
16
  require_relative "dream-ops/version"
19
17
  require_relative "dream-ops/errors"
20
18
 
21
- autoload :Shell, "dream-ops/shell"
19
+ module Mixin
20
+ autoload :Logging, "dream-ops/mixin/logging"
21
+ end
22
+
23
+ autoload :Shell, "dream-ops/shell"
22
24
 
23
- autoload :BaseFormatter, "dream-ops/formatters/base"
24
- autoload :HumanFormatter, "dream-ops/formatters/human"
25
- # autoload :JsonFormatter, "dream-ops/formatters/json"
26
- # autoload :NullFormatter, "dream-ops/formatters/null"
25
+ autoload :BaseFormatter, "dream-ops/formatters/base"
26
+ autoload :HumanFormatter, "dream-ops/formatters/human"
27
+ # autoload :JsonFormatter, "dream-ops/formatters/json"
28
+ # autoload :NullFormatter, "dream-ops/formatters/null"
27
29
 
28
- autoload :BaseDeployer, "dream-ops/deployment/base"
30
+ autoload :BaseDeployer, "dream-ops/deployment/base"
29
31
  autoload :OpsWorksDeployer, "dream-ops/deployment/opsworks"
30
32
 
31
33
  class << self
34
+ include Mixin::Logging
35
+
32
36
  # @return [DreamOps::Shell]
33
37
  def ui
34
38
  @ui ||= DreamOps::Shell.new
@@ -57,4 +61,8 @@ module DreamOps
57
61
  end
58
62
  end
59
63
 
60
- require_relative "dream-ops/cli"
64
+ require_relative "dream-ops/cli"
65
+ require_relative "dream-ops/logger"
66
+
67
+ Ridley.logger = DreamOps.logger
68
+ DreamOps.logger.level = Logger::WARN
@@ -27,12 +27,8 @@ module DreamOps
27
27
  @kernel.exit(0)
28
28
  rescue DreamOps::DreamOpsError => e
29
29
  DreamOps.ui.error e
30
- DreamOps.ui.error "\t" + e.backtrace.join("\n\t") if ENV["BERKSHELF_DEBUG"]
30
+ DreamOps.ui.error "\t" + e.backtrace.join("\n\t") if ENV["DREAMOPS_DEBUG"]
31
31
  @kernel.exit(e.status_code)
32
- # rescue Ridley::Errors::RidleyError => e
33
- # DreamOps.ui.error "#{e.class} #{e}"
34
- # DreamOps.ui.error "\t" + e.backtrace.join("\n\t") if ENV["BERKSHELF_DEBUG"]
35
- # @kernel.exit(47)
36
32
  end
37
33
  end
38
34
 
@@ -56,17 +52,11 @@ module DreamOps
56
52
  def initialize(*args)
57
53
  super(*args)
58
54
 
59
- # if @options[:config]
60
- # unless File.exist?(@options[:config])
61
- # raise ConfigNotFound.new(:berkshelf, @options[:config])
62
- # end
63
-
64
- # DreamOps.config = DreamOps::Config.from_file(@options[:config])
65
- # end
66
-
67
55
  if @options[:debug]
68
- ENV["BERKSHELF_DEBUG"] = "true"
56
+ ENV["DREAMOPS_DEBUG"] = "true"
69
57
  DreamOps.logger.level = ::Logger::DEBUG
58
+ else
59
+ Berkshelf.ui.mute!
70
60
  end
71
61
 
72
62
  if @options[:quiet]
@@ -77,19 +67,13 @@ module DreamOps
77
67
  @options = options.dup # unfreeze frozen options Hash from Thor
78
68
  end
79
69
 
80
- namespace "berkshelf"
70
+ namespace "dream-ops"
81
71
 
82
- map "ls" => :list
83
- map "book" => :cookbook
72
+ # map "ls" => :list
84
73
  map ["ver", "-v", "--version"] => :version
85
74
 
86
- default_task :install
75
+ default_task :version
87
76
 
88
- class_option :config,
89
- type: :string,
90
- desc: "Path to DreamOps configuration to use.",
91
- aliases: "-c",
92
- banner: "PATH"
93
77
  class_option :format,
94
78
  type: :string,
95
79
  default: "human",
@@ -115,8 +99,9 @@ module DreamOps
115
99
  method_option :stacks,
116
100
  type: :array,
117
101
  desc: "Only these stack IDs.",
118
- aliases: "-s"
119
- desc "deploy", "Creates deploy"
102
+ aliases: "-s",
103
+ required: true
104
+ desc "deploy [TYPE]", "Deploys to specified targets"
120
105
  def deploy(type)
121
106
  deployer = nil
122
107
 
@@ -125,26 +110,14 @@ module DreamOps
125
110
  deployer = OpsWorksDeployer.new
126
111
  stack_ids = options[:stacks]
127
112
  args = [*stack_ids]
113
+ else
114
+ DreamOps.ui.error "Deployment of type '#{type}' is not supported"
115
+ exit(1)
128
116
  end
129
117
 
130
118
  if !deployer.nil?
131
119
  deployer.deploy(*args)
132
120
  end
133
121
  end
134
-
135
- # tasks["cookbook"].options = DreamOps::CookbookGenerator.class_options
136
-
137
- private
138
-
139
- # Print a list of the given cookbooks. This is used by various
140
- # methods like {list} and {contingent}.
141
- #
142
- # @param [Array<CachedCookbook>] cookbooks
143
- #
144
- def print_list(cookbooks)
145
- Array(cookbooks).sort.each do |cookbook|
146
- DreamOps.formatter.msg " * #{cookbook.cookbook_name} (#{cookbook.version})"
147
- end
148
- end
149
122
  end
150
- end
123
+ end
@@ -20,13 +20,47 @@ module DreamOps
20
20
  end
21
21
  end
22
22
 
23
- # These MUST be implemented by subclasses
23
+ # This method MUST be implemented by subclasses.
24
+ #
25
+ # @return [Hash]
26
+ # a hash containing cookbooks that need to be built/updated
27
+ # and deploy targets
28
+ #
29
+ # Example:
30
+ # {
31
+ # :cookbooks => [
32
+ # {
33
+ # :bucket => "chef-app",
34
+ # :cookbook_key => "chef-app-dev.zip",
35
+ # :sha_key => "chef-app-dev_SHA.txt",
36
+ # :name => "chef-app",
37
+ # :path => "./chef",
38
+ # :local_sha => "7bfa19491170563f422a321c144800f4435323b1",
39
+ # :remote_sha => ""
40
+ # }
41
+ # ],
42
+ # deploy_targets: [
43
+ # #<Hash>,
44
+ # #<Hash>
45
+ # ]
46
+ # }
24
47
  deployer_method :analyze
48
+
49
+ # This method MUST be implemented by subclasses.
50
+ #
51
+ # @param [Hash] cookbook
52
+ # a hash containing the cookbook details
25
53
  deployer_method :deploy_cookbook
54
+
55
+
56
+ # This method MUST be implemented by subclasses.
57
+ #
58
+ # @param [Hash] target
59
+ # a hash containing the deploy target details
26
60
  deployer_method :deploy_target
27
61
 
28
62
  # It may turn out that cookbook building will be different for different
29
- # deployments, but for now all deployments build them the same way.
63
+ # deployments, but for now all deployments will build them the same way.
30
64
  def build_cookbook(cookbook)
31
65
  berksfile = Berkshelf::Berksfile.from_file(File.join(cookbook[:path], "Berksfile"))
32
66
  berksfile.vendor("berks-cookbooks")
@@ -69,11 +103,13 @@ module DreamOps
69
103
  deploy_threads.each do |t|
70
104
  begin
71
105
  t.join
72
- rescue DreamOps::FatalDeployError
106
+ rescue DreamOps::DreamOpsError
107
+ DreamOps.ui.error "#{$!}"
73
108
  deploy_success = deploy_success && false
74
109
  end
75
110
  end
76
111
 
112
+ # If ANY deploy threads failed, exit with failure
77
113
  exit(1) if !deploy_success
78
114
  end
79
115
  end
@@ -6,13 +6,48 @@ Aws.use_bundled_cert!
6
6
  module DreamOps
7
7
  class OpsWorksDeployer < BaseDeployer
8
8
 
9
- # Output the version of DreamOps
9
+ # Analyze the OpsWorks stacks for deployment
10
+ #
11
+ # @return [Hash]
12
+ # a hash containing cookbooks that need to be built/updated
13
+ # and deploy targets
14
+ #
15
+ # Example:
16
+ # {
17
+ # :cookbooks => [
18
+ # {
19
+ # :bucket => "chef-app",
20
+ # :cookbook_key => "chef-app-dev.zip",
21
+ # :sha_key => "chef-app-dev_SHA.txt",
22
+ # :name => "chef-app",
23
+ # :path => "./chef",
24
+ # :local_sha => "7bfa19491170563f422a321c144800f4435323b1",
25
+ # :remote_sha => ""
26
+ # }
27
+ # ],
28
+ # deploy_targets: [
29
+ # {
30
+ # :stack => #<Stack: name="my-stack">,
31
+ # :apps => [
32
+ # #<App: name="my-app"
33
+ # ],
34
+ # :cookbook => {
35
+ # :bucket => "chef-app",
36
+ # :cookbook_key => "chef-app-dev.zip",
37
+ # :sha_key => "chef-app-dev_SHA.txt",
38
+ # :name => "chef-app",
39
+ # :path => "./chef",
40
+ # :local_sha => "7bfa19491170563f422a321c144800f4435323b1",
41
+ # :remote_sha => ""
42
+ # }
43
+ # }
44
+ # ]
45
+ # }
10
46
  def analyze(stack_ids)
11
47
  begin
12
48
  @opsworks = Aws::OpsWorks::Client.new
13
49
  stacks = @opsworks.describe_stacks({ stack_ids: stack_ids, }).stacks
14
50
  rescue => e
15
- DreamOps.ui.error "Failed to fetch OpsWorks stacks\n"
16
51
  DreamOps.ui.error "#{$!}"
17
52
  exit(1)
18
53
  end
@@ -42,8 +77,6 @@ module DreamOps
42
77
  return result
43
78
  end
44
79
 
45
- ################## OpsWorks specific methods ##################
46
-
47
80
  # Retrieves stack apps and gets all information about remote/local cookbook
48
81
  def analyze_stack(stack)
49
82
  cookbook = nil
@@ -118,13 +151,8 @@ module DreamOps
118
151
  # If this stack has a new cookbook
119
152
  if !target[:cookbook].nil?
120
153
  if __cookbook_in_array(target[:cookbook], cookbooks)
121
- begin
122
- # Grab a fresh copy of the cookbook on all instances in the stack
123
- update_custom_cookbooks(target[:stack])
124
- rescue Aws::OpsWorks::Errors::ValidationException
125
- DreamOps.ui.error "Stack \"#{target[:stack].name}\" has no running instances."
126
- __bail_with_fatal_error
127
- end
154
+ # Grab a fresh copy of the cookbook on all instances in the stack
155
+ update_custom_cookbooks(target[:stack])
128
156
 
129
157
  # Re-run the setup step for all layers
130
158
  setup(target[:stack])
@@ -133,22 +161,27 @@ module DreamOps
133
161
 
134
162
  # Deploy all apps for stack
135
163
  target[:apps].each do |app|
136
- begin
137
- deploy_app(app, target[:stack])
138
- rescue Aws::OpsWorks::Errors::ValidationException
139
- DreamOps.ui.error "Stack \"#{target[:stack].name}\" has no running instances."
140
- __bail_with_fatal_error
141
- end
164
+ deploy_app(app, target[:stack])
142
165
  end
143
166
  end
144
167
 
145
168
  def update_custom_cookbooks(stack)
146
169
  DreamOps.ui.info "...Updating custom cookbooks [stack=\"#{stack.name}\"]"
147
- response = @opsworks.create_deployment({
148
- stack_id: stack.stack_id,
149
- command: { name: "update_custom_cookbooks" }
150
- })
151
- return wait_for_deployment(response.deployment_id)
170
+ begin
171
+ response = @opsworks.create_deployment({
172
+ stack_id: stack.stack_id,
173
+ command: { name: "update_custom_cookbooks" }
174
+ })
175
+ rescue Aws::OpsWorks::Errors::ValidationException
176
+ bail_with_fatal_error(NoRunningInstancesError.new(stack))
177
+ end
178
+
179
+ status = wait_for_deployment(response.deployment_id)
180
+ if status != 'successful'
181
+ bail_with_fatal_error(OpsWorksCommandFailedError.new(
182
+ stack, response.deployment_id, 'update_custom_cookbooks')
183
+ )
184
+ end
152
185
  end
153
186
 
154
187
  def setup(stack)
@@ -157,17 +190,33 @@ module DreamOps
157
190
  stack_id: stack.stack_id,
158
191
  command: { name: "setup" }
159
192
  })
160
- return wait_for_deployment(response.deployment_id)
193
+
194
+ status = wait_for_deployment(response.deployment_id)
195
+ if status != 'successful'
196
+ bail_with_fatal_error(OpsWorksCommandFailedError.new(
197
+ stack, response.deployment_id, 'setup')
198
+ )
199
+ end
161
200
  end
162
201
 
163
202
  def deploy_app(app, stack)
164
203
  DreamOps.ui.info "...Deploying [stack=\"#{stack.name}\"] [app=\"#{app.name}\"]"
165
- response = @opsworks.create_deployment({
166
- stack_id: stack.stack_id,
167
- app_id: app.app_id,
168
- command: { name: "deploy" }
169
- })
170
- return wait_for_deployment(response.deployment_id)
204
+ begin
205
+ response = @opsworks.create_deployment({
206
+ stack_id: stack.stack_id,
207
+ app_id: app.app_id,
208
+ command: { name: "deploy" }
209
+ })
210
+ rescue Aws::OpsWorks::Errors::ValidationException
211
+ bail_with_fatal_error(NoRunningInstancesError.new(stack))
212
+ end
213
+
214
+ status = wait_for_deployment(response.deployment_id)
215
+ if status != 'successful'
216
+ bail_with_fatal_error(OpsWorksCommandFailedError.new(
217
+ stack, response.deployment_id, 'deploy')
218
+ )
219
+ end
171
220
  end
172
221
 
173
222
  def get_deployment_status(deployment_id)
@@ -188,8 +237,8 @@ module DreamOps
188
237
  return status
189
238
  end
190
239
 
191
- def __bail_with_fatal_error
192
- raise FatalDeployError
240
+ def bail_with_fatal_error(ex)
241
+ raise ex
193
242
  Thread.exit
194
243
  end
195
244
 
@@ -11,5 +11,35 @@ module DreamOps
11
11
  alias_method :message, :to_s
12
12
  end
13
13
 
14
- class FatalDeployError < DreamOpsError; set_status_code(10); end
14
+ class NoRunningInstancesError < DreamOpsError
15
+ set_status_code(10)
16
+
17
+ def initialize(stack)
18
+ @stack = stack
19
+ end
20
+
21
+ def to_s
22
+ "Stack \"#{@stack.name}\" has no running instances."
23
+ end
24
+ end
25
+
26
+ class OpsWorksCommandFailedError < DreamOpsError
27
+ set_status_code(11)
28
+
29
+ def initialize(stack, deployment_id, command)
30
+ @stack = stack
31
+ @deployment_id = deployment_id
32
+ @command = command
33
+ end
34
+
35
+ def to_s
36
+ [
37
+ "Stack \"#{@stack.name}\" failed running command '#{@command}'. To view the failure log, visit:",
38
+ "",
39
+ "https://console.aws.amazon.com/opsworks/home?region=#{@stack.region}#/stack/#{@stack.stack_id}/deployments/#{@deployment_id}",
40
+ ].join("\n")
41
+ end
42
+
43
+ alias_method :message, :to_s
44
+ end
15
45
  end
@@ -2,7 +2,7 @@ module DreamOps
2
2
  class HumanFormatter < BaseFormatter
3
3
  # Output the version of DreamOps
4
4
  def version
5
- DreamOps.ui.info DreamOps::VERSION
5
+ DreamOps.ui.info "Dream Ops v#{DreamOps::VERSION}"
6
6
  end
7
7
 
8
8
  # @param [DreamOps::Dependency] dependency
@@ -0,0 +1,18 @@
1
+ module DreamOps
2
+ class Logger < Ridley::Logging::Logger
3
+ alias_method :fatal, :error
4
+
5
+ def deprecate(message)
6
+ trace = caller.join("\n\t")
7
+ warn "DEPRECATION WARNING: #{message}\n\t#{trace}"
8
+ end
9
+
10
+ # Log an exception and its backtrace to FATAL
11
+ #
12
+ # @param [Exception] ex
13
+ def exception(ex)
14
+ fatal("#{ex.class}: #{ex}")
15
+ fatal(ex.backtrace.join("\n")) unless ex.backtrace.nil?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module DreamOps
2
+ module Mixin
3
+ module Logging
4
+ attr_writer :logger
5
+
6
+ def logger
7
+ @logger ||= DreamOps::Logger.new(STDOUT)
8
+ end
9
+ alias_method :log, :logger
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module DreamOps
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dream-ops
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Allen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-16 00:00:00.000000000 Z
11
+ date: 2017-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -152,6 +152,8 @@ files:
152
152
  - lib/dream-ops/errors.rb
153
153
  - lib/dream-ops/formatters/base.rb
154
154
  - lib/dream-ops/formatters/human.rb
155
+ - lib/dream-ops/logger.rb
156
+ - lib/dream-ops/mixin/logging.rb
155
157
  - lib/dream-ops/shell.rb
156
158
  - lib/dream-ops/utils/zip.rb
157
159
  - lib/dream-ops/version.rb
@@ -175,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
177
  version: 2.0.0
176
178
  requirements: []
177
179
  rubyforge_project:
178
- rubygems_version: 2.4.8
180
+ rubygems_version: 2.6.12
179
181
  signing_key:
180
182
  specification_version: 4
181
183
  summary: This is the summary