silug / simp-puppetsync

Bolt project to standardize assets across many GitHub repos and PR the changes (Should probably be called "boltsync")

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

puppetsync

Description

Run Puppet Bolt Plans to manage your GitHub repos' code like infrastructure-as-code!

  • Enforce a "baseline" across multiple GitHub repos, using Puppet and Bolt tasks
  • Automate the CM/workflow for each change: Jira, git commit messages, GitHub forks & PRs
  • Separate plans to submit, approve, and merge GitHub PRs, so different roles can run them (if required by CM policy)

Puppetsync Plans Overview

Setup

Requirements

  • Puppet Bolt 3.0+, installed from an OS package (don't use the RubyGem)
  • The git command must be available
    • SSH + ssh-agent must be set up to push changes
  • Some specific environment variables are required to handle API authentication (e.g., GitHub)
  • Runtime dependencies (installed by ./Rakefile install)
    • Puppet modules (defined in bolt project's bolt-project.yaml):
    • Ruby Gems (defined in gem.deps.rb): octokit, jira-ruby, etc

Quickstart

Initial setup

  1. Before running any plans, from the top level of this repository:

    command -v rvm && rvm use system    # Make sure you're using packaged `bolt`
    ./Rakefile install                  # Install Puppet module + Ruby Gem deps;
    bolt plan show                      # Verify `puppetsync::` plans are visible
  2. Set the environment variable GITHUB_API_TOKEN.

    • Before using the main puppetsync:: plan, also set JIRA_USER, JIRA_API_TOKEN, and GITLAB_API_TOKEN
  3. At this point, you are ready to run a plan. The plan to run will depend on your role:

    Plan Role Purpose
    puppetsync Maintainer Applies baseline to each repo, submits changes as PRs
    puppetsync::approve_github_prs Approver Approves all PRs from a specific puppetsync session
    puppetsync::merge_github_prs Maintainer Merges all approved PRs from a specific puppetsync

Preparing to run puppetsync

⚠️ You only need to change these files when preparing a new puppetsync!

  1. Find/add a repolist file for the repos you want to affect under data/sync/repolists/
  2. Copy and customize a Puppetsync config file under data/sync/configs/ to fit your workflow
  3. Set environment variables for GITHUB, GITLAB, and JIRA API authentication
  4. (Optional) Develop Puppet code/Hiera data/Tasks to provide new features

Note: If you are just approving or merging PRs, you will reuse the repolist and config files from the puppetsync session you used.

Running puppetsync

# (PROTIP: don't actually expose API tokens on the CLI when running commands)

GITHUB_API_TOKEN=$GITHUB_API_TOKEN \
  JIRA_USER=$JIRA_USER \
  JIRA_API_TOKEN=$JIRA_API_TOKEN \
  GITLAB_API_TOKEN=$JIRA_API_TOKEN \
    bolt plan run puppetsync config={CONFIG_NAME} repolist={REPOLIST_NAME}

See the puppetsync reference for details.

Running puppetsync::approve_github_prs

GITHUB_API_TOKEN=$GITHUB_API_TOKEN \
    bolt plan run puppetsync::approve_github_prs

See the puppetsync::approve_github_prs reference for details.

Running puppetsync::merge_github_prs

GITHUB_API_TOKEN=$GITHUB_API_TOKEN \
    bolt plan run puppetsync::merge_github_prs

See the puppetsync::merge_github_prs reference for details.

Usage

Syncing repos

After setup, sync all repos by running:

    /opt/puppetlabs/bin/bolt plan run puppetsync \
      config=CONFIG_NAME repolist=REPOLIST_NAME

If the config and repolist's latest.yaml files are symlinked to the target config and repolist, you don't have to specify config= or repolist= (for brevity, following examples will assume this is the case):

    /opt/puppetlabs/bin/bolt plan run puppetsync

To see what's going on under the hood (potentially less irritating when apply() appears to hang for a long time when updating a lot of repos):

    /opt/puppetlabs/bin/bolt plan run puppetsync --log-level info

    # Alternatively (warning: LOTS of info):
    /opt/puppetlabs/bin/bolt plan run puppetsync --log-level debug

Inspecting pipeline stages

To list all pipeline stages in a plan (and inspect which stages will be skipped), run:

    bolt plan run puppetsync options='{"list_pipeline_stages": true}' \
      config=CONFIG_NAME repolist=REPOLIST_NAME

These steps can be specified/commented out in the Puppetsync config file, under the corresponding plan.

Example:

bolt plan run puppetsync \
  options='{"list_pipeline_stages": true}' \
  github_token=x jira_username=x  jira_token=x

#   Starting: plan puppetsync
#   ===== SKIPPING PIPELINE STAGE DUE TO CONFIGURATION: install_gems
#   - checkout_git_feature_branch_in_each_repo
#   - ensure_jira_subtask
#   - apply_puppet_role
#   - modernize_gitlab_files
#   - lint_gitlab_ci
#   - git_commit_changes
#   - ensure_github_fork
#   - ensure_git_remote
#   - git_push_to_remote
#   - ensure_gitlab_remote
#   - git_push_to_gitlab
#   - ensure_github_pr
#   Finished: plan puppetsync in 0.04 sec
#   Plan completed successfully with no result

At the time the command above was run, the corresponding Puppetsync config file contained the following puppetsync.plans.sync.stages:

puppetsync:
  plans:
    sync:
      stages:
        # - install_gems
        - checkout_git_feature_branch_in_each_repo
        - ensure_jira_subtask
        - apply_puppet_role
        - modernize_gitlab_files
        - lint_gitlab_ci
        - git_commit_changes
        - ensure_github_fork
        - ensure_git_remote
        - git_push_to_remote
        - ensure_gitlab_remote
        - git_push_to_gitlab
        - ensure_github_pr

Reference

Environment variables

These environment variables are necessary to create Jira subtasks:

Env variable Purpose
JIRA_USER Jira user Probably an email address
JIRA_API_TOKEN Jira API token You MUST generate an API token (basic auth no longer works). To do so, you must have Jira instance access rights. You can generate a token here: https://id.atlassian.com/manage/api-tokens

These environment variables are necessary to fork GitHub repositories and submit Pull Requests:

Env variable Purpose
GITHUB_API_TOKEN GitHub API token

These environment variables are necessary to use GitLab's CI lint API:

Env variable Purpose
GITLAB_API_TOKEN GitLab Personal API Token Requires api scope

(Recommended) To stop Bolt from collecting analytics, set this environment variable:

Env variable Purpose
BOLT_DISABLE_ANALYTICS=true Prevent bolt's analytics from phoning home to tell Puppet about everything you do

Puppetsync config

The workflow of a specific Puppetsync session (sync -> apply -> merge of related PRs) is controlled by a single configuration data structure, defined in Hiera using the key puppetsync::plan_config.

Typically, the puppetsync::plan_config data structure is defined in its own Hiera YAML file, located at data/sync/configs/{CONFIG_NAME}.yaml. The Hiera file's name is is the CONFIG_NAME in a Puppetsync plan's config=CONFIG_NAME

Example:

---
puppetsync::plan_config:
  permitted_project_types:
    - pupmod
    - pupmod_skeleton
  plans:
    # clone_git_repos: false     # set to `false` when applying manual updates on a second run
    # clear_before_clone: false  # set to `false` when applying manual updates on a second run
    sync:
      stages:
        - install_gems
        - checkout_git_feature_branch_in_each_repo
        - ensure_jira_subtask
        - apply_puppet_role
        - modernize_gitlab_files
        - lint_gitlab_ci
        - git_commit_changes
        - ensure_github_fork
        - ensure_git_remote
        - git_push_to_remote
        - ensure_gitlab_remote
        - git_push_to_gitlab
        - ensure_github_pr

    approve_github_pr:
      clone_git_repos: false
      stages:
        - install_gems
        - approve_github_pr_for_each_repo

    merge_github_pr:
      clone_git_repos: false
      stages:
        - install_gems
        - merge_github_pr_for_each_repo

jira:
  parent_issue: SIMP-7035
  project: SIMP
  jira_site: https://simp-project.atlassian.net
  subtask_title: 'Update .travis.yml pipeline in %COMPONENT%'

  # optional subtask fields:
  subtask_story_points: 1
  subtask_assignee: 'chris.tessmer'

git:
  commit_message: |
    (%JIRA_PARENT_ISSUE%) Update to new Travis CI pipeline

    This patch updates the Travis Pipeline to a static, standardized format
    that uses project variables for secrets. It includes an optional
    diagnostic mode to test the project's variables against their respective
    deployment APIs (GitHub and Puppet Forge).

    [%JIRA_PARENT_ISSUE%] #comment Update to latest pipeline in %COMPONENT%
    [%JIRA_SUBTASK%] #close

github:
  pr_user: op-ct  # This should be the account that *submitted* the PRs (Used
                  # by idempotency checks when approving/merging PRs)
  approval_message: ':+1: lgtm'

Puppetsync repolist

Data about each repo/branch to target. The data is defined in a Hiera YAML file, located at data/sync/repolists/{REPOLIST_NAME}.yaml. Each repolist is named after its file.

puppetsync::repos_config:

  https://github.com/simp/pupmod-simp-acpid:
    branch: master

  https://github.com/simp/pupmod-simp-aide:
    branch: master

  https://github.com/simp/pupmod-simp-at:
    branch: master

  https://github.com/simp/pupmod-simp-auditd:
    branch: master

  # ... and so on

Plans

Each plan:

  • Reads its config data from the Puppetsync config file
  • Reads its repolist data from the Puppetsync repolist file
  • Has its own specific configuration under the keys (plans.sync, plans.approve_github_pr, and plans.merge_github_pr)
  • Executes its workflow as a series of pipeline stages for each repo in the repolist file (in Hiera at data/sync/configs/{CONFIG_FILE}.yaml).

puppetsync

The main plan (puppetsync) clones and updates each repo in the Puppetsync repolist. It (idempotently) ensures a Jira subtask and GitHub PR exists for each change.

Workflow:

  1. Clone :git repositories defined in the Puppetsync repolist
    • (disable with clone_git_repos: false)

It will then execute the following pipeline stages for each repo (in parallel):

  1. Ensure a Jira subtask exists to track the change
  2. Check out a new git feature branch
  3. Apply Puppet manifests to enforce a common repository asset baseline
  4. Commit changes to git with a templated commit message (git.commit_message)
  5. Ensure the user has forked repository on GitHub
  6. Push changes up to the user's forked repository
  7. Submit a Pull Request to merge the changes back the original repository and branch

If an individual repo encounters failures during a stage, it will be held back while the other repos proceed with their workflows.

All failures are summarized after the full plan finishes executing.

puppetsync::approve_github_prs

Idempotently approves every open PR from user github.pr_user on branch jira.parent_issue for each repo in the Puppetsync repolist.

puppetsync::merge_github_prs

Idempotently merges every approved PR from user github.pr_user on branch jira.parent_issue for each repo in the Puppetsync repolist.

Manually installing dependencies

Use bolt to download the project's dependencies from bolt-project.yaml and gems.deps.rb:

   /opt/puppetlabs/bolt/bin/gem install --user-install -g gem.deps.rb
   /opt/puppetlabs/bin/bolt module install

The Rakefile can be used as a shortcut:

  ./Rakefile install

Run ./Rakefile -T & ./Rakefile -D to see other tasks & descriptions

Troubleshooting

Error: Ignoring <x> because its extensions are not built.

Cause: Running bolt plan run puppetsync from a Ruby interpreter other than the bolt package.

Fix: Make sure you're not using RVM. If necessary, invoke the packaged bolt executable directly:

command -v rvm && rvm use system    # make sure you're using the packaged `bolt`
./Rakefile install                  # Install Puppet module and Ruby Gem deps
bolt plan show --filter puppetsync  # Validate bolt is working

## ^^^ If that still didn't work:
# /opt/puppetlabs/bolt/bin/bolt show --filter puppetsync

Characteristic error messages:

Ignoring bcrypt_pbkdf-1.0.1 because its extensions are not built. Try: gem pristine bcrypt_pbkdf --version 1.0.1
Ignoring byebug-11.1.3 because its extensions are not built. Try: gem pristine byebug --version 11.1.3
Ignoring byebug-11.1.1 because its extensions are not built. Try: gem pristine byebug --version 11.1.1
Ignoring byebug-11.0.1 because its extensions are not built. Try: gem pristine byebug --version 11.0.1
Ignoring ed25519-1.2.4 because its extensions are not built. Try: gem pristine ed25519 --version 1.2.4
Ignoring executable-hooks-1.6.0 because its extensions are not built. Try: gem pristine executable-hooks --version 1.6.0
Ignoring ffi-1.12.2 because its extensions are not built. Try: gem pristine ffi --version 1.12.2
...

Limitations

  • Requires git to be configured with SSH, with keys loaded into a running agent
  • Probably only works from an *nix host

About

Bolt project to standardize assets across many GitHub repos and PR the changes (Should probably be called "boltsync")


Languages

Language:Ruby 48.3%Language:Puppet 45.0%Language:Shell 6.7%