soloist 0.0.8 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -66,8 +66,8 @@ soloistrc
66
66
  - pivotal_workstation::user_owns_usr_local
67
67
  - pivotal_workstation::workspace_directory
68
68
 
69
- Environment Variable Switching (Alpha)
70
- ======================================
69
+ Environment Variable Switching
70
+ ==============================
71
71
  I'm trying out adding support in the soloistrc file for selecting recipes based on environment variables. Cap should allow setting environment variables fairly easily on deploy, and they can be set permanently on the machine if desired. To use these, add a env_variable_switches key to your soloistrc. They keys of the hash should be the environment variable you wish to change the configuration based on, and the value should be a hash keyed by the value of the variable. It's easier than it sounds - see the example below. (NB: Note that the CamelSnake is gone in the soloistrc, and while the basic config accepts the old keys, environment variable switching requires snake case keys)
72
72
 
73
73
  cookbook_paths:
@@ -93,6 +93,14 @@ and a recipe list of
93
93
  "production::foo"
94
94
  ]
95
95
 
96
+ Log Level
97
+ =========
98
+ Soloist runs chef at log level info by default. Debug is very verbose, but makes debugging chef recipes much easier. Just set the LOG_LEVEL environment variable to 'debug' (or other valid chef log level) and it will be passed through.
99
+
100
+ Local Overrides (experimental)
101
+ ==============================
102
+ Soloist is an easy way to share configuration across workstations. If you want to have configuration in chef that you don't want to share with the rest of the project, you can create a soloistrc_local file in addition to the soloistrc file. This file will be processed after the soloistrc, and everything in it will be added to the run list. Be careful that you are clear what goes where - if it's a dependency of the project, it should be checked into the soloistrc file in the project.
103
+
96
104
  License
97
105
  =======
98
106
  Soloist is MIT Licensed. See MIT-LICENSE for details.
data/bin/soloist CHANGED
@@ -1,40 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- require 'soloist'
4
-
5
- def fileify(contents)
6
- file = Tempfile.new("soloist")
7
- file << contents
8
- file.flush
9
- file
10
- end
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'soloist')
11
3
 
12
- def walk_up_and_find_file(filenames)
13
- pwd = FileUtils.pwd
14
- file = nil
15
- path_to_file = ""
16
- while !file && FileUtils.pwd != '/'
17
- file = filenames.detect { |f| Dir.glob("*").include?(f) }
18
- FileUtils.cd("..")
19
- path_to_file << "../" unless file
20
- end
21
- FileUtils.cd(pwd)
22
- file_contents = File.read(path_to_file + file) if file
23
- [file_contents, path_to_file]
24
- end
4
+ include Soloist::Util
25
5
 
26
6
  log_level = ENV['LOG_LEVEL'] || "info"
27
7
 
28
- soloistrc_contents, soloistrc_path = walk_up_and_find_file(["soloistrc"])
29
- config_generator = ChefConfigGenerator.new(soloistrc_contents, soloistrc_path)
8
+ soloistrc_contents, soloistrc_path = walk_up_and_find_file(with_or_without_dot("soloistrc"))
9
+ config_generator = Soloist::ChefConfigGenerator.new(YAML.load(soloistrc_contents), soloistrc_path)
30
10
 
31
- # if ARGV.length >= 1
32
- # json_config = JSON.parse(File.read(json_file))
33
- # json_config["recipes"] = ARGV
34
- # json_file = "/tmp/#{File.basename(json_file)}"
35
- # puts "OVERRIDING RECIPES: running [#{json_config["recipes"].join(", ")}]. Temp json config file: #{json_file}"
36
- # File.open(json_file, "w+"){|f| f<<JSON.pretty_generate(json_config) }
37
- # end
11
+ soloistrc_contents, soloistrc_path = walk_up_and_find_file(with_or_without_dot("soloistrc_local"), :required => false)
12
+ config_generator.merge_config(YAML.load(soloistrc_contents), soloistrc_path) if soloistrc_contents
13
+
14
+ if ARGV.length >= 1
15
+ requested_recipe = ARGV[0]
16
+ raise "requested recipe '#{requested_recipe}' not in soloistrc or soloistrc_local" unless config_generator.recipes.include?(requested_recipe)
17
+ config_generator.recipes = [requested_recipe]
18
+ end
38
19
 
39
20
  solo_rb = fileify(config_generator.solo_rb)
40
21
  metadata_json = fileify(config_generator.json_file)
@@ -1,60 +1,71 @@
1
1
  require 'yaml'
2
2
 
3
- class ChefConfigGenerator
4
- def initialize(yaml_string, relative_path_to_soloistrc)
5
- @hash = YAML.load(yaml_string)
6
- @relative_path_to_soloistrc = relative_path_to_soloistrc
7
- merge_env_variable_switches
8
- end
9
-
10
- def merge_env_variable_switches
11
- return unless @hash["env_variable_switches"]
12
- @hash["env_variable_switches"].keys.each do |variable|
13
- ENV[variable] && ENV[variable].split(',').each do |env_variable_value|
14
- sub_hash = @hash["env_variable_switches"][variable][env_variable_value]
15
- if sub_hash && sub_hash["recipes"]
16
- @hash["recipes"] ||= []
17
- @hash["recipes"] = (@hash["recipes"] + sub_hash["recipes"]).uniq
18
- end
19
- if sub_hash && sub_hash["cookbook_paths"]
20
- @hash["cookbook_paths"] ||= []
21
- @hash["cookbook_paths"] = (@hash["cookbook_paths"] + sub_hash["cookbook_paths"]).uniq
22
- end
3
+ module Soloist
4
+ class Soloist::ChefConfigGenerator
5
+ def initialize(config, relative_path_to_soloistrc)
6
+ @recipes = []
7
+ @cookbook_paths = []
8
+ @preserved_environment_variables = %w{PATH BUNDLE_PATH GEM_HOME GEM_PATH RAILS_ENV RACK_ENV}
9
+ merge_config(config, relative_path_to_soloistrc)
10
+ end
11
+
12
+ attr_reader :preserved_environment_variables, :cookbook_paths
13
+ attr_accessor :recipes
14
+
15
+ def support_old_format(hash)
16
+ hash['recipes'] ||= hash.delete('Recipes')
17
+ hash['cookbook_paths'] ||= hash.delete('Cookbook_Paths')
18
+ hash
19
+ end
20
+
21
+ def append_path(paths, relative_path_to_soloistrc)
22
+ paths.map do |path|
23
+ path.slice(0,1) == '/' ? path : "#{FileUtils.pwd}/#{relative_path_to_soloistrc}/#{path}"
23
24
  end
24
25
  end
25
- end
26
26
 
27
- def cookbook_paths
28
- (@hash["cookbook_paths"] || @hash["Cookbook_Paths"]).map do |v|
29
- (v =~ /\//) == 0 ? v : "#{FileUtils.pwd}/#{@relative_path_to_soloistrc}/#{v}"
27
+ def merge_config(sub_hash, relative_path_to_soloistrc)
28
+ sub_hash = support_old_format(sub_hash)
29
+ if sub_hash["recipes"]
30
+ @recipes = (@recipes + sub_hash["recipes"]).uniq
31
+ end
32
+ if sub_hash["cookbook_paths"]
33
+ @cookbook_paths = (@cookbook_paths + append_path(sub_hash["cookbook_paths"], relative_path_to_soloistrc)).uniq
34
+ end
35
+ if sub_hash["env_variable_switches"]
36
+ merge_env_variable_switches(sub_hash["env_variable_switches"], relative_path_to_soloistrc)
37
+ end
30
38
  end
31
- end
32
39
 
33
- def solo_rb
34
- "cookbook_path #{cookbook_paths.inspect}"
35
- end
40
+ def merge_env_variable_switches(hash_to_merge, relative_path_to_soloistrc)
41
+ hash_to_merge.keys.each do |variable|
42
+ @preserved_environment_variables << variable
43
+ ENV[variable] && ENV[variable].split(',').each do |env_variable_value|
44
+ sub_hash = hash_to_merge[variable] && hash_to_merge[variable][env_variable_value]
45
+ merge_config(sub_hash, relative_path_to_soloistrc) if sub_hash
46
+ end
47
+ end
48
+ end
36
49
 
37
- def json_hash
38
- recipes = @hash["Recipes"] || @hash["recipes"]
39
- {
40
- "recipes" => recipes
41
- }
42
- end
50
+ def solo_rb
51
+ "cookbook_path #{cookbook_paths.inspect}"
52
+ end
43
53
 
44
- def json_file
45
- json_hash.to_json
46
- end
54
+ def json_hash
55
+ {
56
+ "recipes" => @recipes
57
+ }
58
+ end
47
59
 
48
- def preserved_environment_variables
49
- always_passed = %w{PATH BUNDLE_PATH GEM_HOME GEM_PATH RAILS_ENV RACK_ENV}
50
- always_passed += @hash["env_variable_switches"].keys if @hash["env_variable_switches"]
51
- always_passed
52
- end
60
+ def json_file
61
+ json_hash.to_json
62
+ end
53
63
 
54
- def preserved_environment_variables_string
55
- variable_array = []
56
- preserved_environment_variables.map do |env_variable|
57
- "#{env_variable}=#{ENV[env_variable]}" unless ENV[env_variable].nil?
58
- end.compact.join(" ")
64
+ def preserved_environment_variables_string
65
+ variable_array = []
66
+ preserved_environment_variables.map do |env_variable|
67
+ "#{env_variable}=#{ENV[env_variable]}" unless ENV[env_variable].nil?
68
+ end.compact.join(" ")
69
+ end
59
70
  end
60
71
  end
@@ -0,0 +1,34 @@
1
+ module Soloist
2
+ module Util
3
+ def with_or_without_dot(file_name)
4
+ [file_name, ".#{file_name}"]
5
+ end
6
+
7
+ def fileify(contents)
8
+ file = Tempfile.new("soloist")
9
+ file << contents
10
+ file.flush
11
+ file
12
+ end
13
+
14
+ def walk_up_and_find_file(filenames, opts={})
15
+ pwd = FileUtils.pwd
16
+ file = nil
17
+ path_to_file = ""
18
+ while !file && FileUtils.pwd != '/'
19
+ file = filenames.detect { |f| File.exists?(f) }
20
+ FileUtils.cd("..")
21
+ path_to_file << "../" unless file
22
+ end
23
+ FileUtils.cd(pwd)
24
+ if file
25
+ file_contents = File.read(path_to_file + file) if file
26
+ [file_contents, path_to_file]
27
+ elsif opts[:required] == false
28
+ [nil, nil]
29
+ else
30
+ raise Errno::ENOENT, "#{filenames.join(" or ")} not found" unless file || opts[:required] == false
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,3 @@
1
1
  module Soloist
2
- VERSION = "0.0.8"
2
+ VERSION = "0.9.0"
3
3
  end
data/lib/soloist.rb CHANGED
@@ -1,10 +1,9 @@
1
- require 'soloist/chef_config_generator'
1
+ require 'rubygems'
2
2
  require "json"
3
3
  require 'fileutils'
4
4
  require 'yaml'
5
5
  require 'tempfile'
6
6
 
7
+ require File.join(File.dirname(__FILE__), 'soloist', 'chef_config_generator')
8
+ require File.join(File.dirname(__FILE__), 'soloist', 'util')
7
9
 
8
- module Soloist
9
- # Your code goes here...
10
- end
data/soloist.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Matthew Kocher"]
10
10
  s.email = ["kocher@gmail.com"]
11
- s.homepage = "http://rubygems.org/gems/soloist"
11
+ s.homepage = "http://github.com/mkocher/soloist"
12
12
  s.summary = %q{Soloist is a simple way of running chef-solo}
13
13
  s.description = %q{Soloist is an easy way of running chef solo, but it's not doing much.}
14
14
 
@@ -19,6 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_dependency('chef', '0.9.12')
23
- s.add_dependency('json', '1.4.6')
24
- end
22
+ s.add_dependency('chef')
23
+ s.add_dependency('json')
24
+ end
@@ -1,7 +1,7 @@
1
1
  require 'rspec'
2
2
  require 'lib/soloist'
3
3
 
4
- describe "ChefConfigGenerator" do
4
+ describe Soloist::ChefConfigGenerator do
5
5
  describe "generation" do
6
6
  before do
7
7
  @config = <<-CONFIG
@@ -10,13 +10,20 @@ Cookbook_Paths:
10
10
  Recipes:
11
11
  - pivotal_workstation::ack
12
12
  CONFIG
13
- @generator = ChefConfigGenerator.new(@config, "../..")
13
+ @config = YAML.load(@config)
14
14
  FileUtils.stub(:pwd).and_return("/current/working/directory")
15
+ @generator = Soloist::ChefConfigGenerator.new(@config, "../..")
15
16
  end
16
17
 
17
18
  it "appends the current path and relative path to the cookbooks directory" do
18
19
  @generator.cookbook_paths.should == ["/current/working/directory/../.././chef/cookbooks/"]
19
20
  end
21
+
22
+ it "does not append if an absolute path is given" do
23
+ @config['cookbook_paths'] = ["/foo/bar"]
24
+ @generator = Soloist::ChefConfigGenerator.new(@config, "../..")
25
+ @generator.cookbook_paths.should == ["/foo/bar"]
26
+ end
20
27
 
21
28
  it "can generate a solo.rb contents" do
22
29
  @generator.solo_rb.should == 'cookbook_path ["/current/working/directory/../.././chef/cookbooks/"]'
@@ -60,7 +67,7 @@ env_variable_switches:
60
67
  recipes:
61
68
  - pivotal_dev::foo
62
69
  CONFIG
63
- @generator = ChefConfigGenerator.new(@config, "")
70
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(@config), "")
64
71
  @generator.preserved_environment_variables.should =~ %w{PATH BUNDLE_PATH GEM_HOME GEM_PATH RAILS_ENV RACK_ENV ME_TOO}
65
72
  end
66
73
  end
@@ -73,25 +80,25 @@ env_variable_switches:
73
80
 
74
81
  it "accepts Cookbook_Paths, because the CamelSnake is a typo that must be supported" do
75
82
  @config = "Cookbook_Paths:\n- ./chef/cookbooks/\n"
76
- @generator = ChefConfigGenerator.new(@config, "")
77
- @generator.cookbook_paths.should == ["///./chef/cookbooks/"]
83
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(@config), "..")
84
+ @generator.cookbook_paths.should == ["//.././chef/cookbooks/"]
78
85
  end
79
86
 
80
87
  it "accepts cookbook_paths, because it is sane" do
81
88
  @config = "cookbook_paths:\n- ./chef/cookbooks/\n"
82
- @generator = ChefConfigGenerator.new(@config, "")
83
- @generator.cookbook_paths.should == ["///./chef/cookbooks/"]
89
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(@config), "..")
90
+ @generator.cookbook_paths.should == ["//.././chef/cookbooks/"]
84
91
  end
85
92
 
86
93
  it "accepts Recipes, because that's the way it was" do
87
94
  @config = "Recipes:\n- pivotal_workstation::ack"
88
- @generator = ChefConfigGenerator.new(@config, "")
95
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(@config), "")
89
96
  @generator.json_hash.should == { "recipes" => ["pivotal_workstation::ack"]}
90
97
  end
91
98
 
92
99
  it "accepts recipes, because it's snake now" do
93
100
  @config = "recipes:\n- pivotal_workstation::ack"
94
- @generator = ChefConfigGenerator.new(@config, "")
101
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(@config), "")
95
102
  @generator.json_hash.should == { "recipes" => ["pivotal_workstation::ack"]}
96
103
  end
97
104
  end
@@ -116,7 +123,7 @@ env_variable_switches:
116
123
  - pivotal_dev::foo
117
124
  CONFIG
118
125
  ENV["RACK_ENV"]="development"
119
- @generator = ChefConfigGenerator.new(@config, "../..")
126
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(@config), "../..")
120
127
  @generator.cookbook_paths.should == [
121
128
  "//../.././chef/cookbooks/",
122
129
  "//../.././chef/dev_cookbooks/"
@@ -147,7 +154,7 @@ env_variable_switches:
147
154
  recipes:
148
155
  - pivotal_db::database
149
156
  CONFIG
150
- @generator = ChefConfigGenerator.new(@config, "../..")
157
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(@config), "../..")
151
158
  @generator.cookbook_paths.should =~ [
152
159
  "//../.././chef/cookbooks/",
153
160
  "//../.././chef/app_cookbooks/",
@@ -159,6 +166,15 @@ env_variable_switches:
159
166
  "pivotal_db::database",
160
167
  ]
161
168
  end
169
+
170
+ it "can deal with empty env switched variables, and passes them through" do
171
+ config = <<-CONFIG
172
+ env_variable_switches:
173
+ RACK_ENV:
174
+ CONFIG
175
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(config), "../..")
176
+ @generator.preserved_environment_variables.should include("RACK_ENV")
177
+ end
162
178
 
163
179
 
164
180
  it "can deal with only having environment switched recipes/cookbooks" do
@@ -171,7 +187,7 @@ env_variable_switches:
171
187
  recipes:
172
188
  - pivotal_development::foo
173
189
  CONFIG
174
- @generator = ChefConfigGenerator.new(config, "../..")
190
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(config), "../..")
175
191
  @generator.cookbook_paths.should == [
176
192
  "//../.././chef/development_cookbooks/"
177
193
  ]
@@ -191,7 +207,7 @@ env_variable_switches:
191
207
  recipes:
192
208
  - pivotal_development::foo
193
209
  CONFIG
194
- @generator = ChefConfigGenerator.new(config, "../..")
210
+ @generator = Soloist::ChefConfigGenerator.new(YAML.load(config), "../..")
195
211
  @generator.cookbook_paths.should == [
196
212
  "//../.././chef/development_cookbooks/"
197
213
  ]
data/spec/util_spec.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'rspec'
2
+ require 'lib/soloist'
3
+
4
+ describe Soloist::Util do
5
+ class TestClass
6
+ extend Soloist::Util
7
+ end
8
+
9
+ describe "walk_up_and_find_file" do
10
+ it "raises an error when the file isn't found" do
11
+ lambda do
12
+ TestClass.walk_up_and_find_file(["file_not_on_the_filesystem"])
13
+ end.should raise_error(Errno::ENOENT, "No such file or directory - file_not_on_the_filesystem not found")
14
+ end
15
+
16
+ it "doesn't raise an error if :required => false is passed" do
17
+ TestClass.walk_up_and_find_file(["file_not_on_the_filesystem"], :required => false).should == [nil, nil]
18
+ end
19
+ end
20
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soloist
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 59
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 9
8
9
  - 0
9
- - 8
10
- version: 0.0.8
10
+ version: 0.9.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matthew Kocher
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-04-19 00:00:00 -07:00
18
+ date: 2011-06-06 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -24,14 +24,12 @@ dependencies:
24
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - "="
27
+ - - ">="
28
28
  - !ruby/object:Gem::Version
29
- hash: 35
29
+ hash: 3
30
30
  segments:
31
31
  - 0
32
- - 9
33
- - 12
34
- version: 0.9.12
32
+ version: "0"
35
33
  type: :runtime
36
34
  version_requirements: *id001
37
35
  - !ruby/object:Gem::Dependency
@@ -40,14 +38,12 @@ dependencies:
40
38
  requirement: &id002 !ruby/object:Gem::Requirement
41
39
  none: false
42
40
  requirements:
43
- - - "="
41
+ - - ">="
44
42
  - !ruby/object:Gem::Version
45
- hash: 11
43
+ hash: 3
46
44
  segments:
47
- - 1
48
- - 4
49
- - 6
50
- version: 1.4.6
45
+ - 0
46
+ version: "0"
51
47
  type: :runtime
52
48
  version_requirements: *id002
53
49
  description: Soloist is an easy way of running chef solo, but it's not doing much.
@@ -70,11 +66,13 @@ files:
70
66
  - bin/soloist
71
67
  - lib/soloist.rb
72
68
  - lib/soloist/chef_config_generator.rb
69
+ - lib/soloist/util.rb
73
70
  - lib/soloist/version.rb
74
71
  - soloist.gemspec
75
72
  - spec/chef_config_generator_spec.rb
73
+ - spec/util_spec.rb
76
74
  has_rdoc: true
77
- homepage: http://rubygems.org/gems/soloist
75
+ homepage: http://github.com/mkocher/soloist
78
76
  licenses: []
79
77
 
80
78
  post_install_message:
@@ -109,3 +107,4 @@ specification_version: 3
109
107
  summary: Soloist is a simple way of running chef-solo
110
108
  test_files:
111
109
  - spec/chef_config_generator_spec.rb
110
+ - spec/util_spec.rb