diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh index d8547ad..e4aff0c 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -26,17 +26,13 @@ readonly SCRIPT_DIR # shellcheck source=/dev/null source "${SCRIPT_DIR}/utils.sh" -# Resolve relative install-dir path to absolute. -[[ ${INSTALL_DIR:=$HOME/.bomctl} != /* ]] && INSTALL_DIR="${GITHUB_WORKSPACE}/${INSTALL_DIR#.\/}" - archive_ext=".tar.gz" -install_path="${INSTALL_DIR}/bomctl" +install_path="${INSTALL_DIR:=$HOME/.bomctl}/bomctl" install_version="${VERSION:=latest}" releases_api="https://api.github.com/repos/bomctl/bomctl/releases" semver_pattern="^v[0-9]+(\.[0-9]+){0,2}$" [[ $RUNNER_OS =~ [Ww]indows ]] && archive_ext=".zip" -[[ $install_path != /* ]] && install_path="${GITHUB_WORKSPACE}/${install_path#.\/}" function download_binary { local download_url="https://github.com/bomctl/bomctl/releases/download/${install_version}/${1}" @@ -117,6 +113,8 @@ fi log_info "Successfully installed bomctl to\n\t${install_path}" -echo "bomctl-binary=${install_path}" >> "${GITHUB_OUTPUT}" +# DEBUG +log_info "readlink -f ${INSTALL_DIR} ==> $(readlink -f "${INSTALL_DIR}")" + echo "bomctl-version=${install_version}" >> "${GITHUB_OUTPUT}" echo "${INSTALL_DIR}" >> "${GITHUB_PATH}" diff --git a/.github/scripts/run-command.sh b/.github/scripts/run-command.sh new file mode 100644 index 0000000..57b9494 --- /dev/null +++ b/.github/scripts/run-command.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# ----------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright © 2024 bomctl a Series of LF Projects, LLC +# SPDX-FileName: .github/scripts/run-command.sh +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: Apache-2.0 +# ----------------------------------------------------------------------------- +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" &> /dev/null && pwd)" +readonly SCRIPT_DIR + +# shellcheck source=/dev/null +source "${SCRIPT_DIR}/utils.sh" + +readonly OLD_IFS="${IFS}" + +supported_cmds=(alias export fetch import list merge push tag version) + +IFS="|" pattern=${supported_cmds[*]} +IFS="${OLD_IFS}" + +if [[ ! ${BOMCTL_COMMAND:=version} =~ (^$pattern) ]]; then + IFS="," + exit_with_error \ + "Unsupported command: ${BOMCTL_COMMAND}. Supported subcommands: [${supported_cmds[*]}]" +fi + +# Convert input args string to array. +IFS=" " read -r -a BOMCTL_ARGS <<< "${BOMCTL_ARGS:=}" + +if [[ -n ${DATABASE_DIR:=} ]]; then + BOMCTL_ARGS+=(--cache-dir "${DATABASE_DIR}") +fi + +log_info "Running command: bomctl ${BOMCTL_COMMAND} ${BOMCTL_ARGS[*]}..." + +bomctl "$BOMCTL_COMMAND" "${BOMCTL_ARGS[@]}" diff --git a/.github/scripts/utils.sh b/.github/scripts/utils.sh index 442abef..334e4a6 100755 --- a/.github/scripts/utils.sh +++ b/.github/scripts/utils.sh @@ -46,3 +46,26 @@ function exit_with_error { log_error "${1}" exit 1 } + +function export_db_json { + local db_file=$1 + local json_dump="{}" + local rows table_dump + + for table in $(sqlite3 "${db_file}" ".tables"); do + rows=$(sqlite3 "${db_file}" -json "select * from ${table}") + + [[ -z $rows ]] && rows="[]" + + table_dump=$(echo "${json_dump}" | jq --argjson rows "${rows}" --arg table "${table}" '. += {$table: $rows}') + json_dump=$table_dump + done + + echo "${json_dump}" > bomctl-export.json +} + +function export_db_sql { + local db_file=$1 + + sqlite3 "${db_file}" -cmd ".output bomctl-export.sql" ".dump" +} diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index fe80a94..32cd5ed 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -77,7 +77,7 @@ jobs: - name: Verify installed binary shell: bash env: - BOMCTL_BIN: ${{ steps.install.outputs.bomctl-binary }} + BOMCTL_BIN: ${{ inputs.install-dir }}/bomctl run: | source .github/scripts/utils.sh @@ -91,17 +91,74 @@ jobs: exit_with_error "Not found in PATH or installed to wrong location" fi - expected="${{ matrix.install-dir || '$HOME/.bomctl' }}/bomctl" - [[ $expected != /* ]] && expected="${GITHUB_WORKSPACE}/${expected#.\/}" + - name: Verify version subcommand + shell: bash + run: bomctl version - log_info "DEBUG: expected $expected" - log_info "DEBUG: BOMCTL_BIN $BOMCTL_BIN" + test-command: + runs-on: ${{ matrix.os }} - log_info "Verifying binary was installed into specified install-dir..." - if [[ $BOMCTL_BIN != $expected ]]; then - exit_with_error "Install path does not match expected path" - fi + strategy: + matrix: + os: + - macos-latest + - ubuntu-latest + - windows-latest - - name: Verify version subcommand + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Run fetch command + uses: ./ + with: + command: fetch + args: --verbose + https://github.com/bomctl/bomctl/releases/download/v0.4.1/bomctl_0.4.1_darwin_arm64.tar.gz.cdx.json + https://github.com/bomctl/bomctl/releases/download/v0.4.0/bomctl_0.4.0_linux_amd64.tar.gz.spdx.json + + - name: Run list command + uses: ./ + with: + command: list + + test-export: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - macos-latest + - ubuntu-latest + - windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Run with command and exports + uses: ./ + with: + command: fetch + args: --verbose + https://github.com/bomctl/bomctl/releases/download/v0.4.1/bomctl_0.4.1_darwin_arm64.tar.gz.cdx.json + https://github.com/bomctl/bomctl/releases/download/v0.4.0/bomctl_0.4.0_linux_amd64.tar.gz.spdx.json + export-json: true + export-sql: true + + - name: Verify export shell: bash - run: bomctl version + env: + BOMCTL_BIN: ${{ inputs.install-dir }}/bomctl + run: | + source .github/scripts/utils.sh + + log_info "Verifying bomctl-export.json exists and is not empty..." + if [[ ! -s bomctl-export.json ]]; then + exit_with_error "Empty or does not exist" + fi + + log_info "Verifying bomctl-export.sql exists and is not empty..." + if [[ ! -s bomctl-export.sql ]]; then + exit_with_error "Empty or does not exist" + fi diff --git a/action.yml b/action.yml index a8821d8..66c4894 100644 --- a/action.yml +++ b/action.yml @@ -29,18 +29,56 @@ branding: inputs: version: - description: The version of bomctl to use. Can be a tagged release, commit SHA, branch name, or "latest" + description: The version of bomctl to install. + Can be a tagged release, commit SHA, branch name, or "latest". + A step using the actions/setup-go action must be executed before + this action when specifying a branch name or commit SHA default: latest required: false + install-dir: description: Path of bomctl install directory (will be created if it doesn't exist) default: $HOME/.bomctl required: false + command: + description: | + Name of the command to run. Must be one of: + - alias Edit the alias for a document + - export Export stored SBOM(s) to filesystem + - fetch Fetch SBOM file(s) from HTTP(S), OCI, or Git URLs + - import Import SBOM file(s) from stdin or local filesystem + - list List SBOM documents in local cache + - merge Merge SBOM documents in local storage + - push Push stored SBOM file to remote URL or filesystem + - tag Edit the tags of a document + - version Show version + default: version + required: false + + args: + description: Arguments that will be passed to the specified command + default: "" + required: false + + database-dir: + description: Directory in which to create the SQLite bomctl.db file + default: . + required: false + + export-json: + description: Export contents of database after bomctl commands are run. The contents will be + written to 'bomctl-export.json' + default: "false" + required: false + + export-sql: + description: Export contents of database after bomctl commands are run. The contents will be + written to 'bomctl-export.sql', a script that can be used to recreate the database + default: "false" + required: false + outputs: - bomctl-binary: - description: Path to installed bomctl binary - value: ${{ steps.install.outputs.bomctl-binary }} bomctl-version: description: Version of installed bomctl binary value: ${{ steps.install.outputs.bomctl-version }} @@ -55,3 +93,30 @@ runs: INSTALL_DIR: ${{ inputs.install-dir }} VERSION: ${{ inputs.version }} run: .github/scripts/install.sh + + - name: Run bomctl command + shell: bash + env: + BOMCTL_COMMAND: ${{ inputs.command }} + BOMCTL_ARGS: ${{ inputs.args }} + DATABASE_DIR: ${{ inputs.database-dir }} + run: .github/scripts/run-command.sh + + - name: Install SQLite + if: (fromJSON(inputs.export-json) || fromJSON(inputs.export-sql)) && runner.os != 'Linux' + shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }} + run: ${{ runner.os == 'Windows' && 'choco' || 'brew' }} install sqlite + + - name: Export database to JSON + if: fromJSON(inputs.export-json) + shell: bash + run: | + source .github/scripts/utils.sh + export_db_json "${{ join(fromJSON('[inputs.database-dir || ".", "bomctl.db"]'), '/') }}" + + - name: Export database to SQL script + if: fromJSON(inputs.export-sql) + shell: bash + run: | + source .github/scripts/utils.sh + export_db_sql "${{ join(fromJSON('[inputs.database-dir || ".", "bomctl.db"]'), '/') }}"