danarchy_deploy 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/Rakefile +6 -0
- data/bin/danarchy_deploy +114 -0
- data/bin/danarchy_deploy-console +14 -0
- data/bin/setup +8 -0
- data/danarchy_deploy.gemspec +36 -0
- data/lib/danarchy_deploy.rb +130 -0
- data/lib/danarchy_deploy/archiver.rb +70 -0
- data/lib/danarchy_deploy/groups.rb +49 -0
- data/lib/danarchy_deploy/helpers.rb +28 -0
- data/lib/danarchy_deploy/installer.rb +86 -0
- data/lib/danarchy_deploy/services.rb +51 -0
- data/lib/danarchy_deploy/templater.rb +164 -0
- data/lib/danarchy_deploy/users.rb +138 -0
- data/lib/danarchy_deploy/version.rb +3 -0
- data/templates/deploy_template.json +98 -0
- metadata +127 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: bc77046b77757e8d847424bced87d9b0a78131a9
|
|
4
|
+
data.tar.gz: f07bf23008b48a3dd3f84b35d1bd87cb9e0a379a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d017636ab2096321579edeb48973913374dd23680328fafada853012394af638c80e9e107b21cfaad81b10a6bc51e93f937e5c81b2368bcfe0a0b3430a399d9a
|
|
7
|
+
data.tar.gz: 91523adf0a963049251406efe47516c91689f89f0a1b423870c71ddb56a65f3fddeefc6541e2da26a62afd0f5b190e1e5a53190bcee332ee2d2b6439473c55e7
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
danarchy_deploy (0.1.0)
|
|
5
|
+
danarchy_couchdb (~> 0.1.0)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
danarchy_couchdb (0.1.2)
|
|
11
|
+
diff-lcs (1.3)
|
|
12
|
+
rake (10.5.0)
|
|
13
|
+
rspec (3.7.0)
|
|
14
|
+
rspec-core (~> 3.7.0)
|
|
15
|
+
rspec-expectations (~> 3.7.0)
|
|
16
|
+
rspec-mocks (~> 3.7.0)
|
|
17
|
+
rspec-core (3.7.1)
|
|
18
|
+
rspec-support (~> 3.7.0)
|
|
19
|
+
rspec-expectations (3.7.0)
|
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
21
|
+
rspec-support (~> 3.7.0)
|
|
22
|
+
rspec-mocks (3.7.0)
|
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
24
|
+
rspec-support (~> 3.7.0)
|
|
25
|
+
rspec-support (3.7.1)
|
|
26
|
+
|
|
27
|
+
PLATFORMS
|
|
28
|
+
ruby
|
|
29
|
+
|
|
30
|
+
DEPENDENCIES
|
|
31
|
+
bundler (~> 1.16)
|
|
32
|
+
danarchy_deploy!
|
|
33
|
+
rake (~> 10.0)
|
|
34
|
+
rspec (~> 3.0)
|
|
35
|
+
|
|
36
|
+
BUNDLED WITH
|
|
37
|
+
1.16.1
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Dan Heneise
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# DanarchyDeploy
|
|
2
|
+
|
|
3
|
+
dAnarchy Deploy is a template-driven Ruby gem to deploy locally or remotely to Gentoo systems (and debian/ubuntu, more being added). This can take a .JSON or .YAML input file, or a CouchDB connection as a deployment template and install necessary packages, add users and groups, write ERB templates, and decompress tar/zip archives. More documentation incoming.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Add this line to your application's Gemfile:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
gem 'danarchy_deploy'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
And then execute:
|
|
15
|
+
|
|
16
|
+
$ bundle
|
|
17
|
+
|
|
18
|
+
Or install it yourself as:
|
|
19
|
+
|
|
20
|
+
$ gem install danarchy_deploy
|
|
21
|
+
|
|
22
|
+
!! Since dAnarchy Deploy takes several actions that require root/sudo access, I install the gem with sudo as any remote deployment will on the target host:
|
|
23
|
+
|
|
24
|
+
$ sudo gem install danarchy_deploy
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
dAnarchy Deploy usage info can be read with -h/--help:
|
|
30
|
+
```ruby
|
|
31
|
+
$ danarchy_deploy -h
|
|
32
|
+
Usage: sudo /usr/local/bin/danarchy_deploy (local|remote) --json /path/to/deployment.json [options]
|
|
33
|
+
-j, --json=file Read configuration from JSON file.
|
|
34
|
+
-y, --yaml=file Read configuration from YAML file.
|
|
35
|
+
-p, --pretend Pretend run: Don't take any action.
|
|
36
|
+
-f, --first-run First Run: Run as a first run causing services to run all init actions.
|
|
37
|
+
-d, --deploy-dir Deployment directory. Defaults to '/danarchy/deploy'.
|
|
38
|
+
--ssh-verbose Verbose SSH stdout/stderr output.
|
|
39
|
+
--version Print /usr/local/bin/danarchy_deploy version.
|
|
40
|
+
-h, --help Print this help info.
|
|
41
|
+
|
|
42
|
+
DanarchyDeploy: 0.1.0
|
|
43
|
+
Exiting! Must be run with sudo!
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
More documentation incoming...
|
data/Rakefile
ADDED
data/bin/danarchy_deploy
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require_relative '../lib/danarchy_deploy'
|
|
3
|
+
require 'danarchy_couchdb'
|
|
4
|
+
require 'date'
|
|
5
|
+
require 'optparse'
|
|
6
|
+
|
|
7
|
+
deployment = nil
|
|
8
|
+
options = { couchdb: "/home/#{ENV['SUDO_USER']}/.danarchy/danarchy_deploy/danarchy_deploy.json",
|
|
9
|
+
deploy_dir: '/danarchy/deploy',
|
|
10
|
+
deploy_file: nil,
|
|
11
|
+
pretend: false,
|
|
12
|
+
ssh_verbose: false,
|
|
13
|
+
first_run: false }
|
|
14
|
+
|
|
15
|
+
ARGV.push('--help') if ARGV.empty?
|
|
16
|
+
optparse = OptionParser.new do |opts|
|
|
17
|
+
opts.banner = "Usage: sudo #{$PROGRAM_NAME} (local|remote) --json /path/to/deployment.json [options]"
|
|
18
|
+
|
|
19
|
+
opts.on('-j=file', '--json=file', 'Read configuration from JSON file.') do |file|
|
|
20
|
+
require 'json'
|
|
21
|
+
options[:deploy_file] = file
|
|
22
|
+
deployment = JSON.parse(File.read(file), symbolize_names: true)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
opts.on('-y=file', '--yaml=file', 'Read configuration from YAML file.') do |file|
|
|
26
|
+
require 'yaml'
|
|
27
|
+
options[:deploy_file] = file
|
|
28
|
+
deployment = YAML.load_file(file)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
opts.on('-p', '--pretend', 'Pretend run: Don\'t take any action.') do |val|
|
|
32
|
+
options[:pretend] = true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts.on('-f', '--first-run', 'First Run: Run as a first run causing services to run all init actions.') do |val|
|
|
36
|
+
options[:first_run] = true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on('-d', '--deploy-dir', "Deployment directory. Defaults to '/danarchy/deploy'.") do |val|
|
|
40
|
+
options[:deploy_dir] = val.gsub(/\/$/, '')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
opts.on('--ssh-verbose', "Verbose SSH stdout/stderr output.") do |val|
|
|
44
|
+
options[:ssh_verbose] = true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
opts.on('--version', "Print #{$PROGRAM_NAME} version.") do |val|
|
|
48
|
+
puts "DanarchyDeploy: #{DanarchyDeploy::VERSION}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
opts.on('-h', '--help', 'Print this help info.') do |val|
|
|
52
|
+
puts opts, ''
|
|
53
|
+
end
|
|
54
|
+
end.parse!
|
|
55
|
+
|
|
56
|
+
puts "DanarchyDeploy: #{DanarchyDeploy::VERSION}"
|
|
57
|
+
abort('Exiting! Must be run with sudo!') if Process.uid != 0
|
|
58
|
+
|
|
59
|
+
location = ARGV.delete('remote') || ARGV.delete('local') || abort("ERROR: Need an option 'local' or 'remote' to know what to do!")
|
|
60
|
+
cdb_config = File.exist?(options[:couchdb]) ? JSON.parse(File.read(options[:couchdb]), symbolize_names: true)[:couchdb] : nil
|
|
61
|
+
cdb = DanarchyCouchDB::Connection.new(cdb_config) if cdb_config
|
|
62
|
+
|
|
63
|
+
if !deployment && !options[:deploy_file] && cdb
|
|
64
|
+
deployment_name = ARGV.shift
|
|
65
|
+
abort("Need a deployment name!") if !deployment_name
|
|
66
|
+
puts "DanarchyCouchDB: #{DanarchyCouchDB::VERSION}"
|
|
67
|
+
puts "CouchDB connection found! Loading deployment for #{cdb_config[:database]}:#{deployment_name}"
|
|
68
|
+
deployment = cdb.get(cdb_config[:database], deployment_name)
|
|
69
|
+
abort("ERROR: Deployment #{deployment_name} => #{deployment[:reason]}") if deployment[:error]
|
|
70
|
+
puts "Found deployment: #{deployment[:_id]} | rev => #{deployment[:_rev]}"
|
|
71
|
+
options[:deploy_file] = "#{options[:deploy_dir]}/#{deployment_name}/#{deployment_name}.json"
|
|
72
|
+
puts "Temp JSON path: #{options[:deploy_file]}"
|
|
73
|
+
File.write(options[:deploy_file], deployment.to_json)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if !deployment[:last_deploy] || deployment[:last_deploy].empty?
|
|
77
|
+
puts "This looks like a first-time run since a last_deploy time wasn't found."
|
|
78
|
+
options[:first_run] = true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if location == 'remote'
|
|
82
|
+
deployment = DanarchyDeploy::RemoteDeploy.new(deployment, options)
|
|
83
|
+
elsif location == 'local'
|
|
84
|
+
puts "Deploying #{deployment[:hostname]} locally to #{`hostname`.chomp}."
|
|
85
|
+
|
|
86
|
+
if !options[:pretend]
|
|
87
|
+
puts ' ! Ctrl-c out if this is not what you want to do!'
|
|
88
|
+
10.downto(0) do |i|
|
|
89
|
+
trap('SIGINT') { abort("\nExiting!") }
|
|
90
|
+
print "\rDeploying in #{i} seconds"
|
|
91
|
+
sleep(1)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
puts "\nDeploying!"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
deployment = DanarchyDeploy::LocalDeploy.new(deployment, options)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if deployment && cdb && !options[:pretend]
|
|
101
|
+
puts "Saving deployment to CouchDB."
|
|
102
|
+
old_rev = cdb.get(cdb_config[:database], deployment_name)[:_rev]
|
|
103
|
+
save = cdb.put(cdb_config[:database], deployment[:_id], deployment)
|
|
104
|
+
|
|
105
|
+
if save[:ok] == true
|
|
106
|
+
puts ' |+ Saved deployment to CouchDB!'
|
|
107
|
+
puts " |_id: #{save[:id]}"
|
|
108
|
+
puts " |_rev: #{save[:rev]}\n\n"
|
|
109
|
+
else
|
|
110
|
+
puts ' ! Unable to save deployment to CouchDB'
|
|
111
|
+
puts " |_error: #{save[:error]}"
|
|
112
|
+
puts " |_rev: #{old_rev[:rev]}\n\n"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "danarchy_deploy"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require "danarchy_deploy/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "danarchy_deploy"
|
|
8
|
+
spec.version = DanarchyDeploy::VERSION
|
|
9
|
+
spec.authors = ["Dan James"]
|
|
10
|
+
spec.email = ["dan@danarchy.me"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{Pushes deployments locally or remotely based on a JSON/YAML/CouchDB template.}
|
|
13
|
+
spec.description = %q{DanarchyDeploy intends to simplify Gentoo Linux (and other distro) deployments down to a single template from an input JSON or YAML file, or from a CouchDB file.}
|
|
14
|
+
spec.homepage = "https://github.com/danarchy85/danarchy_deploy"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
if spec.respond_to?(:metadata)
|
|
18
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
19
|
+
else
|
|
20
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
|
21
|
+
"public gem pushes."
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
+
f.match(%r{^(test|spec|features)/})
|
|
26
|
+
end
|
|
27
|
+
spec.bindir = "bin"
|
|
28
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
29
|
+
spec.require_paths = ["lib"]
|
|
30
|
+
|
|
31
|
+
spec.add_dependency "danarchy_couchdb", "~> 0.1.0"
|
|
32
|
+
|
|
33
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
|
34
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
35
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
36
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require_relative 'danarchy_deploy/version'
|
|
2
|
+
|
|
3
|
+
module DanarchyDeploy
|
|
4
|
+
require_relative './danarchy_deploy/archiver'
|
|
5
|
+
require_relative './danarchy_deploy/groups'
|
|
6
|
+
require_relative './danarchy_deploy/helpers'
|
|
7
|
+
require_relative './danarchy_deploy/installer'
|
|
8
|
+
require_relative './danarchy_deploy/users'
|
|
9
|
+
require_relative './danarchy_deploy/services'
|
|
10
|
+
require_relative './danarchy_deploy/templater'
|
|
11
|
+
|
|
12
|
+
class LocalDeploy
|
|
13
|
+
def self.new(deployment, options)
|
|
14
|
+
puts "\n" + self.name
|
|
15
|
+
puts "Pretend run! Not making any changes." if options[:pretend]
|
|
16
|
+
|
|
17
|
+
puts 'Begining Deployment:'
|
|
18
|
+
printf("%12s %0s\n", 'Hostname:', deployment[:hostname])
|
|
19
|
+
printf("%12s %0s\n", 'OS:', deployment[:os])
|
|
20
|
+
printf("%12s %0s\n", 'Packages:', deployment[:packages].join(', ')) if deployment[:packages]
|
|
21
|
+
|
|
22
|
+
deployment = DanarchyDeploy::Installer.new(deployment, options)
|
|
23
|
+
deployment = DanarchyDeploy::Services.new(deployment, options) if deployment[:services]
|
|
24
|
+
deployment = DanarchyDeploy::Groups.new(deployment, options) if deployment[:groups]
|
|
25
|
+
deployment = DanarchyDeploy::Users.new(deployment, options) if deployment[:users]
|
|
26
|
+
deployment = DanarchyDeploy::Services.init(deployment, options) if deployment[:services]
|
|
27
|
+
|
|
28
|
+
deployment[:last_deploy] = DateTime.now.strftime("%Y/%m/%d %H:%M:%S")
|
|
29
|
+
puts "\nFinished Local Deployment at #{deployment[:last_deploy]}!"
|
|
30
|
+
File.write(options[:deploy_file], deployment.to_json) if options[:deploy_file].end_with?('.json')
|
|
31
|
+
File.write(options[:deploy_file], deployment.to_yaml) if options[:deploy_file].end_with?('.yaml')
|
|
32
|
+
deployment
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class RemoteDeploy
|
|
37
|
+
def self.new(deployment, options)
|
|
38
|
+
puts "\n" + self.name
|
|
39
|
+
options[:working_dir] = options[:deploy_dir] + '/' + deployment[:hostname]
|
|
40
|
+
connector = { hostname: deployment[:hostname],
|
|
41
|
+
ipv4: deployment[:ipv4],
|
|
42
|
+
ssh_user: deployment[:ssh_user],
|
|
43
|
+
ssh_key: deployment[:ssh_key] }
|
|
44
|
+
|
|
45
|
+
install_gem(connector, options)
|
|
46
|
+
push_deployment(connector, options)
|
|
47
|
+
deploy_result = remote_LocalDeploy(connector, options)
|
|
48
|
+
|
|
49
|
+
if deploy_result[:stderr]
|
|
50
|
+
puts ' ! Deployment failed!'
|
|
51
|
+
abort("STDERR:\n#{deploy_result[:stderr]}")
|
|
52
|
+
else
|
|
53
|
+
puts deploy_result[:stdout]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
pull_deployment(connector, options)
|
|
57
|
+
remote_Cleanup(connector, options)
|
|
58
|
+
|
|
59
|
+
puts "\nRemote deployment complete!"
|
|
60
|
+
deployment = JSON.parse(File.read(options[:deploy_file]), symbolize_names: true) if options[:deploy_file].end_with?('.json')
|
|
61
|
+
deployment = YAML.load_file(options[:deploy_file]) if options[:deploy_file].end_with?('.yaml')
|
|
62
|
+
deployment
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
def self.install_gem(connector, options)
|
|
67
|
+
puts "\n > Installing danarchy_deploy on #{connector[:hostname]}"
|
|
68
|
+
install_result = DanarchyDeploy::Helpers.run_command("ssh -i #{connector[:ssh_key]} #{connector[:ssh_user]}@#{connector[:ipv4]} -o ConnectTimeout=30 'sudo gem install -f danarchy_deploy && test -d #{options[:working_dir]} || sudo mkdir -vp #{options[:working_dir]} && sudo chown -Rc #{connector[:ssh_user]}:#{connector[:ssh_user]} #{options[:working_dir]}'", options)
|
|
69
|
+
|
|
70
|
+
if install_result[:stderr]
|
|
71
|
+
puts ' ! Gem install failed!'
|
|
72
|
+
abort("STDERR:\n#{install_result[:stderr]}")
|
|
73
|
+
else
|
|
74
|
+
puts " |+ Gem installed!"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Add ssh-keygen function, then update
|
|
79
|
+
def self.push_deployment(connector, options)
|
|
80
|
+
puts "\n > Pushing deployment: #{options[:deploy_file]}"
|
|
81
|
+
push_result = DanarchyDeploy::Helpers.run_command("rsync --rsh 'ssh -i #{connector[:ssh_key]}' -Havzu --delete #{options[:working_dir]}/ #{connector[:ssh_user]}@#{connector[:ipv4]}:#{options[:working_dir]}/", options)
|
|
82
|
+
# push_result = DanarchyDeploy::Helpers.run_command("scp -i #{connector[:ssh_key]} #{options[:deploy_file]} #{connector[:ssh_user]}@#{connector[:ipv4]}:#{options[:deploy_dir]}/#{connector[:hostname]}/", options)
|
|
83
|
+
|
|
84
|
+
if push_result[:stderr]
|
|
85
|
+
puts ' ! Deployment push failed!'
|
|
86
|
+
abort("STDERR:\n#{push_result[:stderr]}")
|
|
87
|
+
else
|
|
88
|
+
puts " |+ Deployment pushed!"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.remote_LocalDeploy(connector, options)
|
|
93
|
+
puts "\n > Running LocalDeploy on #{connector[:hostname]}\n\tOutput will return at the end of deployment."
|
|
94
|
+
|
|
95
|
+
deployment = "ssh -i #{connector[:ssh_key]} #{connector[:ssh_user]}@#{connector[:ipv4]} 'sudo danarchy_deploy local "
|
|
96
|
+
deployment += '--first-run ' if options[:first_run]
|
|
97
|
+
deployment += '--ssh-verbose ' if options[:ssh_verbose]
|
|
98
|
+
deployment += '--pretend ' if options[:pretend]
|
|
99
|
+
deployment += '--json ' if options[:deploy_file].end_with?('.json')
|
|
100
|
+
deployment += '--yaml ' if options[:deploy_file].end_with?('.yaml')
|
|
101
|
+
deployment += options[:deploy_file] + '\''
|
|
102
|
+
|
|
103
|
+
DanarchyDeploy::Helpers.run_command(deployment, options)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.pull_deployment(connector, options)
|
|
107
|
+
puts "\n > Pulling deployment: #{options[:deploy_file]}"
|
|
108
|
+
pull_result = DanarchyDeploy::Helpers.run_command("scp -i #{connector[:ssh_key]} #{connector[:ssh_user]}@#{connector[:ipv4]}:#{options[:deploy_file]} #{options[:deploy_file]}", options)
|
|
109
|
+
|
|
110
|
+
if pull_result[:stderr]
|
|
111
|
+
puts ' ! Deployment pull failed!'
|
|
112
|
+
abort("STDERR:\n#{pull_result[:stderr]}")
|
|
113
|
+
else
|
|
114
|
+
puts " |+ Deployment pulled!"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.remote_Cleanup(connector, options)
|
|
119
|
+
puts "\n > Cleaning up: #{options[:deploy_dir]}"
|
|
120
|
+
cleanup_result = DanarchyDeploy::Helpers.run_command("ssh -i #{connector[:ssh_key]} #{connector[:ssh_user]}@#{connector[:ipv4]} 'sudo rm -rfv #{options[:working_dir]}'", options)
|
|
121
|
+
|
|
122
|
+
if cleanup_result[:stderr]
|
|
123
|
+
puts ' ! Deployment cleanup failed!'
|
|
124
|
+
abort("STDERR:\n#{cleanup_result[:stderr]}")
|
|
125
|
+
else
|
|
126
|
+
puts " |+ Deployment cleaned up!"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
|
|
2
|
+
module DanarchyDeploy
|
|
3
|
+
class Archiver
|
|
4
|
+
def self.new(archives, options)
|
|
5
|
+
puts "\n" + self.name
|
|
6
|
+
|
|
7
|
+
archives.each do |archive|
|
|
8
|
+
abort("No target destination set for archive: #{archive[:source]}!") if !archive[:target]
|
|
9
|
+
|
|
10
|
+
tmparchive = false
|
|
11
|
+
if !archive[:source]
|
|
12
|
+
archive[:source] = options[:deploy_dir] + "/.tmp_archive_#{DateTime.now.strftime("%Y%m%d_%H%M%S")}"
|
|
13
|
+
tmparchive = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if archive[:data]
|
|
17
|
+
data = DanarchyDeploy::Helpers.decode_base64(archive[:data])
|
|
18
|
+
write_tmp_archive(archive[:source], data)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
puts " > Extracting #{archive[:source]} to #{archive[:target]}"
|
|
22
|
+
|
|
23
|
+
if !File.exist?(archive[:source])
|
|
24
|
+
puts " ! Source file not found!: #{archive[:source]}"
|
|
25
|
+
return false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Dir.exist?(archive[:target]) || FileUtils.mkdir_p(archive[:target])
|
|
29
|
+
command = prep_extraction(archive, options)
|
|
30
|
+
archive_result = DanarchyDeploy::Helpers.run_command(command, options)
|
|
31
|
+
|
|
32
|
+
if archive_result[:stderr]
|
|
33
|
+
puts ' ! Archive extraction failed!'
|
|
34
|
+
abort("STDERR:\n#{archive_result[:stderr]}")
|
|
35
|
+
else
|
|
36
|
+
puts " |+ Archive extracted: #{archive[:source]}\n"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
cleanup_source_archive(archive[:source]) if tmparchive
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
def self.prep_extraction(archive, options)
|
|
45
|
+
file_type = `file #{archive[:source]}`
|
|
46
|
+
command = 'tar xvf ' if file_type.include?('POSIX tar archive')
|
|
47
|
+
command = 'tar xvfj ' if file_type.include?('bzip2 compressed data')
|
|
48
|
+
command = 'tar xvfz ' if file_type.include?('gzip compressed data')
|
|
49
|
+
command = 'unzip ' if file_type.include?('Zip archive data')
|
|
50
|
+
|
|
51
|
+
if options[:pretend]
|
|
52
|
+
command = command.gsub(/x/, 't') if command.start_with?('tar')
|
|
53
|
+
command += '-t ' if command.start_with?('unzip')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
command += archive[:source]
|
|
57
|
+
command += " -C #{archive[:target]}" if command.start_with?('tar')
|
|
58
|
+
command += " -d #{archive[:target]}" if command.start_with?('unzip')
|
|
59
|
+
command
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.write_tmp_archive(source, data)
|
|
63
|
+
File.write(source, data)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.cleanup_source_archive(source)
|
|
67
|
+
File.delete(source)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
module DanarchyDeploy
|
|
3
|
+
class Groups
|
|
4
|
+
def self.new(deployment, options)
|
|
5
|
+
puts "\n" + self.name
|
|
6
|
+
(groupadd_result, groupdel_result) = nil
|
|
7
|
+
|
|
8
|
+
deployment[:groups].each do |group|
|
|
9
|
+
puts " > Checking if group '#{group[:groupname]}' already exists."
|
|
10
|
+
groupcheck_result = groupcheck(group, options)
|
|
11
|
+
|
|
12
|
+
if groupcheck_result[:stdout]
|
|
13
|
+
puts " - Group: #{group[:groupname]} already exists!"
|
|
14
|
+
else
|
|
15
|
+
puts " |+ Adding group: #{group[:groupname]}"
|
|
16
|
+
groupadd_result = groupadd(group, options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# [groupadd_result, groupdel_result]
|
|
21
|
+
deployment
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
def self.groupadd(group, options)
|
|
26
|
+
groupadd_cmd = "groupadd #{group[:groupname]} "
|
|
27
|
+
groupadd_cmd += "--gid #{group[:gid]} " if group[:gid]
|
|
28
|
+
groupadd_cmd += "--system " if group[:system]
|
|
29
|
+
if options[:pretend]
|
|
30
|
+
puts "\tFake run: #{groupadd_cmd}"
|
|
31
|
+
else
|
|
32
|
+
DanarchyDeploy::Helpers.run_command(groupadd_cmd, options)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.groupdel(group, options)
|
|
37
|
+
groupdel_cmd = "groupdel #{group[:groupname]}"
|
|
38
|
+
if options[:pretend]
|
|
39
|
+
puts "\tFake run: #{groupdel_cmd}"
|
|
40
|
+
else
|
|
41
|
+
DanarchyDeploy::Helpers.run_command(groupdel_cmd, options)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.groupcheck(group, options)
|
|
46
|
+
DanarchyDeploy::Helpers.run_command("/usr/bin/getent group #{group[:groupname]}", options)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
|
|
3
|
+
module DanarchyDeploy
|
|
4
|
+
class Helpers
|
|
5
|
+
def self.run_command(command, options)
|
|
6
|
+
pid, stdout, stderr = nil
|
|
7
|
+
printf("%14s %0s\n", 'Running:', "#{command}")
|
|
8
|
+
Open3.popen3(command) do |i, o, e, t|
|
|
9
|
+
pid = t.pid
|
|
10
|
+
(out, err) = o.read, e.read
|
|
11
|
+
stdout = !out.empty? ? out : nil
|
|
12
|
+
stderr = !err.empty? ? err : nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if options[:ssh_verbose]
|
|
16
|
+
puts "------\nSTDOUT: ", stdout, '------' if stdout
|
|
17
|
+
puts "------\nSTDERR: ", stderr, '------' if stderr
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
{ pid: pid, stdout: stdout, stderr: stderr }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.decode_base64(data)
|
|
24
|
+
require 'base64'
|
|
25
|
+
Base64.decode64(data)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
|
|
2
|
+
module DanarchyDeploy
|
|
3
|
+
class Installer
|
|
4
|
+
def self.new(deployment, options)
|
|
5
|
+
# available opts: { pretend: true|false }
|
|
6
|
+
abort('Operating System not defined! Exiting!') if !deployment[:os]
|
|
7
|
+
puts "\n" + self.name
|
|
8
|
+
|
|
9
|
+
os = deployment[:os]
|
|
10
|
+
packages = deployment[:packages]
|
|
11
|
+
installer, updater, cleaner, packages = prep_operating_system(os, packages, options)
|
|
12
|
+
install_result = nil
|
|
13
|
+
if packages
|
|
14
|
+
install_result = DanarchyDeploy::Helpers.run_command("#{installer} #{packages}", options)
|
|
15
|
+
else
|
|
16
|
+
puts 'No packages to install.'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if !options[:pretend]
|
|
20
|
+
puts "\nRunning system updates..."
|
|
21
|
+
updater_result = DanarchyDeploy::Helpers.run_command(updater, options)
|
|
22
|
+
puts updater_result[:stdout] if updater_result[:stdout]
|
|
23
|
+
puts "\nCleaning up unused packages..."
|
|
24
|
+
cleanup_result = DanarchyDeploy::Helpers.run_command(cleaner, options)
|
|
25
|
+
puts cleanup_result[:stdout] if cleanup_result[:stdout]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# [install_result, cleanup_result, updater_result]
|
|
29
|
+
deployment
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
def self.prep_operating_system(os, packages, options)
|
|
34
|
+
(installer, updater, cleaner) = nil
|
|
35
|
+
if os.downcase == 'gentoo'
|
|
36
|
+
puts "#{os.capitalize} detected! Using emerge."
|
|
37
|
+
|
|
38
|
+
if packages
|
|
39
|
+
installer = 'emerge --usepkg --quiet --noreplace '
|
|
40
|
+
installer += '--pretend ' if options[:pretend]
|
|
41
|
+
|
|
42
|
+
packages.each do |pkg|
|
|
43
|
+
IO.popen("qlist -I #{pkg}") do |o|
|
|
44
|
+
packages.delete(pkg) if !o.read.empty?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
cleaner = 'emerge --depclean --quiet '
|
|
50
|
+
cleaner += '--pretend ' if options[:pretend]
|
|
51
|
+
|
|
52
|
+
updater = 'emerge --update --deep --newuse --quiet --with-bdeps=y @world'
|
|
53
|
+
updater += ' --pretend' if options[:pretend]
|
|
54
|
+
elsif %w[debian ubuntu].include?(os.downcase)
|
|
55
|
+
puts "#{os.capitalize} detected! Using apt."
|
|
56
|
+
installer = 'export DEBIAN_FRONTEND=noninteractive ; apt install -y -qq '
|
|
57
|
+
installer += '--dry-run ' if options[:pretend]
|
|
58
|
+
updater = 'apt-get upgrade -y -qq'
|
|
59
|
+
cleaner = 'apt-get autoclean -y -qq'
|
|
60
|
+
elsif os.downcase == 'opensuse'
|
|
61
|
+
puts "#{os.capitalize} detected! Using zypper."
|
|
62
|
+
installer = 'zypper install '
|
|
63
|
+
updater = nil
|
|
64
|
+
cleaner = nil
|
|
65
|
+
# Needs package checking & testing
|
|
66
|
+
elsif %w[centos redhat].include?(os.downcase)
|
|
67
|
+
# needs more testing
|
|
68
|
+
puts "#{os.capitalize} detected! Using yum."
|
|
69
|
+
if packages
|
|
70
|
+
installer = 'yum install -y '
|
|
71
|
+
|
|
72
|
+
packages.each do |pkg|
|
|
73
|
+
IO.popen("rpm -q #{pkg}") do |o|
|
|
74
|
+
packages.delete(pkg) if !o.read.include?('not installed')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
updater = nil
|
|
80
|
+
cleaner = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
[installer, updater, cleaner, packages.join(' ')]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
module DanarchyDeploy
|
|
3
|
+
class Services
|
|
4
|
+
def self.new(deployment, options)
|
|
5
|
+
puts "\n" + self.name
|
|
6
|
+
|
|
7
|
+
deployment[:services].each do |service, params|
|
|
8
|
+
puts "Configuring service: #{service}"
|
|
9
|
+
|
|
10
|
+
if params[:templates] && !params[:templates].empty?
|
|
11
|
+
puts " > COnfiguring templates for #{service}"
|
|
12
|
+
DanarchyDeploy::Templater.new(params[:templates], options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if params[:archives] && !params[:archives].empty?
|
|
16
|
+
puts "\n" + self.name
|
|
17
|
+
puts " > Deploying archives for #{service}"
|
|
18
|
+
DanarchyDeploy::Archiver.new(params[:archives], options)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
deployment
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def self.init(deployment, options)
|
|
27
|
+
puts "\n" + self.name
|
|
28
|
+
|
|
29
|
+
deployment[:services].each do |service, params|
|
|
30
|
+
next if !params[:init]
|
|
31
|
+
if options[:first_run] == false
|
|
32
|
+
puts " ! Not a first-time run! Setting actions to 'reload'.\n\tUse --first-run to run actions: #{params[:init].join(' ,')}\n"
|
|
33
|
+
params[:init] = ['reload']
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
params[:init].each do |action|
|
|
37
|
+
puts " > Taking action: #{action} on #{service}"
|
|
38
|
+
command = "systemctl #{action} #{service}"
|
|
39
|
+
|
|
40
|
+
if options[:pretend]
|
|
41
|
+
puts " Fake run: #{command}\n"
|
|
42
|
+
else
|
|
43
|
+
DanarchyDeploy::Helpers.run_command(command, options)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
deployment
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
module DanarchyDeploy
|
|
5
|
+
class Templater
|
|
6
|
+
def self.new(templates, options)
|
|
7
|
+
puts "\n" + self.name
|
|
8
|
+
|
|
9
|
+
templates.each do |template|
|
|
10
|
+
abort("No target destination set for template: #{template[:source]}!") if !template[:target]
|
|
11
|
+
abort("No source or data for template: #{template[:target]}") if !template[:source] && !template[:data]
|
|
12
|
+
|
|
13
|
+
target = template[:target]
|
|
14
|
+
source = template[:source] || '-- encoded data --'
|
|
15
|
+
dir_perms = template[:dir_perms]
|
|
16
|
+
file_perms = template[:file_perms]
|
|
17
|
+
@variables = template[:variables] || nil
|
|
18
|
+
puts "\n > Checking: #{target}"
|
|
19
|
+
puts " Source: #{source}"
|
|
20
|
+
puts " |> Dir Permissions: #{dir_perms || '--undefined--'}"
|
|
21
|
+
puts " |> File Permissions: #{file_perms || '--undefined--'}"
|
|
22
|
+
puts " |> Variables: #{@variables}" if @variables
|
|
23
|
+
|
|
24
|
+
targetdir = File.dirname(target)
|
|
25
|
+
tmpdir = options[:deploy_dir] + '/' + File.basename(File.dirname(target))
|
|
26
|
+
p tmpdir
|
|
27
|
+
Dir.exist?(targetdir) || FileUtils.mkdir_p(targetdir, mode: 0755)
|
|
28
|
+
Dir.exist?(tmpdir) || FileUtils.mkdir_p(tmpdir, mode: 0755)
|
|
29
|
+
tmpfile = tmpdir + '/' + File.basename(target) + '.tmp'
|
|
30
|
+
|
|
31
|
+
if source == '-- encoded data --'
|
|
32
|
+
data = DanarchyDeploy::Helpers.decode_base64(template[:data])
|
|
33
|
+
source = tmpfile + '.erb'
|
|
34
|
+
write_tmpfile(source, data)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
File.open(tmpfile, 'w') do |f|
|
|
38
|
+
result = ERB.new(File.read(source)).result(binding)
|
|
39
|
+
f.write(result)
|
|
40
|
+
f.close
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
result = { write_erb: [], verify_permissions: {} }
|
|
44
|
+
if options[:pretend]
|
|
45
|
+
diff(target, tmpfile)
|
|
46
|
+
puts "\n - Fake Run: Not changing '#{target}'."
|
|
47
|
+
result[:verify_permissions][File.dirname(tmpfile)] = verify_permissions(File.dirname(tmpfile), dir_perms, options)
|
|
48
|
+
result[:verify_permissions][tmpfile] = verify_permissions(tmpfile, file_perms, options)
|
|
49
|
+
elsif md5sum(target,tmpfile) == true
|
|
50
|
+
puts "\n - No change in '#{target}': Nothing to update here."
|
|
51
|
+
result[:verify_permissions][File.dirname(target)] = verify_permissions(File.dirname(target), dir_perms, options)
|
|
52
|
+
result[:verify_permissions][target] = verify_permissions(target, file_perms, options)
|
|
53
|
+
else
|
|
54
|
+
diff(target, tmpfile)
|
|
55
|
+
result[:write_erb] = enable_erb(target, tmpfile)
|
|
56
|
+
puts " => #{target} was updated!"
|
|
57
|
+
result[:verify_permissions][File.dirname(target)] = verify_permissions(File.dirname(target), dir_perms, options)
|
|
58
|
+
result[:verify_permissions][target] = verify_permissions(target, file_perms, options)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
FileUtils.rm_rf(File.dirname(tmpfile))
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
def self.verify_permissions(target, perms, options)
|
|
68
|
+
(owner, group, mode) = nil
|
|
69
|
+
chmod = nil
|
|
70
|
+
puts "\n > Verifying ownership and permissions for '#{target}'"
|
|
71
|
+
if perms
|
|
72
|
+
(owner, group, mode) = perms[:owner], perms[:group], perms[:mode]
|
|
73
|
+
else
|
|
74
|
+
if File.stat(target).mode & 07777 == '0777'.to_i(8)
|
|
75
|
+
puts " ! '#{target}' has 0777 permissions! Setting those to something more sane."
|
|
76
|
+
if File.ftype(target) == 'directory'
|
|
77
|
+
puts " |+ Setting file mode to: 0775"
|
|
78
|
+
chmod = File.chmod(0775, target) ? true : false if !options[:pretend]
|
|
79
|
+
elsif File.ftype(target) == 'file'
|
|
80
|
+
puts " |+ Setting file mode to: 0644"
|
|
81
|
+
chmod = File.chmod(0644, target) ? true : false if !options[:pretend]
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
puts " - Permissions were not defined for '#{target}'! Leaving them alone."
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
return [chmod]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
(owner, uid, group, gid) = check_user_group(owner, group)
|
|
91
|
+
|
|
92
|
+
updated = []
|
|
93
|
+
file_stat = File.stat(target)
|
|
94
|
+
if file_stat.uid != uid || file_stat.gid != gid
|
|
95
|
+
puts " |+ Setting ownership to: #{owner}:#{group}"
|
|
96
|
+
chown = File.chown(uid, gid, target) ? true : false if !options[:pretend]
|
|
97
|
+
updated.push(chown)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if file_stat.mode & 07777 != mode.to_i(8)
|
|
101
|
+
puts " |+ Setting file mode to: #{mode}"
|
|
102
|
+
chmod = File.chmod(mode.to_i(8), target) ? true : false if !options[:pretend]
|
|
103
|
+
updated.push(chmod)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
updated
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.check_user_group(owner, group)
|
|
110
|
+
(uid, gid) = nil
|
|
111
|
+
|
|
112
|
+
IO.popen("id -u #{owner} 2>/dev/null") do |id|
|
|
113
|
+
uid = id.gets
|
|
114
|
+
if uid == nil
|
|
115
|
+
puts " ! User: #{owner} not found! Using: 'root'"
|
|
116
|
+
owner = 'root'
|
|
117
|
+
uid = 0
|
|
118
|
+
end
|
|
119
|
+
uid = uid.chomp.to_i if uid.class == String
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
IO.popen("id -g #{group} 2>/dev/null") do |id|
|
|
123
|
+
gid = id.gets
|
|
124
|
+
if gid == nil
|
|
125
|
+
puts " ! Group: #{group} not found! Using: 'root'"
|
|
126
|
+
group = 'root'
|
|
127
|
+
gid = 0
|
|
128
|
+
end
|
|
129
|
+
gid = gid.chomp.to_i if gid.class == String
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
[owner, uid, group, gid]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.md5sum(target, tmpfile)
|
|
136
|
+
if File.exist?(target) && File.exist?(tmpfile)
|
|
137
|
+
FileUtils.identical?(target, tmpfile)
|
|
138
|
+
elsif File.exist?(target) && !File.exist?(tmpfile)
|
|
139
|
+
return false
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def self.diff(target, tmpfile)
|
|
144
|
+
if File.exist?(target) && File.exist?(tmpfile)
|
|
145
|
+
puts "\n !! Diff between #{target} <=> #{tmpfile}"
|
|
146
|
+
IO.popen("diff -Naur #{target} #{tmpfile}") do |o|
|
|
147
|
+
puts o.read
|
|
148
|
+
end
|
|
149
|
+
puts "\n !! End Diff \n\n"
|
|
150
|
+
elsif File.exist?(target) && !File.exist?(tmpfile)
|
|
151
|
+
return false
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def self.enable_erb(target, tmpfile)
|
|
156
|
+
puts "\n |+ Moving #{tmpfile} => #{target}"
|
|
157
|
+
system("mv #{tmpfile} #{target}")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def self.write_tmpfile(source, data)
|
|
161
|
+
File.write(source, data)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
|
|
2
|
+
module DanarchyDeploy
|
|
3
|
+
class Users
|
|
4
|
+
def self.new(deployment, options)
|
|
5
|
+
puts "\n" + self.name
|
|
6
|
+
(useradd_result, userdel_result, archives_result) = nil
|
|
7
|
+
|
|
8
|
+
deployment[:users].each do |user|
|
|
9
|
+
puts " > Checking if user '#{user[:username]}' already exists."
|
|
10
|
+
usercheck_result = usercheck(user, options)
|
|
11
|
+
|
|
12
|
+
if usercheck_result[:stdout]
|
|
13
|
+
puts " - User: #{user[:username]} already exists!"
|
|
14
|
+
else
|
|
15
|
+
group = { groupname: user[:username] }
|
|
16
|
+
group[:gid] = user[:gid] ? user[:gid] : nil
|
|
17
|
+
group[:system] = user[:system] ? user[:system] : nil
|
|
18
|
+
|
|
19
|
+
groupcheck_result = DanarchyDeploy::Groups.groupcheck(group, options)
|
|
20
|
+
if !groupcheck_result[:stdout] && group[:gid]
|
|
21
|
+
puts " |+ Adding group: #{group[:groupname]}"
|
|
22
|
+
DanarchyDeploy::Groups.groupadd(group, options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
puts " |+ Adding user: #{user[:username]}"
|
|
26
|
+
useradd_result = useradd(user, options)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if !options[:pretend]
|
|
30
|
+
puts "\n > Checking groups for user: #{user[:username]}"
|
|
31
|
+
if user[:groups] && checkgroups(usercheck_result, user, options) == false
|
|
32
|
+
updategroups(user, options)
|
|
33
|
+
puts " |+ Updated groups: #{user[:groups].join(',')}"
|
|
34
|
+
else
|
|
35
|
+
puts " - No change to groups needed."
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if user[:authorized_keys]
|
|
39
|
+
puts "\n > Checking on #{user[:authorized_keys].count} authorized_keys for user: #{user[:username]}"
|
|
40
|
+
authorized_keys(user)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if user[:sudoer]
|
|
44
|
+
puts "\n > Checking sudo rules for user: #{user[:username]}"
|
|
45
|
+
sudoer(user)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if user[:archives] && !user[:archives].empty?
|
|
50
|
+
puts " > Deploying archives for #{user[:username]}"
|
|
51
|
+
DanarchyDeploy::Archiver.new(user[:archives], options)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# [useradd_result, userdel_result]
|
|
56
|
+
deployment
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
def self.useradd(user, options)
|
|
61
|
+
useradd_cmd = "useradd #{user[:username]} "
|
|
62
|
+
useradd_cmd += "--home-dir #{user[:home]} " if user[:home]
|
|
63
|
+
useradd_cmd += "--uid #{user[:uid]} " if user[:uid]
|
|
64
|
+
useradd_cmd += "--gid #{user[:gid]} " if user[:gid]
|
|
65
|
+
useradd_cmd += "--groups #{user[:groups].join(',')} " if user[:groups]
|
|
66
|
+
useradd_cmd += "--shell /sbin/nologin " if user[:nologin]
|
|
67
|
+
useradd_cmd += "--system " if user[:system]
|
|
68
|
+
if options[:pretend]
|
|
69
|
+
puts "\tFake run: #{useradd_cmd}"
|
|
70
|
+
else
|
|
71
|
+
DanarchyDeploy::Helpers.run_command(useradd_cmd, options)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.userdel(user, options)
|
|
76
|
+
userdel_cmd = "userdel --remove #{user[:username]}"
|
|
77
|
+
if options[:pretend]
|
|
78
|
+
puts "\tFake run: #{userdel_cmd}"
|
|
79
|
+
else
|
|
80
|
+
DanarchyDeploy::Helpers.run_command(userdel_cmd, options)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.usercheck(user, options)
|
|
85
|
+
DanarchyDeploy::Helpers.run_command("id #{user[:username]}", options)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.checkgroups(usercheck_result, user, options)
|
|
89
|
+
return nil if !usercheck_result[:stdout]
|
|
90
|
+
livegroups = usercheck_result[:stdout].split(/\s+/).last.split('=').last.gsub(/\(([^)]*)\)/, '').split(',').map(&:to_i)
|
|
91
|
+
livegroups.delete(user[:gid])
|
|
92
|
+
livegroups.sort == user[:groups].sort
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.updategroups(user, options)
|
|
96
|
+
groups = user[:groups].join(',')
|
|
97
|
+
groupupdate_cmd = "usermod #{user[:username]} --groups #{groups}"
|
|
98
|
+
if options[:pretend]
|
|
99
|
+
puts "\tFake run: #{groupupdate_cmd}"
|
|
100
|
+
else
|
|
101
|
+
DanarchyDeploy::Helpers.run_command(groupupdate_cmd, options)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.authorized_keys(user)
|
|
106
|
+
ssh_path = user[:home] + '/.ssh'
|
|
107
|
+
authkeys = ssh_path + '/authorized_keys'
|
|
108
|
+
|
|
109
|
+
Dir.exist?(ssh_path) || Dir.mkdir(ssh_path)
|
|
110
|
+
File.open(authkeys, 'a+') do |f|
|
|
111
|
+
user[:authorized_keys].each do |authkey|
|
|
112
|
+
if !f.read.include?(authkey)
|
|
113
|
+
puts " + Adding authorized_key: #{authkey}"
|
|
114
|
+
f.puts authkey
|
|
115
|
+
else
|
|
116
|
+
puts ' - No change needed'
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
f.close
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.sudoer(user)
|
|
125
|
+
sudoer_file = '/etc/sudoers.d/danarchy_deploy-' + user[:username]
|
|
126
|
+
File.open(sudoer_file, 'r+') do |f|
|
|
127
|
+
if !f.read.include?(user[:sudoer])
|
|
128
|
+
puts " |+ Added: '#{user[:sudoer]}'"
|
|
129
|
+
f.puts user[:sudoer]
|
|
130
|
+
else
|
|
131
|
+
puts ' - No change needed'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
f.close
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hostname": "hostname",
|
|
3
|
+
"os": "gentoo|debian|ubuntu",
|
|
4
|
+
"ipv4": "IPv4 to use for deployment",
|
|
5
|
+
"ssh_user": "deploy-user",
|
|
6
|
+
"ssh_key": "/home/path/to/deploy-user/ssh_key.",
|
|
7
|
+
"packages": [
|
|
8
|
+
"package1",
|
|
9
|
+
"package2"
|
|
10
|
+
],
|
|
11
|
+
"users": [
|
|
12
|
+
{
|
|
13
|
+
"username": "username",
|
|
14
|
+
"home": "/home/username",
|
|
15
|
+
"uid": int,
|
|
16
|
+
"gid": int,
|
|
17
|
+
"sudoer": "username ALL = NOPASSWD: ALL",
|
|
18
|
+
"ssh-authorized_keys": [
|
|
19
|
+
"ssh-ed25519 it0C5o6GHC8lxqctpexakfdA5o7LeSe+QbMhIl+GYtZ2OCMFliLsODDrrazR+u2y user@hostname",
|
|
20
|
+
"ssh-rsa K0APeEvotGunpBrl/LvSAG/gLUldCnOrL60v47QYjuqoGJmM3Fk8V29+8jZPp9Dl user@hostname"
|
|
21
|
+
],
|
|
22
|
+
"groups": [
|
|
23
|
+
int,
|
|
24
|
+
int
|
|
25
|
+
],
|
|
26
|
+
"archives": [
|
|
27
|
+
{
|
|
28
|
+
"target": "/path/to/extract/to/",
|
|
29
|
+
"source": "/path/to/tarball.(tar.{gz,bz2}|zip)"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"target": "/path/to/extract/to/",
|
|
33
|
+
"data": "couchdb:base64_encoded_data"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"groups": [
|
|
39
|
+
{
|
|
40
|
+
"groupname": "groupname",
|
|
41
|
+
"gid": int,
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"services": {
|
|
45
|
+
"service_name": {
|
|
46
|
+
"init": {
|
|
47
|
+
"service": [
|
|
48
|
+
"enable",
|
|
49
|
+
"restart"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
"archives": [
|
|
53
|
+
{
|
|
54
|
+
"target": "/path/to/extract/to/",
|
|
55
|
+
"source": "/path/to/tarball.(tar.{gz,bz2}|zip)"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"target": "/path/to/extract/to/",
|
|
59
|
+
"data": "couchdb:base64_encoded_data"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"templates": [
|
|
63
|
+
{
|
|
64
|
+
"target": "/path/to/target/file",
|
|
65
|
+
"source": "/path/to/source/erb",
|
|
66
|
+
"dir_perms": {
|
|
67
|
+
"owner": "username",
|
|
68
|
+
"group": "groupname",
|
|
69
|
+
"mode": "0755"
|
|
70
|
+
},
|
|
71
|
+
"file_perms": {
|
|
72
|
+
"owner": "username",
|
|
73
|
+
"group": "groupname",
|
|
74
|
+
"mode": "0644"
|
|
75
|
+
},
|
|
76
|
+
"variables": {
|
|
77
|
+
"var1": "value",
|
|
78
|
+
"var2": "value"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"target": "/path/to/target/file",
|
|
83
|
+
"data": "couchdb:base64_encoded_erb",
|
|
84
|
+
"dir_perms": {
|
|
85
|
+
"owner": "username",
|
|
86
|
+
"group": "groupname",
|
|
87
|
+
"mode": "0755"
|
|
88
|
+
},
|
|
89
|
+
"file_perms": {
|
|
90
|
+
"owner": "username",
|
|
91
|
+
"group": "groupname",
|
|
92
|
+
"mode": "0644"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
metadata
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: danarchy_deploy
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dan James
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-03-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: danarchy_couchdb
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.1.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.1.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.16'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.16'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '10.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '10.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.0'
|
|
69
|
+
description: DanarchyDeploy intends to simplify Gentoo Linux (and other distro) deployments
|
|
70
|
+
down to a single template from an input JSON or YAML file, or from a CouchDB file.
|
|
71
|
+
email:
|
|
72
|
+
- dan@danarchy.me
|
|
73
|
+
executables:
|
|
74
|
+
- danarchy_deploy
|
|
75
|
+
- danarchy_deploy-console
|
|
76
|
+
- setup
|
|
77
|
+
extensions: []
|
|
78
|
+
extra_rdoc_files: []
|
|
79
|
+
files:
|
|
80
|
+
- ".gitignore"
|
|
81
|
+
- ".rspec"
|
|
82
|
+
- ".travis.yml"
|
|
83
|
+
- Gemfile
|
|
84
|
+
- Gemfile.lock
|
|
85
|
+
- LICENSE.txt
|
|
86
|
+
- README.md
|
|
87
|
+
- Rakefile
|
|
88
|
+
- bin/danarchy_deploy
|
|
89
|
+
- bin/danarchy_deploy-console
|
|
90
|
+
- bin/setup
|
|
91
|
+
- danarchy_deploy.gemspec
|
|
92
|
+
- lib/danarchy_deploy.rb
|
|
93
|
+
- lib/danarchy_deploy/archiver.rb
|
|
94
|
+
- lib/danarchy_deploy/groups.rb
|
|
95
|
+
- lib/danarchy_deploy/helpers.rb
|
|
96
|
+
- lib/danarchy_deploy/installer.rb
|
|
97
|
+
- lib/danarchy_deploy/services.rb
|
|
98
|
+
- lib/danarchy_deploy/templater.rb
|
|
99
|
+
- lib/danarchy_deploy/users.rb
|
|
100
|
+
- lib/danarchy_deploy/version.rb
|
|
101
|
+
- templates/deploy_template.json
|
|
102
|
+
homepage: https://github.com/danarchy85/danarchy_deploy
|
|
103
|
+
licenses:
|
|
104
|
+
- MIT
|
|
105
|
+
metadata:
|
|
106
|
+
allowed_push_host: https://rubygems.org
|
|
107
|
+
post_install_message:
|
|
108
|
+
rdoc_options: []
|
|
109
|
+
require_paths:
|
|
110
|
+
- lib
|
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - ">="
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '0'
|
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
|
+
requirements:
|
|
118
|
+
- - ">="
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '0'
|
|
121
|
+
requirements: []
|
|
122
|
+
rubyforge_project:
|
|
123
|
+
rubygems_version: 2.6.14
|
|
124
|
+
signing_key:
|
|
125
|
+
specification_version: 4
|
|
126
|
+
summary: Pushes deployments locally or remotely based on a JSON/YAML/CouchDB template.
|
|
127
|
+
test_files: []
|