├── .DS_Store ├── .gitignore ├── Jenkinsfile ├── README.md ├── images ├── global_pipeline_libraries.png └── pipeline_workflow.png ├── src └── org │ └── dxrf │ └── chef_automation.groovy ├── utilities ├── .rubocop.yml ├── create_data_bag_from_json.rb ├── generate_env_from_bu_json.rb └── update_global_env_pins.rb └── vars ├── parseGlobalEnvironments.groovy ├── promoteCookbook.groovy ├── promotePolicyfile.groovy ├── updateEnvironment.groovy └── uploadProfile.groovy /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmassardo/Chef-Jenkins-Library/a8e68601d7695085aed2bed0cadc6cb064315116/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Pre-req Tests') { 5 | parallel { 6 | stage('Check ChefDK') { 7 | steps { 8 | sh ''' 9 | # Make sure ChefDK is installed 10 | if [ ! $(which chef) ]; then 11 | echo "ChefDK is missing! Please visit https://downloads.chef.io." 12 | exit 1 13 | fi 14 | ''' 15 | } 16 | } 17 | stage('Check the chef_repo') { 18 | steps { 19 | sh ''' 20 | if [ ! -d "/var/lib/jenkins/chef_repo/.chef" ]; then 21 | mkdir -p /var/lib/jenkins/chef_repo/.chef 22 | fi 23 | if [ ! -d "/var/lib/jenkins/chef_repo/cookbooks" ]; then 24 | mkdir -p /var/lib/jenkins/chef_repo/cookbooks 25 | fi 26 | if [ ! -d "/var/lib/jenkins/chef_repo/environments" ]; then 27 | mkdir -p /var/lib/jenkins/chef_repo/environments 28 | fi 29 | if [ ! -f "/var/lib/jenkins/chef_repo/.chef/knife.rb" ]; then 30 | echo "WARNING" 31 | echo "We are creating empty files so the setup can proceed." 32 | echo "Replace the contents of /var/lib/jenkins/chef_repo/.chef/knife.rb with your information" 33 | touch /var/lib/jenkins/chef_repo/.chef/knife.rb 34 | fi 35 | if [ ! -f "/var/lib/jenkins/chef_repo/.chef/client.pem" ]; then 36 | touch /var/lib/jenkins/chef_repo/.chef/client.pem 37 | fi 38 | ''' 39 | } 40 | } 41 | } 42 | } 43 | stage('Verify Ruby files') { 44 | steps { 45 | sh 'chef exec rubocop utilities/.' 46 | } 47 | } 48 | stage('Stage Utilities') { 49 | steps { 50 | sh ''' 51 | if [ ! -d "/var/lib/jenkins/chef_automation" ]; then 52 | mkdir -p /var/lib/jenkins/chef_automation 53 | fi 54 | cp utilities/* /var/lib/jenkins/chef_automation/ 55 | ''' 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chef Jenkins Library 2 | 3 | This repository contains a Jenkins library for automating Chef things and all the utilities used to automate multi-tenant usage of Chef. By using this shared library, individual business unit pipelines are greatly simplified and pipeline changes only need to be made in one place. In addition, using this type of library ensures that all activities follow a standardized process. This process provides a self-service path for BU's to perform actions such as updating their environment's run list/version pins, creating/modifying data bags, and working with Chef-Vault. 4 | 5 | This is a visual representation of how the various pieces interact: 6 | ![pipeline workflow image](./images/pipeline_workflow.png "pipeline workflow image") 7 | 8 | `See the Chef_Test_BU (https://github.com/jmassardo/Chef-Jenkins-Test-BU) for a complete example of a BU repo.` 9 | 10 | ## Installation 11 | 12 | ### Pre-req Installation 13 | 14 | Perform these actions on the Jenkins Server. 15 | 16 | * Ensure Git is installed and updated 17 | 18 | ``` bash 19 | sudo yum -y install git 20 | ``` 21 | 22 | * Ensure [ChefDK](https://downloads.chef.io/chefdk) is installed. 23 | 24 | > NOTE: This process has only been tested on a single Jenkins master. Additional steps may be needed if using Jenkins slaves. 25 | 26 | ### Shared Library Installation 27 | 28 | Steps to install shared library in Jenkins: 29 | 30 | * [Fork this repository](https://www.github.com/jmassardo/Chef-Jenkins-Library/fork) 31 | * Browse to Jenkins configuration page `Home > Manage Jenkins > Configure System` i.e. `https://jenkins.domain.tld/configure` 32 | * Name the library (i.e. chef_automation) 33 | * Select `Modern SCM` then `GitHub` 34 | * Click `Add` 35 | * Select `Credentials` then select the `Chef_Jenkins_Library` repository. 36 | 37 | > NOTE: You will need a Personal Access Token from GitHub. Make sure this is configured as a Username/Password Credential in Jenkins. If you are forking to an organization, you may need to authorize the token for the org. 38 | 39 | ![Global pipeline Library screenshot](./images/global_pipeline_libraries.png "Screenshot of Jenkins config page") 40 | 41 | ### Library Pipeline Setup 42 | 43 | * Create a pipeline in [Jenkins BlueOcean](https://jenkins.domain.tld/blue) 44 | * Select GitHub 45 | * Select the appropriate organization 46 | * Select the repository (In this case, select Chef-Jenkins-Library.) 47 | * Click `Create Pipeline` 48 | 49 | > NOTE: You will need a Personal Access Token from GitHub. Make sure this is configured as a Username/Password Credential in Jenkins. If you are forking to an organization, you may need to authorize the token for the org. 50 | 51 | ## Usage 52 | 53 | ### Add/Modify/Remove Feature to Library 54 | 55 | All of the functions in the library are contained in their own file in the `/vars/` folder. In this library, we are essentially using these functions to hold our pipeline steps from a Jenkinsfile. To create new (or modify existing), it's better create a new repo with a standalone `Jenkinsfile` and do the testing there. This allows you to use the editor in Blue Ocean to build/modify the pipeline. Once it's functioning as expected, you can copy the steps into the `pipeline { }` section in `groovy` file as shown below. 56 | 57 | * Open (or create) `verbNoun.groovy` file 58 | * This is the format of the file: 59 | 60 | ``` groovy 61 | def call(String myString){ 62 | pipeline { 63 | agent any 64 | stages { 65 | // add pipeline stages here 66 | } 67 | environment { 68 | // add environment variables here 69 | // i.e. my_var = "${myString}" 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | ### Jenkinsfile 76 | 77 | To use a function, create a `Jenkinsfile` in the root of the repository using this format: 78 | 79 | ``` groovy 80 | @Library('chef_automation') _ 81 | 82 | // call function with defaults 83 | myFunction() 84 | 85 | // call function and pass parameters 86 | myFunction 'my_var' 87 | ``` 88 | 89 | > NOTE: If you elected to call the library by a different name in the setup steps, you'll need to reference that new name here instead of the `chef_automation` name. 90 | 91 | ## Available Library Functions 92 | 93 | ### Parse Global Environments 94 | 95 | The `parseGlobalEnvironments` function performs the following steps: 96 | 97 | * Stage Environment Files 98 | * Parse and update Environments on the Chef server 99 | 100 | Usage: 101 | 102 | ``` groovy 103 | @Library('chef_automation') _ 104 | 105 | parseGlobalEnvironments() 106 | ``` 107 | 108 | > NOTE: The parseGlobalEnvironments() method should only be used in your main Global_Environments repository. 109 | 110 | ### Promote Cookbook 111 | 112 | The `promoteCookbook` function performs the following steps: 113 | 114 | * Verify Stage 115 | * Lint (foodcritic) 116 | * Syntax (cookstyle) 117 | * Unit (chefspec) 118 | * Smoke (test kitchen) 119 | * Wait for approval 120 | * Upload 121 | * Chef server 122 | 123 | Usage: 124 | 125 | ``` groovy 126 | @Library('chef_automation') _ 127 | 128 | promoteCookbook 'cookbook_name' 129 | ``` 130 | 131 | > NOTE: Make sure the cookbook name specified matches the folder name of the actual cookbook otherwise this step will fail. 132 | 133 | ### Promote Policyfile 134 | 135 | This function is based on the policyfile cookbook pattern outlined [here](http://blog.jerryaldrichiii.com/chef_infra/2019/05/28/using-policyfile-cookbooks.html). While this pattern isn't 100% pure policyfiles, it is a easy to consume stepping stone in that direction. It provides many of the benefits and eliminates a number of issues in the Berkshelf/Roles/Environments patterns. The pattern also has the benefit of allowing one to use one's existing Chef Infra Server without breaking the existing patterns in use. 136 | 137 | The `promotePolicyfile` function performs the following steps: 138 | 139 | * Verify Stage 140 | * Lint (foodcritic) 141 | * Syntax (cookstyle) 142 | * Unit (chefspec) 143 | * Generate Lock File 144 | * Push policy to `dev` policy group 145 | * Wait for approval 146 | * Push policy to `stg` policy group 147 | * Wait for approval 148 | * Push policy to `prod` policy group 149 | 150 | Usage: 151 | 152 | ``` groovy 153 | @Library('chef_automation') _ 154 | 155 | promotePolicyfile 'cookbook_name' 156 | ``` 157 | 158 | > NOTE: Make sure the cookbook name specified matches the folder name of the actual cookbook otherwise this step will fail. 159 | 160 | ### Update Environment 161 | 162 | the `updateEnvironment` function performs the following steps: 163 | 164 | * Merges BU managed environment file with globally managed environment file 165 | * Validates that BU specified cookbooks/versions exist on Chef server 166 | * Compares merged environment file with environment on the Chef server and uploads merge file if changes are detected. 167 | * Parses the `data_bags`folder for json files and creates/updates data bags and data bag items. 168 | 169 | Usage: 170 | 171 | ``` groovy 172 | @Library('chef_automation') _ 173 | 174 | updateEnvironment() 175 | ``` 176 | 177 | ### Upload Profile 178 | 179 | the `uploadProfile` function performs the following steps: 180 | 181 | * Performs an initial check on the profile `inspec check .` 182 | * Logs into Automate using the data_token 183 | > Set the following environment variables in Jenkins (Manage Jenkins > Manage Nodes > master > Configure > Environment Variables): 184 | > * AUTOMATE_SERVER_NAME="https://automate.domain.tld" 185 | > * AUTOMATE_USER="profile_admin" 186 | > * AUTOMATE_ENTERPRISE="my_ent" 187 | > * DC_TOKEN="SuperLongAndSecretDataCollectorTokenThatShouldntBeShared..." 188 | * Uploads profile to Automate `inspec compliance upload . --overwrite` 189 | 190 | > NOTE: The `--overwrite` option doesn't do what one would think it does. Using this option allow Inspec to upload additional versions of the same profile. 191 | 192 | Usage: 193 | 194 | ``` groovy 195 | @Library('chef_automation') _ 196 | 197 | uploadProfile() 198 | ``` 199 | 200 | ## Utilities 201 | 202 | ### Create_data_bag_from_json 203 | 204 | This utility creates/modifies data bags from a BU specified folder. It uses the folder name as the name of the data bag, then creates the data bag items from each of the files within the folder. 205 | 206 | Example: 207 | 208 | ``` bash 209 | ruby create_data_bag_from_json.rb -f /path/to/my/folder -k /path/to/my/knife.rb 210 | ``` 211 | 212 | Command line options: 213 | 214 | ``` bash 215 | Usage: create_data_bag_from_json.rb [options] 216 | -f, --folder /path/to/my/folder Folder Name 217 | -k, --knife /path/to/my/knife.rb Knife Path 218 | -h, --help Displays Help 219 | ``` 220 | 221 | Example data bag folder structure: 222 | 223 | ``` bash 224 | data_bags 225 | ├── admin_users 226 | │   ├── admin1.json 227 | │   ├── admin2.json 228 | │   └── admin3.json 229 | ├── limited_users 230 | │   ├── user1.json 231 | │   ├── user2.json 232 | │   └── user3.json 233 | └── op_users 234 | ├── op1.json 235 | ├── op2.json 236 | └── op3.json 237 | ``` 238 | 239 | ### Generate_env_from_bu_json 240 | 241 | This utility parses the bu provided environment files along with the corresponding global environment file then merges the cookbook, default attributes, and override attributes. It then verifies that all of the cookbooks and versions exist on the Chef server. Finally it uploads the environment file to the Chef server via the API. 242 | 243 | To change a default version pin, submit a PR to your `global_environments` repository with an update to the json file for the specific environment. 244 | 245 | Example: 246 | 247 | ``` bash 248 | ruby generate_env_from_bu_json.rb -k /path/to/my/knife.rb 249 | ``` 250 | 251 | Command line options: 252 | 253 | ``` bash 254 | Usage: generate_env_from_bu_json.rb [options] 255 | -k, --knife /path/to/my/knife.rb Knife Path 256 | -h, --help Displays Help 257 | ``` 258 | 259 | ### Update_global_env_pins 260 | 261 | This utility parses all the environment JSON files in the `global_envs` folder then compares them to the current environment configuration on the Chef Server. If it detects a delta, it updates the Chef Server with the newest version pins. 262 | 263 | Example: 264 | 265 | ``` bash 266 | ruby Update_global_env_pins.rb -f /path/to/my/folder -k /path/to/my/knife.rb 267 | ``` 268 | 269 | Command line options: 270 | 271 | ``` bash 272 | Usage: Update_global_env_pins.rb [options] 273 | -f, --folder /path/to/my/folder Folder Name 274 | -k, --knife /path/to/my/knife.rb Knife Path 275 | -h, --help Displays Help 276 | ``` 277 | -------------------------------------------------------------------------------- /images/global_pipeline_libraries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmassardo/Chef-Jenkins-Library/a8e68601d7695085aed2bed0cadc6cb064315116/images/global_pipeline_libraries.png -------------------------------------------------------------------------------- /images/pipeline_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmassardo/Chef-Jenkins-Library/a8e68601d7695085aed2bed0cadc6cb064315116/images/pipeline_workflow.png -------------------------------------------------------------------------------- /src/org/dxrf/chef_automation.groovy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmassardo/Chef-Jenkins-Library/a8e68601d7695085aed2bed0cadc6cb064315116/src/org/dxrf/chef_automation.groovy -------------------------------------------------------------------------------- /utilities/.rubocop.yml: -------------------------------------------------------------------------------- 1 | # disable annoying metric cops 2 | Metrics/LineLength: 3 | Enabled: false 4 | Metrics/BlockLength: 5 | Enabled: false 6 | Style/ConditionalAssignment: 7 | Enabled: false -------------------------------------------------------------------------------- /utilities/create_data_bag_from_json.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'optparse' 3 | require 'chef' 4 | require 'pathname' 5 | 6 | # Get the options from the command line 7 | options = { folder: nil, knife: nil } 8 | 9 | parser = OptionParser.new do |opts| 10 | opts.banner = 'Usage: create_data_bag_from_json.rb [options]' 11 | opts.on('-f', '--folder /path/to/my/folder', 'Folder Name') do |folder| 12 | options[:folder] = folder 13 | end 14 | 15 | opts.on('-k', '--knife /path/to/my/knife.rb', 'Knife Path') do |knife| 16 | options[:knife] = knife 17 | end 18 | 19 | opts.on('-h', '--help', 'Displays Help') do 20 | puts opts 21 | exit 22 | end 23 | end 24 | 25 | parser.parse! 26 | 27 | # Ask if the user missed an option 28 | if options[:folder].nil? 29 | print 'Enter path to data bag folder: ' 30 | options[:folder] = gets.chomp 31 | end 32 | if options[:knife].nil? 33 | print 'Enter path to knife.rb: ' 34 | options[:knife] = gets.chomp 35 | end 36 | 37 | # Setup connection to Chef Server 38 | Chef::Config.from_file(options[:knife]) 39 | rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url]) 40 | 41 | # Test and make sure the directory that was passed actually exists... 42 | abort("ERROR: File: #{options[:folder]}/data_bags does not exist!") unless Dir.exist?("#{options[:folder]}/data_bags") 43 | 44 | Dir["#{options[:folder]}/data_bags/*/*.json"].each do |item| 45 | data_bag_name = File.basename(File.dirname(item)) 46 | data_bag_item = JSON.parse(File.read(item)) 47 | 48 | # See if the data bag exists, if not, create it. 49 | begin 50 | rest.get_rest("/data/#{data_bag_name}") 51 | puts "Found #{data_bag_name}" 52 | rescue StandardError 53 | puts "Data Bag #{data_bag_name} not found, attempting to create" 54 | json = { name: data_bag_name } 55 | begin 56 | rest.post_rest('/data', json) 57 | puts "Successfully created #{data_bag_name}" 58 | rescue StandardError 59 | puts "Failed to create #{data_bag_name}" 60 | end 61 | end 62 | 63 | # Now that the data bag is there, let's see if it needs updating 64 | begin 65 | current_data_bag_item = rest.get_rest("/data/#{data_bag_name}/#{data_bag_item['id']}") 66 | puts "Found Data Bag Item: #{data_bag_item['id']}" 67 | if current_data_bag_item == data_bag_item 68 | puts 'Data bag item is current, no need to update.' 69 | else 70 | begin 71 | rest.put_rest("/data/#{data_bag_name}/#{data_bag_item['id']}", data_bag_item) 72 | puts "Successfully updated #{data_bag_name}/#{data_bag_item['id']}" 73 | rescue StandardError 74 | puts "Failed to update #{data_bag_name}/#{data_bag_item['id']}" 75 | end 76 | end 77 | rescue StandardError 78 | begin 79 | rest.post_rest("/data/#{data_bag_name}", data_bag_item) 80 | puts "Successfully added #{data_bag_name}/#{data_bag_item['id']}" 81 | rescue StandardError 82 | puts "Failed to add #{data_bag_name}/#{data_bag_item['id']}" 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /utilities/generate_env_from_bu_json.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'optparse' 3 | require 'chef' 4 | 5 | # Get the options from the command line 6 | options = { knife: nil } 7 | 8 | parser = OptionParser.new do |opts| 9 | opts.banner = 'Usage: generate_env_from_bu_json.rb [options]' 10 | opts.on('-k', '--knife /path/to/my/knife.rb', 'Knife Path') do |knife| 11 | options[:knife] = knife 12 | end 13 | 14 | opts.on('-h', '--help', 'Displays Help') do 15 | puts opts 16 | exit 17 | end 18 | end 19 | 20 | parser.parse! 21 | 22 | # Ask if the user missed an option 23 | if options[:knife].nil? 24 | print 'Enter path to knife.rb: ' 25 | options[:knife] = gets.chomp 26 | end 27 | 28 | # Setup connection to Chef Server 29 | Chef::Config.from_file(options[:knife]) 30 | rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url]) 31 | 32 | puts 'Attempting to load JSON files from the ./environments folder of the repository.' 33 | 34 | # Loop through all json files in the environments folder 35 | Dir['./environments/*.json'].each do |item| 36 | # Parse the file and merge 37 | 38 | begin 39 | bu_env = JSON.parse(File.read(item)) 40 | env_name = bu_env['name'] 41 | puts "Successfully loaded the #{env_name} environment." 42 | rescue StandardError 43 | abort("Failed to load the #{env_name} environment.") 44 | end 45 | 46 | begin 47 | global_env = JSON.parse(File.read("/var/lib/jenkins/chef_automation/global_envs/#{env_name}.json")) 48 | rescue StandardError 49 | abort("Can't open /var/lib/jenkins/chef_automation/global_envs/#{env_name}.json.") 50 | end 51 | 52 | begin 53 | env = Chef::Mixin::DeepMerge.deep_merge(global_env, bu_env) 54 | puts "Successfully merged the #{env_name} environment." 55 | puts 'Result of merge:' 56 | puts JSON.pretty_generate(env) 57 | rescue StandardError 58 | abort("Failed to merge the #{env_name} environment.") 59 | end 60 | 61 | # Compare env with what's on the Chef server 62 | result = rest.get_rest("/environments/#{env_name}") 63 | puts "Result from Chef server for the #{env_name} environment." 64 | puts JSON.pretty_generate(result) 65 | if env == result 66 | puts "No change for the #{env_name} environment" 67 | else 68 | puts "Change detected in #{env_name}" 69 | # Verify cookbooks and versions 70 | cookbooks = Marshal.load(Marshal.dump(env)) 71 | cookbooks['cookbook_versions'].each do |cookbook, version| 72 | # Strip out operator characters from the json data 73 | version.tr!('=', '') if version.include?('=') 74 | version.tr!('>', '') if version.include?('>') 75 | version.tr!('<', '') if version.include?('<') 76 | version.tr!('~', '') if version.include?('~') 77 | version.tr!(' ', '') if version.include?(' ') 78 | 79 | begin 80 | rest.get_rest("/cookbooks/#{cookbook}/#{version}") 81 | puts "SUCCESS! Found #{cookbook}/#{version}!" 82 | rescue StandardError 83 | abort("FAILURE! Couldn't find #{cookbook}/#{version} on Chef server.") 84 | end 85 | end 86 | 87 | # Save a copy of the validated environment file to disk 88 | File.write("/var/lib/jenkins/chef_repo/environments/#{env_name}.json", JSON.pretty_generate(env)) 89 | 90 | # Attempt to save the change to the Chef server. 91 | begin 92 | rest.put_rest("/environments/#{env_name}", env) 93 | puts "Successfully updated #{env_name}" 94 | rescue StandardError 95 | abort("Failed to update #{env_name}") 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /utilities/update_global_env_pins.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'optparse' 3 | require 'chef' 4 | require 'pathname' 5 | # Get the options from the command line 6 | options = { folder: nil, knife: nil } 7 | 8 | parser = OptionParser.new do |opts| 9 | opts.banner = 'Usage: update_global_env_pins.rb [options]' 10 | opts.on('-f', '--folder /path/to/my/folder', 'Folder Name') do |folder| 11 | options[:folder] = folder 12 | end 13 | 14 | opts.on('-k', '--knife /path/to/my/knife.rb', 'Knife Path') do |knife| 15 | options[:knife] = knife 16 | end 17 | 18 | opts.on('-h', '--help', 'Displays Help') do 19 | puts opts 20 | exit 21 | end 22 | end 23 | 24 | parser.parse! 25 | 26 | # Ask if the user missed an option 27 | if options[:folder].nil? 28 | print 'Enter path to project folder: ' 29 | options[:folder] = gets.chomp 30 | end 31 | if options[:knife].nil? 32 | print 'Enter path to knife.rb: ' 33 | options[:knife] = gets.chomp 34 | end 35 | 36 | # Setup connection to Chef Server 37 | Chef::Config.from_file(options[:knife]) 38 | rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url]) 39 | 40 | Dir["#{options[:folder]}/global_envs/*.json"].each do |item| 41 | bu_env = JSON.parse(File.read(item)) 42 | env_name = File.basename(item, File.extname(item)) 43 | puts "Processing the #{env_name} environment file." 44 | 45 | # # If the env doesn't exist, create it 46 | # begin 47 | # result = rest.get_rest("/environments/#{env_name}") 48 | # rescue Net::HTTPServerException => e 49 | # if e.response.code == '404' 50 | # puts "Attempting to create the #{env_name} environment" 51 | # rest.post_rest("/environments/#{env_name}", bu_env) 52 | # result = rest.get_rest("/environments/#{env_name}") 53 | # puts "Successfully created the #{env_name} environment" 54 | # else 55 | # abort("failed to create the #{env_name} environment") 56 | # end 57 | # end 58 | 59 | result = rest.get_rest("/environments/#{env_name}") 60 | env = Chef::Mixin::DeepMerge.deep_merge(bu_env, result) 61 | 62 | # Pull the env from the chef server again b/c 63 | # deep_merge is doing something weird with the variables.... 64 | result = rest.get_rest("/environments/#{env_name}") 65 | 66 | if env == result 67 | puts "No change for the #{env_name} environment" 68 | else 69 | puts "Change detected in #{env_name}" 70 | begin 71 | rest.put_rest("/environments/#{env_name}", env) 72 | puts "Successfully updated #{env_name}" 73 | rescue StandardError 74 | abort("Failed to update #{env_name}") 75 | end 76 | 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /vars/parseGlobalEnvironments.groovy: -------------------------------------------------------------------------------- 1 | def call(){ 2 | pipeline { 3 | agent any 4 | stages { 5 | stage('Stage Environments') { 6 | steps { 7 | sh ''' 8 | if [ ! -d "/var/lib/jenkins/chef_automation/global_envs" ]; then 9 | mkdir -p /var/lib/jenkins/chef_automation/global_envs 10 | fi 11 | cp * /var/lib/jenkins/chef_automation/global_envs/ 12 | ''' 13 | } 14 | } 15 | stage('Publish Environments to Production') { 16 | steps { 17 | input 'Publish Enviornments to Production Chef Server?' 18 | sh 'chef exec ruby /var/lib/jenkins/chef_automation/update_global_env_pins.rb -k /var/lib/jenkins/chef_repo/.chef/knife.rb -f /var/lib/jenkins/chef_automation' 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /vars/promoteCookbook.groovy: -------------------------------------------------------------------------------- 1 | def call(String projName){ 2 | pipeline { 3 | agent any 4 | stages { 5 | stage('Pre-Cleanup') { 6 | steps { 7 | sh ''' 8 | rm -rf ~/chef_repo/cookbooks/${proj_name} 9 | ''' 10 | } 11 | } 12 | stage('Verify') { 13 | parallel { 14 | stage('Lint') { 15 | steps { 16 | sh 'chef exec foodcritic .' 17 | } 18 | } 19 | stage('Syntax') { 20 | steps { 21 | sh 'chef exec cookstyle .' 22 | } 23 | } 24 | stage('Unit') { 25 | steps { 26 | sh 'chef exec rspec .' 27 | } 28 | } 29 | } 30 | } 31 | stage('Smoke') { 32 | steps { 33 | sh '# kitchen test' 34 | } 35 | } 36 | stage('Stage files') { 37 | when { 38 | // Only execute when on the master branch 39 | expression { env.BRANCH_NAME == 'master' } 40 | } 41 | steps { 42 | sh ''' 43 | mkdir -p ~/chef_repo/cookbooks/${proj_name} 44 | cp -r * ~/chef_repo/cookbooks/${proj_name} 45 | ''' 46 | } 47 | } 48 | stage('Approval') { 49 | when { 50 | // Only execute when on the master branch 51 | expression { env.BRANCH_NAME == 'master' } 52 | } 53 | steps { 54 | input 'Release to Production?' 55 | } 56 | } 57 | stage('Upload') { 58 | when { 59 | // Only execute when on the master branch 60 | expression { env.BRANCH_NAME == 'master' } 61 | } 62 | steps { 63 | sh ''' 64 | cd ~/chef_repo/cookbooks/${proj_name} 65 | berks install 66 | berks upload --ssl-verify=false 67 | ''' 68 | } 69 | } 70 | stage('Post-Cleanup') { 71 | when { 72 | // Only execute when on the master branch 73 | expression { env.BRANCH_NAME == 'master' } 74 | } 75 | steps { 76 | sh ''' 77 | rm -rf ~/chef_repo/cookbooks/${proj_name} 78 | ''' 79 | } 80 | } 81 | } 82 | environment { 83 | proj_name = "${projName}" 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /vars/promotePolicyfile.groovy: -------------------------------------------------------------------------------- 1 | def call(String projName){ 2 | pipeline { 3 | agent any 4 | stages { 5 | stage('Pre-Cleanup') { 6 | steps { 7 | sh ''' 8 | rm -rf ~/chef_repo/cookbooks/${proj_name} 9 | ''' 10 | } 11 | } 12 | stage('Verify') { 13 | parallel { 14 | stage('Lint') { 15 | steps { 16 | sh 'chef exec foodcritic .' 17 | } 18 | } 19 | stage('Syntax') { 20 | steps { 21 | sh 'chef exec cookstyle .' 22 | } 23 | } 24 | stage('Unit') { 25 | steps { 26 | sh 'chef exec rspec .' 27 | } 28 | } 29 | } 30 | } 31 | stage('Smoke') { 32 | steps { 33 | sh '# kitchen test' 34 | } 35 | } 36 | stage('Stage files') { 37 | when { 38 | // Only execute when on the master branch 39 | expression { env.BRANCH_NAME == 'master' } 40 | } 41 | steps { 42 | sh ''' 43 | mkdir -p ~/chef_repo/cookbooks/${proj_name} 44 | cp -r * ~/chef_repo/cookbooks/${proj_name} 45 | ''' 46 | } 47 | } 48 | stage('Generate Lock File') { 49 | when { 50 | // Only execute when on the master branch 51 | expression { env.BRANCH_NAME == 'master' } 52 | } 53 | steps { 54 | sh ''' 55 | cd ~/chef_repo/cookbooks/${proj_name} 56 | chef install 57 | ''' 58 | } 59 | } 60 | stage('Dev Deployment') { 61 | when { 62 | // Only execute when on the master branch 63 | expression { env.BRANCH_NAME == 'master' } 64 | } 65 | steps { 66 | sh ''' 67 | cd ~/chef_repo/cookbooks/${proj_name} 68 | chef push dev 69 | ''' 70 | } 71 | } 72 | stage('Approve Staging Deployment?') { 73 | when { 74 | // Only execute when on the master branch 75 | expression { env.BRANCH_NAME == 'master' } 76 | } 77 | steps { 78 | input "Deploy ${proj_name} to Staging?" 79 | } 80 | } 81 | stage('Staging Deployment') { 82 | when { 83 | // Only execute when on the master branch 84 | expression { env.BRANCH_NAME == 'master' } 85 | } 86 | steps { 87 | sh ''' 88 | cd ~/chef_repo/cookbooks/${proj_name} 89 | chef push stg 90 | ''' 91 | } 92 | } 93 | stage('Approve Prod Deployment?') { 94 | when { 95 | // Only execute when on the master branch 96 | expression { env.BRANCH_NAME == 'master' } 97 | } 98 | steps { 99 | input "Deploy ${proj_name} to Prod?" 100 | } 101 | } 102 | stage('Prod Deployment') { 103 | when { 104 | // Only execute when on the master branch 105 | expression { env.BRANCH_NAME == 'master' } 106 | } 107 | steps { 108 | sh ''' 109 | cd ~/chef_repo/cookbooks/${proj_name} 110 | chef push prod 111 | ''' 112 | } 113 | } 114 | stage('Post-Cleanup') { 115 | when { 116 | // Only execute when on the master branch 117 | expression { env.BRANCH_NAME == 'master' } 118 | } 119 | steps { 120 | sh ''' 121 | rm -rf ~/chef_repo/cookbooks/${proj_name} 122 | ''' 123 | } 124 | } 125 | } 126 | environment { 127 | proj_name = "${projName}" 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /vars/updateEnvironment.groovy: -------------------------------------------------------------------------------- 1 | def call(){ 2 | pipeline { 3 | agent any 4 | stages { 5 | stage('Process Environment file(s)') { 6 | steps { 7 | sh 'chef exec ruby ~/chef_automation/generate_env_from_bu_json.rb -k ~/chef_repo/.chef/knife.rb' 8 | } 9 | } 10 | stage('Process Data Bags') { 11 | steps { 12 | sh 'chef exec ruby ~/chef_automation/create_data_bag_from_json.rb -f . -k ~/chef_repo/.chef/knife.rb' 13 | } 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /vars/uploadProfile.groovy: -------------------------------------------------------------------------------- 1 | def call(){ 2 | pipeline { 3 | agent any 4 | stages { 5 | stage('Validate Profile') { 6 | steps { 7 | sh 'inspec check .' 8 | } 9 | } 10 | stage('Log into Automate') { 11 | steps { 12 | sh '''#!/bin/sh -e 13 | inspec compliance login $AUTOMATE_SERVER_NAME --insecure --user=$AUTOMATE_USER --ent=$AUTOMATE_ENTERPRISE --dctoken=$DC_TOKEN''' 14 | } 15 | } 16 | stage('Upload Profile') { 17 | steps { 18 | sh 'inspec compliance upload . --overwrite' 19 | } 20 | } 21 | stage('Log out of Automate') { 22 | steps { 23 | sh 'inspec compliance logout' 24 | } 25 | } 26 | } 27 | } 28 | } --------------------------------------------------------------------------------