slugforge 4.0.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/README.md +316 -0
- data/bin/slugforge +9 -0
- data/lib/slugforge.rb +19 -0
- data/lib/slugforge/build.rb +4 -0
- data/lib/slugforge/build/build_project.rb +31 -0
- data/lib/slugforge/build/export_upstart.rb +85 -0
- data/lib/slugforge/build/package.rb +63 -0
- data/lib/slugforge/cli.rb +125 -0
- data/lib/slugforge/commands.rb +130 -0
- data/lib/slugforge/commands/build.rb +20 -0
- data/lib/slugforge/commands/config.rb +24 -0
- data/lib/slugforge/commands/deploy.rb +383 -0
- data/lib/slugforge/commands/project.rb +21 -0
- data/lib/slugforge/commands/tag.rb +148 -0
- data/lib/slugforge/commands/wrangler.rb +142 -0
- data/lib/slugforge/configuration.rb +125 -0
- data/lib/slugforge/helper.rb +186 -0
- data/lib/slugforge/helper/build.rb +46 -0
- data/lib/slugforge/helper/config.rb +37 -0
- data/lib/slugforge/helper/enumerable.rb +46 -0
- data/lib/slugforge/helper/fog.rb +90 -0
- data/lib/slugforge/helper/git.rb +89 -0
- data/lib/slugforge/helper/path.rb +76 -0
- data/lib/slugforge/helper/project.rb +86 -0
- data/lib/slugforge/models/host.rb +233 -0
- data/lib/slugforge/models/host/fog_host.rb +33 -0
- data/lib/slugforge/models/host/hostname_host.rb +9 -0
- data/lib/slugforge/models/host/ip_address_host.rb +9 -0
- data/lib/slugforge/models/host_group.rb +65 -0
- data/lib/slugforge/models/host_group/aws_tag_group.rb +22 -0
- data/lib/slugforge/models/host_group/ec2_instance_group.rb +21 -0
- data/lib/slugforge/models/host_group/hostname_group.rb +16 -0
- data/lib/slugforge/models/host_group/ip_address_group.rb +16 -0
- data/lib/slugforge/models/host_group/security_group_group.rb +20 -0
- data/lib/slugforge/models/logger.rb +36 -0
- data/lib/slugforge/models/tag_manager.rb +125 -0
- data/lib/slugforge/slugins.rb +125 -0
- data/lib/slugforge/version.rb +9 -0
- data/scripts/post-install.sh +143 -0
- data/scripts/unicorn-shepherd.sh +305 -0
- data/spec/fixtures/array.yaml +3 -0
- data/spec/fixtures/fog_credentials.yaml +4 -0
- data/spec/fixtures/invalid_syntax.yaml +1 -0
- data/spec/fixtures/one.yaml +3 -0
- data/spec/fixtures/two.yaml +3 -0
- data/spec/fixtures/valid.yaml +4 -0
- data/spec/slugforge/commands/deploy_spec.rb +72 -0
- data/spec/slugforge/commands_spec.rb +33 -0
- data/spec/slugforge/configuration_spec.rb +200 -0
- data/spec/slugforge/helper/fog_spec.rb +81 -0
- data/spec/slugforge/helper/git_spec.rb +152 -0
- data/spec/slugforge/models/host_group/aws_tag_group_spec.rb +54 -0
- data/spec/slugforge/models/host_group/ec2_instance_group_spec.rb +51 -0
- data/spec/slugforge/models/host_group/hostname_group_spec.rb +20 -0
- data/spec/slugforge/models/host_group/ip_address_group_spec.rb +54 -0
- data/spec/slugforge/models/host_group/security_group_group_spec.rb +52 -0
- data/spec/slugforge/models/tag_manager_spec.rb +75 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/env.rb +3 -0
- data/spec/support/example_groups/configuration_writer.rb +24 -0
- data/spec/support/example_groups/helper_provider.rb +10 -0
- data/spec/support/factories.rb +18 -0
- data/spec/support/fog.rb +15 -0
- data/spec/support/helpers.rb +18 -0
- data/spec/support/mock_logger.rb +6 -0
- data/spec/support/ssh.rb +8 -0
- data/spec/support/streams.rb +13 -0
- data/templates/foreman/master.conf.erb +21 -0
- data/templates/foreman/process-master.conf.erb +2 -0
- data/templates/foreman/process.conf.erb +19 -0
- metadata +344 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'slugforge/models/tag_manager'
|
2
|
+
|
3
|
+
module Slugforge
|
4
|
+
module Commands
|
5
|
+
class Project < Slugforge::SubCommand
|
6
|
+
desc 'list', 'list available projects'
|
7
|
+
def list
|
8
|
+
logger.say "Available projects:"
|
9
|
+
|
10
|
+
tag_manager.projects.map do |project|
|
11
|
+
out = [set_color(project, :green)]
|
12
|
+
pc = tag_manager.slug_for_tag(project, 'production-current')
|
13
|
+
out << "(production-current: #{set_color(pc, :yellow)})" unless pc.nil?
|
14
|
+
out
|
15
|
+
end.sort.each do |o|
|
16
|
+
logger.say " #{o * ' '}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Slugforge
|
2
|
+
module Commands
|
3
|
+
class Tag < Slugforge::SubCommand
|
4
|
+
desc 'clean [ARGS]', 'remove tags that point to missing slugs (excluding production-current)'
|
5
|
+
def clean
|
6
|
+
verify_project_name!
|
7
|
+
|
8
|
+
tags = tag_manager.tags(project_name)
|
9
|
+
tags.delete('production-current')
|
10
|
+
|
11
|
+
bucket # initialize value before using threads
|
12
|
+
logger.say_status :clean, "Reviewing #{tags.count} tags for #{project_name}", :cyan
|
13
|
+
results = tags.parallel_map do |tag|
|
14
|
+
begin
|
15
|
+
slug_file = tag_manager.slug_for_tag(project_name, tag)
|
16
|
+
if !slug_file.nil? && bucket.files.head(slug_file)
|
17
|
+
logger.say '.', :green, false
|
18
|
+
[tag, :valid]
|
19
|
+
else
|
20
|
+
if pretend?
|
21
|
+
[tag, :pretend]
|
22
|
+
else
|
23
|
+
tag_manager.delete_tag(project_name, tag)
|
24
|
+
logger.say '.', :red, false
|
25
|
+
[tag, :deleted]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
rescue Excon::Errors::Forbidden
|
29
|
+
logger.say '.', :yellow, false
|
30
|
+
[tag, :forbidden]
|
31
|
+
end
|
32
|
+
end.sort {|a,b| b[1].to_s <=> a[1].to_s}
|
33
|
+
|
34
|
+
logger.say
|
35
|
+
results.each do |result|
|
36
|
+
tag, status = result
|
37
|
+
next if status == :valid
|
38
|
+
logger.say_status status, tag, (status == :deleted) ? :red : :yellow
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'clone <tag> <new_tag> [ARGS]', 'create a new tag with the same slug as an existing tag'
|
43
|
+
def clone(tag, new_tag)
|
44
|
+
verify_project_name!
|
45
|
+
|
46
|
+
slug_name = tag_manager.slug_for_tag(project_name, tag)
|
47
|
+
unless slug_name.nil?
|
48
|
+
tag_manager.clone_tag(project_name, tag, new_tag)
|
49
|
+
logger.say_json :project => project_name, :tag => new_tag, :slug => slug_name
|
50
|
+
logger.say_status :set, "#{project_name} #{new_tag} to Slug #{slug_name}"
|
51
|
+
true
|
52
|
+
else
|
53
|
+
logger.say_json :tag => tag, :exists => false
|
54
|
+
logger.say_status :clone, "could not find existing tag #{tag} for project '#{project_name}'", :red
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'history <tag> [ARGS]', 'show history of a project\'s tag'
|
60
|
+
def history(tag)
|
61
|
+
verify_project_name!
|
62
|
+
|
63
|
+
slug_names = tag_manager.slugs_for_tag(project_name, tag)
|
64
|
+
unless slug_names.empty?
|
65
|
+
logger.say_json :project => project_name, :tag => tag, :slug_names => slug_names, :exists => true
|
66
|
+
slug_names.each.with_index(0) do |slug_name, index|
|
67
|
+
logger.say_status (index == 0 ? 'current' : "-#{index}"), slug_name, :yellow
|
68
|
+
end
|
69
|
+
else
|
70
|
+
logger.say_json :tag => tag, :exists => false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc 'list [ARGS]', 'list a project\'s tags'
|
75
|
+
def list
|
76
|
+
verify_project_name!
|
77
|
+
|
78
|
+
tags = tag_manager.tags(project_name)
|
79
|
+
pc = tags.delete('production-current')
|
80
|
+
|
81
|
+
if json?
|
82
|
+
logger.say_json tags
|
83
|
+
else
|
84
|
+
logger.say "Tags for #{project_name}"
|
85
|
+
logger.say_status :'production-current', tag_manager.slug_for_tag(project_name, 'production-current') unless pc.nil?
|
86
|
+
|
87
|
+
tag_list = tags.parallel_map do |tag|
|
88
|
+
[tag, tag_manager.slug_for_tag(project_name, tag)]
|
89
|
+
end
|
90
|
+
tag_list.sort {|a,b| b[1].to_s<=>a[1].to_s }.each do |tag, slug|
|
91
|
+
logger.say_status tag, slug, :yellow
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
desc 'migrate', 'migrate tags to new format', :hide => true
|
97
|
+
def migrate
|
98
|
+
metadata = JSON.parse(bucket.files.get('projects.json').body)
|
99
|
+
metadata.each do |project, data|
|
100
|
+
data['tags'].each do |tag, value|
|
101
|
+
puts "create_tag(#{project}, #{tag}, #{value['s3']})"
|
102
|
+
tag_manager.create_tag(project, tag, value['s3'])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
desc 'show <tag> [ARGS]', 'show value of a project\'s tag'
|
108
|
+
def show(tag)
|
109
|
+
verify_project_name!
|
110
|
+
|
111
|
+
slug_name = tag_manager.slug_for_tag(project_name, tag)
|
112
|
+
unless slug_name.nil?
|
113
|
+
exists = !bucket.files.head(slug_name).nil?
|
114
|
+
logger.say_json :project => project_name, :tag => tag, :slug_name => slug_name, :exists => exists
|
115
|
+
logger.say_status tag, "#{slug_name} (#{exists ? "exists" : "missing"})", :yellow
|
116
|
+
else
|
117
|
+
logger.say_json :tag => tag, :exists => false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
desc 'set <tag> <name_part>', 'update a tag to point to a slug in s3'
|
122
|
+
def set(tag, name_part)
|
123
|
+
verify_project_name!
|
124
|
+
|
125
|
+
slug = find_slug(name_part)
|
126
|
+
|
127
|
+
tag_manager.create_tag(project_name, tag, slug.key)
|
128
|
+
logger.say_json :project => project_name, :tag => tag, :slug => slug.key
|
129
|
+
logger.say_status :set, "#{project_name} #{tag} to Slug #{slug.key}"
|
130
|
+
end
|
131
|
+
|
132
|
+
desc 'delete <tag> [ARGS]', 'delete a tag'
|
133
|
+
option :yes, :type => :boolean, :aliases => '-y', :default => false,
|
134
|
+
:desc => 'answer "yes" to all questions'
|
135
|
+
def delete(tag)
|
136
|
+
verify_project_name!
|
137
|
+
|
138
|
+
if options[:yes] || (ask("Are you sure you wish to delete tag '#{tag}'? [Yn]").downcase != 'n')
|
139
|
+
tag_manager.delete_tag(project_name, tag)
|
140
|
+
logger.say_status :delete, "#{project_name} #{tag}"
|
141
|
+
else
|
142
|
+
logger.say_status :keep, "#{project_name} #{tag}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Slugforge
|
2
|
+
module Commands
|
3
|
+
class Wrangler < Slugforge::SubCommand
|
4
|
+
|
5
|
+
desc 'push <file>', 'push a slug to S3'
|
6
|
+
option :tag, :type => :string, :aliases => '-t',
|
7
|
+
:desc => 'once pushed tag the slug with this tag.'
|
8
|
+
def push(file)
|
9
|
+
verify_project_name!
|
10
|
+
unless File.exist?(file)
|
11
|
+
raise error_class, "file does not exist"
|
12
|
+
end
|
13
|
+
|
14
|
+
dest = "#{project_name}/#{file}"
|
15
|
+
logger.say_status :upload, "slug #{file} to #{project_name}", :yellow
|
16
|
+
s3.put_object(aws_bucket, dest, File.read(file))
|
17
|
+
logger.say_status :uploaded, "slug saved"
|
18
|
+
|
19
|
+
if options[:tag]
|
20
|
+
logger.say_status :build, "applying tag '#{options[:tag]}' to your fancy new build", :green
|
21
|
+
invoke Slugforge::Commands::Tag, [:set, options[:tag], dest], []
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'pull [name_part]', 'pull a slug from S3 (most recent if no name part is specified)'
|
26
|
+
def pull(name_part)
|
27
|
+
verify_project_name!
|
28
|
+
|
29
|
+
slug = name_part ? find_slug(name_part) : find_latest_slug
|
30
|
+
|
31
|
+
logger.say_status :fetch, "#{slug.attributes[:name]} (#{slug.attributes[:pretty_length]})", :yellow
|
32
|
+
logger.say "Note: process will block until download completes."
|
33
|
+
|
34
|
+
# This should block until the body is downloaded.
|
35
|
+
# We open the file for writing afterwards to prevent creating empty files.
|
36
|
+
slug.body
|
37
|
+
File.open(slug.attributes[:name], 'w+') { |f| f.write(slug.body) }
|
38
|
+
|
39
|
+
logger.say_status :fetched, "pull complete, saved to #{slug.attributes[:name]}"
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'list [ARGS]', 'list published slugs for a project'
|
43
|
+
option :count, :type => :numeric, :aliases => '-c', :default => 10,
|
44
|
+
:desc => 'how many slugs to list'
|
45
|
+
option :sort, :type => :string, :aliases => '-s', :default => 'last_modified:desc',
|
46
|
+
:desc => 'change the sorting option (field:dir)'
|
47
|
+
option :all, :type => :boolean, :aliases => '-a', :default => false,
|
48
|
+
:desc => 'list all slugs'
|
49
|
+
def list
|
50
|
+
raise error_class, "count must be greater than 0" if !options[:all] && options[:count] <= 0
|
51
|
+
|
52
|
+
begin
|
53
|
+
project_name = self.project_name
|
54
|
+
rescue Thor::Error
|
55
|
+
# This is the only case where we don't care if project is not found.
|
56
|
+
end
|
57
|
+
|
58
|
+
slugs = (project_name.nil? ? self.files : self.slugs(project_name)).values
|
59
|
+
raise error_class, "No slugs found for #{project_name}" if slugs.first.nil?
|
60
|
+
|
61
|
+
total = slugs.length
|
62
|
+
|
63
|
+
sorting = options[:sort].split(':')
|
64
|
+
field = sorting.first.to_sym
|
65
|
+
direction = (sorting.last || 'desc')
|
66
|
+
|
67
|
+
unless slugs.first.class.attributes.include?(field)
|
68
|
+
raise error_class, "unknown attribute for sorting: #{field}. Available fields: #{slugs.first.class.attributes * ', '}"
|
69
|
+
end
|
70
|
+
|
71
|
+
slugs = slugs.sort_by { |f| f.attributes[field] }
|
72
|
+
slugs = slugs.reverse! if direction != 'asc'
|
73
|
+
slugs = slugs.slice(0...options[:count]) unless options[:all]
|
74
|
+
|
75
|
+
logger.say "Slugs for #{project_name} (#{slugs.size}", nil, false
|
76
|
+
logger.say " of #{total}", nil, false unless options[:all]
|
77
|
+
logger.say ")"
|
78
|
+
|
79
|
+
tag_manager.memoize_slugs_for_tags(project_name)
|
80
|
+
slugs.each do |slug|
|
81
|
+
tags = tag_manager.tags_for_slug(project_name, slug.key)
|
82
|
+
logger.say " #{tags.size > 0 ? ' (' + tags.join(', ') + ')' : ''} ", :yellow
|
83
|
+
logger.say "#{slug.attributes[:name]} ", :green
|
84
|
+
logger.say "- "
|
85
|
+
logger.say "#{slug.attributes[:pretty_age]} ago ", :cyan
|
86
|
+
logger.say "(#{set_color(slug.attributes[:pretty_length], :magenta)})"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
desc 'delete <name_part>', 'delete a slug'
|
91
|
+
option :yes, :type => :boolean, :aliases => '-y', :default => false,
|
92
|
+
:desc => 'answer "yes" to all questions'
|
93
|
+
def delete(name_part)
|
94
|
+
slug = find_slug(name_part)
|
95
|
+
if options[:yes] || (ask("Are you sure you wish to delete '#{slug.attributes[:name]}'? [yN]").downcase == 'y')
|
96
|
+
slug.destroy
|
97
|
+
logger.say_status :destroy, slug.key, :red
|
98
|
+
else
|
99
|
+
logger.say_status :keep, slug.key, :green
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
desc 'purge', 'purge slugs for a project'
|
104
|
+
option :yes, :type => :boolean, :aliases => '-y', :default => false,
|
105
|
+
:desc => 'answer "yes" to all questions'
|
106
|
+
option :keep, :type => :numeric, :aliases => '-k', :default => 10,
|
107
|
+
:desc => 'number of slugs to keep'
|
108
|
+
option :all, :type => :boolean, :aliases => '-a', :default => false,
|
109
|
+
:desc => 'purge all slugs'
|
110
|
+
def purge
|
111
|
+
raise error_class, "keep must be greater than 0" if !options[:all] && options[:keep] <= 0
|
112
|
+
|
113
|
+
slugs = self.slugs(project_name).values
|
114
|
+
|
115
|
+
if options[:all]
|
116
|
+
if !options[:yes] && ask("Are you sure you wish to delete #{slugs.size} slugs for #{project_name}? [yN]").downcase == 'y'
|
117
|
+
options[:yes] = true
|
118
|
+
end
|
119
|
+
|
120
|
+
if options[:yes]
|
121
|
+
slugs.each do |slug|
|
122
|
+
slug.destroy
|
123
|
+
logger.say_status :destroy, slug.key, :red
|
124
|
+
end
|
125
|
+
end
|
126
|
+
elsif slugs.size > options[:keep]
|
127
|
+
slugs.slice(options[:keep]..-1).each do |slug|
|
128
|
+
if options[:yes] || (ask("Are you sure you wish to delete #{slug.attributes[:name]}? [Yn]").downcase != 'n')
|
129
|
+
slug.destroy
|
130
|
+
logger.say_status :destroy, slug.key, :red
|
131
|
+
else
|
132
|
+
logger.say_status :keep, slug.key, :green
|
133
|
+
end
|
134
|
+
end
|
135
|
+
else
|
136
|
+
logger.say "Aborting, only #{slugs.size} slugs for #{project_name} and we want to keep #{options[:keep]}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'slugforge/slugins'
|
4
|
+
|
5
|
+
module Slugforge
|
6
|
+
# Handles loading configuration data from files and the environment. Order of precedence:
|
7
|
+
#
|
8
|
+
# 1) ENV
|
9
|
+
# 2) `pwd`/.slugforge
|
10
|
+
# 3) $HOME/.slugforge
|
11
|
+
# 4) /etc/slugforge
|
12
|
+
#
|
13
|
+
# We load in reverse order, allowing us to simply overwrite values whenever found.
|
14
|
+
class Configuration
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :configuration_files
|
19
|
+
end
|
20
|
+
self.configuration_files = [ '/etc/slugforge', File.join(ENV['HOME'], '.slugforge'), File.join(Dir.pwd, '.slugforge') ]
|
21
|
+
|
22
|
+
class <<self
|
23
|
+
def options
|
24
|
+
@options ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def option(name, config)
|
28
|
+
raise "configuration option #{name} has already been defined" if options.key?(name)
|
29
|
+
|
30
|
+
options[name] = config
|
31
|
+
define_method(name) { values[name] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
option :aws_access_key, :key => 'aws.access_key', :option => :'aws-access-key-id', :env => 'AWS_ACCESS_KEY_ID'
|
36
|
+
option :aws_secret_key, :key => 'aws.secret_key', :option => :'aws-secret-key', :env => 'AWS_SECRET_ACCESS_KEY'
|
37
|
+
option :aws_region, :key => 'aws.region', :option => :'aws-region', :env => 'AWS_DEFAULT_REGION', :default => 'us-east-1'
|
38
|
+
option :slug_bucket, :key => 'aws.slug_bucket', :option => :'slug-bucket', :env => 'SLUG_BUCKET'
|
39
|
+
option :aws_session_token, :option => :'aws-session-token'
|
40
|
+
|
41
|
+
option :project, :key => 'slugforge.project', :option => :project, :env => 'SLUGFORGE_PROJECT'
|
42
|
+
|
43
|
+
option :ssh_username, :key => 'ssh.username', :option => :'ssh-username', :env => 'SSH_USERNAME'
|
44
|
+
|
45
|
+
option :disable_slugins, :key => 'disable_slugins', :option => :'disable-slugins', :env => 'DISABLE_SLUGINS'
|
46
|
+
|
47
|
+
attr_reader :values
|
48
|
+
|
49
|
+
def initialize(options = {})
|
50
|
+
@slugin_manager = SluginManager.new
|
51
|
+
self.load
|
52
|
+
update_from_options options
|
53
|
+
end
|
54
|
+
|
55
|
+
def_delegators :@slugin_manager, :load_slugins, :locate_slugins, :slugins
|
56
|
+
|
57
|
+
# Get a hash of all options with default values. The list of values is initialized with the result.
|
58
|
+
def defaults
|
59
|
+
@values = Hash[self.class.options.select { |_, c| c.key?(:default) }.map { |n,c| [n, c[:default]] }].merge(@values)
|
60
|
+
end
|
61
|
+
|
62
|
+
def activate_slugins
|
63
|
+
@slugin_manager.activate_slugins(self) unless disable_slugins
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
def load
|
68
|
+
# Read configuration files to load list of slugins. Load the slugin classes so that
|
69
|
+
# their configuration options are added and reload the configs to populate the new
|
70
|
+
# options.
|
71
|
+
@values = {}
|
72
|
+
load_configuration_files
|
73
|
+
defaults
|
74
|
+
|
75
|
+
locate_slugins
|
76
|
+
#TODO: disable individual slugins via configuration
|
77
|
+
load_slugins unless disable_slugins
|
78
|
+
|
79
|
+
load_configuration_files
|
80
|
+
read_env
|
81
|
+
end
|
82
|
+
|
83
|
+
def load_configuration_files
|
84
|
+
self.class.configuration_files.each { |f| read_yaml f }
|
85
|
+
end
|
86
|
+
|
87
|
+
# Attempt to read option keys from a YAML file
|
88
|
+
def read_yaml(path)
|
89
|
+
return unless File.exist?(path)
|
90
|
+
source = YAML.load_file(path)
|
91
|
+
return unless source.is_a?(Hash)
|
92
|
+
|
93
|
+
update_with { |config| read_yaml_key(source, config[:key]) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Split a dot-separated key and locate the value from a hash loaded by YAML.
|
97
|
+
# eg. `aws.bucket` looks for `source['aws']['bucket']`.
|
98
|
+
def read_yaml_key(source, key)
|
99
|
+
return unless key.is_a?(String)
|
100
|
+
paths = key.split('.')
|
101
|
+
source = source[paths.shift] until paths.empty? || source.nil?
|
102
|
+
source
|
103
|
+
end
|
104
|
+
|
105
|
+
# Attempt to read option keys from the environment
|
106
|
+
def read_env
|
107
|
+
update_with { |config| config[:env] && ENV[config[:env]] }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Update values with a hash of options.
|
111
|
+
def update_from_options(options={})
|
112
|
+
update_with { |config| config[:option] && options[config[:option]] }
|
113
|
+
end
|
114
|
+
|
115
|
+
# For every option we yield the configuration and expect a value back. If the block returns a value we set the
|
116
|
+
# option to it.
|
117
|
+
def update_with(&blk)
|
118
|
+
self.class.options.each do |name, config|
|
119
|
+
value = yield(config)
|
120
|
+
@values[name] = value unless value.nil?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'slugforge/helper/build'
|
2
|
+
require 'slugforge/helper/config'
|
3
|
+
require 'slugforge/helper/enumerable'
|
4
|
+
require 'slugforge/helper/fog'
|
5
|
+
require 'slugforge/helper/git'
|
6
|
+
require 'slugforge/helper/path'
|
7
|
+
require 'slugforge/helper/project'
|
8
|
+
require 'slugforge/models/logger'
|
9
|
+
|
10
|
+
module Slugforge
|
11
|
+
module Helper
|
12
|
+
def self.included(base)
|
13
|
+
base.send(:include, Slugforge::Helper::Build)
|
14
|
+
base.send(:include, Slugforge::Helper::Config)
|
15
|
+
base.send(:include, Slugforge::Helper::Fog)
|
16
|
+
base.send(:include, Slugforge::Helper::Git)
|
17
|
+
base.send(:include, Slugforge::Helper::Path)
|
18
|
+
base.send(:include, Slugforge::Helper::Project)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def force?
|
23
|
+
options[:force] == true
|
24
|
+
end
|
25
|
+
|
26
|
+
def json?
|
27
|
+
options[:json] == true
|
28
|
+
end
|
29
|
+
|
30
|
+
def pretend?
|
31
|
+
test? || options[:pretend] == true
|
32
|
+
end
|
33
|
+
|
34
|
+
def test?
|
35
|
+
options[:test] == true
|
36
|
+
end
|
37
|
+
|
38
|
+
def notifications_enabled?
|
39
|
+
test? || !pretend?
|
40
|
+
end
|
41
|
+
|
42
|
+
def quiet?
|
43
|
+
options[:quiet] == true
|
44
|
+
end
|
45
|
+
|
46
|
+
def verbose?
|
47
|
+
options[:verbose] == true
|
48
|
+
end
|
49
|
+
|
50
|
+
def error_class
|
51
|
+
json? ? JsonError : Thor::Error
|
52
|
+
end
|
53
|
+
|
54
|
+
def elapsed_time
|
55
|
+
format_age @command_start_time
|
56
|
+
end
|
57
|
+
|
58
|
+
def logger
|
59
|
+
@logger ||= begin
|
60
|
+
log_level = if quiet?
|
61
|
+
:quiet
|
62
|
+
elsif verbose?
|
63
|
+
:verbose
|
64
|
+
elsif json?
|
65
|
+
:json
|
66
|
+
end
|
67
|
+
Slugforge::Logger.new(self.shell, log_level)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def execute(cmd)
|
72
|
+
unless pretend?
|
73
|
+
if ruby_version_specified?
|
74
|
+
cmd = "rvm #{options[:ruby]} do #{cmd}"
|
75
|
+
elsif has_ruby_version_file?
|
76
|
+
cmd = "rvm #{get_ruby_version_from_file} do #{cmd}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# in thor, if capture is set, it uses backticks to run the command which returns a string.
|
80
|
+
# Otherwise they use `system` which returns true or nil if it worked. So check the return value
|
81
|
+
# and if it used backticks examine $? which keeps the result of the last command run to see
|
82
|
+
# if it worked.
|
83
|
+
returned = run(cmd, {:verbose => verbose?, :capture => verbose?})
|
84
|
+
if returned.is_a?(String)
|
85
|
+
process_status = $?
|
86
|
+
logger.say_status :run, "Command result #{process_status.to_s}. Command output: #{returned}", :green
|
87
|
+
return process_status.success?
|
88
|
+
end
|
89
|
+
|
90
|
+
return returned
|
91
|
+
end
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def with_env(env={}, &blk)
|
96
|
+
original = ENV.to_hash
|
97
|
+
ENV.replace(original.merge(env))
|
98
|
+
|
99
|
+
# Ensure rbenv isn't locked into a version
|
100
|
+
if ENV['RBENV_VERSION']
|
101
|
+
ENV.delete('RBENV_VERSION')
|
102
|
+
|
103
|
+
# when you use a shim provided by rbenv the $PATH is modified to point to the proper ruby version so shims are
|
104
|
+
# bypassed. We need to remove those path entries to totally unset rbenv. We remove every .rbenv path _except_
|
105
|
+
# shims so it can still use the correct version defined by .ruby-version.
|
106
|
+
paths = ENV['PATH'].split(':').reject do |path|
|
107
|
+
path =~ /\.rbenv\/(\w+)/ && !%w(shims bin).include?($1)
|
108
|
+
end
|
109
|
+
ENV['PATH'] = paths * ':'
|
110
|
+
end
|
111
|
+
|
112
|
+
# Ensure RVM isn't locked into a version
|
113
|
+
ENV.delete('RUBY_VERSION')
|
114
|
+
yield
|
115
|
+
ensure
|
116
|
+
ENV.replace(original)
|
117
|
+
end
|
118
|
+
|
119
|
+
def with_gemfile(gemfile, &blk)
|
120
|
+
with_env('BUNDLE_GEMFILE' => gemfile) do
|
121
|
+
ENV.delete('GEM_HOME')
|
122
|
+
ENV.delete('GEM_PATH')
|
123
|
+
ENV.delete('RUBYOPT')
|
124
|
+
yield
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def delete_option(options, option)
|
129
|
+
result = options.dup
|
130
|
+
index = result.index(option)
|
131
|
+
if index
|
132
|
+
result.delete_at(index)
|
133
|
+
result.delete_at(index)
|
134
|
+
end
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
def delete_switch(options, switch)
|
139
|
+
result = options.dup
|
140
|
+
index = result.index(option)
|
141
|
+
result.delete_at(index) if index
|
142
|
+
result
|
143
|
+
end
|
144
|
+
|
145
|
+
def format_size(size)
|
146
|
+
units = %w(B KB MB GB TB)
|
147
|
+
size, unit = units.reduce(size.to_f) do |(fsize, _), utype|
|
148
|
+
fsize > 512 ? [fsize / 1024, utype] : (break [fsize, utype])
|
149
|
+
end
|
150
|
+
|
151
|
+
"#{size > 9 || size.modulo(1) < 0.1 ? '%d' : '%.1f'} %s" % [size, unit]
|
152
|
+
end
|
153
|
+
|
154
|
+
SEGMENTS = {
|
155
|
+
:year => 60 * 60 * 24 * 365,
|
156
|
+
:month => 60 * 60 * 24 * 7 * 4,
|
157
|
+
:week => 60 * 60 * 24 * 7,
|
158
|
+
:day => 60 * 60 * 24,
|
159
|
+
:hour => 60 * 60
|
160
|
+
}
|
161
|
+
|
162
|
+
def format_age(age)
|
163
|
+
age = Time.now - age
|
164
|
+
segments = {}
|
165
|
+
|
166
|
+
SEGMENTS.each do |segment, length|
|
167
|
+
next unless age >= length
|
168
|
+
|
169
|
+
segments[segment] = (age / length).floor
|
170
|
+
age = age % length
|
171
|
+
end
|
172
|
+
|
173
|
+
# We only show Minutes or Seconds if there is no other scope
|
174
|
+
if segments.empty?
|
175
|
+
segments[:minute] = (age / 60).floor if age >= 60
|
176
|
+
segments[:second] = (age % 60).floor
|
177
|
+
end
|
178
|
+
|
179
|
+
segments.map do |seg, size|
|
180
|
+
plural = 's' if size != 1
|
181
|
+
"#{size} #{seg}#{plural}"
|
182
|
+
end.join(', ')
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|