clc-promote 0.4.5 → 0.7.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c7e7e10fbd438abaff88aaf54464fe4aa92b228e
4
- data.tar.gz: 9baa2d648939134acbe44770a1213fb546a767a3
3
+ metadata.gz: 028a2d20e003fc5841ed185d5da849033b2879b2
4
+ data.tar.gz: d1fe5f381fd2ee79759d23a0809b1a7770f1698e
5
5
  SHA512:
6
- metadata.gz: f5d124b3c6dc141ee429b766363215069d9ff7e42438a3af7c3a10bedf94262496c6ab0c97ca123b5faaf01f6ff895ce84998cfbc875f4cb0b0915f744e4dd6e
7
- data.tar.gz: 306e8fed3140ad1c747cafc32b8d1aab7730c303d1385464bab80f549801e0b8442fd369d8abe5280a0665fb25a3b55a866ad62ca16f2ba463cc1445dbb76275
6
+ metadata.gz: 8f3601f98c0eab1f4191f60e9bc22221b2207787157da24665f709865b25bc147e5d4af7c0dfebed73b05f2747f4088698fd1cfc2d3a778a68a81c7172645b80
7
+ data.tar.gz: 07000fd20e24894db386e22bd178ca8268ef0efcab71d71d9d1bde7c9b1cb05844f1f7a3c987acb6281795aedfce08285cda3e10e093dc394834ce006280c61f
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # CLC-Promote Gem
2
2
  Drives our CI/CD pipeline and provides functionality for:
3
- * Versioning cookbooks, data bags and environments
3
+ * Versioning cookbooks, data bags, roles, and environments
4
4
  * Manages Cookbook version constraints within environments
5
5
  * Manages the uploading of chef artifacts from CI to the QA chef Server and eventually to the production chef server
6
6
  * Promotes one environment's cookbooks to another
7
7
 
8
- This functionality is exposed via a collectin of rake tasks used on the CI server and a knife plugin used to perform deployments.
8
+ This functionality is exposed via a collection of rake tasks used on the CI server and a knife plugin used to perform deployments.
9
9
 
10
10
  ## Configuration
11
11
  CLC-Promote uses configuration settings to determine the values of:
@@ -41,6 +41,7 @@ Setting |
41
41
  :cookbook_directory | Defaults to `#{root}/cookbooks`
42
42
  :environment_directory | Defaults to `#{root}/environments`
43
43
  :data_bag_directory | Defaults to `#{root}/data_bags`
44
+ :role_directory | Defaults to `#{root}/roles`
44
45
  :temp_directory | Defaults to `/tmp/promote`
45
46
  :node_name | **mandatory**
46
47
  :client_key | **mandatory**
@@ -48,7 +49,7 @@ Setting |
48
49
 
49
50
 
50
51
  ## CLC-Promote Rake Tasks
51
- **Note:** See the [below section on versioning](/gems/clc-promote#How are version numbers generated) for details on how versin numbers are generated.
52
+ **Note:** See the [below section on versioning](/gems/clc-promote#How are version numbers generated) for details on how version numbers are generated.
52
53
 
53
54
  ### Promote:version_cookbook
54
55
  Bumps the version of an individual cookbook.
@@ -62,6 +63,12 @@ Bumps the version of an individual environment file.
62
63
  ### Promote:version_environments
63
64
  Same as `Promote:version_environment` but iterates all environment files.
64
65
 
66
+ ### Promote:version_role
67
+ Bumps the version of an individual role file.
68
+
69
+ ### Promote:version_roles
70
+ Same as `Promote:version_role` but iterates all role files.
71
+
65
72
  ### Promote:version_data_bag
66
73
  Bumps the version of an individual databag entry.
67
74
  DEPRECATED (breaks encrypted data bags and vaults).
@@ -79,8 +86,11 @@ Uploads all cookbook versions of an environment to the chef server. **Note**: on
79
86
  ### Promote:upload_environment
80
87
  Uploads an environment file to the chef server
81
88
 
89
+ ### Promote:upload_roles
90
+ Uploads all role files to the chef server
91
+
82
92
  ### Promote:upload_data_bags
83
- Uploads all data bage to the chef server
93
+ Uploads all data bags to the chef server
84
94
 
85
95
  ### Promote: constrain_environment
86
96
  Given an environment and its environment cookbook, this task edits the environment file and creates cookbook constraints based on the `Berksfile.lock` of the environment cookbook.
@@ -95,7 +105,7 @@ Version numbers are based on the last version tag which forms the major and mino
95
105
  The knife promote command deploys an environment to production.
96
106
 
97
107
  ```
98
- knife promote environment [source environment] [target environment]
108
+ knife promote environment SOURCE_ENVIRONMENT TARGET_ENVIRONMENT [ --data-bags LIST ]
99
109
  ```
100
110
 
101
111
  This command performs the following:
@@ -103,7 +113,7 @@ This command performs the following:
103
113
  1. Copies the cookbook version constraints from source to target
104
114
  2. Commits the constraints to version control
105
115
  3. Uploads the target environment to the QA chef server
106
- 4. Downloads all data bags from QA
116
+ 4. Downloads all ```secrets_*``` data bags and all roles from QA. Additional data bags can be specified on the command line. Note that data bags containing chef-vault keys ( ```*_keys.json``` ) are always skipped.
107
117
  5. Performs a version diff on all cookbooks between the target environment and production chef server
108
- 6. Downloads all cookbooks from qa that are not on the prod server
109
- 7. uploads all new cookbooks, the target environment and databags to the production chef server.
118
+ 6. Downloads all cookbooks from QA that are not on the production server
119
+ 7. Uploads all new cookbooks, the target environment, databags and roles to the production chef server.
data/Rakefile CHANGED
@@ -1,6 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
2
  require 'rspec/core/rake_task'
3
3
 
4
+ $:.unshift(File.dirname(__FILE__) + '/lib')
5
+ require 'promote/version'
6
+
4
7
  RSpec::Core::RakeTask.new(:test)
5
8
 
6
9
  task :default => [:test]
@@ -17,8 +17,9 @@ Gem::Specification.new do |s|
17
17
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
18
 
19
19
  s.add_runtime_dependency 'clc-git', '~> 1.2', '>= 1.2.8'
20
- s.add_runtime_dependency 'berkshelf', '~> 3.2', '>= 3.2.2'
20
+ s.add_runtime_dependency 'berkshelf', '~> 3.2', '>= 3.2.3'
21
+ s.add_runtime_dependency 'highline', '~> 1.6', '>= 1.6.21'
21
22
 
22
23
  s.add_development_dependency 'rspec', '~> 3.0', '>= 3.0.0'
23
24
  s.add_development_dependency 'rake', '~> 10.3', '>= 10.3.2'
24
- end
25
+ end
@@ -6,6 +6,21 @@ module KnifePromote
6
6
 
7
7
  banner "knife promote environment SOURCE_ENVIRONMENT DESTINATION_ENVIRONMENT"
8
8
 
9
+ option :dry,
10
+ :long => "--dry-run",
11
+ :description => "Do not perform actual promote. Just report what would be done.",
12
+ :default => false
13
+
14
+ option :bags,
15
+ :long => "--data-bags VALUE",
16
+ :description => "A comma-separated list of data bags to be promoted (in addition to all secrets_* data bags)",
17
+ :default => ['secrets_*'],
18
+ :proc => Proc.new { |b|
19
+ b = "secrets_*,#{b}"
20
+ b.split(',')
21
+ }
22
+
23
+
9
24
  def run
10
25
  if @name_args.length < 2
11
26
  show_usage
@@ -16,40 +31,54 @@ module KnifePromote
16
31
  source_env = @name_args[0]
17
32
  dest_env = @name_args[1]
18
33
 
19
- ui.info "#{source_env} is being promoted to #{dest_env}"
20
- ui.confirm "Are you sure #{source_env} deserves this promotion"
21
-
22
- config = Promote::Config.new({
34
+ promote_config = Promote::Config.new({
23
35
  :repo_root => File.expand_path("../",Chef::Config[:cookbook_path][0]),
24
36
  :node_name => Chef::Config[:node_name],
25
37
  :client_key => Chef::Config[:client_key],
26
- :chef_server_url => Chef::Config[:chef_server_url]
38
+ :chef_server_url => Chef::Config[:chef_server_url],
39
+ :temp_directory => '/tmp/promote_staging',
40
+ :bags => config[:bags]
27
41
  })
28
42
  destination_config = Promote::Config.new({
29
- :repo_root => [config.temp_directory],
43
+ :repo_root => promote_config.temp_directory,
30
44
  :node_name => Chef::Config[:knife][:promote_prod_user],
31
45
  :client_key => Chef::Config[:knife][:promote_prod_client_key],
32
46
  :chef_server_url => Chef::Config[:knife][:promote_prod_url]
33
47
  })
34
48
 
35
- promoter = Promote::Promoter.new(config)
36
- uploader = Promote::Uploader.new(config)
37
- #Chef::Config[:verbosity] = 2
49
+ # Confirm local git repo is in sync with origin/master
50
+ source = "environments/#{source_env}.json"
51
+ dest = "environments/#{dest_env}.json"
52
+ r = Promote::GitRepo.new(promote_config.repo_root)
53
+ r.check_sync(source, dest)
38
54
 
39
- promoter.promote_to(source_env, dest_env)
55
+ ui.info "*** #{source_env} is being promoted to #{dest_env}"
56
+ ui.confirm "Are you sure this is what you want to do"
40
57
 
41
- ui.info "Commiting and pushing changes to #{dest_env} back to git"
42
- env_file = File.join(config.environment_directory, "#{dest_env}.json")
43
- repo = Promote::GitRepo.new(env_file)
44
- repo.commit("promoted new constraints from #{source_env} to #{dest_env}", true)
58
+ promoter = Promote::Promoter.new(promote_config)
59
+ uploader = Promote::Uploader.new(promote_config)
60
+ Chef::Config[:verbosity] = 2
45
61
 
46
- ui.info "uploading #{dest_env} to #{config.chef_server_url}"
62
+ # Locally mirror the environment constraints over
63
+ promoter.promote_to(source_env, dest_env, ui)
64
+
65
+ if !config[:dry]
66
+ ui.info "Committing and pushing changes to #{dest_env} back to git"
67
+ env_file = File.join(promote_config.environment_directory, "#{dest_env}.json")
68
+ repo = Promote::GitRepo.new(env_file)
69
+ repo.commit("promoted new constraints from #{source_env} to #{dest_env}", true)
70
+ end
71
+
72
+ # Uploads the environment to QA
73
+ ui.info "uploading #{dest_env} to #{promote_config.chef_server_url}"
47
74
  uploader.upload_environment(dest_env)
48
75
 
49
76
  promoter.stage_promotion(dest_env, destination_config, ui)
50
- promoter.upload_to(dest_env, destination_config, ui)
51
77
 
52
- ui.info "promotion complete. Congratulations #{dest_env} on a well deserved promotion!!"
78
+ if !config[:dry]
79
+ promoter.upload_to(dest_env, destination_config, ui)
80
+ ui.info "promotion complete. Congratulations #{dest_env} on a well deserved promotion!!"
81
+ end
53
82
  end
54
83
  end
55
84
  end
@@ -18,7 +18,7 @@ module Kitchen
18
18
  private
19
19
 
20
20
  def create_node
21
- node_file = File.join(instance.busser[:test_base_path], "nodes/#{instance.suite.name}.json")
21
+ node_file = File.join(instance.verifier[:test_base_path], "nodes/#{instance.suite.name}.json")
22
22
  if File.exist?(node_file)
23
23
  node = JSON.parse(File.read(node_file))
24
24
  node[:run_list] = config[:run_list]
@@ -2,7 +2,8 @@ require 'promote/config'
2
2
  require 'promote/cookbook'
3
3
  require 'promote/environment_file'
4
4
  require 'promote/git_repo'
5
- require 'promote/uploader'
6
- require 'promote/versioner'
5
+ require 'promote/node_finder'
7
6
  require 'promote/promoter'
7
+ require 'promote/uploader'
8
8
  require 'promote/utils'
9
+ require 'promote/versioner'
@@ -2,13 +2,20 @@ module Promote
2
2
  class Config
3
3
  def initialize(options = {})
4
4
  options.each do |k, v|
5
- self.send("#{k}=", v) if self.respond_to?(k)
5
+ self.send("#{k}=", v) if self.respond_to?(k)
6
6
  end
7
7
  self.repo_root ||= Dir.pwd
8
8
  self.cookbook_directory ||= File.join(repo_root, "cookbooks")
9
9
  self.environment_directory ||= File.join(repo_root, "environments")
10
10
  self.data_bag_directory ||= File.join(repo_root, "data_bags")
11
+ self.role_directory ||= File.join(repo_root, "roles")
11
12
  self.temp_directory ||= "/tmp/promote"
13
+ self.bags ||= ['secrets_*']
14
+ end
15
+
16
+ def reset_temp_dir
17
+ FileUtils.rm_rf(temp_directory) if Dir.exist?(temp_directory)
18
+ Dir.mkdir(temp_directory)
12
19
  end
13
20
 
14
21
  def to_hash
@@ -27,9 +34,11 @@ module Promote
27
34
  attr_accessor :cookbook_directory
28
35
  attr_accessor :environment_directory
29
36
  attr_accessor :data_bag_directory
37
+ attr_accessor :role_directory
30
38
  attr_accessor :temp_directory
31
39
  attr_accessor :node_name
32
40
  attr_accessor :client_key
33
41
  attr_accessor :chef_server_url
42
+ attr_accessor :bags
34
43
  end
35
- end
44
+ end
@@ -1,4 +1,5 @@
1
1
  require 'berkshelf'
2
+ require 'digest/sha1'
2
3
  require 'json'
3
4
 
4
5
  module Promote
@@ -77,15 +78,26 @@ module Promote
77
78
  return false
78
79
  end
79
80
 
81
+ def dependency_hash(environment_cookbook_name)
82
+ cb_changes = sync_latest_app_cookbooks(environment_cookbook_name)
83
+ hash_src = ""
84
+ dependencies.each do | k,v |
85
+ hash_src << "#{k}::#{v}::"
86
+ end
87
+ Digest::SHA1.hexdigest(hash_src)
88
+ end
89
+
80
90
  def sync_latest_app_cookbooks(environment_cookbook_name)
81
91
  result = {}
82
92
  latest_server_cookbooks = Utils.chef_server_cookbooks(@config, 1)
83
93
  env_cookbook = Cookbook.new(environment_cookbook_name, @config)
84
- env_cookbook.metadata_dependencies do |key|
85
- latest_version = latest_server_cookbooks[key]['versions'][0]
86
- if dependencies.keys.include?(key) && latest_version > dependencies[key].to_s
87
- berksfile_update(key)
88
- result[key] = latest_version
94
+ env_cookbook.metadata_dependencies.each do |key|
95
+ cb_key = key[0]
96
+ next if cb_key == @name || !latest_server_cookbooks.has_key?(cb_key)
97
+ latest_version = latest_server_cookbooks[cb_key]['versions'][0]['version']
98
+ if dependencies.keys.include?(cb_key) && latest_version > dependencies[cb_key].to_s
99
+ berksfile_update(cb_key)
100
+ result[cb_key] = latest_version
89
101
  end
90
102
  end
91
103
  result
@@ -1,3 +1,5 @@
1
+ require 'git'
2
+
1
3
  module Promote
2
4
  class GitRepo
3
5
  def initialize(scope_path)
@@ -42,6 +44,25 @@ module Promote
42
44
  end
43
45
  end
44
46
 
47
+ def check_sync(source, dest)
48
+ warnings = []
49
+ if git.status.changed.to_h.keys.include? source
50
+ warnings << "File #{source} has uncommitted changes."
51
+ end
52
+ git.fetch
53
+ git.diff('master','origin/master').entries.each do |f|
54
+ if f.path == source || f.path == dest
55
+ warnings << "File #{f.path} is not synced with origin/master."
56
+ end
57
+ end
58
+ if warnings.count > 0
59
+ puts '*** Git sync issues: Fix the following issues and try again:'
60
+ warnings.each { |w| puts "* #{w}" }
61
+ exit 1
62
+ end
63
+ end
64
+
65
+
45
66
  attr_accessor :scope_path
46
67
 
47
68
  private
@@ -68,4 +89,4 @@ module Promote
68
89
  end
69
90
  end
70
91
  end
71
- end
92
+ end
@@ -0,0 +1,18 @@
1
+ module Promote
2
+ class NodeFinder
3
+ def initialize(query, config)
4
+ @query = query
5
+ @config = config
6
+ Chef::Config.reset
7
+ Chef::Config[:client_key] = config.client_key
8
+ Chef::Config[:chef_server_url] = config.chef_server_url
9
+ Chef::Config[:node_name] = config.node_name
10
+ @searcher = Chef::Search::Query.new
11
+ end
12
+
13
+ def search
14
+ results = @searcher.search(:node, @query)
15
+ results[0]
16
+ end
17
+ end
18
+ end
@@ -8,44 +8,68 @@ module Promote
8
8
  @config = config
9
9
  end
10
10
 
11
- def promote_to(source_environment, destination_environment)
11
+ def promote_to(source_environment, destination_environment, ui = nil)
12
12
  source = EnvironmentFile.new(source_environment, config)
13
13
  dest = EnvironmentFile.new(destination_environment, config)
14
-
14
+
15
+ if ui
16
+ ui.info "Cookbook constraints being promoted to #{destination_environment}"
17
+ ui.info source.cookbook_versions
18
+ end
19
+
15
20
  dest.write_cookbook_versions(source.cookbook_versions)
16
21
  end
17
22
 
23
+ def monitor_promotion(source_environment, destination_environments, probe_interval, ui = nil)
24
+ destination_environments.each do |env|
25
+ promote_to(source_environment, env, ui)
26
+ end
27
+ end
28
+
18
29
  def stage_promotion(promote_environment, destination_config, ui)
19
30
  ui.info "Staging artifacts from #{config.chef_server_url} to #{config.temp_directory}"
20
- FileUtils.rm_rf(config.temp_directory) if Dir.exist?(config.temp_directory)
21
- Dir.mkdir(config.temp_directory)
31
+ config.reset_temp_dir
22
32
 
23
33
  deploy_file = "/environments/#{promote_environment}.json"
24
34
  ui.info "Downloading #{deploy_file}..."
25
35
  download_files(config.temp_directory, deploy_file)
36
+
26
37
  ui.info "Downloading data bags..."
27
- download_files(config.temp_directory, "/data_bags/*")
38
+ config.bags.each do |bag|
39
+ download_files(config.temp_directory, "/data_bags/#{bag}")
40
+ end
41
+
42
+ ui.info "Downloading roles..."
43
+ download_files(config.temp_directory, "/roles")
28
44
 
29
- ui.info "Downloading cookbooks..."
45
+ ui.info "Downloading cookbooks missing on #{destination_config.chef_server_url}..."
46
+ ui.info "Fetching all versions of cookbooks on #{destination_config.chef_server_url}..."
30
47
  server_versions = Promote::Utils.chef_server_cookbooks(destination_config)
48
+ ui.info "Fetching the environment file content of #{promote_environment}"
31
49
  env_content = EnvironmentFile.new(promote_environment, Config.new({:repo_root => config.temp_directory}))
32
50
  env_content.cookbook_versions.each do |k,v|
51
+ ui.info "#{k}-#{v}"
33
52
  if !server_versions.has_key?(k) || (server_versions[k]["versions"].select {|version| version['version'] == v}).empty?
34
- ui.info "#{k}-#{v}"
53
+ ui.info "Cookbook missing. Downloading..."
35
54
  download_files(config.temp_directory, "/cookbooks/#{k}", v)
55
+ else
56
+ ui.info "Cookbook already exists."
36
57
  end
37
- end
58
+ end
38
59
  end
39
60
 
40
61
  def upload_to(promote_environment, destination_config, ui)
41
62
  ui.info "Uploading staged artifacts to #{destination_config.chef_server_url}"
42
63
  uploader = Uploader.new(destination_config)
64
+ Chef::Config[:verbosity] = 2
43
65
  ui.info("Uploading #{promote_environment} environment file...")
44
66
  uploader.upload_environment(promote_environment)
45
67
  ui.info("Uploading data bags...")
46
68
  uploader.upload_data_bags
69
+ ui.info("Uploading roles...")
70
+ uploader.upload_roles
47
71
  ui.info("Uploading cookbooks...")
48
- uploader.upload_cookbook_directory(destination_config.cookbook_directory)
72
+ uploader.upload_cookbook_directory(destination_config.cookbook_directory, ui)
49
73
  end
50
74
 
51
75
  private
@@ -55,12 +79,13 @@ module Promote
55
79
  pattern = Chef::ChefFS::FilePattern.new(path)
56
80
  local = Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(
57
81
  {
58
- 'environments' => ["#{local_root}/environments"],
82
+ 'environments' => ["#{local_root}/environments"],
59
83
  'data_bags' => ["#{local_root}/data_bags"],
84
+ 'roles' => ["#{local_root}/roles"],
60
85
  'cookbooks' => ["#{local_root}/cookbooks"]
61
86
  }
62
87
  )
63
- Chef::ChefFS::FileSystem.copy_to(pattern, fs_config.chef_fs, local, 1, Chef::Config)
88
+ Chef::ChefFS::FileSystem.copy_to(pattern, fs_config.chef_fs, local, nil, Chef::Config)
64
89
  end
65
90
  end
66
- end
91
+ end