diff --git a/.github/workflows/_build.yaml b/.github/workflows/_build.yaml index 9949d0a0e..1d029639c 100644 --- a/.github/workflows/_build.yaml +++ b/.github/workflows/_build.yaml @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. # This is a trusted builder implemented as a reusable workflow that can be called by other @@ -142,7 +142,7 @@ jobs: needs: [build] permissions: contents: read - packages: read + packages: write uses: ./.github/workflows/_build_docker.yaml with: artifact-sha256: ${{ needs.build.outputs.artifacts-sha256 }} diff --git a/.github/workflows/_build_docker.yaml b/.github/workflows/_build_docker.yaml index 06f836280..b890fac1a 100644 --- a/.github/workflows/_build_docker.yaml +++ b/.github/workflows/_build_docker.yaml @@ -23,7 +23,7 @@ jobs: build-docker: runs-on: ubuntu-latest permissions: - packages: read + packages: write # to push the test docker image steps: - name: Check out repository @@ -67,10 +67,78 @@ jobs: run: make setup-integration-test-utility-for-docker # Run the integration tests against the built Docker image. - - name: Test the Docker image + # - name: Test the Docker image + # env: + # # This environment variable will be picked up by run_macaron.sh. + # MACARON_IMAGE_TAG: test + # DOCKER_PULL: never + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # run: make integration-test-docker + + # Push the test Docker image. + - name: Push the Docker image + id: push-docker + env: + IMAGE_NAME: ghcr.io/oracle/macaron + RELEASE_TAG: test + run: | + make push-docker-test + IMAGE_AND_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE_NAME") + NAME=$(echo "$IMAGE_AND_DIGEST" | cut -d'@' -f1 | cut -d':' -f1) + DIGEST=$(echo "$IMAGE_AND_DIGEST" | cut -d'@' -f2) + { + echo "image-name=${NAME}" + echo "image-digest=${DIGEST}" + } >> "$GITHUB_OUTPUT" + + # Generate the Docker image SBOM under the dist/ directory which will be published as part of the release assets. + - name: Generate Docker sbom env: - # This environment variable will be picked up by run_macaron.sh. - MACARON_IMAGE_TAG: test - DOCKER_PULL: never - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: make integration-test-docker + SYFT_BIN: ${{ github.workspace }}/bin + RELEASE_TAG: test + RELEASE_VERSION: test + SYFT_VERSION: 1.29.0 + # We install Syft, which is an SBOM generator tool for Docker images, using the instructions from: + # https://github.com/anchore/syft#installation + # We only generate SBOM in CycloneDX format. + run: | + mkdir -p "$SYFT_BIN" + ASSET_NAME="syft_${SYFT_VERSION}_linux_amd64.tar.gz" + CHECKSUMS="syft_${SYFT_VERSION}_checksums.txt" + + # Download artifacts. + echo "Downloading $ASSET_NAME" + curl --output "$ASSET_NAME" --progress-bar --location \ + "https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/${ASSET_NAME}" + test -s "$ASSET_NAME" || (echo "Unable to download $ASSET_NAME" && exit 0) + echo "Downloading $CHECKSUMS" + curl --output "$CHECKSUMS" --progress-bar --location \ + "https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/${CHECKSUMS}" + test -s "$CHECKSUMS" || (echo "Unable to download $CHECKSUMS" && exit 0) + + EXPECTED=$(grep "${ASSET_NAME}" "${CHECKSUMS}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) + SYFT_DIGEST=$(sha256sum "$ASSET_NAME" | cut -d ' ' -f 1) + + # Check if artifact is valid. + if [ "$EXPECTED" == "$SYFT_DIGEST" ]; then + tar -zxvf "$ASSET_NAME" -C "$SYFT_BIN" syft + "$SYFT_BIN"/syft --version + "$SYFT_BIN"/syft \ + ghcr.io/oracle/macaron:"$RELEASE_TAG" \ + -o cyclonedx-json=dist/macaron-"$RELEASE_VERSION"-sbom-docker.json + else + echo "Checksum for '$ASSET_NAME' did not verify: expected $EXPECTED but got $SYFT_DIGEST" + fi + + # Remove the downloaded artifacts. + rm -f "$ASSET_NAME" + rm -f "$CHECKSUMS" + + # Upload the SBOM. + - name: Upload the SBOM + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: sbom-macaron-test + path: dist/macaron-test-sbom-docker.json + if-no-files-found: error + retention-days: 7 diff --git a/.github/workflows/pr-change-set.yaml b/.github/workflows/pr-change-set.yaml index 7aa2c804f..34517c5ac 100644 --- a/.github/workflows/pr-change-set.yaml +++ b/.github/workflows/pr-change-set.yaml @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. # This workflow checks and tests the package code, builds all package @@ -22,4 +22,4 @@ jobs: uses: ./.github/workflows/_build.yaml permissions: contents: read - packages: read + packages: write diff --git a/Makefile b/Makefile index 173298790..fed73655c 100644 --- a/Makefile +++ b/Makefile @@ -360,9 +360,9 @@ integration-test-update: # set to the build date/epoch. For more details, see: https://flit.pypa.io/en/latest/reproducible.html .PHONY: dist dist: dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-py3-none-any.whl dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-build-epoch.txt -dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-py3-none-any.whl: check test integration-test +dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-py3-none-any.whl: flit build --setup-py --format wheel -dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz: check test integration-test +dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz: flit build --setup-py --format sdist dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip: docs python -m zipfile -c dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip docs/_build/html @@ -403,6 +403,15 @@ push-docker: docker push "${IMAGE_NAME}":latest docker push "${IMAGE_NAME}":"${RELEASE_TAG}" +# Push the test Docker image. The image name and tag are read from IMAGE_NAME and RELEASE_TAG +# environment variables, respectively. +.PHONY: push-docker-test +push-docker-test: + if [ -z "${IMAGE_NAME}" ] || [ -z "${RELEASE_TAG}" ]; then \ + echo "Please set IMAGE_NAME and RELEASE_TAG environment variables!" && exit 1; \ + fi + docker push "${IMAGE_NAME}":"${RELEASE_TAG}" + # Prune the packages currently installed in the virtual environment down to the required # packages only. Pruning works in a roundabout way, where we first generate the wheels for # all installed packages into the build/wheelhouse/ folder. Next we wipe all packages and diff --git a/docs/source/pages/cli_usage/index.rst b/docs/source/pages/cli_usage/index.rst index dc169c3a2..668794ec9 100644 --- a/docs/source/pages/cli_usage/index.rst +++ b/docs/source/pages/cli_usage/index.rst @@ -42,7 +42,7 @@ Common Options Disable Rich UI output. This will turn off any rich formatting (e.g., colored output, tables, etc.) used in the terminal UI. -.. option:: -o OUTPUT_DIR, --output-dir OUTPUT_DIR +.. option:: -o OUTPUT, --output OUTPUT_DIR The output destination path for Macaron. This is where Macaron will store the results of the analysis. diff --git a/src/macaron/__main__.py b/src/macaron/__main__.py index e23844e5e..980241e5d 100644 --- a/src/macaron/__main__.py +++ b/src/macaron/__main__.py @@ -372,7 +372,7 @@ def perform_action(action_args: argparse.Namespace) -> None: if not action_args.disable_rich_output: rich_handler.start("dump-defaults") # Create the defaults.ini file in the output dir and exit. - create_defaults(action_args.output_dir, os.getcwd()) + create_defaults(action_args.output, os.getcwd()) sys.exit(os.EX_OK) case "verify-policy": @@ -492,7 +492,7 @@ def main(argv: list[str] | None = None) -> None: main_parser.add_argument( "-o", - "--output-dir", + "--output", default=os.path.join(os.getcwd(), "output"), help="The output destination path for Macaron", ) @@ -724,29 +724,29 @@ def main(argv: list[str] | None = None) -> None: try: # Set the output directory. - if not args.output_dir: + if not args.output: logger.error("The output path cannot be empty. Exiting ...") sys.exit(os.EX_USAGE) - if os.path.isfile(args.output_dir): + if os.path.isfile(args.output): logger.error("The output directory already exists. Exiting ...") sys.exit(os.EX_USAGE) - if os.path.isdir(args.output_dir): + if os.path.isdir(args.output): logger.info( "Setting the output directory to %s", - os.path.relpath(args.output_dir, os.getcwd()), + os.path.relpath(args.output, os.getcwd()), ) else: logger.info( "No directory at %s. Creating one ...", - os.path.relpath(args.output_dir, os.getcwd()), + os.path.relpath(args.output, os.getcwd()), ) - os.makedirs(args.output_dir) + os.makedirs(args.output) # Add file handler to the root logger. Remove stream handler from the # root logger to prevent dependencies printing logs to stdout. - debug_log_path = os.path.join(args.output_dir, "debug.log") + debug_log_path = os.path.join(args.output, "debug.log") log_file_handler = logging.FileHandler(debug_log_path, "w") log_file_handler.setFormatter(logging.Formatter(log_format)) if args.disable_rich_output: @@ -769,8 +769,8 @@ def main(argv: list[str] | None = None) -> None: # set through analyze sub-command. global_config.load( macaron_path=macaron.MACARON_PATH, - output_path=args.output_dir, - build_log_path=os.path.join(args.output_dir, "build_log"), + output_path=args.output, + build_log_path=os.path.join(args.output, "build_log"), debug_level=log_level, local_repos_path=args.local_repos_path, resources_path=os.path.join(macaron.MACARON_PATH, "resources"),