DevOps & CI/CD

Chapter 4
Automation, Scripting and Build Engineering

Advance Diploma in Cloud Computing
DevOps & CI/CD   •   Advance Diploma

Automation, Scripting and Build Engineering

Introduction

Cloud delivery depends on the reliable execution of repeatable activities: checking source code, installing dependencies, compiling or packaging software, running tests, creating container images, publishing artifacts, and preparing deployment environments. When these activities are performed manually, outcomes depend on individual memory, workstation configuration, and timing. Automation converts operational knowledge into executable, reviewable, and repeatable processes.

Automation in DevOps is broader than writing scripts. It includes selecting suitable tools, modelling dependencies, controlling configuration, designing reproducible builds, managing artifacts, and establishing safeguards against unsafe execution. A technically correct script may still be unsuitable for production if it exposes secrets, behaves differently on another operating system, silently ignores errors, or produces an artifact that cannot be traced to its source.

This chapter examines shell and Python automation, dependency management, build tools, artifact repositories, environment variables, and reproducible builds in cloud delivery environments.

Learning Objectives

By the end of this chapter, a learner should be able to:

  • explain the role of automation and build engineering in DevOps;
  • select shell scripting or Python for an automation task;
  • write safer, testable, and maintainable scripts;
  • manage application and tool dependencies deterministically;
  • describe build graphs, incremental builds, and build isolation;
  • manage artifacts and package repositories;
  • use environment variables without compromising secrets;
  • design reproducible and traceable builds;
  • evaluate automation risks and apply professional controls.

1. Automation as a DevOps Capability

Automation is the use of software-controlled procedures to perform a task with limited manual intervention. In a mature delivery system, automation should be:

  1. Repeatable: the same inputs produce the same intended process.
  2. Observable: logs, status codes, and metrics reveal what happened.
  3. Idempotent where appropriate: rerunning an operation does not create unintended additional effects.
  4. Versioned: scripts and build definitions are stored with the system they operate.
  5. Testable: important behaviour can be verified automatically.
  6. Secure: permissions, inputs, and secrets are controlled.

An operation is idempotent when applying it more than once has the same intended final state as applying it once. For example, creating a directory with a command that succeeds if the directory already exists is commonly idempotent. Appending a line to a file on every run is not necessarily idempotent because repeated execution may create duplicates.

Automation can be classified as follows:

TypePurposeExample
Task automationPerforms a local operationCompressing logs
Build automationProduces software outputsCompiling a service
Test automationVerifies behaviourRunning unit tests
Release automationPromotes versioned outputsPublishing a package
Infrastructure automationChanges environmentsProvisioning a network
Policy automationEnforces controlsBlocking an unsigned image

Automation does not remove the need for judgement. It moves judgement into design decisions, policies, code review, and exception handling.

2. Shell Scripting and Python Automation

2.1 Shell scripting

Shells are effective for composing operating-system commands, moving files, setting environment variables, and connecting existing command-line tools. A small POSIX shell script or Bash script is often appropriate for a short CI task.

A safe Bash script commonly begins with:

#!/usr/bin/env bash
set -Eeuo pipefail

source_dir="${1:?source directory is required}"
output_dir="${2:?output directory is required}"

mkdir -p -- "$output_dir"
find "$source_dir" -type f -name '*.log' -print0 |
  while IFS= read -r -d '' file; do
    cp -- "$file" "$output_dir/"
  done

The options have important effects. -e requests termination after an unhandled failure, -u treats unset variables as errors, and pipefail causes a pipeline to fail when an earlier command fails. These options improve safety but do not replace explicit error handling. Commands whose failure is expected should be handled deliberately.

Quoting variables prevents word splitting and unintended wildcard expansion. The -- convention helps prevent a value beginning with a hyphen from being interpreted as an option. Input validation is essential, especially when values influence paths, commands, or permissions.

Shell scripts become difficult to maintain when they contain complex data structures, extensive parsing, platform-specific assumptions, or intricate error recovery. In such cases Python is usually preferable.

2.2 Python scripting

Python provides structured control flow, libraries, testing frameworks, and clearer handling of data. The subprocess module can invoke external programs without constructing an unsafe shell command.

from pathlib import Path
import subprocess
import sys


def run_tests(project: Path) -> None:
    result = subprocess.run(
        [sys.executable, "-m", "pytest", "-q"],
        cwd=project,
        check=False,
        text=True,
    )
    if result.returncode != 0:
        raise SystemExit(f"Tests failed with status {result.returncode}")


if __name__ == "__main__":
    run_tests(Path.cwd())

Passing an argument list rather than a command string avoids unnecessary shell interpretation. If a shell is required, input must be strictly controlled or safely escaped. Python scripts should use explicit exit codes, structured logging, type hints where useful, and automated tests for important logic.

2.3 Choosing between shell and Python

Shell is suitable when the task is primarily command composition and is short enough to remain readable. Python is suitable when the task involves APIs, JSON or YAML processing, complex branching, reusable functions, unit tests, concurrency, or portable behaviour.

A useful engineering rule is to optimise for maintainability rather than minimum line count. A ten-line script that is difficult to test or understand may create more operational risk than a longer, structured program.

3. Dependency Management

A dependency is an external component required by a build or runtime, such as a library, compiler, package manager, operating-system tool, container base image, or CI action. Dependencies create productivity benefits but also introduce version, security, licensing, and availability risks.

3.1 Direct and transitive dependencies

A direct dependency is explicitly requested by a project. A transitive dependency is required by another dependency. For example, an application may directly require framework A, while framework A requires libraries B and C.

Dependency resolution attempts to select versions satisfying declared constraints. A broad constraint such as >=2.0 permits many versions and can reduce short-term maintenance effort, but it may allow unexpected changes. A lock file records the resolved versions, often including integrity hashes, so that subsequent installations use the same dependency graph.

A professional project normally separates:

  • application dependencies, needed at runtime;
  • development dependencies, needed for testing, linting, or packaging;
  • build dependencies, needed to produce the artifact;
  • platform dependencies, such as a compiler or system library.

3.2 Dependency controls

Recommended controls include:

  • declare dependencies in a standard manifest;
  • commit lock files where the ecosystem supports them;
  • use private mirrors or approved repositories;
  • scan dependencies for known vulnerabilities;
  • review licence compatibility;
  • remove unused packages;
  • update dependencies through controlled pull requests;
  • verify package integrity using signatures or hashes;
  • document the supported runtime and operating-system versions.

Locking every dependency forever is not a security strategy. A locked vulnerable version remains vulnerable. Lock files provide determinism, while update automation and vulnerability management provide controlled change.

4. Build Engineering

A build transforms source inputs into a distributable output. The input may include source code, generated files, dependency archives, compiler versions, build flags, and configuration. The output may be a binary, wheel, JAR, package, container image, or static website.

4.1 Build graphs

Builds are naturally represented as directed acyclic graphs. Nodes represent tasks or files, and edges represent dependencies. If task B requires the output of task A, A must complete before B. A directed cycle indicates an invalid dependency structure or a process that needs redesign.

Build tools use this graph to avoid repeating work. If an input and all relevant conditions are unchanged, an existing output may be reused. This is called an incremental build. Incremental builds improve speed, but incorrect dependency declarations can cause stale outputs. Correctness must take priority over speed.

4.2 Build stages

A typical cloud build contains these stages:

  1. obtain a specific source revision;
  2. establish a controlled toolchain;
  3. restore or verify dependencies;
  4. perform static analysis and linting;
  5. compile or package the software;
  6. execute unit and integration tests;
  7. scan the output and dependencies;
  8. generate metadata, checksums, and provenance;
  9. publish an immutable artifact.

The sequence may vary, but a build should fail early on invalid source, dependency, or quality conditions. Expensive tests can follow quick validation, provided that the pipeline still preserves a clear trust boundary.

4.3 Build isolation

A build should not depend accidentally on files or tools installed on an engineer’s laptop or a long-lived CI worker. Containers, virtual environments, ephemeral runners, and declared toolchain versions reduce this risk. Isolation should include the working directory, network access, credentials, and filesystem permissions.

A container build should use a pinned base image, avoid embedding secrets in layers, run as a non-root user when practical, and remove unnecessary build tools from the runtime image. Multi-stage builds allow compilation in one stage and copying only required outputs into a smaller runtime stage.

5. Environment Variables and Configuration

Environment variables are key-value inputs supplied by the operating system or execution platform. They are useful for values that vary between environments, such as service endpoints, feature flags, region names, and credential references.

A useful configuration model separates:

  • code: versioned application logic;
  • configuration: environment-specific non-secret settings;
  • secrets: sensitive values managed by a secret system;
  • state: data that must persist independently of a build.

Environment variables are not automatically secure. They may appear in process listings, diagnostic output, crash reports, CI logs, or child processes. Secrets should be injected through an approved secret manager, masked in logs, scoped to the minimum job, and rotated. A secret must never be placed in source control, a Dockerfile, a command argument, or a build artifact.

Scripts should validate required variables and provide safe defaults only where a default is genuinely appropriate:

import os

api_url = os.environ.get("API_URL")
if not api_url:
    raise RuntimeError("API_URL must be provided")

log_level = os.environ.get("LOG_LEVEL", "INFO")

Configuration validation should occur before destructive actions. Values should also be checked for permitted formats and ranges.

6. Artifacts and Package Repositories

An artifact is a versioned output of a build. Examples include a Debian package, Python wheel, Java archive, container image, Helm chart, or signed release bundle. A repository stores and distributes artifacts and may provide access control, metadata, retention policies, scanning, and replication.

An artifact should be:

  • uniquely identifiable;
  • immutable after publication;
  • linked to a source revision and build record;
  • accompanied by metadata such as version, platform, and dependencies;
  • protected by access controls;
  • retained according to operational and legal requirements.

Using only a mutable label such as latest weakens traceability. A stronger approach uses a semantic version or release identifier and, where supported, a content digest. A digest is a cryptographic representation of content; changing the content changes the digest with overwhelming probability when a secure hash is used.

Checksums help detect accidental corruption and some forms of tampering. Signatures provide stronger provenance because they associate content with a signing identity. Verification must occur before deployment, and signing keys must be protected separately from ordinary build credentials.

Repositories should distinguish snapshot or development artifacts from release artifacts. Promotion of an already-built artifact between environments is generally safer than rebuilding separately for testing and production, because rebuilding may change dependencies, timestamps, or toolchains.

7. Reproducible Builds

A reproducible build produces equivalent output when performed again from the same declared inputs and build procedure. Reproducibility is stronger than “the build usually works”; it supports auditability, debugging, and supply-chain confidence.

Sources of non-reproducibility include:

  • unpinned dependencies;
  • changing base images or package mirrors;
  • timestamps embedded in archives;
  • random identifiers;
  • locale and timezone differences;
  • compiler version differences;
  • unordered file traversal;
  • network resources fetched without verification;
  • undeclared files in the workspace.

Reproducibility does not always mean byte-for-byte identity. Some ecosystems can produce functionally equivalent but byte-different outputs. Where byte identity is required, timestamps, ordering, compression settings, and metadata must be controlled explicitly.

A practical reproducible-build procedure is:

  1. identify the exact source revision;
  2. pin the toolchain and base image by version or digest;
  3. resolve dependencies from a controlled source;
  4. use a clean workspace;
  5. define locale, timezone, and relevant build flags;
  6. control archive ordering and timestamps where possible;
  7. record inputs, commands, and output hashes;
  8. rebuild independently and compare results.

Build provenance records who or what initiated a build, which source was used, which dependencies and tools were present, and where the output was published. Provenance supports incident response and compliance, but it does not itself prove that an artifact is safe. Verification, testing, scanning, and policy enforcement remain necessary.

8. Testing Automation and Failure Handling

Automation must fail clearly. A successful process should return an appropriate success status; a failed prerequisite should stop later actions that would be unsafe. Error messages should identify the operation, relevant non-sensitive context, and recommended remediation.

Tests should be layered:

  • unit tests validate small components;
  • integration tests validate interactions;
  • contract tests validate interfaces between services;
  • smoke tests verify basic deployment health;
  • security and policy tests verify required controls.

Retries require care. Retrying a transient network request may be reasonable, but retrying a non-idempotent payment or database mutation can duplicate effects. A retry policy should define which errors are transient, the maximum attempts, delay strategy, and termination conditions.

Observability is part of build engineering. Logs should include build identifiers, stage names, durations, and result status. They should exclude credentials and unnecessary personal data. Metrics such as build duration, failure frequency, queue time, and artifact publication success help teams identify bottlenecks, but metrics must not encourage unsafe practices such as suppressing tests to improve pass rates.

9. Illustrative Case: Reproducible Build Pipeline for a Retail API

Background

A fictional online retailer, Northstar Commerce, operates a Python API deployed as a container. Developers report that the same commit sometimes produces different images. Production incidents are difficult to investigate because image tags are reused and build logs are retained for only a short period.

Problem

The pipeline installs dependencies without a lock file, uses a floating container base image, stores credentials as ordinary CI variables, and rebuilds the application separately for each environment. A vulnerable dependency also remains in the production image because the pipeline scans source code but not the final artifact.

Analysis

The team maps the build inputs: source revision, Python version, operating-system packages, dependency resolution, environment variables, build commands, and base image. They identify two classes of risk. First, the output is not reproducible because several inputs change over time. Second, the artifact cannot be trusted operationally because its identity and provenance are unclear.

Approach and solution

The team introduces a committed lock file with integrity hashes, pins the base image by digest, and builds in an ephemeral runner. A multi-stage container build keeps compilers out of the runtime image. The pipeline obtains a short-lived credential from the organisation’s secret manager rather than storing a permanent password.

The build produces an immutable image reference containing a release identifier and digest. It runs unit, integration, dependency, and container-image scans. The image is signed by a protected build identity. Testing and production use the same promoted image rather than separate rebuilds. Build metadata records the source revision, tool versions, dependency lock-file hash, and scan results.

Outcome

Rebuilding from the same revision produces matching outputs under the controlled procedure. Operators can identify the exact image running in production, and a failed scan prevents publication. The pipeline takes slightly longer because of scanning and verification, but incident investigation becomes faster and the probability of configuration drift is reduced.

Learning points

The case demonstrates that reproducibility is a system property, not merely a dependency-locking exercise. Artifact identity, secret handling, isolation, scanning, signing, and promotion must work together. It also illustrates an important trade-off: stronger controls may add pipeline time, but they reduce uncertainty and operational risk.

10. Governance, Ethics, and Professional Practice

Automation can amplify both good and bad decisions. A script that deletes resources, changes access policies, or publishes packages may affect customers and colleagues at scale. Professional controls therefore include peer review, protected branches, least-privilege credentials, approval for high-risk actions, audit logs, rollback procedures, and tested recovery plans.

Automation should avoid collecting unnecessary personal data. Logs and artifacts may contain usernames, IP addresses, file paths, or customer information. Retention should be justified, access should be restricted, and sensitive values should be redacted.

Software supply-chain governance increasingly addresses provenance, dependency risk, signing, and build integrity. Organisations may adopt internal policies, industry frameworks, or regulatory obligations requiring evidence that software was produced and released through controlled processes. Compliance is not a substitute for engineering quality: a formally documented insecure process remains insecure.

Artificial intelligence can assist with script generation, dependency analysis, test creation, and log summarisation. However, generated code must be reviewed, tested, scanned, and checked for licence or confidentiality concerns. An AI system may reproduce insecure patterns or expose proprietary input if used with an unsuitable service. Human accountability remains essential for production automation.

Best-Practice Checklist

Before approving an automation or build workflow, ask:

  • Are inputs, outputs, and failure conditions explicit?
  • Is the script safe to rerun where reruns are possible?
  • Are shell variables quoted and external commands controlled?
  • Are dependencies pinned, scanned, and updated deliberately?
  • Is the build isolated from the developer workstation?
  • Are secrets supplied through an approved mechanism?
  • Is the artifact immutable, traceable, and verifiable?
  • Can an independent operator reproduce or explain the build?
  • Are logs useful without exposing sensitive information?
  • Are destructive actions protected by permissions and approvals?

Chapter Summary

Automation turns repeatable operational knowledge into versioned and executable workflows. Shell scripts are effective for concise command composition, whereas Python is better for complex logic, structured data, testing, and portability. Safe automation requires validation, explicit error handling, controlled inputs, observability, and appropriate idempotence.

Dependencies must be declared, resolved, locked where appropriate, scanned, and updated through controlled processes. Build engineering models tasks and inputs, uses dependency graphs, supports incremental execution, and isolates the toolchain. Artifacts should be immutable, traceable, scanned, and stored in controlled repositories. Environment variables provide configuration but are not automatically secure; secrets require dedicated management.

Reproducible builds depend on controlled source, tools, dependencies, timestamps, ordering, workspace contents, and network inputs. Provenance, signing, and promotion of a single tested artifact strengthen software supply-chain assurance. Finally, automation must be governed through least privilege, review, auditability, privacy protection, and responsible use of AI-assisted development.

Key Terms

Automation: Use of software-controlled procedures to perform repeatable tasks.

Idempotence: Property whereby repeated execution produces the same intended final state.

Shell script: Program interpreted by an operating-system command shell.

Dependency: External component required by a build or runtime.

Transitive dependency: Dependency required indirectly through another dependency.

Lock file: File recording resolved dependency versions and often integrity data.

Build: Process that transforms declared inputs into a distributable output.

Build graph: Directed acyclic representation of tasks and their dependencies.

Incremental build: Build that reuses valid outputs instead of repeating unchanged work.

Toolchain: Compilers, interpreters, libraries, and utilities used to build software.

Artifact: Versioned output produced by a build.

Package repository: Controlled service that stores and distributes packages or artifacts.

Immutable artifact: Artifact that cannot be silently changed after publication.

Environment variable: Key-value configuration supplied to a process environment.

Secret manager: System designed to store, control, and rotate sensitive values.

Reproducible build: Build that produces equivalent output from the same declared inputs.

Provenance: Evidence describing the origin and production process of an artifact.

Digest: Cryptographic representation of content used for identification and integrity checking.

Multi-stage build: Container build using separate stages for building and runtime packaging.

Least privilege: Security principle of granting only the permissions required for a task.

Review Questions

Short Answer Questions

  1. What characteristics distinguish professional automation from an ad hoc script?
  2. When is Python generally preferable to shell scripting?
  3. What is the purpose of a dependency lock file?
  4. Why should a production artifact be immutable?
  5. Why are environment variables not automatically suitable for storing secrets?

Long Answer Questions

  1. Explain how a build graph supports incremental builds and discuss the risks of incorrect dependency declarations.
  2. Describe a complete workflow for producing and publishing a secure container artifact.
  3. Explain the principal causes of non-reproducible builds and propose controls for each.
  4. Compare application, development, build, and platform dependencies.
  5. Discuss the governance and ethical implications of automating destructive production operations.

Analytical / Application Questions

  1. A CI job succeeds on one runner but fails on another because of different compiler and operating-system versions. Diagnose the likely causes and design a remediation plan.
  2. A team wants to retry every failed deployment command automatically. Evaluate this proposal, distinguishing safe retries from unsafe retries and specifying appropriate safeguards.
  3. Design a build-and-release workflow for a regulated cloud service. Identify the controls needed for dependency integrity, secret management, artifact traceability, approval, and rollback.

Multiple Choice Questions with Answers

Question 1

Which property means that rerunning an operation does not create unintended additional effects?

A. Idempotence

B. Polymorphism

C. Compilation

D. Serialization

Correct Answer: A. Idempotence

Question 2

Which situation most strongly favours Python over shell scripting?

A. Running one short command

B. Copying a single file

C. Processing structured data with complex branching and tests

D. Setting one environment variable

Correct Answer: C. Processing structured data with complex branching and tests

Question 3

What is the main purpose of a dependency lock file?

A. To remove all dependencies

B. To record resolved dependency versions and integrity information

C. To encrypt source code

D. To replace automated testing

Correct Answer: B. To record resolved dependency versions and integrity information

Question 4

Which practice best protects a build secret?

A. Writing it into a Dockerfile

B. Committing it to the repository

C. Printing it in a diagnostic log

D. Injecting it from an approved secret manager with limited scope

Correct Answer: D. Injecting it from an approved secret manager with limited scope

Question 5

What does a build graph represent?

A. The organisation’s reporting structure

B. Tasks and dependencies between them

C. Only runtime network traffic

D. A list of user passwords

Correct Answer: B. Tasks and dependencies between them

Question 6

Why is using only the mutable image tag latest weak for production traceability?

A. It prevents container startup

B. It cannot identify the exact content reliably over time

C. It always increases image size

D. It disables dependency scanning

Correct Answer: B. It cannot identify the exact content reliably over time

Question 7

Which factor can cause otherwise identical builds to differ?

A. Controlled source revision

B. Pinned toolchain

C. Uncontrolled timestamps embedded in archives

D. A clean workspace

Correct Answer: C. Uncontrolled timestamps embedded in archives

Question 8

What is a major advantage of promoting one tested artifact to production instead of rebuilding it?

A. It guarantees no tests are needed

B. It avoids differences introduced by separate rebuilds

C. It eliminates the need for versioning

D. It permits mutable artifacts

Correct Answer: B. It avoids differences introduced by separate rebuilds

Question 9

What does set -u generally do in Bash?

A. Treats unset variables as errors

B. Deletes unused files

C. Encrypts environment variables

D. Runs commands in parallel

Correct Answer: A. Treats unset variables as errors

Question 10

Which statement about artifact signing is correct?

A. Signing replaces all security testing

B. Signing associates content with a signing identity and supports verification

C. Signing makes an artifact immutable by itself

D. Signing removes the need for access control

Correct Answer: B. Signing associates content with a signing identity and supports verification

DevOps & CI/CD
Chapter 4 of 15