dream-ops 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +67 -2
- data/dream-ops.gemspec +13 -6
- data/lib/dream-ops.rb +32 -2
- data/lib/dream-ops/cli.rb +50 -6
- data/lib/dream-ops/deployment/base.rb +24 -9
- data/lib/dream-ops/deployment/opsworks.rb +36 -33
- data/lib/dream-ops/deployment/solo.rb +200 -0
- data/lib/dream-ops/errors.rb +108 -2
- data/lib/dream-ops/formatters/base.rb +1 -1
- data/lib/dream-ops/formatters/human.rb +1 -1
- data/lib/dream-ops/init/base.rb +69 -0
- data/lib/dream-ops/init/solo.rb +120 -0
- data/lib/dream-ops/logger.rb +1 -1
- data/lib/dream-ops/shell.rb +1 -1
- data/lib/dream-ops/utils/zip.rb +1 -2
- data/lib/dream-ops/version.rb +1 -1
- metadata +20 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a640e2b3d14a77a968e916ceff5699a51a75aa49
|
4
|
+
data.tar.gz: 8beff4a4bc985e83caa02d7c537a8595baf6d6e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54546e61709396f02aaab3f8f5d5653e44b1a9b2c25ff88057da04be2c3eaefba888c42084f3d3ddf8397f8be7a07265db51a77b654200ec10e8c3a257082019
|
7
|
+
data.tar.gz: c79c27aa79c56bf1ac0c5811d74283d9c3df208f9ab5bd5cab2e378a403850f57da4b93945adecb41ba9e34836d04240697f1fb6b0685170eab25c0ecbbc7837
|
data/CHANGELOG.md
ADDED
@@ -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
|
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`.
|
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
|
|
data/dream-ops.gemspec
CHANGED
@@ -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 = "
|
15
|
-
spec.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", "~>
|
32
|
-
spec.add_dependency "
|
33
|
-
spec.add_dependency "
|
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
|
data/lib/dream-ops.rb
CHANGED
@@ -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
|
-
|
68
|
-
DreamOps.logger.level = Logger::WARN
|
98
|
+
DreamOps.logger.level = Logger::WARN
|
data/lib/dream-ops/cli.rb
CHANGED
@@ -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 :
|
109
|
+
method_option :targets,
|
100
110
|
type: :array,
|
101
|
-
desc: "Only these
|
102
|
-
aliases: "-
|
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
|
-
|
112
|
-
|
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
|
-
# :
|
35
|
-
# :
|
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
|
-
|
63
|
-
|
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
|
-
|
73
|
-
|
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[:
|
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
|
-
# :
|
22
|
-
# :
|
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
|
-
# :
|
38
|
-
# :
|
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
|
-
|
97
|
-
|
94
|
+
cookbook_filename: cookbookPath[(firstSlash+1)..-1],
|
95
|
+
sha_filename: cookbookPath[firstSlash+1..-1].sub('.zip', '_SHA.txt')
|
98
96
|
}
|
99
97
|
|
100
|
-
|
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
|
-
|
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] =
|
108
|
-
if cookbook[:
|
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[:
|
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.
|
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[:
|
139
|
-
remoteCookbook = Aws::S3::Object.new(cookbook[:bucket], cookbook[:
|
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[:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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[:
|
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
|
data/lib/dream-ops/errors.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -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
|
data/lib/dream-ops/logger.rb
CHANGED
data/lib/dream-ops/shell.rb
CHANGED
data/lib/dream-ops/utils/zip.rb
CHANGED
data/lib/dream-ops/version.rb
CHANGED
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.
|
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-
|
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: '
|
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: '
|
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.
|
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.
|
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: '
|
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: '
|
131
|
-
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:
|
173
|
+
summary: CLI for automating the deployment of application cookbooks
|
185
174
|
test_files: []
|