puppetfactory 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +0 -0
- data/bin/pfsh +31 -0
- data/bin/puppetfactory +153 -0
- data/lib/puppetfactory.rb +300 -0
- data/lib/puppetfactory/cli.rb +114 -0
- data/lib/puppetfactory/dashboard/rake_tasks.rb +69 -0
- data/lib/puppetfactory/dashboard/serverspec_helper.rb +84 -0
- data/lib/puppetfactory/dashboard/spec_helper.rb +26 -0
- data/lib/puppetfactory/helpers.rb +37 -0
- data/lib/puppetfactory/monkeypatches.rb +30 -0
- data/lib/puppetfactory/plugins.rb +11 -0
- data/lib/puppetfactory/plugins/certificates.rb +28 -0
- data/lib/puppetfactory/plugins/classification.rb +75 -0
- data/lib/puppetfactory/plugins/code_manager.rb +156 -0
- data/lib/puppetfactory/plugins/console_user.rb +62 -0
- data/lib/puppetfactory/plugins/dashboard.rb +128 -0
- data/lib/puppetfactory/plugins/docker.rb +193 -0
- data/lib/puppetfactory/plugins/example.rb +88 -0
- data/lib/puppetfactory/plugins/github.rb +102 -0
- data/lib/puppetfactory/plugins/gitlab.rb +62 -0
- data/lib/puppetfactory/plugins/hooks.rb +46 -0
- data/lib/puppetfactory/plugins/login_shell.rb +10 -0
- data/lib/puppetfactory/plugins/logs.rb +34 -0
- data/lib/puppetfactory/plugins/r10k.rb +112 -0
- data/lib/puppetfactory/plugins/shell_user.rb +69 -0
- data/lib/puppetfactory/plugins/user_environment.rb +77 -0
- data/public/dashboard.js +100 -0
- data/public/font-awesome/css/font-awesome.css +2199 -0
- data/public/font-awesome/css/font-awesome.min.css +4 -0
- data/public/font-awesome/fonts/FontAwesome.otf +0 -0
- data/public/font-awesome/fonts/fontawesome-webfont.eot +0 -0
- data/public/font-awesome/fonts/fontawesome-webfont.svg +685 -0
- data/public/font-awesome/fonts/fontawesome-webfont.ttf +0 -0
- data/public/font-awesome/fonts/fontawesome-webfont.woff +0 -0
- data/public/font-awesome/fonts/fontawesome-webfont.woff2 +0 -0
- data/public/gitviz/LICENSE.md +20 -0
- data/public/gitviz/README.md +13 -0
- data/public/gitviz/css/explaingit.css +227 -0
- data/public/gitviz/css/vendor/1140.css +130 -0
- data/public/gitviz/images/forkme_right_red_aa0000.png +0 -0
- data/public/gitviz/images/grippy-close.png +0 -0
- data/public/gitviz/images/grippy.png +0 -0
- data/public/gitviz/images/prompt.gif +0 -0
- data/public/gitviz/index.html +734 -0
- data/public/gitviz/js/controlbox.js +459 -0
- data/public/gitviz/js/explaingit.js +74 -0
- data/public/gitviz/js/historyview.js +979 -0
- data/public/gitviz/js/main.js +56 -0
- data/public/gitviz/js/vendor/d3.min.js +4 -0
- data/public/gitviz/js/vendor/jquery-latest.min.js +6 -0
- data/public/gitviz/js/vendor/normalize.css +396 -0
- data/public/gitviz/js/vendor/require.min.js +35 -0
- data/public/gitviz/memtest.html +44 -0
- data/public/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/public/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/public/images/ui-bg_flat_10_000000_40x100.png +0 -0
- data/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/public/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/public/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/public/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/public/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/public/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/public/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/public/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/public/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/public/images/ui-icons_222222_256x240.png +0 -0
- data/public/images/ui-icons_228ef1_256x240.png +0 -0
- data/public/images/ui-icons_454545_256x240.png +0 -0
- data/public/images/ui-icons_ef8c08_256x240.png +0 -0
- data/public/images/ui-icons_ffd27a_256x240.png +0 -0
- data/public/images/ui-icons_ffffff_256x240.png +0 -0
- data/public/jquery-1.11.1.min.js +4 -0
- data/public/jquery-ui.css +464 -0
- data/public/jquery-ui.min.css +7 -0
- data/public/jquery-ui.min.js +13 -0
- data/public/jquery-ui.structure.min.css +5 -0
- data/public/jquery-ui.theme.min.css +5 -0
- data/public/jquery.activity-indicator-1.0.0.min.js +10 -0
- data/public/jquery.js +9789 -0
- data/public/loginscripts.js +18 -0
- data/public/scripts.js +36 -0
- data/public/style.css +193 -0
- data/public/usermanagement.js +133 -0
- data/templates/init_scripts.erb +10 -0
- data/templates/puppet.conf.erb +10 -0
- data/templates/site.pp.erb +50 -0
- data/views/dashboard.erb +62 -0
- data/views/home.erb +43 -0
- data/views/index.erb +29 -0
- data/views/logs.erb +26 -0
- data/views/shell.erb +35 -0
- data/views/users.erb +69 -0
- metadata +256 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'puppetfactory'
|
2
|
+
|
3
|
+
# inherit from Puppetfactory::Plugins
|
4
|
+
class Puppetfactory::Plugins::Example < Puppetfactory::Plugins
|
5
|
+
attr_reader :weight
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
super(options) # call the superclass to initialize it
|
9
|
+
|
10
|
+
@weight = 1
|
11
|
+
@example = options[:example] || '/tmp/example'
|
12
|
+
end
|
13
|
+
|
14
|
+
# include one or more of the following methods. Any method you implement
|
15
|
+
# will be called when the corresponding task is invoked.
|
16
|
+
|
17
|
+
def create(username, password)
|
18
|
+
$logger.info "User #{username} created."
|
19
|
+
|
20
|
+
# Log an error with $logger.error
|
21
|
+
# fail user creation with a fatal error by raising an exception
|
22
|
+
|
23
|
+
# return true if our action succeeded
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(username)
|
28
|
+
$logger.info "User #{username} deleted."
|
29
|
+
|
30
|
+
# return true if our action succeeded
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def userinfo(username, extended = false)
|
35
|
+
# we can bail if we don't want to add to the basic user object.
|
36
|
+
# for example, if these are heavy operations.
|
37
|
+
return unless extended
|
38
|
+
|
39
|
+
# return a hash with the :username key
|
40
|
+
{
|
41
|
+
:username => username,
|
42
|
+
:example => username.upcase,
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def deploy(username)
|
47
|
+
environment = Puppetfactory::Helpers.environment_name(username)
|
48
|
+
$logger.info "Deployed environment #{environment} for #{username}"
|
49
|
+
|
50
|
+
# return true if our action succeeded
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def redeploy(username)
|
55
|
+
begin
|
56
|
+
if username == 'production'
|
57
|
+
raise "Can't redeploy production environment"
|
58
|
+
end
|
59
|
+
delete(username)
|
60
|
+
deploy(username)
|
61
|
+
|
62
|
+
rescue => e
|
63
|
+
raise "Error redeploying environment #{username}: #{e.message}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# return true if our action succeeded
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
# used by container plugins to rebuild them
|
71
|
+
def repair(username)
|
72
|
+
$logger.info "Container #{username} repaired"
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
# hook called when users log in. Only one can be active at any time.
|
77
|
+
def login
|
78
|
+
$logger.info 'Logging in with the default system shell'
|
79
|
+
exec('bash --login')
|
80
|
+
end
|
81
|
+
|
82
|
+
# returns an array of all user accounts. Only one can be active at any time.
|
83
|
+
def users
|
84
|
+
usernames = Dir.glob('/home/*').map { |path| File.basename path }
|
85
|
+
usernames.reject { |username| ['centos', 'training', 'showoff'].include? username }
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'puppetfactory'
|
3
|
+
|
4
|
+
class Puppetfactory::Plugins::Github < Puppetfactory::Plugins
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
super(options)
|
8
|
+
|
9
|
+
@gitserver = options[:gitserver]
|
10
|
+
@gituser = options[:gituser]
|
11
|
+
@controlrepo = options[:controlrepo]
|
12
|
+
@repomodel = options[:repomodel]
|
13
|
+
@githubtoken = options[:githubtoken]
|
14
|
+
|
15
|
+
# chomp so we can support repo names with or without the .git
|
16
|
+
@controlrepo.chomp!('.git')
|
17
|
+
|
18
|
+
if @githubtoken
|
19
|
+
@client = Octokit::Client.new(:access_token => @githubtoken)
|
20
|
+
@client.user.login
|
21
|
+
else
|
22
|
+
@client = Octokit::Client.new()
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def create(username, password)
|
27
|
+
# can only do anything on our own repo, and only if we're authorized!
|
28
|
+
return true unless @githubtoken and @repomodel == :single
|
29
|
+
|
30
|
+
begin
|
31
|
+
# can only do anything on our own repo!
|
32
|
+
repo = "#{@gituser}/#{@controlrepo}"
|
33
|
+
sha = @client.branches(repo).select { |branch| branch[:name] == 'production' }.first[:commit][:sha]
|
34
|
+
@client.create_ref(repo, "heads/#{username}", sha)
|
35
|
+
$logger.info "Created Github user branch for #{username}"
|
36
|
+
|
37
|
+
@client.add_collaborator(repo, username)
|
38
|
+
$logger.info "Added #{username} as a collaborator to #{repo}."
|
39
|
+
|
40
|
+
rescue => e
|
41
|
+
$logger.error "Error creating Github user branch for #{username}"
|
42
|
+
$logger.error e.message
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete(username)
|
50
|
+
# can only do anything on our own repo, and only if we're authorized!
|
51
|
+
return true unless @githubtoken and @repomodel == :single
|
52
|
+
|
53
|
+
begin
|
54
|
+
@client.delete_branch("#{@gituser}/#{@controlrepo}", username)
|
55
|
+
$logger.info "Deleted Github user branch for #{username}"
|
56
|
+
|
57
|
+
@client.remove_collaborator(repo, username)
|
58
|
+
$logger.info "Removed #{username} as a collaborator on #{repo}."
|
59
|
+
|
60
|
+
rescue => e
|
61
|
+
$logger.error "Error deleting Github user branch for #{username}"
|
62
|
+
$logger.error e.message
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def userinfo(username, extended = false)
|
70
|
+
if @repomodel == :single
|
71
|
+
repo = "#{@gituser}/#{@controlrepo}"
|
72
|
+
url = "#{@gitserver}/#{@gituser}/#{@controlrepo}/tree/#{username}"
|
73
|
+
else
|
74
|
+
repo = "#{@username}/#{@controlrepo}"
|
75
|
+
url = "#{@gitserver}/#{username}/#{@controlrepo}"
|
76
|
+
end
|
77
|
+
|
78
|
+
userinfo = {
|
79
|
+
:username => username,
|
80
|
+
:controlrepo => url,
|
81
|
+
}
|
82
|
+
userinfo[:latestcommit] = latest_commit(repo, username) if extended
|
83
|
+
userinfo
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def latest_commit(repo, username)
|
88
|
+
begin
|
89
|
+
commit = @client.commits(repo, :author => username).first
|
90
|
+
return if commit.nil?
|
91
|
+
|
92
|
+
{
|
93
|
+
:url => commit[:html_url],
|
94
|
+
:message => commit[:commit][:message].trim(62),
|
95
|
+
:time => Puppetfactory::Helpers.approximate_time_difference(commit[:commit][:author][:date]),
|
96
|
+
}
|
97
|
+
rescue => e
|
98
|
+
$logger.error "Cannot get commits for #{repo}."
|
99
|
+
$logger.error e.message
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'puppetfactory'
|
3
|
+
|
4
|
+
class Puppetfactory::Plugins::Gitlab < Puppetfactory::Plugins
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
super(options)
|
8
|
+
|
9
|
+
@suffix = options[:usersuffix]
|
10
|
+
|
11
|
+
begin
|
12
|
+
# Use default gitlab root password to get session token
|
13
|
+
login = {:login => 'root', :password => '5iveL!fe'}
|
14
|
+
resp = JSON.parse(RestClient.post('http://localhost:8888/api/v3/session', login))
|
15
|
+
@token = resp['private_token']
|
16
|
+
rescue => e
|
17
|
+
raise "GitLab authentication error! (#{e.message})"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(username, password)
|
22
|
+
begin
|
23
|
+
if password.length < 8
|
24
|
+
raise "Password must be at least 8 characters"
|
25
|
+
end
|
26
|
+
|
27
|
+
RestClient.post('http://localhost:8888/api/v3/users',
|
28
|
+
{
|
29
|
+
:email => "#{username}.#{@suffix}",
|
30
|
+
:password => password,
|
31
|
+
:username => username,
|
32
|
+
:name => username,
|
33
|
+
:confirm => false,
|
34
|
+
:private_token => @token # TODO: this invocation does not look like the invocation below?
|
35
|
+
})
|
36
|
+
end
|
37
|
+
|
38
|
+
$logger.info "Created GitLab user #{username}."
|
39
|
+
rescue => e
|
40
|
+
$logger.error "Error creating GitLab user #{username}: #{e.message}"
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete(username)
|
48
|
+
begin
|
49
|
+
users = JSON.parse(RestClient.get('http://localhost:8888/api/v3/users', {"PRIVATE-TOKEN" => @token}))
|
50
|
+
userid = users.select { |record| record['username'] == username }['id']
|
51
|
+
RestClient.delete("http://localhost:8888/api/v3/users/#{userid}" , {"PRIVATE-TOKEN" => @token})
|
52
|
+
|
53
|
+
$logger.info "Removed GitLab user #{username}."
|
54
|
+
rescue => e
|
55
|
+
$logger.error "Error removing GitLab user #{username}: #{e.message}"
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'puppetfactory'
|
3
|
+
|
4
|
+
class Puppetfactory::Plugins::Hooks < Puppetfactory::Plugins
|
5
|
+
attr_reader :weight
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
super(options)
|
9
|
+
|
10
|
+
@weight = 1
|
11
|
+
@path = options[:hooks_path] || '/etc/puppetfactory/hooks'
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(username)
|
15
|
+
call_hooks(:create, username)
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(username)
|
19
|
+
call_hooks(:delete, username)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def call_hooks(hook_type, username)
|
24
|
+
success = true
|
25
|
+
# the .to_s allows us to accept strings or symbols
|
26
|
+
Dir.glob("#{HOOKS_PATH}/#{hook_type.to_s}/*") do |hook|
|
27
|
+
next unless File.file?(hook)
|
28
|
+
next unless File.executable?(hook)
|
29
|
+
|
30
|
+
begin
|
31
|
+
output, status = Open3.capture2e(hook, username)
|
32
|
+
raise "Execution error: #{output}" unless status.success?
|
33
|
+
$logger.info output
|
34
|
+
|
35
|
+
rescue => e
|
36
|
+
$logger.error "Error running hook: #{hook}"
|
37
|
+
$logger.error e.message
|
38
|
+
success = false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# only true if all hooks succeeded.
|
43
|
+
success
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'puppetfactory'
|
2
|
+
|
3
|
+
class Puppetfactory::Plugins::Logs < Puppetfactory::Plugins
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
super(options)
|
7
|
+
return unless options[:puppetfactory]
|
8
|
+
|
9
|
+
@logfile = options[:logfile]
|
10
|
+
server = options[:puppetfactory]
|
11
|
+
|
12
|
+
# Add a web route to the puppetfactory server. Must happen in the initializer
|
13
|
+
server.get '/logs' do
|
14
|
+
protected!
|
15
|
+
erb :logs
|
16
|
+
end
|
17
|
+
|
18
|
+
server.get '/logs/data' do
|
19
|
+
protected!
|
20
|
+
plugin(:Logs, :data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def tabs(privileged = false)
|
25
|
+
return unless privileged # only show this tab to admin users
|
26
|
+
|
27
|
+
# url path => display title
|
28
|
+
{ 'logs' => 'Logs' }
|
29
|
+
end
|
30
|
+
|
31
|
+
def data
|
32
|
+
File.read(@logfile) rescue "Cannot read logfile #{@logfile}"
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'puppetfactory'
|
2
|
+
|
3
|
+
class Puppetfactory::Plugins::R10k < Puppetfactory::Plugins
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
super(options)
|
7
|
+
|
8
|
+
@gitserver = options[:gitserver]
|
9
|
+
@repomodel = options[:repomodel]
|
10
|
+
@controlrepo = options[:controlrepo]
|
11
|
+
@environments = options[:environments]
|
12
|
+
@r10k_config = '/etc/puppetlabs/r10k/r10k.yaml'
|
13
|
+
@pattern = "#{@gitserver}/%s/#{@controlrepo}"
|
14
|
+
|
15
|
+
# the rest of this method is for the big boys only
|
16
|
+
return unless Process.euid == 0
|
17
|
+
|
18
|
+
# in case this is the first run and it doesn't exist yet
|
19
|
+
unless File.exist? @r10k_config
|
20
|
+
File.open(@r10k_config, 'w') { |f| f.write({'sources' => {}}.to_yaml) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create(username, password)
|
25
|
+
begin
|
26
|
+
environment = "#{@environments}/#{Puppetfactory::Helpers.environment_name(username)}"
|
27
|
+
FileUtils.mkdir_p environment
|
28
|
+
FileUtils.chown_R(username, 'pe-puppet', environment)
|
29
|
+
FileUtils.chmod(0750, environment)
|
30
|
+
|
31
|
+
File.open(@r10k_config) do |file|
|
32
|
+
# make sure we don't have any concurrency issues
|
33
|
+
file.flock(File::LOCK_EX)
|
34
|
+
|
35
|
+
r10k = YAML.load_file(@r10k_config)
|
36
|
+
r10k['sources'][username] = {
|
37
|
+
'remote' => sprintf(@pattern, username),
|
38
|
+
'basedir' => @environments,
|
39
|
+
'prefix' => (@repomodel == :peruser),
|
40
|
+
}
|
41
|
+
|
42
|
+
# Ruby 1.8.7, why don't you just go away now
|
43
|
+
File.open(@r10k_config, 'w') { |f| f.write(r10k.to_yaml) }
|
44
|
+
$logger.info "Created r10k source for #{username}"
|
45
|
+
end
|
46
|
+
rescue => e
|
47
|
+
$logger.error "Cannot create r10k source for #{username}"
|
48
|
+
$logger.error e.backtrace
|
49
|
+
return false
|
50
|
+
end
|
51
|
+
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete(username)
|
56
|
+
begin
|
57
|
+
environment = "#{@environments}/#{Puppetfactory::Helpers.environment_name(username)}"
|
58
|
+
FileUtils.rm_rf environment
|
59
|
+
|
60
|
+
# also delete any prefixed environments. Is this even a good idea?
|
61
|
+
FileUtils.rm_rf "#{@environments}/#{username}_*" if @repomodel == :peruser
|
62
|
+
|
63
|
+
File.open(@r10k_config) do |file|
|
64
|
+
# make sure we don't have any concurrency issues
|
65
|
+
file.flock(File::LOCK_EX)
|
66
|
+
|
67
|
+
r10k = YAML.load_file(@r10k_config)
|
68
|
+
r10k['sources'].delete username
|
69
|
+
|
70
|
+
# Ruby 1.8.7, why don't you just go away now
|
71
|
+
File.open(@r10k_config, 'w') { |f| f.write(r10k.to_yaml) }
|
72
|
+
$logger.info "Created r10k source for #{username}"
|
73
|
+
end
|
74
|
+
rescue => e
|
75
|
+
$logger.error "Cannot remove r10k source for #{username}"
|
76
|
+
$logger.error e.backtrace
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def deploy(username)
|
84
|
+
environment = Puppetfactory::Helpers.environment_name(username)
|
85
|
+
|
86
|
+
output, status = Open3.capture2e('r10k', 'deploy', 'environment', environment)
|
87
|
+
unless status.success?
|
88
|
+
$logger.error "Failed to deploy environment #{environment} for #{username}"
|
89
|
+
$logger.error output
|
90
|
+
return false
|
91
|
+
end
|
92
|
+
|
93
|
+
$logger.info "Deployed environment #{environment} for #{username}"
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
def redeploy(username)
|
98
|
+
begin
|
99
|
+
if username == 'production'
|
100
|
+
raise "Can't redeploy production environment"
|
101
|
+
end
|
102
|
+
delete(username)
|
103
|
+
deploy(username)
|
104
|
+
|
105
|
+
rescue => e
|
106
|
+
raise "Error redeploying environment #{username}: #{e.message}"
|
107
|
+
end
|
108
|
+
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|