reading notes on Python for DevOps: Learn Ruthlessly Effective Automation
- Chapter 1. Python Essentials for DevOps
- Chapter 2. Automating Files and the Filesystem
- Chapter 3. Working with the Command Line
- Chapter 4. Useful Linux Utilities
- Chapter 5. Package Management
- Chapter 6. Continuous Integration and Continuous Deployment
- Chapter 7. Monitoring and Logging
- Chapter 8. Pytest for DevOps
- Chapter 9. Cloud Computing
- Chapter 10. Infrastructure as Code
- Chapter 11. Container Technologies: Docker and Docker Compose
- Chapter 12. Container Orchestration: Kubernetes
- Chapter 13. Serverless Technologies
- Chapter 14. MLOps and Machine learning Engineering
- Chapter 15. Data Engineering
- Chapter 16. DevOps War Stories and Interviews
Chapter 1. Python Essentials for DevOps
about format
:
>>> '{} comes before {}'.format('first', 'second')
'first comes before second'
>>> '{1} comes after {0}, but {1} comes before {2}'.format('first',
'second',
'third')
'second comes after first, but second comes before third'
>>> '''{country} is an island.
... {country} is off of the coast of
... {continent} in the {ocean}'''.format(ocean='Indian Ocean',
... continent='Africa',
... country='Madagascar')
'Madagascar is an island.
Madagascar is off of the coast of
Africa in the Indian Ocean'
>>> values = {'first': 'Bill', 'last': 'Bailey'}
>>> "Won't you come home {first} {last}?".format(**values)
"Won't you come home Bill Bailey?"
>>> text = "|{0:>22}||{0:<22}|"
>>> text.format('O','O')
'| O||O |'
>>> text = "|{0:<>22}||{0:><22}|"
>>> text.format('O','O')
'|<<<<<<<<<<<<<<<<<<<<<O||O>>>>>>>>>>>>>>>>>>>>>|'
f-string
>>> a = 1
>>> b = 2
>>> f"a is {a}, b is {b}. Adding them results in {a + b}"
'a is 1, b is 2. Adding them results in 3'
>>> count = 43
>>> f"|{count:5d}"
'| 43'
>>> padding = 10
>>> f"|{count:{padding}d}"
'| 43'
function with no return:
>>> def no_return():
... '''No return defined'''
... pass
...
>>> result = no_return()
>>> print(result)
None
Chapter 2. Automating Files and the Filesystem
read file
In [13]: with open(file_path, 'r') as open_file:
...: text = open_file.readlines()
...:
In [35]: import pathlib
In [36]: path = pathlib.Path("/Users/kbehrman/projects/autoscaler/check_pending.py")
In [37]: path.read_text()
pathlib
will overwrite a file if it already exists
In [38]: path = pathlib.Path("/Users/kbehrman/sp.config")
In [39]: path.write_text("LOG:DEBUG")
json
In [10]: import json
In [11]: with open('service-policy.json', 'r') as opened_file:
...: policy = json.load(opened_file)
In [13]: from pprint import pprint
In [14]: pprint(policy)
In [17]: with open('service-policy.json', 'w') as opened_file:
...: policy = json.dump(policy, opened_file)
yaml
$ pip install PyYAML
In [18]: import yaml
In [19]: with open('verify-apache.yml', 'r') as opened_file:
...: verify_apache = yaml.safe_load(opened_file)
In [20]: pprint(verify_apache)
In [22]: with open('verify-apache.yml', 'w') as opened_file:
...: yaml.dump(verify_apache, opened_file)
csv
In [16]: import csv
In [17]: file_path = '/Users/kbehrman/Downloads/registered_user_count_ytd.csv'
In [18]: with open(file_path, newline='') as csv_file:
...: off_reader = csv.reader(csv_file, delimiter=',')
pandas
In [54]: import pandas as pd
In [55]: df = pd.read_csv('sample-data.csv')
In [56]: type(df)
Out[56]: pandas.core.frame.DataFrame
regexp
In[1]: line = '127.0.0.1 - rj [13/Nov/2019:14:34:30 -0000] "GET HTTP/1.0" 200'
In [2]: re.search(r'(?P<IP>\d+\.\d+\.\d+\.\d+)', line)
Out[2]: <re.Match object; span=(0, 9), match='127.0.0.1'>
In [3]: m = re.search(r'(?P<IP>\d+\.\d+\.\d+\.\d+)', line)
In [4]: m.group('IP')
Out[4]: '127.0.0.1'
Use the finditer
method to process the log
In [62]: r = r'(?P<IP>\d+\.\d+\.\d+\.\d+)'
In [63]: r += r'- (?P<User>\w+)'
In [64]: r += r'\[(?P<Time>08/Nov/\d{4}:\d{2}:\d{2}:\d{2} [-+]\d{4})\]'
In [65]: r += r' (?P<Request>"GET .+")'
In [66]: matched = re.finditer(r, access_log)
In [67]: for m in matched:
...: print(m.group('IP'))
Dealing with Large Files
In [46]: def line_reader(file_path):
...: with open(file_path, 'r') as source_file:
...: for line in source_file:
...: yield line
In [47]: reader = line_reader('big-data.txt')
In [48]: with open('big-data-corrected.txt', 'w') as target_file:
...: for line in reader:
...: target_file.write(line)
hash
In [62]: import hashlib
In [63]: secret = "This is the password or document text"
In [64]: bsecret = secret.encode()
In [65]: m = hashlib.md5()
In [66]: m.update(bsecret)
In [67]: m.digest()
Out[67]: b' \xf5\x06\xe6\xfc\x1c\xbe\x86\xddj\x96C\x10\x0f5E'
Encrypting Text
$ pip install cryptography
In [1]: from cryptography.fernet import Fernet
In [2]: key = Fernet.generate_key()
In [3]: key
Out[3]: b'q-fEOs2JIRINDR8toMG7zhQvVhvf5BRPx3mj5Atk5B8='
In [4]: f = Fernet(key)
In [5]: message = b"Secrets go here"
In [6]: encrypted = f.encrypt(message)
In [7]: encrypted
Out[7]: b'gAAAAABdPyg4 ... plhkpVkC8ezOHaOLIA=='
In [1]: f = Fernet(key)
In [2]: f.decrypt(encrypted)
Out[2]: b'Secrets go here'
RSA
In [1]: from cryptography.hazmat.backends import default_backend
In [2]: from cryptography.hazmat.primitives.asymmetric import rsa
In [3]: private_key = rsa.generate_private_key(public_exponent=65537,
key_size=4096,
backend=default_backend())
In [4]: private_key
Out[4]: <cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey at 0x10d377c18>
In [5]: public_key = private_key.public_key
In [6]: public_key = private_key.public_key()
In [7]: public_key
Out[7]: <cryptography.hazmat.backends.openssl.rsa._RSAPublicKey at 0x10da642b0>
In [8]: message = b"More secrets go here"
In [9]: from cryptography.hazmat.primitives.asymmetric import padding
In [11]: from cryptography.hazmat.primitives import hashes
In [12]: encrypted = public_key.encrypt(message,
...: padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
...: algorithm=hashes.SHA256(),
...: label=None))
In [13]: decrypted = private_key.decrypt(encrypted,
...: padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
...: algorithm=hashes.SHA256(),
...: label=None))
In [14]: decrypted
Out[14]: b'More secrets go here'
os.path
In [1]: import os
In [2]: cur_dir = os.getcwd() 1
In [3]: cur_dir
Out[3]: '/Users/kbehrman/Google-Drive/projects/python-devops/samples/chapter4'
In [4]: os.path.split(cur_dir) 2
Out[4]: ('/Users/kbehrman/Google-Drive/projects/python-devops/samples',
'chapter4')
In [5]: os.path.dirname(cur_dir) 3
Out[5]: '/Users/kbehrman/Google-Drive/projects/python-devops/samples'
In [6]: os.path.basename(cur_dir) 4
Out[6]: 'chapter4'
walk up directory tree
In [7]: while os.path.basename(cur_dir):
...: cur_dir = os.path.dirname(cur_dir)
...: print(cur_dir)
def walk_path(parent_path):
for parent_path, directories, files in os.walk(parent_path):
print(f"Checking: {parent_path}")
for file_name in files:
file_path = os.path.join(parent_path, file_name)
last_access = os.path.getatime(file_path)
size = os.path.getsize(file_path)
Chapter 3. Working with the Command Line
There are two dominant ways to interpret bytes during reading. The first, little endian, interprets each subsequent byte as having higher significance (representing a larger digit). The other, big endian, assumes the first byte has the greatest significance and moves down from there.
In [1]: import sys
In [2]: sys.byteorder
Out[2]: 'little'
environment variables
In [1]: import os
In [5]: os.environ.get('LOGLEVEL') 3
In [6]: os.environ['LOGLEVEL'] = 'DEBUG' 4
In [7]: os.environ.get('LOGLEVEL')
Out[7]: 'DEBUG'
Spawn Processes with the subprocess Module
In [1]: cp = subprocess.run(['ls','-l'],
capture_output=True,
universal_newlines=True)
In [2]: cp.stdout
To modify the script to run a function automatically only when invoked on the command line, but not during import, put the function invocation into the block after the test:
if __name__ == '__main__':
say_it()
Using sys.argv
import sys
if __name__ == '__main__':
print(f"The first argument: '{sys.argv[0]}'")
print(f"The second argument: '{sys.argv[1]}'")
Using argparse
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Echo your input') 1
parser.add_argument('message',
help='Message to echo')
parser.add_argument('--twice', '-t',
help='Do it twice',
action='store_true')
args = parser.parse_args()
print(args.message)
if args.twice:
print(args.message)
Function Decorators
In [2]: def some_decorator(wrapped_function):
...: def wrapper():
...: print('Do something before calling wrapped function')
...: wrapped_function()
...: print('Do something after calling wrapped function')
...: return wrapper
...:
In [3]: def foobat():
...: print('foobat')
...:
In [4]: f = some_decorator(foobat)
In [5]: f()
Do something before calling wrapped function
foobat
Do something after calling wrapped function
In [6]: @some_decorator
...: def batfoo():
...: print('batfoo')
...:
In [7]: batfoo()
Do something before calling wrapped function
batfoo
Do something after calling wrapped function
Using click
$ pip install click
import click
@click.command()
@click.option('--greeting', default='Hiya', help='How do you want to greet?')
@click.option('--name', default='Tammy', help='Who do you want to greet?')
def greet(greeting, name):
print(f"{greeting} {name}")
if __name__ == '__main__':
greet()
fire
$ pip install fire
import fire
def greet(greeting='Hiya', name='Tammy'):
print(f"{greeting} {name}")
def goodbye(goodbye='Bye', name='Tammy'):
print(f"{goodbye} {name}")
if __name__ == '__main__':
fire.Fire()
Chapter 4. Useful Linux Utilities
Load Testing with molotov
mport molotov
@molotov.scenario(100)
async def scenario_one(session):
async with session.get("http://localhost:5000") as resp:
assert resp.status == 200
Debuggers
import ipdb;ipdb.set_trace()
not much interesting stuffs in the later chapsters ..