slugforge 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|