environmate 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 +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +674 -0
- data/README.md +190 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/environmate.gemspec +39 -0
- data/exe/environmate +51 -0
- data/lib/environmate.rb +14 -0
- data/lib/environmate/app.rb +83 -0
- data/lib/environmate/command.rb +20 -0
- data/lib/environmate/configuration.rb +52 -0
- data/lib/environmate/deployment.rb +120 -0
- data/lib/environmate/environment.rb +105 -0
- data/lib/environmate/environment_manager.rb +117 -0
- data/lib/environmate/errors.rb +5 -0
- data/lib/environmate/git_repository.rb +66 -0
- data/lib/environmate/log.rb +16 -0
- data/lib/environmate/user.rb +44 -0
- data/lib/environmate/version.rb +3 -0
- data/lib/environmate/xmpp.rb +24 -0
- metadata +211 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# This class controlls the deployment of puppet environments
|
|
2
|
+
require 'lockfile'
|
|
3
|
+
|
|
4
|
+
module Environmate
|
|
5
|
+
class Deployment
|
|
6
|
+
|
|
7
|
+
# create a new instance of Deployment
|
|
8
|
+
#
|
|
9
|
+
# * +user+ - The user who triggered the deployment
|
|
10
|
+
# * +puppet_env+ - The puppet environment to deploy
|
|
11
|
+
# * +revision+ - The revision which should be deployed in the environment
|
|
12
|
+
# * +old_revision+ - The revision the environment had previously
|
|
13
|
+
def initialize(user, puppet_env, revision, old_revision = nil)
|
|
14
|
+
@user = user
|
|
15
|
+
@puppet_env = puppet_env
|
|
16
|
+
@revision = revision
|
|
17
|
+
@old_revision = old_revision
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Deploy a static environment
|
|
21
|
+
#
|
|
22
|
+
# * +token+ - The access token to deploy the environment
|
|
23
|
+
def deploy_static(token)
|
|
24
|
+
unless is_static_env?
|
|
25
|
+
@user.notify(:error, "'#{@puppet_env}' is not a static environment! Deployment aborted!")
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
return unless token_valid?(token)
|
|
29
|
+
|
|
30
|
+
@user.notify(:info, "Push of static environment '#{@puppet_env}' received, attempting to deploy...")
|
|
31
|
+
deploy
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Deploy a dynamic environment
|
|
35
|
+
def deploy_dynamic
|
|
36
|
+
if is_static_env?
|
|
37
|
+
@user.notify(:error, "'#{@puppet_env}' is a static environment. There should not be a branch of the same name")
|
|
38
|
+
return
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@user.notify(:info, "Push of dynamic environment '#{@puppet_env}' received, attempting to deploy...")
|
|
42
|
+
deploy
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Checks if the puppet environment is a static environment
|
|
46
|
+
def is_static_env?
|
|
47
|
+
Environmate.configuration['static_environments'].keys.include?(@puppet_env)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Checks if the token ist valid
|
|
51
|
+
#
|
|
52
|
+
# * +token+ - The token received from the hook
|
|
53
|
+
def token_valid?(token)
|
|
54
|
+
real_token = Environmate.configuration['static_environments'][@puppet_env]['token']
|
|
55
|
+
if real_token.nil?
|
|
56
|
+
@user.notify(:error, "No token set for '#{@puppet_env}'")
|
|
57
|
+
false
|
|
58
|
+
elsif real_token == token
|
|
59
|
+
true
|
|
60
|
+
else
|
|
61
|
+
@user.notify(:error, "Incorrect token for deployment of '#{@puppet_env}'")
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
rescue
|
|
65
|
+
@user.notify(:error, "Error while parsing configuration for '#{@puppet_env}'")
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Deploy the puppet environment
|
|
70
|
+
def deploy
|
|
71
|
+
lockfile_path = Environmate.configuration['lockfile_path']
|
|
72
|
+
lockfile_options = Environmate.configuration['lockfile_options']
|
|
73
|
+
Lockfile.new(lockfile_path, lockfile_options) do
|
|
74
|
+
begin
|
|
75
|
+
if @revision.nil? || @revision.empty?
|
|
76
|
+
@user.notify(:info, 'Revision was empty. Only doing a cleanup...')
|
|
77
|
+
EnvironmentManager.master
|
|
78
|
+
else
|
|
79
|
+
@user.notify(:info, "Starting deployment of '#{@puppet_env}'...")
|
|
80
|
+
new_env = new_environment
|
|
81
|
+
@user.notify(:info, "Linking '#{@puppet_env}' => '#{@revision}'")
|
|
82
|
+
new_env.link(@puppet_env)
|
|
83
|
+
@user.notify(:info, "Done! Starting cleanup...")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
EnvironmentManager.cleanup
|
|
87
|
+
@user.notify(:info, "Cleanup done!")
|
|
88
|
+
rescue Environmate::DeployError => e
|
|
89
|
+
@user.notify(:error, "Error during deployment: #{e.message}")
|
|
90
|
+
rescue => e
|
|
91
|
+
@user.notify(:error, 'Error during deployment. This is probably a bug')
|
|
92
|
+
@user.notify(:error, e.class)
|
|
93
|
+
@user.notify(:error, e.message)
|
|
94
|
+
@user.notify(:error, e.backtrace)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
# check if the revision is already deployed, if not deploy it
|
|
102
|
+
def new_environment
|
|
103
|
+
new_env = EnvironmentManager.find(@revision)
|
|
104
|
+
if new_env.nil?
|
|
105
|
+
@user.notify(:info, "Revision '#{@revision}' not already deployed, trying to find optimal starting point for deployment")
|
|
106
|
+
env = EnvironmentManager.find(@old_revision) ||
|
|
107
|
+
EnvironmentManager.find(@puppet_env) ||
|
|
108
|
+
EnvironmentManager.master
|
|
109
|
+
@user.notify(:info, "Deploying '#{@revision}' from copy of '#{env.name}'")
|
|
110
|
+
new_env = env.copy(@revision)
|
|
111
|
+
@user.notify(:info, "Deployment of '#{@revision}' done!")
|
|
112
|
+
else
|
|
113
|
+
@user.notify(:info, "Revision '#{@revision}' is already deployed")
|
|
114
|
+
end
|
|
115
|
+
new_env
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
require 'securerandom'
|
|
3
|
+
|
|
4
|
+
module Environmate
|
|
5
|
+
class Environment
|
|
6
|
+
include Command
|
|
7
|
+
|
|
8
|
+
attr_reader :name
|
|
9
|
+
|
|
10
|
+
def initialize(env_path)
|
|
11
|
+
@env_path = env_path
|
|
12
|
+
@name = File.basename(env_path)
|
|
13
|
+
@git = GitRepository.new(env_path)
|
|
14
|
+
@install_modules_command = Environmate.configuration['install_modules_command']
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# This will update the current repository and
|
|
18
|
+
# reset it to the specified revision.
|
|
19
|
+
#
|
|
20
|
+
# * +revision+ - The revision to update the repository to
|
|
21
|
+
def update(revision)
|
|
22
|
+
update_repo(revision)
|
|
23
|
+
update_submodules if has_submodules?
|
|
24
|
+
update_modules if has_puppetfile?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# This create a copy of the current repository
|
|
28
|
+
# under the specified revision name and update
|
|
29
|
+
# that environment to the specified revision.
|
|
30
|
+
#
|
|
31
|
+
# * +revision+ - The revision to update the repository to
|
|
32
|
+
def copy(revision)
|
|
33
|
+
new_path = new_env_path(revision)
|
|
34
|
+
command("cp -r #{@env_path} #{new_path}")
|
|
35
|
+
new_env = Environment.new(new_path)
|
|
36
|
+
new_env.update(revision)
|
|
37
|
+
new_env
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# This will create a environment link to the repository
|
|
41
|
+
#
|
|
42
|
+
# * +puppet_env+ - The name of the link/environment
|
|
43
|
+
def link(puppet_env)
|
|
44
|
+
unless links.include?(puppet_env)
|
|
45
|
+
temp_link_path = new_env_path(SecureRandom.hex)
|
|
46
|
+
real_link_path = new_env_path(puppet_env)
|
|
47
|
+
# relinking is not atomic in linux, but mv is
|
|
48
|
+
# so we create a temporary link and rename it
|
|
49
|
+
File.symlink(@env_path, temp_link_path)
|
|
50
|
+
File.rename(temp_link_path, real_link_path)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# get a list of all the links to this environment
|
|
55
|
+
def links
|
|
56
|
+
found_links = EnvironmentManager.links.find_all do |link,target|
|
|
57
|
+
target == @name
|
|
58
|
+
end
|
|
59
|
+
found_links.map{|link, target| link}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def delete
|
|
63
|
+
FileUtils.remove_entry_secure(@env_path)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def valid?
|
|
67
|
+
@git.valid?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def update_repo(revision)
|
|
73
|
+
@git.fetch
|
|
74
|
+
@git.reset_hard
|
|
75
|
+
@git.clean
|
|
76
|
+
@git.checkout(revision)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def update_submodules
|
|
80
|
+
@git.submodule_update
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def update_modules
|
|
84
|
+
Dir.chdir(@env_path) do
|
|
85
|
+
command(@install_modules_command)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def has_submodules?
|
|
90
|
+
gitmodules = File.join(@env_path, '.gitmodules')
|
|
91
|
+
File.exist?(gitmodules)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def has_puppetfile?
|
|
95
|
+
puppetfile = File.join(@env_path, 'Puppetfile')
|
|
96
|
+
File.exist?(puppetfile)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def new_env_path(revision)
|
|
100
|
+
base_dir = Environmate.configuration['environment_path']
|
|
101
|
+
File.join(base_dir, revision)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
module Environmate
|
|
2
|
+
class EnvironmentManager
|
|
3
|
+
include Command
|
|
4
|
+
|
|
5
|
+
def self.find(env_name)
|
|
6
|
+
environments.find do |env|
|
|
7
|
+
env.name == env_name ||
|
|
8
|
+
env.links.any?{|link| File.basename(link) == env_name}
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.environments
|
|
13
|
+
env_dir_entries('directory').map do |dir|
|
|
14
|
+
env = Environment.new(dir)
|
|
15
|
+
env.valid? ? env : nil
|
|
16
|
+
end.compact
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.orphan_dirs
|
|
20
|
+
env_dir_entries('directory').map do |dir|
|
|
21
|
+
env = Environment.new(dir)
|
|
22
|
+
env.valid? ? nil : env
|
|
23
|
+
end.compact
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.links
|
|
27
|
+
Hash[env_dir_entries('link').map do |dir|
|
|
28
|
+
target = File.readlink(dir)
|
|
29
|
+
[File.basename(dir), File.basename(target)]
|
|
30
|
+
end]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# get the master environment and make sure it is created
|
|
34
|
+
# and updated.
|
|
35
|
+
def self.master
|
|
36
|
+
master_path = Environmate.configuration['master_path']
|
|
37
|
+
master_repository = Environmate.configuration['master_repository']
|
|
38
|
+
master_branch = Environmate.configuration['master_branch']
|
|
39
|
+
unless File.exists?(File.join(master_path,'.git'))
|
|
40
|
+
git = GitRepository.new(master_path)
|
|
41
|
+
git.clone(master_repository)
|
|
42
|
+
end
|
|
43
|
+
master_env = Environment.new(master_path)
|
|
44
|
+
master_env.update("origin/#{master_branch}")
|
|
45
|
+
master_env
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# cleanup old links and environments
|
|
49
|
+
def self.cleanup
|
|
50
|
+
cleanup_links
|
|
51
|
+
cleanup_environments
|
|
52
|
+
cleanup_orphan_dirs
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# puppet environment name from branch name
|
|
56
|
+
def self.env_from_branch(branch)
|
|
57
|
+
prefix = Environmate.configuration['dynamic_environments_prefix']
|
|
58
|
+
if branch.start_with?(prefix)
|
|
59
|
+
branch.gsub(prefix, '').gsub(/(\/|\-)/,'_')
|
|
60
|
+
else
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# returns an array of environments which has remote branches
|
|
68
|
+
def self.envs_with_branches
|
|
69
|
+
env_path = Environmate.configuration['master_path']
|
|
70
|
+
git = GitRepository.new(env_path)
|
|
71
|
+
git.remote_branches.map do |branch|
|
|
72
|
+
env_from_branch(branch)
|
|
73
|
+
end.compact
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# returns an array of direcories in the environement path
|
|
77
|
+
def self.env_dir_entries(ftype)
|
|
78
|
+
env_path = Environmate.configuration['environment_path']
|
|
79
|
+
Dir[env_path + '/*'].map do |dir|
|
|
80
|
+
File.ftype(dir) == ftype ? dir : nil
|
|
81
|
+
end.compact
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# cleanup all the old links
|
|
85
|
+
def self.cleanup_links
|
|
86
|
+
env_path = Environmate.configuration['environment_path']
|
|
87
|
+
static_envs = Environmate.configuration['static_environments'].keys
|
|
88
|
+
valid_links = envs_with_branches + static_envs
|
|
89
|
+
old_links = links.keys - valid_links
|
|
90
|
+
old_links.each do |old_link|
|
|
91
|
+
old_link_path = File.join(env_path, old_link)
|
|
92
|
+
Environmate.log.debug("Cleanup: Removing old link #{old_link_path}")
|
|
93
|
+
File.delete(old_link_path)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# cleanup all the old environments
|
|
98
|
+
def self.cleanup_environments
|
|
99
|
+
used_envs = links.values.uniq
|
|
100
|
+
environments.each do |env|
|
|
101
|
+
unless used_envs.include?(env.name)
|
|
102
|
+
Environmate.log.debug("Cleanup: Removing old environment #{env.name}")
|
|
103
|
+
env.delete
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# cleanup all orphan directories
|
|
109
|
+
def self.cleanup_orphan_dirs
|
|
110
|
+
orphan_dirs.each do |env|
|
|
111
|
+
Environmate.log.debug("Cleanup: Removing orphan dir #{env.name}")
|
|
112
|
+
env.delete
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
|
|
3
|
+
module Environmate
|
|
4
|
+
class GitRepository
|
|
5
|
+
include Command
|
|
6
|
+
|
|
7
|
+
def initialize(dir)
|
|
8
|
+
@dir = dir
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def submodule_update
|
|
12
|
+
#git('submodule update --init') if submodules_outdated?
|
|
13
|
+
git('submodule update --init')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fetch
|
|
17
|
+
git('fetch --prune')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset_hard(revision = 'HEAD')
|
|
21
|
+
git("reset --hard #{revision}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clean
|
|
25
|
+
git('clean -dff')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def checkout(revision)
|
|
29
|
+
git("checkout #{revision}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remote_branches
|
|
33
|
+
git('branch -r').each_line.map do |branch|
|
|
34
|
+
branch[/^\s+origin\/(\S+).*$/, 1]
|
|
35
|
+
end.compact
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clone(url)
|
|
39
|
+
command("git clone #{url} #{@dir}")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def valid?
|
|
43
|
+
status
|
|
44
|
+
true
|
|
45
|
+
rescue
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def git(cmd)
|
|
52
|
+
Dir.chdir(@dir) do
|
|
53
|
+
command("git #{cmd}")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def status
|
|
58
|
+
git('status')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def submodules_outdated?
|
|
62
|
+
!status.each_line.grep(/modified:.*\(new commits\)/).empty?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This class represents a user which triggered the hook.
|
|
3
|
+
#
|
|
4
|
+
require 'xmpp4r'
|
|
5
|
+
|
|
6
|
+
module Environmate
|
|
7
|
+
class User
|
|
8
|
+
|
|
9
|
+
def initialize(email = nil)
|
|
10
|
+
@email = email
|
|
11
|
+
@xmpp_client = Environmate::Xmpp.client
|
|
12
|
+
@xmpp_settings = Environmate.configuration['xmpp']
|
|
13
|
+
if @xmpp_settings && @xmpp_settings['users'].has_key?(@email)
|
|
14
|
+
@xmpp_user = @xmpp_settings['users'][@email]
|
|
15
|
+
Envionmate.log.info("Xmpp user found #{@xmpp_user}")
|
|
16
|
+
end
|
|
17
|
+
@response = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Send a message to the user
|
|
21
|
+
#
|
|
22
|
+
# * +severity+ - Message severity (log level)
|
|
23
|
+
# * +message+ - The message
|
|
24
|
+
def notify(severity, message)
|
|
25
|
+
Environmate.log.log(Logger.const_get(severity.to_s.upcase), message)
|
|
26
|
+
@response << [severity, message]
|
|
27
|
+
if @xmpp_client && @xmpp_user
|
|
28
|
+
xmpp_message = Jabber::Message.new(@xmpp_user, format(severity, message))
|
|
29
|
+
@xmpp_client.send(xmpp_message)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def format(severity, message)
|
|
34
|
+
"#{severity.to_s.upcase}: #{message}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get array of messages for the response
|
|
38
|
+
def response
|
|
39
|
+
@response
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|