knife-cookbook-bump 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in knife-cookbook-bump.gemspec
4
+ gemspec
@@ -0,0 +1,76 @@
1
+ Knife (Cookbook) Bump
2
+ =====================
3
+
4
+ Knife bump is a knife plugin designed to simplify a cookbook development workflow where cookbooks map onto git repositories.
5
+
6
+ Rationale
7
+ =========
8
+
9
+ For any infrastructure developer who works with more than one Opscode Hosted Chef Organization, or has more than one Chef Server (perhaps mapping to a different project, client, or job), the mainstream cookbook development pattern of using the Ospcode Community site, perhaps augmented by your own or third party cookbooks in your Chef repository, doesn't scale very well. Cookbooks are code - it makes sense to encourage code resuse wherever possible, and bug fixes and enhancements to cookbooks used on one project/organization ought to benefit other organizations. This proves to be trickier than it sounds, when eeping a collection of dozens of these cookbooks. These cookbooks are likely to be used in lots of different combinations for different projects/organizations, requiring a great deal of flexibility.
10
+
11
+ Managing versioning of this code with a single project is made easy because of the versioning and constraining system that Chef server allows. We can upload every new version to the Chef server, and constrain which versions are used by employing Chef Environments. When working with multiple organizations or project, trying to use the Chef server as the authoritative source of versioned cookbooks would require all orgs to have all cookbooks - and every time a change was made, the cookbook(s) being pushed to all orgs/servers. No, the canonical source of versioning needs to be the version control system in which the cookbook resides. The normal pattern for cookbooks is to keep them within a Chef repository. However, Git makes it difficult to tag and branch based on subdirectories. If I have a single repository, and I make a change to the mongodb cookbook, and increment its version in the metadata, and tag that commit, I will be able to pull the commit again in the future. Unfortunately, even with the advent of sparse checkouts, I can't really checkout just that directory for just that repository. The situation becomes much more complicated when we try to scale this over a large number of cookbooks. Suppose for client A I want mongodb-1.0.1, nginx-1.1.2 and mysql-1.2.6, I can't guarantee that I can find a place in the commit history where all three cookbooks were at the correct version. I'd need to clone the repo (perhaps sparsely) three times. Rarely does a project use as few as three cookbooks, so it's apparent that this isn't an elegant solution. It turns out that, on considersation, the best way to guarantee the degree of flexibility required to work with multiple clients and multiple cookbooks and versions, the best model is to have one git repo per cookbook.
12
+
13
+ Workflow
14
+ ========
15
+
16
+ The idea of having dozens of cookbook repositories sounds initially clumsy and inelegant, but it's actually a very effective strategy. We want the following
17
+
18
+ * A centralised, versioned history of all fixes and improvements for a given cookbook
19
+ * To reinforce the idea of modularity - cookbooks should solve problems in one domain, and should ship the documentation and support code to achieve this in one place
20
+ * To improve the shareability of cookbooks, both between projects and organizations, and within the broader Chef community. Mapping cookbooks onto Git repositories opens up the same collaborative benefits of Github and similar tools to all infrastructure developers
21
+
22
+ In order to make the idea workable, it's important to develop a reliable and repeatable workflow which describes the lifecycle of cookbook development. The following has been tested by Atalanta Systems across a number of clients and technology platforms, and represents a proven approach upon which to base such a workflow:
23
+
24
+ * Create a git repository for every cookbook you're going to develop on or contribute to.
25
+ * If you're using one of the cookbooks already residing in a dedicated repository, simply fork it as any other software project
26
+ * If you wish to use the Opscode community cookbooks as a starting point, either download the cookbook from the community site using knife, or a browser, or pull the code from the opscode/cookbooks Github repository. Be aware the Opscode cookbooks repo is fast-moving and in the spirit of Debian Sid could well be in an unstable state - if it breaks, you get to keep the pieces.
27
+ * As you add features or fix bugs, when you're satisfied with your code, increment the version in the cookbook metadata and README and tag the repository with the same name, then push your changes and your tags to Github or your Git server.
28
+ * Curating disparate cookbooks into a meaningful whole can be done manually, but a powerful approach is to use `librarian-chef`, which introduces the concept of a Cheffile, modelled after Ruby's bundler. A Cheffile simply describes the cookbooks needed for the organization/project, the location of the source code, and any version constraints. `librarian-chef` also attempts to solve dependencies based on cookbook metadata. See https://github.com/applicationsonline/librarian for more information.
29
+ * Testing cookbooks and guaranteeing stability for production can be achieved using Chef Environments. Create an environment called `stable`, and use cookbook version constraints and freezing to provide certainty around the use of known-good and tested cookbooks. Create one or more `unstable` environments to explore and develop cookbooks and their dependencies. Bootstrap nodes into stable or unstable environments depending on whether you want to experiment and develop, or provide a repeatable and guaranteed set of cookbooks.
30
+
31
+ Usage
32
+ =====
33
+
34
+ Knife bump is designed to automate as much of the workflow described above. At present, the following functionality is supported:
35
+
36
+ * Bumping of cookbook metadata
37
+ * Tagging of cookbook git repositories
38
+
39
+ Knife bump is not yet packaged as a Rubygem, so for now, you can just drop the main code, lib/cookbook-bump/bump.rb in your plugins/knife directory, and knife will find it. It has a dependency on the `grit` gem to provide object-oriented access to Git repositories.
40
+
41
+ Bumping
42
+ -------
43
+
44
+ Cookbooks follow a simple three level versioning pattern. Knife bump allows the patch, minor or major version to be bumped, and will automatically update the version in the metadata.
45
+
46
+ $ knife cookbook create netscape
47
+ ** Creating cookbook netscape
48
+ ** Creating README for cookbook: netscape
49
+ ** Creating metadata for cookbook: netscape
50
+ $ knife bump netscape patch
51
+ Bumping patch level of the netscape cookbook from 0.0.1 to 0.0.2
52
+ $ grep version cookbooks/netscape/metadata.rb
53
+ version "0.0.2"
54
+ $ knife bump netscape minor
55
+ Bumping minor level of the netscape cookbook from 0.0.2 to 0.1.2
56
+ $ grep version cookbooks/netscape/metadata.rb
57
+ version "0.1.2"
58
+
59
+ Tagging
60
+ -------
61
+
62
+ *In development*
63
+
64
+ Supplying the --tag option will additionally tag the git repository corresponding to the cookbook you're working on. Knife bump attempts to find this repository intelligently, but will ask for confirmation before tagging. It will also warn you if it thinks the name of the repository it found doesn't sound like a plausible name for the cookbook.
65
+
66
+
67
+ Tests
68
+ -----
69
+
70
+ The core functionality is tested using rspec. The plugin mechanics are assumed to have been tested by Opscode.
71
+
72
+ Changes/Roadmap
73
+ ---------------
74
+
75
+ No formal release yet.
76
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cookbook-bump/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "knife-cookbook-bump"
7
+ s.version = Knife::Cookbook::Bump::VERSION
8
+ s.authors = ["Stephen Nelson-Smith", "Fletcher Nichol"]
9
+ s.email = ["support@atalanta-systems.com"]
10
+ s.homepage = "https://github.com/Atalanta/knife-cookbook-bump"
11
+ s.summary = %q{A Chef knife plugin designed to simplify a cookbook development workflow where cookbooks map onto git repositories.}
12
+ s.description = s.summary
13
+
14
+ s.rubyforge_project = "knife-cookbook-bump"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+
23
+ s.add_runtime_dependency "chef"
24
+ s.add_runtime_dependency "grit"
25
+ end
@@ -0,0 +1 @@
1
+ require 'cookbook-bump/bump'
@@ -0,0 +1,102 @@
1
+ require 'chef/knife'
2
+ require 'chef/cookbook_loader'
3
+ require 'chef/cookbook_uploader'
4
+ require 'grit'
5
+
6
+ module CookbookBump
7
+ class Bump < Chef::Knife
8
+
9
+ TYPE_INDEX = { "major" => 0, "minor" => 1, "patch" => 2 }
10
+
11
+ banner "knife bump COOKBOOK [MAJOR|MINOR|PATCH]"
12
+
13
+
14
+ def run
15
+
16
+ self.config = Chef::Config.merge!(config)
17
+ if config.has_key?(:cookbook_path)
18
+ cookbook_path = config["cookbook_path"]
19
+ else
20
+ ui.fatal "No default cookbook_path; Specify with -o or fix your knife.rb."
21
+ show_usage
22
+ exit 1
23
+ end
24
+
25
+ if name_args.size == 0
26
+ show_usage
27
+ exit 0
28
+ end
29
+
30
+ unless name_args.size == 2
31
+ ui.fatal "Please specify the cookbook whose version you which to bump, and the type of bump you wish to apply."
32
+ show_usage
33
+ exit 1
34
+ end
35
+
36
+ unless TYPE_INDEX.has_key?(name_args.last.downcase)
37
+ ui.fatal "Sorry, '#{name_args.last}' isn't a valid bump type. Specify one of 'major', 'minor' or 'patch'"
38
+ show_usage
39
+ exit 1
40
+ end
41
+ cookbook = name_args.first
42
+ patch_type = name_args.last
43
+ cookbook_path = Array(config[:cookbook_path]).first
44
+
45
+ patch(cookbook_path, cookbook, patch_type)
46
+
47
+ end
48
+
49
+
50
+ def patch(cookbook_path, cookbook, type)
51
+ t = TYPE_INDEX[type]
52
+ current_version = get_version(cookbook_path, cookbook).split(".").map{|i| i.to_i}
53
+ bumped_version = current_version.clone
54
+ bumped_version[t] = bumped_version[t] + 1
55
+ metadata_file = File.join(cookbook_path, cookbook, "metadata.rb")
56
+ old_version = current_version.join('.')
57
+ new_version = bumped_version.join('.')
58
+ update_metadata(old_version, new_version, metadata_file)
59
+ ui.msg("Bumping #{type} level of the #{cookbook} cookbook from #{old_version} to #{new_version}")
60
+ end
61
+
62
+ def update_metadata(old_version, new_version, metadata_file)
63
+ open_file = File.open(metadata_file, "r")
64
+ body_of_file = open_file.read
65
+ open_file.close
66
+ body_of_file.gsub!(old_version, new_version)
67
+ File.open(metadata_file, "w") { |file| file << body_of_file }
68
+ end
69
+
70
+ def get_version(cookbook_path, cookbook)
71
+ loader = ::Chef::CookbookLoader.new(cookbook_path)
72
+ return loader[cookbook].version
73
+ end
74
+
75
+ def get_tags(cookbook_path, cookbook)
76
+ git_repo = find_git_repo(cookbook_path, cookbook)
77
+ g = Grit::Repo.new(git_repo)
78
+ if g.config["remote.origin.url"].split(File::SEPARATOR).last.scan(cookbook).size > 0
79
+ ui.confirm("I found a repo at #{git_repo} - do you want to tag it?")
80
+ else
81
+ ui.confirm("I didn't find a repo with a name like #{cookbook}. I did find #{git_repo} - are you sure you want to tag it?")
82
+ end
83
+ g.tags.map { |t| t.name }
84
+ end
85
+
86
+ def tag
87
+ end
88
+
89
+ def find_git_repo(cookbook_path, cookbook)
90
+ loader = ::Chef::CookbookLoader.new(cookbook_path)
91
+ cookbook_dir = loader[cookbook].root_dir
92
+ full_path = cookbook_dir.split(File::SEPARATOR)
93
+ (full_path.length - 1).downto(0) do |search_path_index|
94
+ git_config = File.join(full_path[0..search_path_index] + [".git", "config"])
95
+ if File.exist?(git_config)
96
+ return File.join(full_path[0..search_path_index])
97
+ end
98
+ end
99
+ ui.fatal("Unable to find a git repo for this cookbook.")
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,7 @@
1
+ module Knife
2
+ module Cookbook
3
+ module Bump
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ module CookbookBump
4
+
5
+ describe Bump do
6
+ before(:all) do
7
+ bare_repo_dir = File.join(File.dirname(__FILE__), 'origin_example_cookbook.git')
8
+ clone_repo_dir = File.join(File.dirname(__FILE__), 'example_cookbook2')
9
+
10
+ FileUtils.mkdir_p(bare_repo_dir)
11
+ g = Grit::Repo.init_bare(bare_repo_dir)
12
+ git_cloner = Grit::Git.new(clone_repo_dir)
13
+ git_cloner.clone({:quiet => false, :verbose => true, :progress => true, :branch => 'master'}, bare_repo_dir, clone_repo_dir)
14
+ end
15
+
16
+ before :all do
17
+ @bumper = Bump.new
18
+ @bumper.ui = Chef::Knife::UI.new(StringIO.new, StringIO.new, StringIO.new, {})
19
+ @cookbook_path = File.dirname(__FILE__)
20
+ @index = { "major" => 0, "minor" => 1, "patch" => 2 }
21
+ end
22
+
23
+ describe "patch" do
24
+ it "should increment the cookbook patch level in the metadata by one" do
25
+ original_version = @bumper.get_version(@cookbook_path, "example_cookbook")
26
+ original_patch_level = original_version.split('.')[@index["patch"]].to_i
27
+ @bumper.patch(@cookbook_path, "example_cookbook", "patch")
28
+ bumped_version = @bumper.get_version(@cookbook_path, "example_cookbook")
29
+ bumped_patch_level = bumped_version.split('.')[@index["patch"]].to_i
30
+ result = bumped_patch_level - original_patch_level
31
+ result.should eq(1)
32
+ end
33
+ end
34
+
35
+ describe "minor" do
36
+ it "should increment the cookbook minor level in the metadata by one" do
37
+ original_version = @bumper.get_version(@cookbook_path, "example_cookbook")
38
+ original_patch_level = original_version.split('.')[@index["minor"]].to_i
39
+ @bumper.patch(@cookbook_path, "example_cookbook", "minor")
40
+ bumped_version = @bumper.get_version(@cookbook_path, "example_cookbook")
41
+ bumped_patch_level = bumped_version.split('.')[@index["minor"]].to_i
42
+ result = bumped_patch_level - original_patch_level
43
+ result.should eq(1)
44
+ end
45
+ end
46
+
47
+ describe "major" do
48
+ it "should increment the cookbook minor level in the metadata by one" do
49
+ original_version = @bumper.get_version(@cookbook_path, "example_cookbook")
50
+ original_patch_level = original_version.split('.')[@index["major"]].to_i
51
+ @bumper.patch(@cookbook_path, "example_cookbook", "major")
52
+ bumped_version = @bumper.get_version(@cookbook_path, "example_cookbook")
53
+ bumped_patch_level = bumped_version.split('.')[@index["major"]].to_i
54
+ result = bumped_patch_level - original_patch_level
55
+ result.should eq(1)
56
+ end
57
+ end
58
+
59
+ describe "tag" do
60
+ it "should tag the git repository in which the cookbook resides with the bumped version number" do
61
+ existing_tags = @bumper.get_tags(@cookbook_path, "example_cookbook")
62
+ @bumper.patch(@cookbook_path, "example_cookbook", "patch")
63
+ latest_version = @bumper.get_version(@cookbook_path, "example_cookbook")
64
+ @bumper.tag
65
+ latest_tags = @bumper.get_tags(@cookbook_path, "example_cookbook")
66
+ new_tag = latest_tags - existing_tags
67
+ new_tag.should eq(latest_version)
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,6 @@
1
+ maintainer "Atalanta Systems Ltd"
2
+ maintainer_email "support@atalanta-systems.com"
3
+ license "Apache 2.0"
4
+ description "Example cookbook metadata file"
5
+ long_description "Long version"
6
+ version "0.0.0"
@@ -0,0 +1,4 @@
1
+ require 'chef/knife'
2
+ require 'cookbook-bump'
3
+ require 'grit'
4
+
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-cookbook-bump
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stephen Nelson-Smith
9
+ - Fletcher Nichol
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-10-30 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &69759060 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *69759060
26
+ - !ruby/object:Gem::Dependency
27
+ name: chef
28
+ requirement: &69758790 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *69758790
37
+ - !ruby/object:Gem::Dependency
38
+ name: grit
39
+ requirement: &69758580 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *69758580
48
+ description: A Chef knife plugin designed to simplify a cookbook development workflow
49
+ where cookbooks map onto git repositories.
50
+ email:
51
+ - support@atalanta-systems.com
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - .gitignore
57
+ - Gemfile
58
+ - README.md
59
+ - Rakefile
60
+ - knife-cookbook-bump.gemspec
61
+ - lib/cookbook-bump.rb
62
+ - lib/cookbook-bump/bump.rb
63
+ - lib/cookbook-bump/version.rb
64
+ - spec/cookbook-bump/bump.spec
65
+ - spec/cookbook-bump/example_cookbook/metadata.rb
66
+ - spec/spec_helper.rb
67
+ homepage: https://github.com/Atalanta/knife-cookbook-bump
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project: knife-cookbook-bump
87
+ rubygems_version: 1.8.10
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: A Chef knife plugin designed to simplify a cookbook development workflow
91
+ where cookbooks map onto git repositories.
92
+ test_files: []