dream-ops 0.3.0 → 0.4.0

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: cb363370cd9c9603a6971dcf9cc367abaec8824b
4
- data.tar.gz: 1b501140522af9ce3f984fe76ea8b498e4b44118
3
+ metadata.gz: a640e2b3d14a77a968e916ceff5699a51a75aa49
4
+ data.tar.gz: 8beff4a4bc985e83caa02d7c537a8595baf6d6e9
5
5
  SHA512:
6
- metadata.gz: b15b31e9d7eed839854be3bd5589d56f4306b42664a9ac5a18bf222583d04bdf32dcab1320d616f941a37cbb1fed4d98a06df2b21fc83940532c100a128e0412
7
- data.tar.gz: 230fc144ae650fbbe0d45cf5b8d56e29d8948786c04bd16ad316bf5b37d2a661b576c7594536dd42cba28a8bbbebcf8127903ac484c5f33743f1cc5e7c506ee3
6
+ metadata.gz: 54546e61709396f02aaab3f8f5d5653e44b1a9b2c25ff88057da04be2c3eaefba888c42084f3d3ddf8397f8be7a07265db51a77b654200ec10e8c3a257082019
7
+ data.tar.gz: c79c27aa79c56bf1ac0c5811d74283d9c3df208f9ab5bd5cab2e378a403850f57da4b93945adecb41ba9e34836d04240697f1fb6b0685170eab25c0ecbbc7837
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
+ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.4.0]
10
+
11
+ ### Changed
12
+ - **BREAKING CHANGE**: Renamed `-s` and `--stacks` options to `-T` and `--targets` respectively
13
+ - Updated to `"berkshelf" ~> 7.0`
14
+ - Updated to `"chef" ~> 13.6`
15
+ - Updated to `"thor" ~> 0.20`
16
+
17
+ ### Added
18
+ - Added `solo` deploy type
19
+ - New `init` command for initializing configuration
20
+ - Added global `ssh_key` option
21
+ - Added global `force_setup` option
22
+ - Better error handling / logging
23
+
24
+ ### Removed
25
+ - Removed `ridley` as a dependency
26
+
27
+ ## [0.3.0]
28
+
29
+ ### Fixes
30
+ - Problem with newer S3 domains
31
+
32
+ ### Added
33
+ - Travis CI integration along with sample unit test
34
+
35
+ ## [0.2.0]
36
+
37
+ ### Added
38
+ - Better error handling / logging
39
+ - Gem version badge to README
40
+ - Usage documentation
41
+
42
+ [Unreleased]: https://github.com/chris-allen/dream-ops/compare/v0.4.0...HEAD
43
+ [0.4.0]: https://github.com/chris-allen/dream-ops/compare/v0.3.0...v0.4.0
44
+ [0.3.0]: https://github.com/chris-allen/dream-ops/compare/v0.2.0...v0.3.0
45
+ [0.2.0]: https://github.com/chris-allen/dream-ops/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -26,12 +26,25 @@ Or install it yourself as:
26
26
 
27
27
  ```bash
28
28
  dream help
29
+ Commands:
30
+ dream deploy [TYPE] -T, --targets=one two three # Deploys to specified targets
31
+ dream help [COMMAND] # Describe available commands or one specific command
32
+ dream init [TYPE] -T, --targets=one two three # Initialize configuration on specified targets
33
+ dream version # Display version
34
+
35
+ Options:
36
+ -F, [--format=FORMAT] # Output format to use.
37
+ # Default: human
38
+ -q, [--quiet], [--no-quiet] # Silence all informational output.
39
+ -d, [--debug], [--no-debug] # Output debug information
40
+ -i, [--ssh-key=SSH_KEY] # Path to SSH key
41
+
29
42
  ```
30
43
 
31
44
  ### Deploying to OpsWorks
32
45
 
33
46
  ```bash
34
- dream deploy opsworks --stacks 08137c03-1e85-4787-b82c-cb825638cdfa
47
+ dream deploy opsworks -T 08137c03-1e85-4787-b82c-cb825638cdfa
35
48
  Stack: nodeapp
36
49
  --- Cookbook: chef-nodeapp
37
50
  --- Apps: ["nodeapp"]
@@ -42,11 +55,63 @@ Stack: nodeapp
42
55
  ...Deploying [stack="nodeapp"] [app="nodeapp"]
43
56
  ```
44
57
 
58
+ ### Deploy Using `chef-solo`
59
+
60
+ ```bash
61
+ dream deploy solo -T ubuntu@example.com -i /path/to/key.pem
62
+ Target: ip-172-31-53-232
63
+ --- Cookbook: chef-nodeapp (outdated)
64
+ ...Building cookbook [chef-nodeapp]
65
+ ...Deploying cookbook [chef-nodeapp]
66
+ ...Syncing [repo="nodeapp" target="ubuntu@example.com"]
67
+ ...Running setup role [target="ubuntu@example.com"]
68
+ ...Running deploy role [target="ubuntu@example.com"]
69
+ ```
70
+
71
+ ### Initialize `chef-solo`
72
+
73
+ ```bash
74
+ dream init solo -T ubuntu@example.com -i /path/to/key.pem
75
+ Target: ip-172-31-53-232
76
+ --- ChefDK Installed: false
77
+ --- Valid chef.json: false
78
+ --- Valid role[setup]: false
79
+ --- Valid role[deploy]: false
80
+ ...Installing ChefDK [target="ubuntu@example.com"]
81
+ ...Creating boilerplate /var/chef/chef.json [target="ubuntu@example.com"]
82
+ ...Creating boilerplate /var/chef/roles/setup.json [target="ubuntu@example.com"]
83
+ ...Creating boilerplate /var/chef/roles/deploy.json [target="ubuntu@example.com"]
84
+ ```
85
+
45
86
  ## Development
46
87
 
47
88
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
48
89
 
49
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
90
+ To install this gem onto your local machine, run `bundle exec rake install`.
91
+
92
+
93
+ ## Release
94
+ ### switch to `master` branch:
95
+ - Change package version in `version.rb` according to release changes (`major|minor|patch`).
96
+ - Update `CHANGELOG.md`:
97
+ - Rename `[Unreleased]` section to reflect new release version and release date, same format as for all previous releases
98
+ - Create new `[Unreleased]` section on top of file, as it was previously
99
+ - On the bottom of `CHANGELOG.md` file, create comparison reference for current release changes:
100
+ ```
101
+ # was
102
+ [Unreleased]: https://github.com/chris-allen/dream-ops/compare/v0.3.0...HEAD
103
+ [0.3.0]: https://github.com/chris-allen/dream-ops/compare/v0.2.0...v0.3.0
104
+
105
+ # became
106
+ # - "Unreleased" renamed to commit version
107
+ # - new "Unreleased" created, comparing last "0.4.0" commit with "HEAD"
108
+ [Unreleased]: https://github.com/chris-allen/dream-ops/compare/v0.4.0...HEAD
109
+ [0.4.0]: https://github.com/chris-allen/dream-ops/compare/v0.3.0...v0.4.0
110
+ [0.3.0]: https://github.com/chris-allen/dream-ops/compare/v0.2.0...v0.3.0
111
+ ```
112
+ - Commit `CHANGELOG.md` and `version.rb` with message `:rocket: {version}` (where version is your release version)
113
+
114
+ - Then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
50
115
 
51
116
  ## Contributing
52
117
 
@@ -11,10 +11,18 @@ Gem::Specification.new do |spec|
11
11
  spec.required_ruby_version = ">= 2.3.1"
12
12
  spec.required_rubygems_version = ">= 2.0.0"
13
13
 
14
- spec.summary = "This is the summary"
15
- spec.description = "This is the description"
14
+ spec.summary = "CLI for automating the deployment of application cookbooks"
15
+ spec.description = <<-EOF
16
+ This CLI automatically rebuilds and deploys your cookbook
17
+ when changes are detected. Application deployment is
18
+ supported for OpsWorks stacks and ubuntu SSH hosts.
19
+ EOF
16
20
  spec.homepage = "https://github.com/chris-allen/dream-ops"
17
21
  spec.license = "MIT"
22
+ spec.metadata = {
23
+ "source_code_uri" => "https://github.com/chris-allen/dream-ops",
24
+ "changelog_uri" => "https://github.com/chris-allen/dream-ops/blob/master/CHANGELOG.md"
25
+ }
18
26
 
19
27
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
28
  f.match(%r{^(test|spec|features)/})
@@ -28,8 +36,7 @@ Gem::Specification.new do |spec|
28
36
 
29
37
  spec.add_dependency "rubyzip", "~> 1.2"
30
38
  spec.add_dependency "aws-sdk", "~> 2"
31
- spec.add_dependency "berkshelf", "~> 6.2"
32
- spec.add_dependency "ridley", "~> 5.0"
33
- spec.add_dependency "thor", "~> 0.19", "< 0.19.2"
34
- spec.add_dependency "chef", "~> 12.7"
39
+ spec.add_dependency "berkshelf", "~> 7.0"
40
+ spec.add_dependency "thor", "~> 0.20"
41
+ spec.add_dependency "chef", "~> 13.6"
35
42
  end
@@ -29,6 +29,9 @@ module DreamOps
29
29
 
30
30
  autoload :BaseDeployer, "dream-ops/deployment/base"
31
31
  autoload :OpsWorksDeployer, "dream-ops/deployment/opsworks"
32
+ autoload :SoloDeployer, "dream-ops/deployment/solo"
33
+ autoload :BaseInitializer, "dream-ops/init/base"
34
+ autoload :SoloInitializer, "dream-ops/init/solo"
32
35
 
33
36
  class << self
34
37
  include Mixin::Logging
@@ -58,11 +61,38 @@ module DreamOps
58
61
  id = name.to_s.capitalize
59
62
  @formatter = DreamOps.const_get("#{id}Formatter").new
60
63
  end
64
+
65
+ # Get path for the SSH key
66
+ #
67
+ # @return [~String]
68
+ def ssh_key
69
+ @ssh_key ||= nil
70
+ end
71
+
72
+ # Specify path to use for the SSH key
73
+ #
74
+ # @return [~String]
75
+ def set_ssh_key(key)
76
+ @ssh_key = key
77
+ end
78
+
79
+ # Get whether to always run setup
80
+ #
81
+ # @return [~boolean]
82
+ def force_setup
83
+ @force_setup ||= false
84
+ end
85
+
86
+ # Specify whether to always run setup
87
+ #
88
+ # @return [~boolean]
89
+ def set_force_setup(force)
90
+ @force_setup = force
91
+ end
61
92
  end
62
93
  end
63
94
 
64
95
  require_relative "dream-ops/cli"
65
96
  require_relative "dream-ops/logger"
66
97
 
67
- Ridley.logger = DreamOps.logger
68
- DreamOps.logger.level = Logger::WARN
98
+ DreamOps.logger.level = Logger::WARN
@@ -64,6 +64,11 @@ module DreamOps
64
64
  end
65
65
 
66
66
  DreamOps.set_format @options[:format]
67
+
68
+ if @options[:ssh_key]
69
+ DreamOps.set_ssh_key @options[:ssh_key]
70
+ end
71
+
67
72
  @options = options.dup # unfreeze frozen options Hash from Thor
68
73
  end
69
74
 
@@ -90,26 +95,37 @@ module DreamOps
90
95
  desc: "Output debug information",
91
96
  aliases: "-d",
92
97
  default: false
98
+ class_option :ssh_key,
99
+ type: :string,
100
+ desc: "Path to SSH key",
101
+ aliases: "-i",
102
+ default: ""
93
103
 
94
104
  desc "version", "Display version"
95
105
  def version
96
106
  DreamOps.formatter.version
97
107
  end
98
108
 
99
- method_option :stacks,
109
+ method_option :targets,
100
110
  type: :array,
101
- desc: "Only these stack IDs.",
102
- aliases: "-s",
111
+ desc: "Only these targets",
112
+ aliases: "-T",
103
113
  required: true
114
+ method_option :force_setup,
115
+ type: :boolean,
116
+ desc: "Always run setup",
117
+ aliases: "-f"
104
118
  desc "deploy [TYPE]", "Deploys to specified targets"
105
119
  def deploy(type)
106
120
  deployer = nil
121
+ DreamOps.set_force_setup options[:force_setup]
122
+ targets = options[:targets]
123
+ args = [*targets]
107
124
 
108
- args = []
109
125
  if type == 'opsworks'
110
126
  deployer = OpsWorksDeployer.new
111
- stack_ids = options[:stacks]
112
- args = [*stack_ids]
127
+ elsif type == 'solo'
128
+ deployer = SoloDeployer.new
113
129
  else
114
130
  DreamOps.ui.error "Deployment of type '#{type}' is not supported"
115
131
  exit(1)
@@ -119,5 +135,33 @@ module DreamOps
119
135
  deployer.deploy(*args)
120
136
  end
121
137
  end
138
+
139
+ method_option :targets,
140
+ type: :array,
141
+ desc: "Only these targets",
142
+ aliases: "-T",
143
+ required: true
144
+ method_option :dryrun,
145
+ type: :boolean,
146
+ desc: "Only print what actions will be taken",
147
+ aliases: "-x",
148
+ default: false
149
+ desc "init [TYPE]", "Initialize configuration on specified targets"
150
+ def init(type)
151
+ initializer = nil
152
+ targets = options[:targets]
153
+ args = [*targets]
154
+
155
+ if type == 'solo'
156
+ initializer = SoloInitializer.new
157
+ else
158
+ DreamOps.ui.error "Type '#{type}' is not supported"
159
+ exit(1)
160
+ end
161
+
162
+ if !initializer.nil?
163
+ initializer.init(*args, options[:dryrun])
164
+ end
165
+ end
122
166
  end
123
167
  end
@@ -1,4 +1,3 @@
1
- require "berkshelf"
2
1
  require "dream-ops/utils/zip"
3
2
  require 'fileutils'
4
3
 
@@ -31,8 +30,8 @@ module DreamOps
31
30
  # :cookbooks => [
32
31
  # {
33
32
  # :bucket => "chef-app",
34
- # :cookbook_key => "chef-app-dev.zip",
35
- # :sha_key => "chef-app-dev_SHA.txt",
33
+ # :cookbook_filename => "chef-app-dev.zip",
34
+ # :sha_filename => "chef-app-dev_SHA.txt",
36
35
  # :name => "chef-app",
37
36
  # :path => "./chef",
38
37
  # :local_sha => "7bfa19491170563f422a321c144800f4435323b1",
@@ -59,8 +58,19 @@ module DreamOps
59
58
  # a hash containing the deploy target details
60
59
  deployer_method :deploy_target
61
60
 
62
- # It may turn out that cookbook building will be different for different
63
- # deployments, but for now all deployments will build them the same way.
61
+ def __get_cookbook_paths()
62
+ # Treat any directory with a Berksfile as a cookbook
63
+ cookbooks = Dir.glob('./**/Berksfile')
64
+
65
+ return cookbooks.map { |path| path.gsub(/(.*)(\/Berksfile)(.*)/, '\1') }
66
+ end
67
+
68
+ def __bail_with_fatal_error(ex)
69
+ raise ex
70
+ Thread.exit
71
+ end
72
+
73
+ # Collect cookbook dependencies and compress based on file extension
64
74
  def build_cookbook(cookbook)
65
75
  berksfile = Berkshelf::Berksfile.from_file(File.join(cookbook[:path], "Berksfile"))
66
76
  berksfile.vendor("berks-cookbooks")
@@ -69,13 +79,18 @@ module DreamOps
69
79
  file.write("source \"https://supermarket.chef.io\"\n\n")
70
80
  file.write("cookbook \"#{cookbook[:name]}\", path: \"./#{cookbook[:name]}\"")
71
81
  }
72
- zf = ZipFileGenerator.new("berks-cookbooks", cookbook[:cookbook_key])
73
- zf.write
82
+
83
+ if cookbook[:cookbook_filename].end_with? ".zip"
84
+ zf = ZipFileGenerator.new("berks-cookbooks", cookbook[:cookbook_filename])
85
+ zf.write
86
+ elsif cookbook[:cookbook_filename].end_with? ".tar.gz"
87
+ system("tar -czvf #{cookbook[:cookbook_filename]} -C berks-cookbooks . > /dev/null 2>&1")
88
+ end
74
89
  end
75
90
 
76
91
  def cleanup_cookbooks(cookbooks)
77
92
  cookbooks.each do |cookbook|
78
- File.delete(cookbook[:cookbook_key])
93
+ File.delete(cookbook[:cookbook_filename])
79
94
  FileUtils.remove_dir("berks-cookbooks")
80
95
  end
81
96
  end
@@ -113,4 +128,4 @@ module DreamOps
113
128
  exit(1) if !deploy_success
114
129
  end
115
130
  end
116
- end
131
+ end
@@ -1,5 +1,4 @@
1
1
  require "aws-sdk"
2
- require "ridley"
3
2
  require "uri"
4
3
 
5
4
  Aws.use_bundled_cert!
@@ -18,8 +17,8 @@ module DreamOps
18
17
  # :cookbooks => [
19
18
  # {
20
19
  # :bucket => "chef-app",
21
- # :cookbook_key => "chef-app-dev.zip",
22
- # :sha_key => "chef-app-dev_SHA.txt",
20
+ # :cookbook_filename => "chef-app-dev.zip",
21
+ # :sha_filename => "chef-app-dev_SHA.txt",
23
22
  # :name => "chef-app",
24
23
  # :path => "./chef",
25
24
  # :local_sha => "7bfa19491170563f422a321c144800f4435323b1",
@@ -34,8 +33,8 @@ module DreamOps
34
33
  # ],
35
34
  # :cookbook => {
36
35
  # :bucket => "chef-app",
37
- # :cookbook_key => "chef-app-dev.zip",
38
- # :sha_key => "chef-app-dev_SHA.txt",
36
+ # :cookbook_filename => "chef-app-dev.zip",
37
+ # :sha_filename => "chef-app-dev_SHA.txt",
39
38
  # :name => "chef-app",
40
39
  # :path => "./chef",
41
40
  # :local_sha => "7bfa19491170563f422a321c144800f4435323b1",
@@ -81,7 +80,6 @@ module DreamOps
81
80
  # Retrieves stack apps and gets all information about remote/local cookbook
82
81
  def analyze_stack(stack)
83
82
  cookbook = nil
84
- cookbookName = nil
85
83
 
86
84
  DreamOps.ui.info "Stack: #{stack.name}"
87
85
  if !stack.custom_cookbooks_source.nil?
@@ -93,30 +91,40 @@ module DreamOps
93
91
  firstSlash = cookbookPath.index('/')
94
92
  cookbook = {
95
93
  bucket: cookbookPath[0..(firstSlash-1)],
96
- cookbook_key: cookbookPath[(firstSlash+1)..-1],
97
- sha_key: cookbookPath[firstSlash+1..-1].sub('.zip', '_SHA.txt')
94
+ cookbook_filename: cookbookPath[(firstSlash+1)..-1],
95
+ sha_filename: cookbookPath[firstSlash+1..-1].sub('.zip', '_SHA.txt')
98
96
  }
99
97
 
100
- # Treat any directory with a Berksfile as a cookbook
101
- cookbooks = Dir.glob('./**/Berksfile')
98
+ cookbooks = __get_cookbook_paths()
102
99
 
103
100
  # For now we only handle if we find one cookbook
104
101
  if cookbooks.length == 1
105
- metadata = Ridley::Chef::Cookbook::Metadata.from_file(cookbooks[0].sub("Berksfile", "metadata.rb"))
102
+ path = cookbooks[0]
103
+ loader = Chef::Cookbook::CookbookVersionLoader.new(path)
104
+ loader.load_cookbooks
105
+ cookbook_version = loader.cookbook_version
106
+ metadata = cookbook_version.metadata
107
+
106
108
  cookbook[:name] = metadata.name
107
- cookbook[:path] = cookbooks[0].sub('/Berksfile', '')
108
- if cookbook[:cookbook_key].include? cookbook[:name]
109
+ cookbook[:path] = path
110
+ if cookbook[:cookbook_filename].include? cookbook[:name]
109
111
  cookbook[:local_sha] = `git log --pretty=%H -1 #{cookbook[:path]}`.chomp
110
112
 
111
113
  begin
112
- obj = Aws::S3::Object.new(cookbook[:bucket], cookbook[:sha_key])
114
+ obj = Aws::S3::Object.new(cookbook[:bucket], cookbook[:sha_filename])
113
115
  cookbook[:remote_sha] = obj.get.body.string
114
116
  rescue Aws::S3::Errors::NoSuchKey
115
- cookbook[:remote_sha] = ''
117
+ cookbook[:remote_sha] = ""
116
118
  end
117
119
  else
118
- DreamOps.ui.info "Stack cookbook source is '#{cookbook[:cookbook_key]}' but found '#{cookbook[:name]}' locally"
120
+ DreamOps.ui.warn "Stack cookbook source is '#{cookbook[:cookbook_filename]}' but found '#{cookbook[:name]}' locally"
119
121
  end
122
+ elsif cookbooks.length > 1
123
+ DreamOps.ui.warn "Found more than one cookbook at paths #{cookbooks}. Skipping build."
124
+ cookbook[:name] = "???"
125
+ else
126
+ DreamOps.ui.warn "No cookbook found"
127
+ cookbook[:name] = "???"
120
128
  end
121
129
  DreamOps.ui.info "--- Cookbook: #{cookbook[:name]}"
122
130
  end
@@ -135,12 +143,12 @@ module DreamOps
135
143
  # Deploys cookbook to S3
136
144
  def deploy_cookbook(cookbook)
137
145
  begin
138
- archiveFile = File.open(cookbook[:cookbook_key])
139
- remoteCookbook = Aws::S3::Object.new(cookbook[:bucket], cookbook[:cookbook_key])
146
+ archiveFile = File.open(cookbook[:cookbook_filename])
147
+ remoteCookbook = Aws::S3::Object.new(cookbook[:bucket], cookbook[:cookbook_filename])
140
148
  response = remoteCookbook.put({ acl: "private", body: archiveFile })
141
149
  archiveFile.close
142
150
 
143
- remoteSha = Aws::S3::Object.new(cookbook[:bucket], cookbook[:sha_key])
151
+ remoteSha = Aws::S3::Object.new(cookbook[:bucket], cookbook[:sha_filename])
144
152
  response = remoteSha.put({ acl: "private", body: cookbook[:local_sha] })
145
153
  rescue => e
146
154
  DreamOps.ui.error "#{$!}"
@@ -150,8 +158,8 @@ module DreamOps
150
158
  #
151
159
  def deploy_target(target, cookbooks)
152
160
  # If this stack has a new cookbook
153
- if !target[:cookbook].nil?
154
- if __cookbook_in_array(target[:cookbook], cookbooks)
161
+ if !target[:cookbook].nil? || DreamOps.force_setup
162
+ if __cookbook_in_array(target[:cookbook], cookbooks) || DreamOps.force_setup
155
163
  # Grab a fresh copy of the cookbook on all instances in the stack
156
164
  update_custom_cookbooks(target[:stack])
157
165
 
@@ -174,12 +182,12 @@ module DreamOps
174
182
  command: { name: "update_custom_cookbooks" }
175
183
  })
176
184
  rescue Aws::OpsWorks::Errors::ValidationException
177
- bail_with_fatal_error(NoRunningInstancesError.new(stack))
185
+ __bail_with_fatal_error(NoRunningInstancesError.new(stack))
178
186
  end
179
187
 
180
188
  status = wait_for_deployment(response.deployment_id)
181
189
  if status != 'successful'
182
- bail_with_fatal_error(OpsWorksCommandFailedError.new(
190
+ __bail_with_fatal_error(OpsWorksCommandFailedError.new(
183
191
  stack, response.deployment_id, 'update_custom_cookbooks')
184
192
  )
185
193
  end
@@ -194,7 +202,7 @@ module DreamOps
194
202
 
195
203
  status = wait_for_deployment(response.deployment_id)
196
204
  if status != 'successful'
197
- bail_with_fatal_error(OpsWorksCommandFailedError.new(
205
+ __bail_with_fatal_error(OpsWorksCommandFailedError.new(
198
206
  stack, response.deployment_id, 'setup')
199
207
  )
200
208
  end
@@ -209,12 +217,12 @@ module DreamOps
209
217
  command: { name: "deploy" }
210
218
  })
211
219
  rescue Aws::OpsWorks::Errors::ValidationException
212
- bail_with_fatal_error(NoRunningInstancesError.new(stack))
220
+ __bail_with_fatal_error(NoRunningInstancesError.new(stack))
213
221
  end
214
222
 
215
223
  status = wait_for_deployment(response.deployment_id)
216
224
  if status != 'successful'
217
- bail_with_fatal_error(OpsWorksCommandFailedError.new(
225
+ __bail_with_fatal_error(OpsWorksCommandFailedError.new(
218
226
  stack, response.deployment_id, 'deploy')
219
227
  )
220
228
  end
@@ -238,15 +246,10 @@ module DreamOps
238
246
  return status
239
247
  end
240
248
 
241
- def bail_with_fatal_error(ex)
242
- raise ex
243
- Thread.exit
244
- end
245
-
246
249
  def __cookbook_in_array(cb, cookbooks)
247
250
  return cookbooks.any? {|c|
248
- c[:cookbook_key] == cb[:cookbook_key] and c[:bucket] == cb[:bucket]
251
+ c[:cookbook_filename] == cb[:cookbook_filename] and c[:bucket] == cb[:bucket]
249
252
  }
250
253
  end
251
254
  end
252
- end
255
+ end
@@ -0,0 +1,200 @@
1
+ require 'securerandom'
2
+
3
+ module DreamOps
4
+ class SoloDeployer < BaseDeployer
5
+ def __cookbook_in_array(cb, cookbooks)
6
+ return cookbooks.any? {|c| c[:name] == cb[:name]}
7
+ end
8
+
9
+ def __cookbook_was_updated(target, cookbooks)
10
+ return cookbooks.any? {|c| c[:targets].include? target }
11
+ end
12
+
13
+ # Analyze the SSH hosts for deployment
14
+ #
15
+ # @return [Hash]
16
+ # a hash containing cookbooks that need to be built/updated
17
+ # and deploy targets
18
+ #
19
+ # Example:
20
+ # {
21
+ # :cookbooks => [
22
+ # {
23
+ # :bucket => "chef-app",
24
+ # :cookbook_filename => "chef-app-dev.tar.gz",
25
+ # :sha_filename => "chef-app-dev_SHA.txt",
26
+ # :name => "chef-app",
27
+ # :path => "./chef",
28
+ # :local_sha => "7bfa19491170563f422a321c144800f4435323b1",
29
+ # :targets => [ "ubuntu@example.com" ]
30
+ # }
31
+ # ],
32
+ # deploy_targets: [
33
+ # {
34
+ # :host => "ubuntu@example.com"
35
+ # :remote_sha => ""
36
+ # }
37
+ # ]
38
+ # }
39
+ def analyze(targets)
40
+ @ssh_opts = "-i #{DreamOps.ssh_key} -o 'StrictHostKeyChecking no'"
41
+ @q_all = "> /dev/null 2>&1"
42
+ @q_stdout = "> /dev/null"
43
+
44
+ # Collect and print target info
45
+ result = { cookbooks: [], deploy_targets: [] }
46
+ targets.each do |target|
47
+ target_result = analyze_target(target)
48
+ # Add the target to the deploy targets
49
+ result[:deploy_targets] << target_result[:deploy_target]
50
+ # Determine whether the cookbook needs to be built
51
+ cbook = target_result[:cookbook]
52
+ if !cbook.nil? && !cbook[:local_sha].nil?
53
+ # We only build the cookbook if we don't have this version remotely
54
+ if target_result[:deploy_target][:remote_sha] != cbook[:local_sha]
55
+ # Don't build the same destination cookbook more than once
56
+ if !__cookbook_in_array(cbook, result[:cookbooks])
57
+ cbook[:targets] = [target]
58
+ result[:cookbooks] << cbook
59
+ else
60
+ # Add target to be deployed to
61
+ cbook_index = result[:cookbooks].index { |c| c[:name] == cbook[:name] }
62
+ result[:cookbooks][cbook_index][:targets] << target
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ return result
69
+ end
70
+
71
+ def analyze_target(target)
72
+ # Validate SSH creds
73
+ if DreamOps.ssh_key.empty?
74
+ __bail_with_fatal_error(NoSshKeyError.new())
75
+ end
76
+ hostname = `ssh #{@ssh_opts} #{target} sudo hostname 2>&1`.chomp
77
+ if ! $?.success?
78
+ __bail_with_fatal_error(InvalidSshKeyError.new(target, hostname))
79
+ end
80
+
81
+ DreamOps.ui.info "Target: #{hostname}"
82
+
83
+ result = { host: target }
84
+ cookbook = { }
85
+ cookbooks = __get_cookbook_paths()
86
+
87
+ # For now we only handle if we find one cookbook
88
+ if cookbooks.length == 1
89
+ path = cookbooks[0]
90
+ loader = Chef::Cookbook::CookbookVersionLoader.new(path)
91
+ loader.load_cookbooks
92
+ cookbook_version = loader.cookbook_version
93
+ metadata = cookbook_version.metadata
94
+
95
+ cookbook[:sha_filename] = "#{metadata.name}_SHA.txt"
96
+ cookbook[:cookbook_filename] = "#{metadata.name}.tar.gz"
97
+ cookbook[:name] = metadata.name
98
+ cookbook[:path] = path
99
+ cookbook[:local_sha] = `git log --pretty=%H -1 #{cookbook[:path]}`.chomp
100
+
101
+ `ssh #{@ssh_opts} #{target} sudo mkdir -p /var/chef/cookbooks`
102
+
103
+ if system("ssh #{@ssh_opts} #{target} stat /var/chef/#{cookbook[:sha_filename]} #{@q_all}")
104
+ result[:remote_sha] = `ssh #{@ssh_opts} #{target} cat /var/chef/#{cookbook[:sha_filename]}`.chomp
105
+ else
106
+ result[:remote_sha] = ""
107
+ end
108
+ elsif cookbooks.length > 1
109
+ DreamOps.ui.warn "Found more than one cookbook at paths #{cookbooks}. Skipping build."
110
+ cookbook[:name] = "???"
111
+ else
112
+ DreamOps.ui.warn "No cookbook found"
113
+ cookbook[:name] = "???"
114
+ end
115
+
116
+ suffix = if cookbook[:local_sha] != result[:remote_sha] then "(outdated)" else "(up to date)" end
117
+ DreamOps.ui.info "--- Cookbook: #{cookbook[:name]} #{suffix}"
118
+
119
+ return { deploy_target: result, cookbook: cookbook }
120
+ end
121
+
122
+ # Deploys cookbook to server
123
+ def deploy_cookbook(cookbook)
124
+ cookbook[:targets].each do |target|
125
+ if system("scp #{@ssh_opts} #{cookbook[:cookbook_filename]} #{target}:/tmp #{@q_all}")
126
+ `ssh #{@ssh_opts} #{target} sudo rm -rf /var/chef/cookbooks/* #{@q_all}`
127
+ `ssh #{@ssh_opts} #{target} sudo tar -xzvf /tmp/#{cookbook[:cookbook_filename]} -C /var/chef/cookbooks #{@q_all}`
128
+ `ssh #{@ssh_opts} #{target} sudo rm -rf /tmp/#{cookbook[:cookbook_filename]} #{@q_all}`
129
+ else
130
+ DreamOps.ui.error "Failed to copy cookbook to host '#{target}'"
131
+ end
132
+
133
+ if !system(
134
+ "ssh #{@ssh_opts} #{target} "+
135
+ "\"echo '#{cookbook[:local_sha]}' | sudo tee /var/chef/#{cookbook[:sha_filename]}\" #{@q_all}"
136
+ )
137
+ DreamOps.ui.error "Failed to update remote cookbook sha"
138
+ end
139
+ end
140
+ end
141
+
142
+ def run_chef_role(target, role)
143
+ if !system("ssh #{@ssh_opts} #{target[:host]} stat /var/log/chef #{@q_all}")
144
+ `ssh #{@ssh_opts} #{target[:host]} sudo mkdir -p /var/log/chef`
145
+ end
146
+
147
+ uuid = SecureRandom.uuid
148
+
149
+ if ! system(
150
+ "ssh #{@ssh_opts} #{target[:host]} \"" +
151
+ "set -o pipefail && " +
152
+ "sudo chef-solo -j /var/chef/chef.json -o \"role[#{role}]\" 2>&1 | sudo tee /var/log/chef/#{uuid}.log #{@q_all}\""
153
+ )
154
+ __bail_with_fatal_error(ChefSoloFailedError.new(target[:host], "/var/log/chef/#{uuid}.log"))
155
+ end
156
+ end
157
+
158
+ # Deploy latest code
159
+ def deploy_target(target, cookbooks)
160
+ # Sync code to home directory
161
+ repo_path = `git rev-parse --show-toplevel`.chomp
162
+ repo_name = `basename #{repo_path}`.chomp
163
+ DreamOps.ui.info "...Syncing [repo=\"#{repo_name}\" target=\"#{target[:host]}\"]"
164
+ `rsync -r -e "ssh #{@ssh_opts}" . #{target[:host]}:~/#{repo_name}`
165
+
166
+
167
+ # Bail if ChefDK is not installed
168
+ if !system("ssh #{@ssh_opts} #{target[:host]} which chef #{@q_stdout}")
169
+ __bail_with_fatal_error(ChefDKNotInstalledError.new(target[:host]))
170
+ end
171
+
172
+ # Bail if chef.json doesn't exist
173
+ if !system("ssh #{@ssh_opts} #{target[:host]} stat /var/chef/chef.json #{@q_all}")
174
+ __bail_with_fatal_error(ChefJsonNotFoundError.new(target[:host]))
175
+ end
176
+
177
+ # Bail if setup.json doesn't exist
178
+ if !system("ssh #{@ssh_opts} #{target[:host]} stat /var/chef/roles/setup.json #{@q_all}")
179
+ __bail_with_fatal_error(RoleNotFoundError.new(target[:host], "setup"))
180
+ end
181
+
182
+ # Bail if deploy.json doesn't exist
183
+ if !system("ssh #{@ssh_opts} #{target[:host]} stat /var/chef/roles/deploy.json #{@q_all}")
184
+ __bail_with_fatal_error(RoleNotFoundError.new(target[:host], "deploy"))
185
+ end
186
+
187
+ # If this stack has a new cookbook, re-run the setup role
188
+ if (
189
+ __cookbook_was_updated(target[:host], cookbooks) ||
190
+ DreamOps.force_setup
191
+ )
192
+ DreamOps.ui.info "...Running setup role [target=\"#{target[:host]}\"]"
193
+ run_chef_role(target, "setup")
194
+ end
195
+
196
+ DreamOps.ui.info "...Running deploy role [target=\"#{target[:host]}\"]"
197
+ run_chef_role(target, "deploy")
198
+ end
199
+ end
200
+ end
@@ -39,7 +39,113 @@ module DreamOps
39
39
  "https://console.aws.amazon.com/opsworks/home?region=#{@stack.region}#/stack/#{@stack.stack_id}/deployments/#{@deployment_id}",
40
40
  ].join("\n")
41
41
  end
42
+ end
42
43
 
43
- alias_method :message, :to_s
44
+ class NoSshKeyError < DreamOpsError
45
+ set_status_code(12)
46
+
47
+ def to_s
48
+ "You mush specify a SSH key for authentication."
49
+ end
50
+ end
51
+
52
+ class InvalidSshKeyError < DreamOpsError
53
+ set_status_code(13)
54
+
55
+ def initialize(target, output)
56
+ @target = target
57
+ @output = output
58
+ end
59
+
60
+ def to_s
61
+ [
62
+ "Failed to communicate with '#{@target}' over ssh:",
63
+ "",
64
+ @output,
65
+ ].join("\n")
66
+ end
67
+ end
68
+
69
+ class ChefDKNotInstalledError < DreamOpsError
70
+ set_status_code(14)
71
+
72
+ def initialize(target)
73
+ @target = target
74
+ end
75
+
76
+ def to_s
77
+ [
78
+ "ChefDK not installed on target \"#{@target}\". To initialize chef-solo, run:",
79
+ "",
80
+ "dream init solo -t #{@target} -i #{DreamOps.ssh_key}",
81
+ ].join("\n")
82
+ end
83
+ end
84
+
85
+ class ChefDKFailedError < DreamOpsError
86
+ set_status_code(15)
87
+
88
+ def initialize(target, wget_url)
89
+ @target = target
90
+ @wget_url = wget_url
91
+ end
92
+
93
+ def to_s
94
+ [
95
+ "Target \"#{@target}\" failed installing ChefDK from:",
96
+ "",
97
+ @wget_url,
98
+ ].join("\n")
99
+ end
100
+ end
101
+
102
+ class ChefSoloFailedError < DreamOpsError
103
+ set_status_code(16)
104
+
105
+ def initialize(target, log_path)
106
+ @target = target
107
+ @log_path = log_path
108
+ end
109
+
110
+ def to_s
111
+ [
112
+ "Target \"#{@target}\" failed running chef-solo. The failure log is located at:",
113
+ "",
114
+ @log_path,
115
+ ].join("\n")
116
+ end
117
+ end
118
+
119
+ class ChefJsonNotFoundError < DreamOpsError
120
+ set_status_code(17)
121
+
122
+ def initialize(target)
123
+ @target = target
124
+ end
125
+
126
+ def to_s
127
+ [
128
+ "Could not find /var/chef/chef.json \"#{@target}\". To initialize with an empty runlist, run:",
129
+ "",
130
+ "dream init solo -t #{@target} -i #{DreamOps.ssh_key}",
131
+ ].join("\n")
132
+ end
133
+ end
134
+
135
+ class RoleNotFoundError < DreamOpsError
136
+ set_status_code(18)
137
+
138
+ def initialize(target, role)
139
+ @target = target
140
+ @role = role
141
+ end
142
+
143
+ def to_s
144
+ [
145
+ "Could not find /var/chef/roles/#{@role}.json \"#{@target}\". To initialize with an empty runlist, run:",
146
+ "",
147
+ "dream init solo -t #{@target} -i #{DreamOps.ssh_key}",
148
+ ].join("\n")
149
+ end
44
150
  end
45
- end
151
+ end
@@ -40,4 +40,4 @@ module DreamOps
40
40
  # The cleanup hook is defined by subclasses and is called by the CLI.
41
41
  def cleanup_hook; end
42
42
  end
43
- end
43
+ end
@@ -160,4 +160,4 @@ module DreamOps
160
160
  DreamOps.ui.info "DEPRECATED: #{message}"
161
161
  end
162
162
  end
163
- end
163
+ end
@@ -0,0 +1,69 @@
1
+ module DreamOps
2
+ class BaseInitializer
3
+ class << self
4
+ #
5
+ # @macro initiailizer_method
6
+ # @method $1(*args)
7
+ # Create a initiailizer method for the declaration
8
+ #
9
+ def initiailizer_method(name)
10
+ class_eval <<-EOH, __FILE__, __LINE__ + 1
11
+ def #{name}(*args)
12
+ raise AbstractFunction,
13
+ "##{name} must be implemented on \#{self.class.name}!"
14
+ end
15
+ EOH
16
+ end
17
+ end
18
+
19
+ # This method MUST be implemented by subclasses.
20
+ #
21
+ # @return [Array]
22
+ # an array of configuration for each target
23
+ #
24
+ # Example:
25
+ # [
26
+ # {
27
+ # :host => "ubuntu@example.com"
28
+ # :chefdk_installed => false,
29
+ # :solo_json_exists => false,
30
+ # :setup_role_exists => true,
31
+ # :deploy_role_exists => true
32
+ # }
33
+ # ]
34
+ initiailizer_method :analyze
35
+
36
+ # This method MUST be implemented by subclasses.
37
+ #
38
+ # @param [String] target
39
+ # a target host
40
+ initiailizer_method :init_target
41
+
42
+ def __bail_with_fatal_error(ex)
43
+ raise ex
44
+ Thread.exit
45
+ end
46
+
47
+ def init(*args, dryrun)
48
+ targets = analyze(args)
49
+
50
+ # Update cookbooks if needed and deploy to all targets
51
+ deploy_success = true
52
+ deploy_threads = []
53
+ targets.each do |target|
54
+ deploy_threads << Thread.new { init_target(target, dryrun) }
55
+ end
56
+ deploy_threads.each do |t|
57
+ begin
58
+ t.join
59
+ rescue DreamOps::DreamOpsError
60
+ DreamOps.ui.error "#{$!}"
61
+ deploy_success = deploy_success && false
62
+ end
63
+ end
64
+
65
+ # If ANY deploy threads failed, exit with failure
66
+ exit(1) if !deploy_success
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,120 @@
1
+ module DreamOps
2
+ class SoloInitializer < BaseInitializer
3
+ def analyze(targets)
4
+ @ssh_opts = "-i #{DreamOps.ssh_key} -o 'StrictHostKeyChecking no'"
5
+ @q_all = "> /dev/null 2>&1"
6
+ @q_stdout = "> /dev/null"
7
+
8
+ result = []
9
+
10
+ targets.each do |target|
11
+ # Validate SSH creds
12
+ if DreamOps.ssh_key.empty?
13
+ __bail_with_fatal_error(NoSshKeyError.new())
14
+ end
15
+ hostname = `ssh #{@ssh_opts} #{target} sudo hostname 2>&1`.chomp
16
+ if ! $?.success?
17
+ __bail_with_fatal_error(InvalidSshKeyError.new(target, hostname))
18
+ end
19
+
20
+ DreamOps.ui.info "Target: #{hostname}"
21
+
22
+ target_result = { host: target }
23
+
24
+ target_result[:chefdk_installed] = system("ssh #{@ssh_opts} #{target} which chef #{@q_stdout}")
25
+ DreamOps.ui.info "--- ChefDK Installed: #{target_result[:chefdk_installed]}"
26
+
27
+ target_result[:solo_json_exists] = system("ssh #{@ssh_opts} #{target} stat /var/chef/chef.json #{@q_all}")
28
+ DreamOps.ui.info "--- Valid chef.json: #{target_result[:solo_json_exists]}"
29
+
30
+ target_result[:setup_role_exists] = system("ssh #{@ssh_opts} #{target} stat /var/chef/roles/setup.json #{@q_all}")
31
+ DreamOps.ui.info "--- Valid role[setup]: #{target_result[:setup_role_exists]}"
32
+
33
+ target_result[:deploy_role_exists] = system("ssh #{@ssh_opts} #{target} stat /var/chef/roles/deploy.json #{@q_all}")
34
+ DreamOps.ui.info "--- Valid role[deploy]: #{target_result[:deploy_role_exists]}"
35
+
36
+ result << target_result
37
+ end
38
+
39
+ return result
40
+ end
41
+
42
+ def init_target(target, dryrun)
43
+ # Install ChefDK if not already
44
+ if !target[:chefdk_installed]
45
+ if dryrun
46
+ DreamOps.ui.warn "...WOULD Install ChefDK [target=\"#{target[:host]}\"]"
47
+ else
48
+ DreamOps.ui.warn "...Installing ChefDK [target=\"#{target[:host]}\"]"
49
+
50
+ # Get ubuntu version
51
+ ubuntu_ver = `ssh #{@ssh_opts} #{target[:host]} "awk 'BEGIN { FS = \\"=\\" } /DISTRIB_RELEASE/ { print \\$2 }' /etc/lsb-release"`.chomp
52
+
53
+ # Download and install the package
54
+ chefdk_url = "https://packages.chef.io/files/stable/chefdk/3.3.23/ubuntu/#{ubuntu_ver}/chefdk_3.3.23-1_amd64.deb"
55
+ if system("ssh #{@ssh_opts} #{target[:host]} \"wget #{chefdk_url} -P /tmp\" #{@q_all}")
56
+ `ssh #{@ssh_opts} #{target[:host]} "sudo dpkg -i /tmp/chefdk_3.3.23-1_amd64.deb" #{@q_all}`
57
+ `ssh #{@ssh_opts} #{target[:host]} "sudo rm /tmp/chefdk_3.3.23-1_amd64.deb" #{@q_all}`
58
+ else
59
+ __bail_with_fatal_error(ChefDKFailedError.new(target, chefdk_url))
60
+ end
61
+ end
62
+ end
63
+
64
+ if !dryrun
65
+ `ssh #{@ssh_opts} #{target[:host]} sudo mkdir -p /var/chef/roles`
66
+ end
67
+
68
+ # Create empty json file for chef-solo
69
+ if !target[:solo_json_exists]
70
+ json_path = "/var/chef/chef.json"
71
+ if dryrun
72
+ DreamOps.ui.warn "...WOULD Create boilerplate #{json_path} [target=\"#{target[:host]}\"]"
73
+ else
74
+ DreamOps.ui.warn "...Creating boilerplate #{json_path} [target=\"#{target[:host]}\"]"
75
+ `ssh #{@ssh_opts} #{target[:host]} "echo '{\n \\"run_list\\": []\n}' | sudo tee -a #{json_path}"`
76
+ end
77
+ end
78
+
79
+ # Create setup role with empty run_list for chef-solo
80
+ if !target[:setup_role_exists]
81
+ setup_path = "/var/chef/roles/setup.json"
82
+ if dryrun
83
+ DreamOps.ui.warn "...WOULD Create boilerplate #{setup_path} [target=\"#{target[:host]}\"]"
84
+ else
85
+ DreamOps.ui.warn "...Creating boilerplate #{setup_path} [target=\"#{target[:host]}\"]"
86
+ role_contents = [
87
+ '{',
88
+ ' \"name\": \"setup\",',
89
+ ' \"json_class\": \"Chef::Role\",',
90
+ ' \"description\": \"This role is intended for use when cookbook changes are detected\",',
91
+ ' \"chef_type\": \"role\",',
92
+ ' \"run_list\": []',
93
+ '}',
94
+ ].join("\n")
95
+ `ssh #{@ssh_opts} #{target[:host]} "echo '#{role_contents}' | sudo tee -a #{setup_path}"`
96
+ end
97
+ end
98
+
99
+ # Create deploy role with empty run_list for chef-solo
100
+ if !target[:deploy_role_exists]
101
+ deploy_path = "/var/chef/roles/deploy.json"
102
+ if dryrun
103
+ DreamOps.ui.warn "...WOULD Create boilerplate #{deploy_path} [target=\"#{target[:host]}\"]"
104
+ else
105
+ DreamOps.ui.warn "...Creating boilerplate #{deploy_path} [target=\"#{target[:host]}\"]"
106
+ role_contents = [
107
+ '{',
108
+ ' \"name\": \"deploy\",',
109
+ ' \"json_class\": \"Chef::Role\",',
110
+ ' \"description\": \"This role is intended for use when code changes\",',
111
+ ' \"chef_type\": \"role\",',
112
+ ' \"run_list\": []',
113
+ '}',
114
+ ].join("\n")
115
+ `ssh #{@ssh_opts} #{target[:host]} "echo '#{role_contents}' | sudo tee -a #{deploy_path}"`
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -1,5 +1,5 @@
1
1
  module DreamOps
2
- class Logger < Ridley::Logging::Logger
2
+ class Logger < Logger
3
3
  alias_method :fatal, :error
4
4
 
5
5
  def deprecate(message)
@@ -28,4 +28,4 @@ module DreamOps
28
28
  super(message)
29
29
  end
30
30
  end
31
- end
31
+ end
@@ -43,5 +43,4 @@ class ZipFileGenerator
43
43
  end
44
44
  }
45
45
  end
46
-
47
- end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module DreamOps
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.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.3.0
4
+ version: 0.4.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: 2018-08-28 00:00:00.000000000 Z
11
+ date: 2018-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -72,63 +72,46 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '6.2'
75
+ version: '7.0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '6.2'
83
- - !ruby/object:Gem::Dependency
84
- name: ridley
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '5.0'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '5.0'
82
+ version: '7.0'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: thor
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - "~>"
102
88
  - !ruby/object:Gem::Version
103
- version: '0.19'
104
- - - "<"
105
- - !ruby/object:Gem::Version
106
- version: 0.19.2
89
+ version: '0.20'
107
90
  type: :runtime
108
91
  prerelease: false
109
92
  version_requirements: !ruby/object:Gem::Requirement
110
93
  requirements:
111
94
  - - "~>"
112
95
  - !ruby/object:Gem::Version
113
- version: '0.19'
114
- - - "<"
115
- - !ruby/object:Gem::Version
116
- version: 0.19.2
96
+ version: '0.20'
117
97
  - !ruby/object:Gem::Dependency
118
98
  name: chef
119
99
  requirement: !ruby/object:Gem::Requirement
120
100
  requirements:
121
101
  - - "~>"
122
102
  - !ruby/object:Gem::Version
123
- version: '12.7'
103
+ version: '13.6'
124
104
  type: :runtime
125
105
  prerelease: false
126
106
  version_requirements: !ruby/object:Gem::Requirement
127
107
  requirements:
128
108
  - - "~>"
129
109
  - !ruby/object:Gem::Version
130
- version: '12.7'
131
- description: This is the description
110
+ version: '13.6'
111
+ description: |2
112
+ This CLI automatically rebuilds and deploys your cookbook
113
+ when changes are detected. Application deployment is
114
+ supported for OpsWorks stacks and ubuntu SSH hosts.
132
115
  email:
133
116
  - chris@apaxsoftware.com
134
117
  executables:
@@ -138,6 +121,7 @@ extra_rdoc_files: []
138
121
  files:
139
122
  - ".gitignore"
140
123
  - ".travis.yml"
124
+ - CHANGELOG.md
141
125
  - Gemfile
142
126
  - LICENSE.txt
143
127
  - README.md
@@ -150,9 +134,12 @@ files:
150
134
  - lib/dream-ops/cli.rb
151
135
  - lib/dream-ops/deployment/base.rb
152
136
  - lib/dream-ops/deployment/opsworks.rb
137
+ - lib/dream-ops/deployment/solo.rb
153
138
  - lib/dream-ops/errors.rb
154
139
  - lib/dream-ops/formatters/base.rb
155
140
  - lib/dream-ops/formatters/human.rb
141
+ - lib/dream-ops/init/base.rb
142
+ - lib/dream-ops/init/solo.rb
156
143
  - lib/dream-ops/logger.rb
157
144
  - lib/dream-ops/mixin/logging.rb
158
145
  - lib/dream-ops/shell.rb
@@ -161,7 +148,9 @@ files:
161
148
  homepage: https://github.com/chris-allen/dream-ops
162
149
  licenses:
163
150
  - MIT
164
- metadata: {}
151
+ metadata:
152
+ source_code_uri: https://github.com/chris-allen/dream-ops
153
+ changelog_uri: https://github.com/chris-allen/dream-ops/blob/master/CHANGELOG.md
165
154
  post_install_message:
166
155
  rdoc_options: []
167
156
  require_paths:
@@ -181,5 +170,5 @@ rubyforge_project:
181
170
  rubygems_version: 2.6.14
182
171
  signing_key:
183
172
  specification_version: 4
184
- summary: This is the summary
173
+ summary: CLI for automating the deployment of application cookbooks
185
174
  test_files: []