whisk 0.2.3 → 0.3.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.
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: