whisk 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  Whisk
2
2
  =====
3
3
 
4
+ [![Build Status](https://secure.travis-ci.org/kisoku/whisk.png)](http://travis-ci.org/kisoku/whisk)
5
+
4
6
  Whisk is a simple cookbook manager for Chef, inspired by librarian and
5
7
  berkshelf.
6
8
 
@@ -48,6 +50,17 @@ Whisk requires that the following code be added to your knife.rb
48
50
 
49
51
  # Commands #
50
52
 
53
+ ## whisk destroy ##
54
+
55
+ Whisk destroys deletes the specified bowls or cookbooks from your filesystem.
56
+ Use with care.
57
+
58
+ ## whisk diff ##
59
+
60
+ Whisk diff simply runs 'git diff' in each of the ingredients specified. It
61
+ currently has no support for running diff with anything other than the default
62
+ arguments
63
+
51
64
  ## whisk list ##
52
65
 
53
66
  The list subcommand will list the short name of all of the configured
data/lib/whisk/cli.rb CHANGED
@@ -24,7 +24,6 @@ class Whisk
24
24
  class CLI < Thor
25
25
  def initialize(*)
26
26
  super
27
- # JW TODO: Replace Chef::Knife::UI with our own UI class
28
27
  ::Whisk.ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
29
28
  @options = options.dup # unfreeze frozen options Hash from Thor
30
29
  rescue Error => e
@@ -34,77 +33,53 @@ class Whisk
34
33
 
35
34
  namespace "whisk"
36
35
 
37
- method_option :whiskfile,
36
+ class_option :whiskfile,
38
37
  type: :string,
39
38
  default: File.join(Dir.pwd, Whisk::DEFAULT_FILENAME),
40
39
  desc: "Path to a Whiskfile to operate off of.",
41
40
  aliases: "-w",
42
41
  banner: "PATH"
42
+
43
+ desc "destroy", "destroy your bowls"
44
+ def destroy(filter=nil)
45
+ runner = Whisk::Runner.new(options[:whiskfile], filter)
46
+ runner.run('destroy')
47
+ end
48
+
43
49
  desc "diff", "run git diff in your bowls"
44
50
  def diff(filter=nil)
45
51
  runner = Whisk::Runner.new(options[:whiskfile], filter)
46
52
  runner.run('diff')
47
53
  end
48
54
 
49
- method_option :whiskfile,
50
- type: :string,
51
- default: File.join(Dir.pwd, Whisk::DEFAULT_FILENAME),
52
- desc: "Path to a Whiskfile to operate off of.",
53
- aliases: "-w",
54
- banner: "PATH"
55
55
  desc "list", "list the configured bowls and ingredients"
56
56
  def list(filter=nil)
57
57
  runner = Whisk::Runner.new(options[:whiskfile], filter)
58
58
  runner.run('list')
59
59
  end
60
60
 
61
- method_option :whiskfile,
62
- type: :string,
63
- default: File.join(Dir.pwd, Whisk::DEFAULT_FILENAME),
64
- desc: "Path to a Whiskfile to operate off of.",
65
- aliases: "-w",
66
- banner: "PATH"
67
61
  desc "prepare", "prepare a bowl by cloning any missing repositories"
68
62
  def prepare(filter=nil)
69
63
  runner = Whisk::Runner.new(options[:whiskfile], filter)
70
64
  runner.run('prepare')
71
65
  end
72
66
 
73
- method_option :whiskfile,
74
- type: :string,
75
- default: File.join(Dir.pwd, Whisk::DEFAULT_FILENAME),
76
- desc: "Path to a Whiskfile to operate off of.",
77
- aliases: "-w",
78
- banner: "PATH"
79
67
  desc "status", "run git status in your bowls"
80
68
  def status(filter=nil)
81
69
  runner = Whisk::Runner.new(options[:whiskfile], filter)
82
70
  runner.run('status')
83
71
  end
84
72
 
85
- method_option :whiskfile,
86
- type: :string,
87
- default: File.join(Dir.pwd, Whisk::DEFAULT_FILENAME),
88
- desc: "Path to a Whiskfile to operate off of.",
89
- aliases: "-w",
90
- banner: "PATH"
91
73
  desc "update", "run git remote update in your bowls"
92
74
  def update(filter=nil)
93
75
  runner = Whisk::Runner.new(options[:whiskfile], filter)
94
76
  runner.run('update')
95
77
  end
96
78
 
97
- method_option :whiskfile,
98
- type: :string,
99
- default: File.join(Dir.pwd, Whisk::DEFAULT_FILENAME),
100
- desc: "Path to a Whiskfile to operate off of.",
101
- aliases: "-w",
102
- banner: "PATH"
103
79
  desc "upload", "upload the specified bowls to a chef server"
104
80
  def upload(filter=nil)
105
81
  runner = Whisk::Runner.new(options[:whiskfile], filter)
106
82
  runner.run('upload')
107
83
  end
108
-
109
84
  end
110
85
  end
@@ -25,5 +25,9 @@ class Whisk
25
25
  def initialize(resource)
26
26
  @resource = resource
27
27
  end
28
+
29
+ def action_nothing
30
+ nil
31
+ end
28
32
  end
29
33
  end
@@ -16,6 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
+ require 'chef/json_compat'
19
20
  require 'whisk'
20
21
  require 'whisk/provider'
21
22
  require 'whisk/mixin/shellout'
@@ -26,12 +27,34 @@ class Whisk
26
27
 
27
28
  include Whisk::Mixin::ShellOut
28
29
 
30
+ def initialize(resource)
31
+ super
32
+ @environment = nil
33
+ end
34
+
35
+ def environment
36
+ if resource.environment
37
+ unless @environment.is_a? Chef::Environment
38
+ env_json = run_command!("knife environment show -F json #{resource.environment}").stdout
39
+ @environment = Chef::JSONCompat.from_json(env_json)
40
+ end
41
+ end
42
+ @environment
43
+ end
44
+
29
45
  def exist?
30
46
  ::Dir.exist? resource.path
31
47
  end
32
48
 
33
49
  def ingredients_run(action)
34
50
  resource.ingredients.each do |name, ingredient|
51
+ if ingredient.ref == :ref_from_environment
52
+ if environment and environment.cookbook_versions.has_key? ingredient.name
53
+ ingredient.ref environment.cookbook_versions[ingredient.name]
54
+ else
55
+ Whisk.ui.warn "Cookbook version for ingredient #{name} not found in environment #{resource.environment}"
56
+ end
57
+ end
35
58
  ingredient.run_action(action)
36
59
  end
37
60
  end
@@ -50,6 +73,16 @@ class Whisk
50
73
  end
51
74
  end
52
75
 
76
+ def action_destroy
77
+ if self.exist?
78
+ ingredients_run("destroy")
79
+ if Dir.entries(resource.path) == ["..", "."]
80
+ Whisk.ui.info("Destroying empty bowl #{resource.name}")
81
+ Dir.unlink(resource.path)
82
+ end
83
+ end
84
+ end
85
+
53
86
  def action_diff
54
87
  if self.exist?
55
88
  ::Dir.chdir resource.path
@@ -90,7 +123,8 @@ class Whisk
90
123
  def action_upload
91
124
  if self.exist?
92
125
  Whisk.ui.info "Uploading ingredients in bowl '#{resource.name}'"
93
- shell_out!("knife cookbook upload --all", :env => knife_env)
126
+ cookbooks = resource.ingredients.to_a.map {|name, cb| name}
127
+ shell_out!("knife cookbook upload #{cookbooks.join(' ')}", :env => knife_env)
94
128
  end
95
129
  end
96
130
  end
@@ -16,6 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
+ require 'grit'
19
20
  require 'whisk/mixin/shellout'
20
21
 
21
22
  class Whisk
@@ -24,53 +25,158 @@ class Whisk
24
25
 
25
26
  include Whisk::Mixin::ShellOut
26
27
 
28
+ def add_remotes
29
+ resource.remotes.each_pair do |remote, url|
30
+ add_remote(remote, url)
31
+ end
32
+ end
33
+
34
+ def add_remote(remote, url)
35
+ ::Dir.chdir resource.path
36
+ if repo.config.keys.include?("remote.#{remote}.url")
37
+ if remote_changed? remote
38
+ Whisk.ui.info "Remote #{remote} points to a different url"
39
+ a = Whisk.ui.ask_question("Would you like to change it to point to #{url} ?",
40
+ {:default => 'y'}
41
+ )
42
+ if a =~ /y/i
43
+ run_command!("git remote rm #{remote}")
44
+ else
45
+ Whisk.ui.info("Skipping out of date remote #{remote}")
46
+ return
47
+ end
48
+ else
49
+ Whisk.ui.info "Remote #{remote} already added"
50
+ return
51
+ end
52
+ end
53
+ Whisk.ui.info "Adding remote #{remote} to ingredient #{resource.name}"
54
+ run_command!("git remote add #{remote} #{url}")
55
+ end
56
+
57
+ def checkout
58
+ if resource.ref
59
+ if self.current_ref == resource.ref
60
+ Whisk.ui.info "Ingredient '#{resource.name}' already at ref '#{resource.ref}'"
61
+ else
62
+ Whisk.ui.info "Checking out ref '#{resource.ref}' for ingredient '#{resource.name}'"
63
+ cmd = run_command!("git checkout #{resource.ref}", :cwd => resource.path)
64
+ cmd.stdout.lines.each do |line|
65
+ Whisk.ui.info "\s\s#{line}"
66
+ end
67
+ cmd.stderr.lines.each do |line|
68
+ Whisk.ui.info "\s\s#{line}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+
27
74
  def clone
28
- if ::File.exists? File.join(Dir.pwd, resource.name, ".git", "config")
29
- Whisk.ui.info "Ingredient '#{resource.name}' already prepared"
75
+ if git.exist?
76
+ if remote_changed?("origin", resource.source)
77
+ reset_origin
78
+ else
79
+ Whisk.ui.info "Ingredient '#{resource.name}' already prepared"
80
+ end
30
81
  else
31
82
  Whisk.ui.info "Cloning ingredient '#{resource.name}', " + "from url #{resource.source}"
32
- shell_out!("git clone #{resource.source} #{resource.name}")
83
+ cmd = run_command!("git clone #{resource.source} #{resource.path}")
84
+ cmd.stdout.lines.each do |line|
85
+ Whisk.ui.info "\s\s#{line}"
86
+ end
87
+ cmd.stderr.lines.each do |line|
88
+ Whisk.ui.info "\s\s#{line}"
89
+ end
33
90
  end
34
91
  end
35
92
 
36
93
  def current_ref
37
- cref = run_command!("git rev-parse --abbrev-ref HEAD", :cwd => resource.name).stdout.chomp
94
+ cref = run_command!("git rev-parse --abbrev-ref HEAD", :cwd => resource.path).stdout.chomp
38
95
  if cref == 'HEAD'
39
- return run_command!("git describe --tags", :cwd => resource.name).stdout.chomp
96
+ return run_command!("git describe --tags", :cwd => resource.path).stdout.chomp
40
97
  else
41
98
  return cref
42
99
  end
43
100
  end
44
101
 
45
- def checkout
46
- if resource.ref
47
- if self.current_ref == resource.ref
48
- Whisk.ui.info "Ingredient '#{resource.name}' already at ref '#{resource.ref}'"
102
+ def destroy
103
+ if git.exist?
104
+ Whisk.ui.info "Destroying Ingredient #{resource.name}"
105
+ ::FileUtils.rm_rf resource.path
106
+ else
107
+ Whisk.ui.info "Ingredient #{resource.name} already destroyed"
108
+ end
109
+ end
110
+
111
+ def git
112
+ Grit::Git.new(resource.path)
113
+ end
114
+
115
+ def repo
116
+ Grit::Repo.new(resource.path)
117
+ end
118
+
119
+ def remote_changed?(remote, source)
120
+ if git.exist? and resource.source != repo.config["remote.#{remote}.url"]
121
+ Whisk.ui.info "Remote origin has changed for ingredient #{resource.name}"
122
+ true
123
+ else
124
+ false
125
+ end
126
+ end
127
+
128
+ def reset_origin
129
+ if remote_changed?("origin", new_source.source)
130
+ a = Whisk.ui.ask_question(
131
+ "Would you like to remove ingredient #{resource.name} before proceeding ?",
132
+ { :default => 'y' }
133
+ )
134
+ if a =~ /y/i
135
+ destroy
136
+ clone
49
137
  else
50
- Whisk.ui.info "Checking out ref '#{resource.ref}' for ingredient '#{resource.name}'"
51
- shell_out!("git checkout #{resource.ref}", :cwd => resource.name)
138
+ Whisk.ui.warn "Aborting whisk"
139
+ exit 1
52
140
  end
53
141
  end
54
142
  end
55
143
 
144
+ def action_destroy
145
+ destroy
146
+ end
147
+
56
148
  def action_diff
57
149
  Whisk.ui.info "Diff for ingredient '#{resource.name}'"
58
- shell_out!("git diff", :cwd => resource.name)
150
+ shell_out!("git diff", :cwd => resource.path)
59
151
  end
60
152
 
61
153
  def action_prepare
62
154
  self.clone
63
155
  self.checkout
156
+ self.add_remotes
64
157
  end
65
158
 
66
159
  def action_status
67
160
  Whisk.ui.info "Status for ingredient '#{resource.name}'"
68
- shell_out!("git status", :cwd => resource.name)
161
+ cmd = run_command!("git status", :cwd => resource.path)
162
+ cmd.stdout.lines.each do |line|
163
+ Whisk.ui.info "\s\s#{line}"
164
+ end
165
+ cmd.stderr.lines.each do |line|
166
+ Whisk.ui.info "\s\s#{line}"
167
+ end
168
+ Whisk.ui.info "\n"
69
169
  end
70
170
 
71
171
  def action_update
72
172
  Whisk.ui.info "Updating ingredient '#{resource.name}'"
73
- shell_out!("git remote update", :cwd => resource.name)
173
+ cmd = run_command!("git remote update", :cwd => resource.path)
174
+ cmd.stdout.lines.each do |line|
175
+ Whisk.ui.info "\s\s#{line}"
176
+ end
177
+ cmd.stderr.lines.each do |line|
178
+ Whisk.ui.info "\s\s#{line}"
179
+ end
74
180
  end
75
181
  end
76
182
  end
@@ -37,15 +37,9 @@ class Whisk
37
37
  end
38
38
 
39
39
  def provider(arg=nil)
40
- klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
41
- raise ArgumentError, "must provide provider by klass"
42
- # lookup_provider_constant(arg)
43
- else
44
- arg
45
- end
46
40
  set_or_return(
47
41
  :provider,
48
- klass,
42
+ arg,
49
43
  :kind_of => [ Class ]
50
44
  )
51
45
  end
@@ -38,12 +38,23 @@ class Whisk
38
38
  raise ArgumentError, "Ingredient '#{iname}' has already been added to bowl '#{name}'"
39
39
  else
40
40
  ingredients[iname] = Whisk::Resource::Ingredient.new(iname, self, &block)
41
+ if refs_from_environment
42
+ ingredients[iname].ref :ref_from_environment
43
+ end
41
44
  end
42
45
  end
43
46
 
47
+ def environment(arg=nil)
48
+ set_or_return(:environment, arg, :kind_of => String)
49
+ end
50
+
44
51
  def path(arg=nil)
45
52
  set_or_return(:path, arg, :default => File.join(Dir.getwd, name))
46
53
  end
54
+
55
+ def refs_from_environment(arg=nil)
56
+ set_or_return(:refs_from_environment, arg, :kind_of => [TrueClass, FalseClass], :default => false)
57
+ end
47
58
  end
48
59
  end
49
60
  end
@@ -25,23 +25,36 @@ class Whisk
25
25
 
26
26
  include Chef::Mixin::ParamsValidate
27
27
 
28
- attr_accessor :bowl
28
+ attr_accessor :bowl, :remotes
29
29
 
30
30
  def initialize(name, bowl, &block)
31
31
  @bowl = bowl
32
32
  @provider = Whisk::Provider::Ingredient
33
33
  @ref = nil
34
+ @remotes = {}
34
35
  @source = nil
35
36
 
36
37
  super(name, &block)
37
38
  end
38
39
 
40
+ def path
41
+ File.join(self.bowl.path, self.name)
42
+ end
43
+
39
44
  def source(arg=nil)
40
45
  set_or_return(:source, arg, :required => true)
41
46
  end
42
47
 
43
48
  def ref(arg=nil)
44
- set_or_return(:ref, arg, :kind_of => String)
49
+ set_or_return(:ref, arg, :kind_of => [String, Symbol])
50
+ end
51
+
52
+ def remote(rname, url)
53
+ if remotes.has_key?(name)
54
+ raise ArgumentError, "remote #{rname} already defined for ingredient #{name}"
55
+ else
56
+ remotes[rname] = url
57
+ end
45
58
  end
46
59
  end
47
60
  end
data/lib/whisk/version.rb CHANGED
@@ -17,5 +17,5 @@
17
17
  #
18
18
 
19
19
  class Whisk
20
- VERSION = '0.2.3'
20
+ VERSION = '0.3.0'
21
21
  end
@@ -26,31 +26,33 @@ require 'whisk'
26
26
 
27
27
  class Whisk
28
28
  class WhiskFile
29
- @@bowls = {}
30
29
 
31
- class << self
32
- def add_bowl(bowl)
33
- if @@bowls.has_key? name
34
- raise ArgumentError, "bowl #{name} already exists"
35
- else
36
- @@bowls[bowl.name] = bowl
37
- end
38
- end
30
+ attr_accessor :bowls
39
31
 
40
- def bowl(name, &block)
41
- b = Whisk::Resource::Bowl.new(name)
42
- b.instance_eval(&block)
43
- add_bowl(b)
44
- end
32
+ def initialize
33
+ @bowls = {}
34
+ end
45
35
 
46
- def bowls
47
- @@bowls
36
+ def add_bowl(bowl)
37
+ if bowls.has_key? bowl.name
38
+ raise ArgumentError, "bowl #{bowl.name} already exists"
39
+ else
40
+ bowls[bowl.name] = bowl
48
41
  end
42
+ end
49
43
 
44
+ def bowl(name, &block)
45
+ b = Whisk::Resource::Bowl.new(name)
46
+ b.instance_eval(&block) if block_given?
47
+ add_bowl(b)
48
+ end
49
+
50
+ class << self
50
51
  def from_file(filename)
51
52
  if ::File.exists?(filename) && ::File.readable?(filename)
52
- instance_eval(::IO.read(filename), filename, 1)
53
- self
53
+ whiskfile = Whisk::WhiskFile.new
54
+ whiskfile.instance_eval(::IO.read(filename), filename, 1)
55
+ whiskfile
54
56
  else
55
57
  raise IOError, "Cannot open or read #{filename}!"
56
58
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whisk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-28 00:00:00.000000000 Z
12
+ date: 2013-03-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: grit
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: mixlib-log
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -91,6 +107,22 @@ dependencies:
91
107
  - - ! '>='
92
108
  - !ruby/object:Gem::Version
93
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: simplecov
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
94
126
  description: A simple Chef cookbook dependency manager
95
127
  email: msf@kisoku.net
96
128
  executables: