Python : Read and Update helm chart

Recently I was working on a release pipeline where the helm chart of 30+ environments need to be updated in git with the new chart versions from Jenkins input.

Here the helm chart was in yaml format and it was a umbrella chart and individual service chart was needed to be updated from Jenkins.

The umbrella chart file looks like this. https://raw.githubusercontent.com/divyaimca/my_projects/main/update_helm_chart/chart.yaml

apiVersion: v2
description: Helm chart to deploy application NG
name:app-main
version: 0.0.1
dependencies:
- name: service-a
  version: 0.1.014bf574
  repository: '@helm-repo'
  tags:
  - application
  enabled: true
- name: service-b
  version: 0.1.014bf575
  repository: '@helm-repo'
  tags:
  - application
  enabled: true
- name: service-c
  version: 0.1.014bf475
  repository: '@helm-repo'
  tags:
  - application
  enabled: true
- name: service-d
  version: 0.1.024bf575
  repository: '@helm-repo'
  tags:
  - application
  enabled: true
- name: service-e
  version: 0.1.014bf559
  repository: '@helm-repo'
  tags:
  - application
  enabled: true

Here you can see there are 5 dependent services and each version needs to be updated from

I used python module pyyaml.

Here is the code that is used in one stage to achieve this task.

https://raw.githubusercontent.com/divyaimca/my_projects/main/update_helm_chart/helmchart_parser.py

The function takes the input as chart.yaml file path and the subchart and versions in keyword arguments format. Refer the full code from the above link.

e.g.

    update_helm_chart("./chart.yaml",service-b="0.2.574",service-d="0.2.585",service-e="0.2.576")    

chef attribute : avoiding “undefined method `[]’ for nil:NilClass” error

In chef, when a nested attribute that might not exist/or not crated yet, you can use rescue as a modifier to an if statement.

For example, assuming that only some of your nodes have node[‘install_wls’][‘isManaged’]defined:

if node['install_wls']['isManaged']
do the stuff
end rescue NoMethodError

will avoid the chef run bailing with “undefined method `[]’ for nil:NilClass” error.

Though there are any legit method errors within your if block to handle empty/nil/blank attributes, you’ve just caught an exception you shouldn’t.

I use this in cookbooks that have extra functionality if some dependency of other component recipe happens to be installed. For example, the odi cookbook  depend on wls or odi depends on oracldb.

Knife remove all recipes from the run_list

There is a simple knife command which can be used to remove all recipes from the run_list of all nodes in a environment.

For this you have to create a dummy role like suppose dummy_role.

#knife role create dummy_role

Once you create the dummy role, assign this role to all the nodes in the environment using the below knife command.

 

#knife exec -E ‘nodes.transform(:all) {|n| n.run_list([“role[dummy_role]”])}'”

 

Now this command would remove all the recipes added to the run_list of the nodes in the environment and add dummy_role to the run_list.

We can remove the dummy_role from the run_list of all the nodes and make it empty.

#knife exec -E ‘nodes.find(“role:dummy_role”) {|n|  n.run_list.remove(“role[dummy_role]”); n.save}’

This is helpful in scenarios where you need to remove all the recipes irrespective of the nodes in the environment and start adding fresh.

Chef – Create encrypted data bag and keep secrets

Sometimes we have to deal with global variables like User passwords, database password, API Keys, middleware boot properties in our chef recipes which shouldn’t be exposed outside.

One solution is we have to keep all the secrets in a data bag and encrypt them using a random secret key and later distribute the key to other node where the secrets are accessed.

diagram_01.png

The other solution if using chef-vault which we will cover in a later topics.

First we have to create  a random encryption key:

openssl rand -base64 512 | tr -d ‘\r\n’ > rev_secret_key 

We have to use this secret key now to encrypt the databag item “revpass” in data bag “rev_secret”.

[code language=”bash”]

export EDITOR=vi
knife data bag create  −−secret-file ./rev_secret_key rev_secret revpass

[/code]

This will open the vi editor with JSON data would be:

{
“id”: “revpass”
}

Now add your secrets here in json format which will be:

{
“id”: “revpass”,

“boot_pass”: “bootpassword”,
“db_pass”: “dbpassword”,
}

Save and exit.

Show the encrypted contents of your databag:

knife data bag show rev_secret revpass

Show the decrypted contents of your databag:

knife data bag show −−secret-file=./rev_secret_key rev_secret revpass

For your chef clients to be able to decrypt the databag when needed, just copy over the secret key (replace client-node with your IP/node name):

scp ./rev_secret_key client-node:/etc/chef/encrypted_data_bag_secret

OR

keep it in ~/.chef directory ad update settings in knife.rb file.

encrypted_data_bag_secret “~/.chef/encrypted_data_bag_secret”

Accessing secret in recipe:

OR Mention the secret key in recipe as below.

In your db recipe, add below line to

secret = Chef::EncryptedDataBagItem.load_secret(“/var/chef/cache/cookbooks/revrec-chef/files/default/revrec_secret_key”)

passwords = Chef::EncryptedDataBagItem.load(“rev_secret “, “revpass”)

dbpasswd = passwds[“db_pass”]

Use it inside a resource:

………

oradb_password = “#{dbpasswd}”

……..

Or keep it in a template how ever its suitable.

Note : If you are using password in a template turn of logging by adding this attribute in template resource:

……

sensitive true

…..

Chef – Deleting existing attributes

Sometimes we face situation like :

  1. May need to remove some persistent attributes which we set a flag after some work is done
  2. May be we set some attributes wrong so need to remove and reset the existing attribute.

The attribute may be set in attributes/default.rb OR insdie recipe with node normal attribute OR node set OR node override attributes.

There is a nice tool of chef (chef exec)  with which we can transform the attributes.

Usage:

Suppose a node with attribute hierarchy :

“normal” :[

{

“install_oradb” :  [

“is_installed” : “True”,

“is_running” :”True”

}

]

knife exec -E "nodes.transform(:all) {|n| n.normal_attrs[:install_oradb].delete(:is_installed) rescue nil }"
knife exec -E "nodes.transform(:all) {|n| n.normal_attrs[:install_oradb].delete(:is_running) rescue nil }"

 

Here we can replace normal_attrs with override_attrs OR default_attrs as required.

This will remove the attributes from all the nodes.

 

Suppose we want remove the attributes from a particular node. In this case you have to get search the node name from the chef solr index and replace the all with the required node.

e.g.

knife exec -E "nodes.transform(:node2) {|n| n.normal_attrs[:install_oradb].delete(:is_installed) rescue nil }"
knife exec -E "nodes.transform(:node2) {|n| n.normal_attrs[:install_oradb].delete(:is_running) rescue nil }"

Writing chef Library

In many cases we have to reuse same code again and again in our recipes. So to reduce this we can write our own library module and reuse it’s methods whenever required. This can help us use our own custom methods.

Like in my previous post we have to access the environment variables in many recipes. So we can use the same code as our library to create a module (lets say ProdEnvSetup)

 

  1. Create a library file under library directory of cook-book.(cook-book/library/envsetup.rb) and add below code.

 

[code language=”ruby”]module ProdEnvSetup
def setupenv()
hash1 = {}
File.open(“/u01/data/wor/app/conf/conf.prop”) do |fp|
fp.each do |line|
key, value = line.chomp.split(“=”,2)
hash1[key] = value
end
end

hash1.each do |key,value|
skey = “#{key.to_s}”.gsub(/\s|”|’/, ”)
svalue = “#{value.to_s}”.gsub(/\s|”|’/, ”)
ENV[skey] = svalue
end
end
end
end[/code]

2. Using library inside recipe ( 1st way )

So if the same environment variables are required inside a recipe, we can directly include the module inside our recipe.

Use below code inside begining of recipe:

[code language=”ruby”]class Chef::Recipe
include ProdEnvSetup
end
setupenv() [/code]

2. Using library ( 2nd way )

We can add additional encapsulation to our module by keeping it inside a recipe and include that recipe in every recipe, wherever its required.

create a recipe setupenv.rb and add above codes in 1st way(above)

and include it in other recipes like this:

[code language=”ruby”]include “prod-multiode-cookbook:setupenv”

[/code]

 


			

Chef/ruby way – Read a file and expose as environment variable

Many time we have to read a property file in in which the variable and value are comma separated and we have to set those in our environment variable to execute certain recipes.

 

e.g. property file (/u01/data/wor/app/conf/conf.prop)

ops_home = ‘/u01/data/work/app/ops-home-1.2.30’

node_instance = ‘/u01/data/work/app’

 

So here we have to read the file , create a hash and then save the LHS as key and RHS as value. Then we are good to expose them as environment variable.

Note : This is the approach I used, there may be other solution available.

Here the properties are = separated. It can be any separator.

This is a reusable function and can be called where ever required.

[code language=”ruby”]def setupenv()
hash1 = {}
File.open("/u01/data/wor/app/conf/conf.prop") do |fp|
fp.each do |line|
key, value = line.chomp.split("=",2)
hash1[key] = value
end
end</pre>
hash1.each do |key,value|
skey = "#{key.to_s}".gsub(/\s|"|’/, ”)
svalue = "#{value.to_s}".gsub(/\s|"|’/, ”)
ENV[skey] = svalue
end
end
end
end[/code]

 

Here setupenv( ) can be called anywhere the ENV variables are required.

Note : Here gsub(/\s|”|’/, ”) is used to trim  leading and trailing space, single quote, double quote of the key and value.

 

 

Puppet Quick Tutorial with examples

Puppet Version Used : 3.8

Distro Used : RHEL,CentOS, OEL

1. Puppet master :

Get the repo with below link.
rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm

install the master
yum install puppet-server

Enter the master server hostname in the puppet.conf file under [main]
dns_alt_names = puppet,puppetmaster01,vmf0270,vmf0270.us.xxx.com

certname = vmf0270.us.xxx.com
server = vmf0270.us.xxx.com
environment = prod
runinterval = 1h
strict_variables = true

If this is the only puppet master in your deployment, or if it will be acting as the CA server :
puppet master –verbose –no-daemonize

2. Install puppet agent;

Get the repo –
rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm
Install the puppet package
yum install puppet /

For upgrade the existing package –
puppet resource package puppet ensure=latest and restart the service

Update puppet.conf and add master server IP.
Update under [main]
certname = vmf0207.us.xxx.com
server = vmf0270.us.xxx.com
environment = prod
runinterval = 1h

start the puppet service
puppet resource service puppet ensure=running enable=true

create a cronjob which will pull the configurations in every 30 mins
puppet resource cron puppet-agent ensure=present user=root minute=30 command=’/usr/bin/puppet agent –onetime –no-daemonize –splay’

3. Keep all the required manifests and modules in the directory server or create an empty site.pp file.
touch /etc/puppet/manifests/site.pp

4. A prod ready webserver is required to operate and manage from GUI Webpage.(else all can be managed from CLI)

5. Sign all the client requests by the server:

To check the list:
puppet cert list

To sign;
puppet cert sign –all OR individually by puppet cert sign

 

I have created few puppet manifests to manage the compute infrastructure.

Can be found here : https://github.com/kumarprd/puppet-manifests