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.
@@ -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,5 @@
1
+
2
+ module Environmate
3
+ class DeployError < StandardError
4
+ end
5
+ 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,16 @@
1
+ #
2
+ # Logger Helper
3
+ #
4
+ require 'logger'
5
+
6
+ module Environmate
7
+
8
+ def self.log
9
+ @log ||= Logger.new(STDOUT)
10
+ end
11
+
12
+ def self.logger=(logger)
13
+ @log = logger
14
+ end
15
+
16
+ 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
+