diff --git a/.ansible-lint b/.ansible-lint index 0e80b05..4ffc0ef 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -1,10 +1,9 @@ --- -# See https://ansible-lint.readthedocs.io/en/latest/configuring.html -# for a list of the configuration elements that can exist in this -# file. +# See https://ansible-lint.readthedocs.io/configuring/ for a list of +# the configuration elements that can exist in this file. enable_list: # Useful checks that one must opt-into. See here for more details: - # https://ansible-lint.readthedocs.io/en/latest/rules.html + # https://ansible-lint.readthedocs.io/rules/ - fcqn-builtins - no-log-password - no-same-owner diff --git a/.bandit.yml b/.bandit.yml index 2b618f6..663c521 100644 --- a/.bandit.yml +++ b/.bandit.yml @@ -3,7 +3,7 @@ # https://bandit.readthedocs.io/en/latest/config.html # Tests are first included by `tests`, and then excluded by `skips`. -# If `tests` is empty, all tests are are considered included. +# If `tests` is empty, all tests are considered included. tests: # - B101 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 371258c..8f5c8c5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,8 +3,8 @@ # These owners will be the default owners for everything in the # repo. Unless a later match takes precedence, these owners will be # requested for review when someone opens a pull request. -* @dav3r @felddy @jsf9k @mcdonnnj +* @dav3r @felddy @jasonodoom @jsf9k @mcdonnnj # These folks own any files in the .github directory at the root of # the repository and any of its subdirectories. -/.github/ @dav3r @felddy @jsf9k @mcdonnnj +/.github/ @dav3r @felddy @jasonodoom @jsf9k @mcdonnnj diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b38be54..3f2b022 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,22 +5,30 @@ # these updates when the pull request(s) in the appropriate skeleton are merged # and Lineage processes these changes. -version: 2 updates: - - package-ecosystem: "docker" - directory: "/" + - directory: / + package-ecosystem: docker schedule: - interval: "weekly" + interval: weekly - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" + - directory: / ignore: + # Managed by cisagov/skeleton-generic - dependency-name: actions/cache - dependency-name: actions/checkout + - dependency-name: actions/setup-go - dependency-name: actions/setup-python +<<<<<<< HEAD # Managed by cisagov/cyhy-commander-docker +======= + - dependency-name: crazy-max/ghaction-dump-context + - dependency-name: crazy-max/ghaction-github-labeler + - dependency-name: crazy-max/ghaction-github-status + - dependency-name: hashicorp/setup-terraform + - dependency-name: mxschmitt/action-tmate + - dependency-name: step-security/harden-runner + # Managed by cisagov/skeleton-docker +>>>>>>> 1ea8a3fa98e790d66f8d5e10375f73c5be4d5fd5 # - dependency-name: actions/download-artifact # - dependency-name: actions/github-script # - dependency-name: actions/upload-artifact @@ -28,13 +36,18 @@ updates: # - dependency-name: docker/login-action # - dependency-name: docker/setup-buildx-action # - dependency-name: docker/setup-qemu-action + # - dependency-name: github/codeql-action + package-ecosystem: github-actions + schedule: + interval: weekly - - package-ecosystem: "pip" - directory: "/" + - directory: / + package-ecosystem: pip schedule: - interval: "weekly" + interval: weekly - - package-ecosystem: "terraform" - directory: "/" + - directory: / + package-ecosystem: terraform schedule: - interval: "weekly" + interval: weekly +version: 2 diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..014cc00 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,73 @@ +--- +# Rather than breaking up descriptions into multiline strings we disable that +# specific rule in yamllint for this file. +# yamllint disable rule:line-length +- color: "eb6420" + description: This issue or pull request is awaiting the outcome of another issue or pull request + name: blocked +- color: "000000" + description: This issue or pull request involves changes to existing functionality + name: breaking change +- color: "d73a4a" + description: This issue or pull request addresses broken functionality + name: bug +- color: "07648d" + description: This issue will be advertised on code.gov's Open Tasks page (https://code.gov/open-tasks) + name: code.gov +- color: "0366d6" + description: Pull requests that update a dependency file + name: dependencies +- color: "2497ed" + description: Pull requests that update Docker code + name: docker +- color: "5319e7" + description: This issue or pull request improves or adds to documentation + name: documentation +- color: "cfd3d7" + description: This issue or pull request already exists or is covered in another issue or pull request + name: duplicate +- color: "b005bc" + description: A high-level objective issue encompassing multiple issues instead of a specific unit of work + name: epic +- color: "000000" + description: Pull requests that update GitHub Actions code + name: github-actions +- color: "0e8a16" + description: This issue or pull request is well-defined and good for newcomers + name: good first issue +- color: "ff7518" + description: Pull request that should count toward Hacktoberfest participation + name: hacktoberfest-accepted +- color: "a2eeef" + description: This issue or pull request will add or improve functionality, maintainability, or ease of use + name: improvement +- color: "fef2c0" + description: This issue or pull request is not applicable, incorrect, or obsolete + name: invalid +- color: "ce099a" + description: This pull request is ready to merge during the next Lineage Kraken release + name: kraken 🐙 +- color: "a4fc5d" + description: This issue or pull request requires further information + name: need info +- color: "fcdb45" + description: This pull request is awaiting an action or decision to move forward + name: on hold +- color: "ef476c" + description: This issue is a request for information or needs discussion + name: question +- color: "d73a4a" + description: This issue or pull request addresses a security issue + name: security +- color: "00008b" + description: This issue or pull request adds or otherwise modifies test code + name: test +- color: "1d76db" + description: This issue or pull request pulls in upstream updates + name: upstream update +- color: "d4c5f9" + description: This issue or pull request increments the version number + name: version bump +- color: "ffffff" + description: This issue will not be incorporated + name: wontfix diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc56c8e..d8ba132 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,38 +35,61 @@ env: RUN_TMATE: ${{ secrets.RUN_TMATE }} jobs: + diagnostics: + name: Run diagnostics + runs-on: ubuntu-latest + steps: + # Note that a duplicate of this step must be added at the top of + # each job. + - id: harden-runner + name: Harden the runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + - id: github-status + name: Check GitHub status + uses: crazy-max/ghaction-github-status@v3 + - id: dump-context + name: Dump context + uses: crazy-max/ghaction-dump-context@v2 lint: # Checks out the source and runs pre-commit hooks. Detects coding errors # and style deviations. - name: "Lint sources" + name: Lint sources + needs: + - diagnostics runs-on: ubuntu-latest steps: + - id: harden-runner + name: Harden the runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit - id: setup-env uses: cisagov/setup-env-github-action@develop - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - id: setup-python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" # We need the Go version and Go cache location for the actions/cache step, # so the Go installation must happen before that. - - uses: actions/setup-go@v2 + - id: setup-go + uses: actions/setup-go@v4 with: - go-version: "1.16" - - name: Store installed Go version - id: go-version - run: | - echo "::set-output name=version::"\ - "$(go version | sed 's/^go version go\([0-9.]\+\) .*/\1/')" + # There is no expectation for actual Go code so we disable caching as + # it relies on the existence of a go.sum file. + cache: false + go-version: "1.20" - name: Lookup Go cache directory id: go-cache run: | - echo "::set-output name=dir::$(go env GOCACHE)" + echo "dir=$(go env GOCACHE)" >> $GITHUB_OUTPUT - uses: actions/cache@v3 env: BASE_CACHE_KEY: "${{ github.job }}-${{ runner.os }}-\ py${{ steps.setup-python.outputs.python-version }}-\ - go${{ steps.go-version.outputs.version }}-\ + go${{ steps.setup-go.outputs.go-version }}-\ packer${{ steps.setup-env.outputs.packer-version }}-\ tf${{ steps.setup-env.outputs.terraform-version }}-" with: @@ -102,14 +125,29 @@ jobs: ${{ env.CURL_CACHE_DIR }}/"${PACKER_ZIP}" sudo mv /usr/local/bin/packer /usr/local/bin/packer-default sudo ln -s /opt/packer/packer /usr/local/bin/packer - - uses: hashicorp/setup-terraform@v1 + - uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ steps.setup-env.outputs.terraform-version }} + - name: Install go-critic + env: + PACKAGE_URL: github.com/go-critic/go-critic/cmd/gocritic + PACKAGE_VERSION: ${{ steps.setup-env.outputs.go-critic-version }} + run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} + - name: Install gosec + env: + PACKAGE_URL: github.com/securego/gosec/v2/cmd/gosec + PACKAGE_VERSION: ${{ steps.setup-env.outputs.gosec-version }} + run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} - name: Install shfmt env: PACKAGE_URL: mvdan.cc/sh/v3/cmd/shfmt PACKAGE_VERSION: ${{ steps.setup-env.outputs.shfmt-version }} run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} + - name: Install staticcheck + env: + PACKAGE_URL: honnef.co/go/tools/cmd/staticcheck + PACKAGE_VERSION: ${{ steps.setup-env.outputs.staticcheck-version }} + run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} - name: Install Terraform-docs env: PACKAGE_URL: github.com/terraform-docs/terraform-docs @@ -117,7 +155,7 @@ jobs: run: go install ${PACKAGE_URL}@${PACKAGE_VERSION} - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools wheel pip install --upgrade --requirement requirements-test.txt - name: Set up pre-commit hook environments run: pre-commit install-hooks @@ -167,18 +205,25 @@ jobs: # with the value specified by the user. # # Scheduled builds are tagged with `:nightly`. - name: "Prepare build variables" - runs-on: ubuntu-latest + name: Prepare build variables + needs: + - diagnostics outputs: created: ${{ steps.prep.outputs.created }} repometa: ${{ steps.repo.outputs.result }} source_version: ${{ steps.prep.outputs.source_version }} tags: ${{ steps.prep.outputs.tags }} + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - id: harden-runner + name: Harden the runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + - uses: actions/checkout@v4 - name: Gather repository metadata id: repo - uses: actions/github-script@v5 + uses: actions/github-script@v7 with: script: | const repo = await github.rest.repos.get(context.repo) @@ -219,9 +264,9 @@ jobs: do TAGS="${TAGS},ghcr.io/${i}" done - echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ') - echo ::set-output name=source_version::$(./bump_version.sh show) - echo ::set-output name=tags::${TAGS} + echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + echo "source_version=$(./bump_version.sh show)" >> $GITHUB_OUTPUT + echo "tags=${TAGS}" >> $GITHUB_OUTPUT echo tags=${TAGS} - name: Setup tmate debug session uses: mxschmitt/action-tmate@v3 @@ -229,16 +274,23 @@ jobs: build: # Builds a single test image for the native platform. This image is saved # as an artifact and loaded by the test job. - name: "Build test image" + name: Build test image + needs: + - diagnostics + - prepare runs-on: ubuntu-latest - needs: [prepare] steps: + - id: harden-runner + name: Harden the runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Cache Docker layers uses: actions/cache@v3 env: @@ -252,7 +304,7 @@ jobs: run: mkdir -p dist - name: Build image id: docker_build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: build-args: | VERSION=${{ needs.prepare.outputs.source_version }} @@ -261,6 +313,11 @@ jobs: context: . file: ./Dockerfile outputs: type=docker,dest=dist/image.tar + # Uncomment the following option if you are building an image for use + # on Google Cloud Run or AWS Lambda. The current default image output + # is unable to run on either. Please see the following issue for more + # information: https://github.com/docker/buildx/issues/1533 + # provenance: false tags: ${{ env.IMAGE_NAME }}:latest # not to be pushed # For a list of pre-defined annotation keys and value types see: # https://github.com/opencontainers/image-spec/blob/master/annotations.md @@ -299,15 +356,22 @@ jobs: if: env.RUN_TMATE test: # Executes tests on the single-platform image created in the "build" job. - name: "Test image" + name: Test image + needs: + - diagnostics + - build runs-on: ubuntu-latest - needs: [build] steps: - - uses: actions/checkout@v3 + - id: harden-runner + name: Harden the runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + - uses: actions/checkout@v4 - id: setup-python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.11" - name: Cache testing environments uses: actions/cache@v3 env: @@ -322,7 +386,7 @@ jobs: ${{ env.BASE_CACHE_KEY }} - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools wheel pip install --upgrade --requirement requirements-test.txt - name: Download docker image artifact uses: actions/download-artifact@v3 @@ -345,28 +409,41 @@ jobs: # GitHub Container Registry. The contents of README.md are pushed as the # image's description to Docker Hub. This job is skipped when the # triggering event is a pull request. - name: "Build and push all platforms" - runs-on: ubuntu-latest - needs: [lint, prepare, test] if: github.event_name != 'pull_request' + name: Build and push all platforms + needs: + - diagnostics + - lint + - prepare + - test + # When Dependabot creates a PR it requires this permission in + # order to push Docker images to ghcr.io. + permissions: + packages: write + runs-on: ubuntu-latest steps: + - id: harden-runner + name: Harden the runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Cache Docker layers uses: actions/cache@v3 env: @@ -380,7 +457,7 @@ jobs: run: ./buildx-dockerfile.sh - name: Build and push platform images to registries id: docker_build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: build-args: | VERSION=${{ needs.prepare.outputs.source_version }} @@ -389,6 +466,11 @@ jobs: context: . file: ./Dockerfile-x platforms: ${{ env.PLATFORMS }} + # Uncomment the following option if you are building an image for use + # on Google Cloud Run or AWS Lambda. The current default image output + # is unable to run on either. Please see the following issue for more + # information: https://github.com/docker/buildx/issues/1533 + # provenance: false push: true tags: ${{ needs.prepare.outputs.tags }} # For a list of pre-defined annotation keys and value types see: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 33d1999..dc49271 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,69 +1,92 @@ --- - # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. -name: "CodeQL" +name: CodeQL on: push: # Dependabot triggered push events have read-only access, but uploading code # scanning requires write access. - branches-ignore: [dependabot/**] + branches-ignore: + - dependabot/** pull_request: # The branches below must be a subset of the branches above - branches: [develop] + branches: + - develop schedule: - cron: '0 21 * * 6' jobs: + diagnostics: + name: Run diagnostics + runs-on: ubuntu-latest + steps: + # Note that a duplicate of this step must be added at the top of + # each job. + - id: harden-runner + name: Harden the runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + - id: github-status + name: Check GitHub status + uses: crazy-max/ghaction-github-status@v3 + - id: dump-context + name: Dump context + uses: crazy-max/ghaction-dump-context@v2 analyze: name: Analyze + needs: + - diagnostics runs-on: ubuntu-latest - + permissions: + # required for all workflows + security-events: write strategy: fail-fast: false matrix: # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', - # 'python'] - language: ['python'] + # Supported options are go, javascript, csharp, python, cpp, and java + language: + - python # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: + - id: harden-runner + name: Harden the runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a - # config file. By default, queries listed here will override any - # specified in a config file. Prefix the list here with "+" to use - # these queries and those in the config file. queries: - # ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or + # Autobuild attempts to build any compiled languages (C/C++, C#, or # Java). If this step fails, then you should remove it and run the build - # manually (see below) + # manually (see below). - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following - # three lines and modify them (or add more) to build your code if your - # project uses a compiled language + # three lines and modify them (or add more) to build your code if your + # project uses a compiled language # - run: | - # make bootstrap - # make release + # make bootstrap + # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..44e8e19 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,29 @@ +--- +name: sync-labels + +on: + push: + paths: + - '.github/labels.yml' + - '.github/workflows/sync-labels.yml' + +permissions: + contents: read + +jobs: + labeler: + permissions: + # actions/checkout needs this to fetch code + contents: read + # crazy-max/ghaction-github-labeler needs this to manage repository labels + issues: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Sync repository labels + if: success() + uses: crazy-max/ghaction-github-labeler@v5 + with: + # This is a hideous ternary equivalent so we only do a dry run unless + # this workflow is triggered by the develop branch. + dry-run: ${{ github.ref_name == 'develop' && 'false' || 'true' }} diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index 8950263..0000000 --- a/.lgtm.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -extraction: - python: - python_setup: - version: 3 - requirements_files: - - requirements-test.txt - setup_py: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6cc81b..79894a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ default_language_version: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.4.0 hooks: - id: check-case-conflict - id: check-executables-have-shebangs @@ -18,7 +18,6 @@ repos: args: - --allow-missing-credentials - id: detect-private-key - exclude: src/secrets/privkey.pem - id: end-of-file-fixer exclude: files/(issue|motd) - id: mixed-line-ending @@ -32,17 +31,17 @@ repos: # Text file hooks - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.31.1 + rev: v0.36.0 hooks: - id: markdownlint args: - --config=.mdl_config.yaml - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.6.1 + rev: v3.0.3 hooks: - id: prettier - repo: https://github.com/adrienverge/yamllint - rev: v1.26.3 + rev: v1.32.0 hooks: - id: yamllint args: @@ -50,17 +49,42 @@ repos: # GitHub Actions hooks - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.14.2 + rev: 0.26.3 hooks: - id: check-github-actions - id: check-github-workflows # pre-commit hooks - repo: https://github.com/pre-commit/pre-commit - rev: v2.17.0 + rev: v3.4.0 hooks: - id: validate_manifest + # Go hooks + - repo: https://github.com/TekWizely/pre-commit-golang + rev: v1.0.0-rc.1 + hooks: + # Style Checkers + - id: go-critic + # StaticCheck + - id: go-staticcheck-repo-mod + # Go Build + - id: go-build-repo-mod + # Go Mod Tidy + - id: go-mod-tidy-repo + # Go Test + - id: go-test-repo-mod + # Go Vet + - id: go-vet-repo-mod + # GoSec + - id: go-sec-repo-mod + + # Nix hooks + - repo: https://github.com/nix-community/nixpkgs-fmt + rev: v1.3.0 + hooks: + - id: nixpkgs-fmt + # Shell script hooks - repo: https://github.com/cisagov/pre-commit-shfmt rev: v0.0.2 @@ -82,61 +106,62 @@ repos: - id: shell-lint # Python hooks + # Run bandit on the "tests" tree with a configuration - repo: https://github.com/PyCQA/bandit - rev: 1.7.4 + rev: 1.7.5 hooks: - id: bandit name: bandit (tests tree) files: tests args: - --config=.bandit.yml - # Run bandit everything but tests directory + # Run bandit on everything except the "tests" tree - repo: https://github.com/PyCQA/bandit - rev: 1.7.0 + rev: 1.7.5 hooks: - id: bandit name: bandit (everything else) exclude: tests - - repo: https://github.com/psf/black - rev: 22.3.0 + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 23.9.1 hooks: - id: black - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + - repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 hooks: - id: flake8 additional_dependencies: - flake8-docstrings - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.942 + rev: v1.5.1 hooks: - id: mypy - repo: https://github.com/asottile/pyupgrade - rev: v2.31.1 + rev: v3.10.1 hooks: - id: pyupgrade # Ansible hooks - - repo: https://github.com/ansible-community/ansible-lint - rev: v5.4.0 + - repo: https://github.com/ansible/ansible-lint + rev: v6.19.0 hooks: - id: ansible-lint # files: molecule/default/playbook.yml # Terraform hooks - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.64.0 + rev: v1.83.2 hooks: - id: terraform_fmt - id: terraform_validate # Docker hooks - repo: https://github.com/IamTheFij/docker-pre-commit - rev: v2.1.0 + rev: v3.0.1 hooks: - id: docker-compose-check diff --git a/.yamllint b/.yamllint index 76a1cce..2a119a6 100644 --- a/.yamllint +++ b/.yamllint @@ -8,6 +8,16 @@ rules: # this behavior. comments-indentation: disable + # yamllint does not allow inline mappings that exceed the line length by + # default. There are many scenarios where the inline mapping may be a key, + # hash, or other long value that would exceed the line length but cannot + # reasonably be broken across lines. + line-length: + # This rule implies the allow-non-breakable-words rule + allow-non-breakable-inline-mappings: true + # Allows a 10% overage from the default limit of 80 + max: 88 + # yamllint doesn't like when we use yes and no for true and false, # but that's pretty standard in Ansible. truthy: disable diff --git a/Dockerfile b/Dockerfile index 248d6cf..fc7b5c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,94 @@ ARG VERSION=unspecified -FROM python:3.10.1-alpine +FROM python:3.12.0-alpine ARG VERSION +### # For a list of pre-defined annotation keys and value types see: # https://github.com/opencontainers/image-spec/blob/master/annotations.md +# # Note: Additional labels are added by the build workflow. -LABEL org.opencontainers.image.authors="mark.feldhousen@cisa.dhs.gov" +### +# github@cisa.dhs.gov is a very generic email distribution, and it is +# unlikely that anyone on that distribution is familiar with the +# particulars of your repository. It is therefore *strongly* +# suggested that you use an email address here that is specific to the +# person or group that maintains this repository; for example: +# LABEL org.opencontainers.image.authors="vm-fusion-dev-group@trio.dhs.gov" +LABEL org.opencontainers.image.authors="github@cisa.dhs.gov" LABEL org.opencontainers.image.vendor="Cybersecurity and Infrastructure Security Agency" +### +# Unprivileged user setup variables +### ARG CISA_UID=421 -ENV CISA_HOME="/home/cisa" -ENV ECHO_MESSAGE="Hello World from Dockerfile" +ARG CISA_GID=${CISA_UID} +ARG CISA_USER="cisa" +ENV CISA_GROUP=${CISA_USER} +ENV CISA_HOME="/home/${CISA_USER}" -RUN addgroup --system --gid ${CISA_UID} cisa \ - && adduser --system --uid ${CISA_UID} --ingroup cisa cisa +### +# Upgrade the system +# +# Note that we use apk --no-cache to avoid writing to a local cache. +# This results in a smaller final image, at the cost of slightly +# longer install times. +### +RUN apk --update --no-cache --quiet upgrade -RUN apk --update --no-cache add \ -ca-certificates \ -openssl \ -py-pip +### +# Create unprivileged user +### +RUN addgroup --system --gid ${CISA_GID} ${CISA_GROUP} \ + && adduser --system --uid ${CISA_UID} --ingroup ${CISA_GROUP} ${CISA_USER} -WORKDIR ${CISA_HOME} +### +# Dependencies +# +# Note that we use apk --no-cache to avoid writing to a local cache. +# This results in a smaller final image, at the cost of slightly +# longer install times. +### +ENV DEPS \ + ca-certificates \ + openssl \ + py-pip +RUN apk --no-cache --quiet add ${DEPS} -RUN wget -O sourcecode.tgz https://github.com/cisagov/skeleton-python-library/archive/v${VERSION}.tar.gz && \ - tar xzf sourcecode.tgz --strip-components=1 && \ - pip install --requirement requirements.txt && \ - ln -snf /run/secrets/quote.txt src/example/data/secret.txt && \ - rm sourcecode.tgz +### +# Make sure pip, setuptools, and wheel are the latest versions +# +# Note that we use pip3 --no-cache-dir to avoid writing to a local +# cache. This results in a smaller final image, at the cost of +# slightly longer install times. +### +RUN pip3 install --no-cache-dir --upgrade \ + pip \ + setuptools \ + wheel -USER cisa +WORKDIR ${CISA_HOME} +### +# Install Python dependencies +# +# Note that we use pip3 --no-cache-dir to avoid writing to a local +# cache. This results in a smaller final image, at the cost of +# slightly longer install times. +### +RUN wget --output-document sourcecode.tgz \ + https://github.com/cisagov/skeleton-python-library/archive/v${VERSION}.tar.gz \ + && tar --extract --gzip --file sourcecode.tgz --strip-components=1 \ + && pip3 install --no-cache-dir --requirement requirements.txt \ + && ln -snf /run/secrets/quote.txt src/example/data/secret.txt \ + && rm sourcecode.tgz + +### +# Prepare to run +### +ENV ECHO_MESSAGE="Hello World from Dockerfile" +USER ${CISA_USER}:${CISA_GROUP} EXPOSE 8080/TCP VOLUME ["/var/log"] ENTRYPOINT ["example"] diff --git a/bump_version.sh b/bump_version.sh index a6c8ed9..0071670 100755 --- a/bump_version.sh +++ b/bump_version.sh @@ -1,6 +1,12 @@ #!/usr/bin/env bash -# bump_version.sh (show|major|minor|patch|prerelease|build) +# Usage: +# bump_version.sh (show|major|minor|patch|finalize) +# bump_version.sh (build|prerelease) [token] +# Notes: +# - If you specify a token it will only be used if the current version is +# tokenless or if the provided token matches the token used in the current +# version. set -o nounset set -o errexit @@ -9,43 +15,67 @@ set -o pipefail VERSION_FILE=src/version.txt README_FILE=README.md -HELP_INFORMATION="bump_version.sh (show|major|minor|patch|prerelease|build|finalize)" +function usage { + cat << HELP +Usage: + ${0##*/} (show|major|minor|patch|finalize) + ${0##*/} (build|prerelease) [token] -old_version=$(sed -n "s/^__version__ = \"\(.*\)\"$/\1/p" $VERSION_FILE) +Notes: + - If you specify a token it will only be used if the current version is + tokenless or if the provided token matches the token used in the current + version. +HELP + exit 1 +} -if [ $# -ne 1 ]; then - echo "$HELP_INFORMATION" +function update_version { + # Comment out periods so they are interpreted as periods and don't + # just match any character + old_version_regex=${1//\./\\\.} + + echo Changing version from "$1" to "$2" + tmp_file=/tmp/version.$$ + sed "s/$old_version_regex/$2/" $VERSION_FILE > $tmp_file + mv $tmp_file $VERSION_FILE + sed "s/$old_version_regex/$2/" $README_FILE > $tmp_file + mv $tmp_file $README_FILE + git add $VERSION_FILE $README_FILE + git commit --message "$3" +} + +if [ $# -lt 1 ] || [ $# -gt 2 ]; then + usage else + old_version=$(sed -n "s/^__version__ = \"\(.*\)\"$/\1/p" $VERSION_FILE) case $1 in - major | minor | patch | prerelease | build) + major | minor | patch) + if [ $# -ne 1 ]; then + usage + fi new_version=$(python -c "import semver; print(semver.bump_$1('$old_version'))") - echo Changing version from "$old_version" to "$new_version" - tmp_file=/tmp/version.$$ - sed "s/$old_version/$new_version/" $VERSION_FILE > $tmp_file - mv $tmp_file $VERSION_FILE - sed "s/$old_version/$new_version/" $README_FILE > $tmp_file - mv $tmp_file $README_FILE - git add $VERSION_FILE $README_FILE - git commit -m"Bump version from $old_version to $new_version" - git push + update_version "$old_version" "$new_version" "Bump version from $old_version to $new_version" + ;; + build | prerelease) + if [ $# -eq 2 ]; then + new_version=$(python -c "import semver; print(semver.bump_$1('$old_version', token='$2'))") + else + new_version=$(python -c "import semver; print(semver.bump_$1('$old_version'))") + fi + update_version "$old_version" "$new_version" "Bump version from $old_version to $new_version" ;; finalize) + if [ $# -ne 1 ]; then + usage + fi new_version=$(python -c "import semver; print(semver.finalize_version('$old_version'))") - echo Changing version from "$old_version" to "$new_version" - tmp_file=/tmp/version.$$ - sed "s/$old_version/$new_version/" $VERSION_FILE > $tmp_file - mv $tmp_file $VERSION_FILE - sed "s/$old_version/$new_version/" $README_FILE > $tmp_file - mv $tmp_file $README_FILE - git add $VERSION_FILE $README_FILE - git commit -m"Bump version from $old_version to $new_version" - git push + update_version "$old_version" "$new_version" "Finalize version from $old_version to $new_version" ;; show) echo "$old_version" ;; *) - echo "$HELP_INFORMATION" + usage ;; esac fi diff --git a/requirements-test.txt b/requirements-test.txt index 5f3337c..8b41b2f 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,4 @@ --requirement requirements.txt pre-commit pytest -pytest-dockerc +python-on-whales diff --git a/setup-env b/setup-env index f526cdb..77926bf 100755 --- a/setup-env +++ b/setup-env @@ -65,7 +65,7 @@ done eval set -- "$PARAMS" # Check to see if pyenv is installed -if [ -z "$(command -v pyenv)" ] || [ -z "$(command -v pyenv-virtualenv)" ]; then +if [ -z "$(command -v pyenv)" ] || { [ -z "$(command -v pyenv-virtualenv)" ] && [ ! -f "$(pyenv root)/plugins/pyenv-virtualenv/bin/pyenv-virtualenv" ]; }; then echo "pyenv and pyenv-virtualenv are required." if [[ "$OSTYPE" == "darwin"* ]]; then cat << 'END_OF_LINE' @@ -186,5 +186,5 @@ else: END_OF_LINE )" -# Qapla +# Qapla' echo "Success!" diff --git a/tests/conftest.py b/tests/conftest.py index 90938e6..054f09d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,16 +4,25 @@ """ # Third-Party Libraries import pytest +from python_on_whales import docker MAIN_SERVICE_NAME = "example" VERSION_SERVICE_NAME = f"{MAIN_SERVICE_NAME}-version" +@pytest.fixture(scope="session") +def dockerc(): + """Start up the Docker composition.""" + docker.compose.up(detach=True) + yield docker + docker.compose.down() + + @pytest.fixture(scope="session") def main_container(dockerc): """Return the main container from the Docker composition.""" # find the container by name even if it is stopped already - return dockerc.containers(service_names=[MAIN_SERVICE_NAME], stopped=True)[0] + return dockerc.compose.ps(services=[MAIN_SERVICE_NAME], all=True)[0] @pytest.fixture(scope="session") @@ -23,7 +32,7 @@ def version_container(dockerc): The version container should just output the version of its underlying contents. """ # find the container by name even if it is stopped already - return dockerc.containers(service_names=[VERSION_SERVICE_NAME], stopped=True)[0] + return dockerc.compose.ps(services=[VERSION_SERVICE_NAME], all=True)[0] def pytest_addoption(parser): diff --git a/tests/container_test.py b/tests/container_test.py index b92ff86..c2e1874 100644 --- a/tests/container_test.py +++ b/tests/container_test.py @@ -20,9 +20,9 @@ def test_container_count(dockerc): """Verify the test composition and container.""" - # stopped parameter allows non-running containers in results + # all parameter allows non-running containers in results assert ( - len(dockerc.containers(stopped=True)) == 2 + len(dockerc.compose.ps(all=True)) == 2 ), "Wrong number of containers were started." @@ -30,7 +30,7 @@ def test_wait_for_ready(main_container): """Wait for container to be ready.""" TIMEOUT = 10 for i in range(TIMEOUT): - if READY_MESSAGE in main_container.logs().decode("utf-8"): + if READY_MESSAGE in main_container.logs(): break time.sleep(1) else: @@ -40,18 +40,21 @@ def test_wait_for_ready(main_container): ) -def test_wait_for_exits(main_container, version_container): +def test_wait_for_exits(dockerc, main_container, version_container): """Wait for containers to exit.""" - assert main_container.wait() == 0, "Container service (main) did not exit cleanly" assert ( - version_container.wait() == 0 + dockerc.wait(main_container.id) == 0 + ), "Container service (main) did not exit cleanly" + assert ( + dockerc.wait(version_container.id) == 0 ), "Container service (version) did not exit cleanly" -def test_output(main_container): +def test_output(dockerc, main_container): """Verify the container had the correct output.""" - main_container.wait() # make sure container exited if running test isolated - log_output = main_container.logs().decode("utf-8") + # make sure container exited if running test isolated + dockerc.wait(main_container.id) + log_output = main_container.logs() assert SECRET_QUOTE in log_output, "Secret not found in log output." @@ -69,10 +72,11 @@ def test_release_version(): ), "RELEASE_TAG does not match the project version" -def test_log_version(version_container): +def test_log_version(dockerc, version_container): """Verify the container outputs the correct version to the logs.""" - version_container.wait() # make sure container exited if running test isolated - log_output = version_container.logs().decode("utf-8").strip() + # make sure container exited if running test isolated + dockerc.wait(version_container.id) + log_output = version_container.logs().strip() pkg_vars = {} with open(VERSION_FILE) as f: exec(f.read(), pkg_vars) # nosec @@ -89,5 +93,6 @@ def test_container_version_label_matches(version_container): exec(f.read(), pkg_vars) # nosec project_version = pkg_vars["__version__"] assert ( - version_container.labels["org.opencontainers.image.version"] == project_version + version_container.config.labels["org.opencontainers.image.version"] + == project_version ), "Dockerfile version label does not match project version"