diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 017681a..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,642 +0,0 @@ -name: CI Builds - -on: - workflow_dispatch: - inputs: - logLevel: - description: 'Log level' - required: true - default: 'warning' - type: choice - options: - - info - - push: - paths-ignore: - - 'README.md' - - '.github/**' - - '.dockerignore' - - 'docs/**' - - 'data_platform/**' - - '.gitignore' - - '.gitleaksignore' - - 'demo**' - - 'go.mod' - - 'go.sum' - - '.goreleaser.yaml' - - 'mkdocs.yml' - - 'nfpm.yaml' - - 'pokemon.svg' - branches: - - main - -env: - VERSION_NUMBER: 'v2.0.0' - DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli' - AWS_REGION: 'us-west-2' - -permissions: - actions: read - contents: read - id-token: write - security-events: write - -jobs: - smoke-tests: - strategy: - fail-fast: false - matrix: - os: [ubuntu-22.04, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: '1.25' - - - name: Build - shell: bash - run: go build -o poke-cli${{ matrix.os == 'windows-latest' && '.exe' || '' }} . - - - name: Smoke test - shell: bash - run: | - BIN=./poke-cli${{ matrix.os == 'windows-latest' && '.exe' || '' }} - "$BIN" --help - "$BIN" pokemon pikachu - "$BIN" pokemon charizard -a -d - "$BIN" ability overgrow - "$BIN" move thunderbolt - "$BIN" mechanics --natures - - gosec: - runs-on: ubuntu-22.04 - needs: [smoke-tests] - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Run Gosec Security Scanner - uses: securego/gosec@v2.25.0 - with: - args: '-no-fail -fmt sarif -out results.sarif ./...' - - - name: Upload SARIF Report - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: results.sarif - - bandit: - runs-on: ubuntu-22.04 - needs: [smoke-tests] - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Run Bandit Security Scanner - run: | - uv tool run --from 'bandit[sarif,toml]' bandit \ - -r data_platform/pipelines \ - -f sarif \ - -o bandit-results.sarif \ - || true - - - name: Upload SARIF Report - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: bandit-results.sarif - - rust-cache: - runs-on: ubuntu-22.04 - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - - - name: Build poke-cache - run: cargo build --release --manifest-path services/Cargo.toml --bin poke-cache - - - name: Test cache module - run: cargo test --manifest-path services/Cargo.toml --lib cache - - - name: Smoke test poke-cache - run: | - BIN=services/target/release/poke-cache - "$BIN" get "https://pokeapi.co/api/v2/pokemon/pikachu" > out.json - test -s out.json - "$BIN" get "https://pokeapi.co/api/v2/pokemon/pikachu" 2> hit.log > /dev/null - grep -q "Cache hit" hit.log - echo "poke-cache smoke test passed" - - gitleaks: - runs-on: ubuntu-22.04 - needs: [gosec, bandit] - if: needs.gosec.result == 'success' && needs.bandit.result == 'success' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Run Gitleaks - run: | - docker run --rm -v ${{ github.workspace }}:/path \ - ghcr.io/gitleaks/gitleaks:v8.29.0 \ - dir /path -c /path/gitleaks.toml --redact -v \ - --report-format sarif --report-path /path/gitleaks-results.sarif - - - name: Upload SARIF Report - if: always() - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: gitleaks-results.sarif - - build-linux-packages: - runs-on: ubuntu-22.04 - needs: [gitleaks, rust-cache] - if: needs.gitleaks.result == 'success' && needs.rust-cache.result == 'success' - strategy: - matrix: - arch: [ amd64, arm64 ] - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: '1.25' - - - name: Build Go Binary - env: - GOOS: linux - GOARCH: ${{ matrix.arch }} - CGO_ENABLED: 0 - run: | - go build -ldflags="-s -w -X main.version=${{ env.VERSION_NUMBER }}" -o poke-cli - - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu' }} - - - name: Install aarch64 cross-linker - if: matrix.arch == 'arm64' - run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu - - - name: Build poke-cache Binary - env: - RUST_TARGET: ${{ matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu' }} - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc - run: | - cargo build --release --manifest-path services/Cargo.toml --bin poke-cache --target "$RUST_TARGET" - cp "services/target/$RUST_TARGET/release/poke-cache" ./poke-cache - - - name: Install nFPM - run: | - go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.43.0 - echo "$HOME/go/bin" >> $GITHUB_PATH - - - name: Create nFPM Config for Architecture - run: | - # Create architecture-specific config by modifying the base config - sed "s/arch: \"arm64\"/arch: \"${{ matrix.arch }}\"/" nfpm.yaml > nfpm-${{ matrix.arch }}.yaml - - - name: Build packages - run: | - # Create output directory - mkdir -p dist - - # Build DEB package - nfpm package \ - --config nfpm-${{ matrix.arch }}.yaml \ - --packager deb \ - --target dist/poke-cli_${{ env.VERSION_NUMBER }}_linux_${{ matrix.arch }}.deb - - # Build RPM package - nfpm package \ - --config nfpm-${{ matrix.arch }}.yaml \ - --packager rpm \ - --target dist/poke-cli_${{ env.VERSION_NUMBER }}_linux_${{ matrix.arch }}.rpm - - - name: Upload packages as artifacts - uses: actions/upload-artifact@v6 - with: - name: linux-packages-${{ matrix.arch }} - path: dist/* - - upload-deb-packages: - runs-on: ubuntu-22.04 - needs: [build-linux-packages] - if: needs.build-linux-packages.result == 'success' - strategy: - matrix: - arch: [ amd64, arm64 ] - fail-fast: false # Don't cancel other uploads if one fails - - steps: - - name: Download package artifact - uses: actions/download-artifact@v7 - with: - name: linux-packages-${{ matrix.arch }} - path: packages/ - - - name: Install Cloudsmith CLI - uses: cloudsmith-io/cloudsmith-cli-action@v2 - with: - api-key: ${{ secrets.CLOUDSMITH_API_KEY }} - - - name: Upload DEB to Cloudsmith - working-directory: packages - run: | - cloudsmith push deb \ - digitalghost-dev/poke-cli/debian/bookworm \ - poke-cli_${{ env.VERSION_NUMBER }}_linux_${{ matrix.arch }}.deb - - upload-rpm-packages: - runs-on: ubuntu-22.04 - needs: [build-linux-packages] - if: needs.build-linux-packages.result == 'success' - strategy: - matrix: - arch: [ amd64, arm64 ] - fail-fast: false - - steps: - - name: Download package artifact - uses: actions/download-artifact@v7 - with: - name: linux-packages-${{ matrix.arch }} - path: packages/ - - - name: Install Cloudsmith CLI - uses: cloudsmith-io/cloudsmith-cli-action@v2 - with: - api-key: ${{ secrets.CLOUDSMITH_API_KEY }} - - - name: Upload RPM to Cloudsmith - working-directory: packages - run: | - cloudsmith push rpm \ - digitalghost-dev/poke-cli/fedora/42 \ - poke-cli_${{ env.VERSION_NUMBER }}_linux_${{ matrix.arch }}.rpm - - upload-summary: - runs-on: ubuntu-22.04 - needs: [upload-deb-packages, upload-rpm-packages] - if: always() - - steps: - - name: Check all Uploads - run: | - echo "DEB uploads: ${{ needs.upload-deb-packages.result }}" - echo "RPM uploads: ${{ needs.upload-rpm-packages.result }}" - - if [ "${{ needs.upload-deb-packages.result }}" != "success" ] || \ - [ "${{ needs.upload-rpm-packages.result }}" != "success" ]; then - echo "⚠️ Some uploads failed" - exit 1 - fi - echo "✅ All packages uploaded successfully" - - validate-links: - runs-on: ubuntu-22.04 - needs: [gitleaks] - if: needs.gitleaks.result == 'success' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Check Links - uses: lycheeverse/lychee-action@v2 - with: - args: --verbose --config lychee.toml ./docs/**/*.md - fail: false - output: ./lychee-report.md - - - name: Upload Report - if: always() - uses: actions/upload-artifact@v6 - with: - name: lychee-report - path: ./lychee-report.md - - build-docs-docker-image: - runs-on: ubuntu-22.04 - needs: [validate-links] - if: needs.validate-links.result == 'success' - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - sparse-checkout: | - docs - - - name: Set up Docker Buildx - uses: 'docker/setup-buildx-action@v4' - - - name: Prepare Docker Build Context - run: | - mkdir docker-context - rsync -av --exclude=docker-context . docker-context/ - - - name: Build and Export - uses: 'docker/build-push-action@v7' - with: - context: ./docker-context - file: ./docker-context/docs/Dockerfile - tags: docs:latest - outputs: type=docker,dest=/tmp/docs.tar - - - name: Upload Artifact - uses: actions/upload-artifact@v6 - with: - name: docs - path: /tmp/docs.tar - - upload-docs-to-ecr: - runs-on: ubuntu-22.04 - needs: [build-docs-docker-image] - if: needs.build-docs-docker-image.result == 'success' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Configure AWS - uses: aws-actions/configure-aws-credentials@v6 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - - - name: Download Artifact - uses: actions/download-artifact@v7 - with: - name: docs - path: /tmp - - - name: Load Image - run: docker load -i /tmp/docs.tar - - - name: Tag and Push - run: | - docker tag docs:latest ${{ secrets.AWS_DOCS_ECR_NAME }}:latest - docker push ${{ secrets.AWS_DOCS_ECR_NAME }}:latest - - lint-cli-dockerfile: - runs-on: ubuntu-22.04 - needs: [gitleaks] - if: needs.gitleaks.result == 'success' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Lint Dockerfile - uses: 'hadolint/hadolint-action@v3.1.0' - with: - dockerfile: Dockerfile - failure-threshold: 'error' - - build-cli-docker-image: - runs-on: ubuntu-22.04 - needs: [lint-cli-dockerfile] - if: needs.lint-cli-dockerfile.result == 'success' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Set up Docker Buildx - uses: 'docker/setup-buildx-action@v4' - - - name: Prepare Docker Build Context - run: | - mkdir docker-context - rsync -av --exclude=docker-context . docker-context/ - - - name: Build and Export - uses: 'docker/build-push-action@v7' - with: - context: ./docker-context - tags: poke-cli:${{ env.VERSION_NUMBER }} - outputs: type=docker,dest=/tmp/poke-cli.tar - - - name: Upload Artifact - uses: actions/upload-artifact@v6 - with: - name: poke-cli - path: /tmp/poke-cli.tar - - # Uploading to Elastic Container Registry as a backup method. - upload-cli-to-ecr: - runs-on: ubuntu-22.04 - needs: [build-cli-docker-image] - if: needs.build-cli-docker-image.result == 'success' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Configure AWS - uses: aws-actions/configure-aws-credentials@v6 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - - - name: Build, tag, and push image to Amazon ECR - run : | - docker build -t poke-cli:${{ env.VERSION_NUMBER }} . - docker tag poke-cli:${{ env.VERSION_NUMBER }} ${{ secrets.AWS_ECR_NAME }}:${{ env.VERSION_NUMBER }} - docker push ${{ secrets.AWS_ECR_NAME }}:${{ env.VERSION_NUMBER }} - - syft: - runs-on: ubuntu-22.04 - needs: [build-cli-docker-image] - if: needs.build-cli-docker-image.result == 'success' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Set up Docker Buildx - uses: 'docker/setup-buildx-action@v4' - - - name: Download Artifact - uses: actions/download-artifact@v7 - with: - name: poke-cli - path: /tmp - - - name: Load Image - run: | - docker load --input /tmp/poke-cli.tar - docker image ls -a - - - name: Create and Upload SBOM - uses: anchore/sbom-action@v0 - with: - image: poke-cli:${{ env.VERSION_NUMBER }} - format: spdx-json - artifact-name: poke-cli-sbom-${{ env.VERSION_NUMBER }}.spdx.json - output-file: /tmp/poke-cli-sbom-${{ env.VERSION_NUMBER }}.spdx.json - upload-artifact: true - - grype: - runs-on: ubuntu-22.04 - needs: [syft] - if: needs.syft.result == 'success' - - steps: - - name: Download SBOM - uses: actions/download-artifact@v7 - with: - name: poke-cli-sbom-${{ env.VERSION_NUMBER }}.spdx.json - path: /tmp - - - name: Scan SBOM - uses: anchore/scan-action@v7 - id: scan - with: - sbom: /tmp/poke-cli-sbom-${{ env.VERSION_NUMBER }}.spdx.json - fail-build: false - output-format: sarif - severity-cutoff: critical - - - name: Upload SARIF Report - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: ${{ steps.scan.outputs.sarif }} - - architecture-build: - runs-on: ubuntu-22.04 - needs: [gitleaks] - if: needs.gitleaks.result == 'success' - - strategy: - fail-fast: false - matrix: - platform: [linux/amd64, linux/arm64] - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Docker Meta - id: meta - uses: 'docker/metadata-action@v6' - with: - images: ${{ env.DOCKERHUB_REGISTRY_NAME }} - - - name: Set up QEMU - uses: 'docker/setup-qemu-action@v4' - - - name: Set up Docker Buildx - uses: 'docker/setup-buildx-action@v4' - - - name: Login to Docker Hub - uses: 'docker/login-action@v4' - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and Push by Digest - id: build - uses: 'docker/build-push-action@v7' - with: - context: . - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.DOCKERHUB_REGISTRY_NAME }},push-by-digest=true,name-canonical=true,push=true - - - name: Export Digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload Digest for AMD64 - if: matrix.platform == 'linux/amd64' - uses: actions/upload-artifact@v6 - with: - name: digests-amd64 - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - - name: Upload Digest for ARM64 - if: matrix.platform == 'linux/arm64' - uses: actions/upload-artifact@v6 - with: - name: digests-arm64 - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - create-manifest-and-push: - runs-on: ubuntu-22.04 - needs: [architecture-build] - - steps: - - name: Download Digests - uses: actions/download-artifact@v7 - with: - pattern: digests-* - path: /tmp/digests - merge-multiple: true - - - name: Set up Docker Buildx - uses: 'docker/setup-buildx-action@v4' - - - name: Docker meta - id: meta - uses: 'docker/metadata-action@v6' - with: - images: ${{ env.DOCKERHUB_REGISTRY_NAME }} - tags: ${{ env.VERSION_NUMBER }} - - - name: Login to Docker Hub - uses: 'docker/login-action@v4' - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Create Manifest List and Push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.DOCKERHUB_REGISTRY_NAME }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY_NAME }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8ce1d8..dfbf16f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: goreleaser +name: Release on: push: @@ -7,9 +7,149 @@ on: workflow_dispatch: permissions: - contents: write + actions: read + contents: read + +env: + VERSION_NUMBER: ${{ github.ref_name }} + DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli' + AWS_REGION: 'us-west-2' jobs: + smoke-tests: + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: '1.25' + + - name: Build + shell: bash + run: go build -o poke-cli${{ matrix.os == 'windows-latest' && '.exe' || '' }} . + + - name: Smoke test + shell: bash + run: | + BIN=./poke-cli${{ matrix.os == 'windows-latest' && '.exe' || '' }} + "$BIN" --help + "$BIN" pokemon pikachu + "$BIN" pokemon charizard -a -d + "$BIN" ability overgrow + "$BIN" move thunderbolt + "$BIN" mechanics --natures + + gosec: + runs-on: ubuntu-22.04 + needs: [smoke-tests] + permissions: + contents: read + security-events: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Run Gosec Security Scanner + uses: securego/gosec@v2.25.0 + with: + args: '-no-fail -fmt sarif -out results.sarif ./...' + + - name: Upload SARIF Report + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: results.sarif + + bandit: + runs-on: ubuntu-22.04 + needs: [smoke-tests] + permissions: + contents: read + security-events: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Run Bandit Security Scanner + run: | + uv tool run --from 'bandit[sarif,toml]' bandit \ + -r data_platform/pipelines \ + -f sarif \ + -o bandit-results.sarif \ + || true + + - name: Upload SARIF Report + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: bandit-results.sarif + + rust-cache: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build poke-cache + run: cargo build --release --manifest-path services/Cargo.toml --bin poke-cache + + - name: Test cache module + run: cargo test --manifest-path services/Cargo.toml --lib cache + + - name: Smoke test poke-cache + run: | + BIN=services/target/release/poke-cache + "$BIN" get "https://pokeapi.co/api/v2/pokemon/pikachu" > out.json + test -s out.json + "$BIN" get "https://pokeapi.co/api/v2/pokemon/pikachu" 2> hit.log > /dev/null + grep -q "Cache hit" hit.log + echo "poke-cache smoke test passed" + + gitleaks: + runs-on: ubuntu-22.04 + needs: [gosec, bandit] + if: needs.gosec.result == 'success' && needs.bandit.result == 'success' + permissions: + contents: read + security-events: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Run Gitleaks + run: | + docker run --rm -v ${{ github.workspace }}:/path \ + ghcr.io/gitleaks/gitleaks:v8.29.0 \ + dir /path -c /path/gitleaks.toml --redact -v \ + --report-format sarif --report-path /path/gitleaks-results.sarif + + - name: Upload SARIF Report + if: always() + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: gitleaks-results.sarif + build-poke-cache: strategy: fail-fast: false @@ -78,9 +218,489 @@ jobs: path: staging/poke-cache${{ matrix.ext }} if-no-files-found: error + build-linux-packages: + runs-on: ubuntu-22.04 + needs: [gitleaks, rust-cache] + if: startsWith(github.ref, 'refs/tags/') && needs.gitleaks.result == 'success' && needs.rust-cache.result == 'success' + strategy: + matrix: + arch: [ amd64, arm64 ] + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: '1.25' + + - name: Build Go Binary + env: + GOOS: linux + GOARCH: ${{ matrix.arch }} + CGO_ENABLED: 0 + run: | + go build -ldflags="-s -w -X main.version=${{ env.VERSION_NUMBER }}" -o poke-cli + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu' }} + + - name: Install aarch64 cross-linker + if: matrix.arch == 'arm64' + run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: Build poke-cache Binary + env: + RUST_TARGET: ${{ matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu' }} + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc + run: | + cargo build --release --manifest-path services/Cargo.toml --bin poke-cache --target "$RUST_TARGET" + cp "services/target/$RUST_TARGET/release/poke-cache" ./poke-cache + + - name: Install nFPM + run: | + go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.43.0 + echo "$HOME/go/bin" >> $GITHUB_PATH + + - name: Create nFPM Config for Architecture + run: | + # Create architecture-specific config by modifying the base config + sed "s/arch: \"arm64\"/arch: \"${{ matrix.arch }}\"/" nfpm.yaml > nfpm-${{ matrix.arch }}.yaml + + - name: Build packages + run: | + # Create output directory + mkdir -p dist + + # Build DEB package + nfpm package \ + --config nfpm-${{ matrix.arch }}.yaml \ + --packager deb \ + --target dist/poke-cli_${{ env.VERSION_NUMBER }}_linux_${{ matrix.arch }}.deb + + # Build RPM package + nfpm package \ + --config nfpm-${{ matrix.arch }}.yaml \ + --packager rpm \ + --target dist/poke-cli_${{ env.VERSION_NUMBER }}_linux_${{ matrix.arch }}.rpm + + - name: Upload packages as artifacts + uses: actions/upload-artifact@v6 + with: + name: linux-packages-${{ matrix.arch }} + path: dist/* + + upload-deb-packages: + runs-on: ubuntu-22.04 + needs: [build-linux-packages] + if: needs.build-linux-packages.result == 'success' + strategy: + matrix: + arch: [ amd64, arm64 ] + fail-fast: false # Don't cancel other uploads if one fails + + steps: + - name: Download package artifact + uses: actions/download-artifact@v7 + with: + name: linux-packages-${{ matrix.arch }} + path: packages/ + + - name: Install Cloudsmith CLI + uses: cloudsmith-io/cloudsmith-cli-action@v2 + with: + api-key: ${{ secrets.CLOUDSMITH_API_KEY }} + + - name: Upload DEB to Cloudsmith + working-directory: packages + run: | + cloudsmith push deb \ + digitalghost-dev/poke-cli/debian/bookworm \ + poke-cli_${{ env.VERSION_NUMBER }}_linux_${{ matrix.arch }}.deb + + upload-rpm-packages: + runs-on: ubuntu-22.04 + needs: [build-linux-packages] + if: needs.build-linux-packages.result == 'success' + strategy: + matrix: + arch: [ amd64, arm64 ] + fail-fast: false + + steps: + - name: Download package artifact + uses: actions/download-artifact@v7 + with: + name: linux-packages-${{ matrix.arch }} + path: packages/ + + - name: Install Cloudsmith CLI + uses: cloudsmith-io/cloudsmith-cli-action@v2 + with: + api-key: ${{ secrets.CLOUDSMITH_API_KEY }} + + - name: Upload RPM to Cloudsmith + working-directory: packages + run: | + cloudsmith push rpm \ + digitalghost-dev/poke-cli/fedora/42 \ + poke-cli_${{ env.VERSION_NUMBER }}_linux_${{ matrix.arch }}.rpm + + upload-summary: + runs-on: ubuntu-22.04 + needs: [upload-deb-packages, upload-rpm-packages] + if: always() && startsWith(github.ref, 'refs/tags/') + + steps: + - name: Check all Uploads + run: | + echo "DEB uploads: ${{ needs.upload-deb-packages.result }}" + echo "RPM uploads: ${{ needs.upload-rpm-packages.result }}" + + if [ "${{ needs.upload-deb-packages.result }}" != "success" ] || \ + [ "${{ needs.upload-rpm-packages.result }}" != "success" ]; then + echo "⚠️ Some uploads failed" + exit 1 + fi + echo "✅ All packages uploaded successfully" + + validate-links: + runs-on: ubuntu-22.04 + needs: [gitleaks] + if: needs.gitleaks.result == 'success' + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check Links + uses: lycheeverse/lychee-action@v2 + with: + args: --verbose --config lychee.toml ./docs/**/*.md + fail: false + output: ./lychee-report.md + + - name: Upload Report + if: always() + uses: actions/upload-artifact@v6 + with: + name: lychee-report + path: ./lychee-report.md + + build-docs-docker-image: + runs-on: ubuntu-22.04 + needs: [validate-links] + if: startsWith(github.ref, 'refs/tags/') && needs.validate-links.result == 'success' + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + sparse-checkout: | + docs + + - name: Set up Docker Buildx + uses: 'docker/setup-buildx-action@v4' + + - name: Prepare Docker Build Context + run: | + mkdir docker-context + rsync -av --exclude=docker-context . docker-context/ + + - name: Build and Export + uses: 'docker/build-push-action@v7' + with: + context: ./docker-context + file: ./docker-context/docs/Dockerfile + tags: docs:latest + outputs: type=docker,dest=/tmp/docs.tar + + - name: Upload Artifact + uses: actions/upload-artifact@v6 + with: + name: docs + path: /tmp/docs.tar + + upload-docs-to-ecr: + runs-on: ubuntu-22.04 + needs: [build-docs-docker-image] + if: needs.build-docs-docker-image.result == 'success' + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Configure AWS + uses: aws-actions/configure-aws-credentials@v6 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Download Artifact + uses: actions/download-artifact@v7 + with: + name: docs + path: /tmp + + - name: Load Image + run: docker load -i /tmp/docs.tar + + - name: Tag and Push + run: | + docker tag docs:latest ${{ secrets.AWS_DOCS_ECR_NAME }}:latest + docker push ${{ secrets.AWS_DOCS_ECR_NAME }}:latest + + lint-cli-dockerfile: + runs-on: ubuntu-22.04 + needs: [gitleaks] + if: needs.gitleaks.result == 'success' + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Lint Dockerfile + uses: 'hadolint/hadolint-action@v3.1.0' + with: + dockerfile: Dockerfile + failure-threshold: 'error' + + build-cli-docker-image: + runs-on: ubuntu-22.04 + needs: [lint-cli-dockerfile] + if: startsWith(github.ref, 'refs/tags/') && needs.lint-cli-dockerfile.result == 'success' + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: 'docker/setup-buildx-action@v4' + + - name: Prepare Docker Build Context + run: | + mkdir docker-context + rsync -av --exclude=docker-context . docker-context/ + + - name: Build and Export + uses: 'docker/build-push-action@v7' + with: + context: ./docker-context + tags: poke-cli:${{ env.VERSION_NUMBER }} + outputs: type=docker,dest=/tmp/poke-cli.tar + + - name: Upload Artifact + uses: actions/upload-artifact@v6 + with: + name: poke-cli + path: /tmp/poke-cli.tar + + # Uploading to Elastic Container Registry as a backup method. + upload-cli-to-ecr: + runs-on: ubuntu-22.04 + needs: [build-cli-docker-image] + if: needs.build-cli-docker-image.result == 'success' + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Configure AWS + uses: aws-actions/configure-aws-credentials@v6 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Build, tag, and push image to Amazon ECR + run : | + docker build -t poke-cli:${{ env.VERSION_NUMBER }} . + docker tag poke-cli:${{ env.VERSION_NUMBER }} ${{ secrets.AWS_ECR_NAME }}:${{ env.VERSION_NUMBER }} + docker push ${{ secrets.AWS_ECR_NAME }}:${{ env.VERSION_NUMBER }} + + syft: + runs-on: ubuntu-22.04 + needs: [build-cli-docker-image] + if: needs.build-cli-docker-image.result == 'success' + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: 'docker/setup-buildx-action@v4' + + - name: Download Artifact + uses: actions/download-artifact@v7 + with: + name: poke-cli + path: /tmp + + - name: Load Image + run: | + docker load --input /tmp/poke-cli.tar + docker image ls -a + + - name: Create and Upload SBOM + uses: anchore/sbom-action@v0 + with: + image: poke-cli:${{ env.VERSION_NUMBER }} + format: spdx-json + artifact-name: poke-cli-sbom-${{ env.VERSION_NUMBER }}.spdx.json + output-file: /tmp/poke-cli-sbom-${{ env.VERSION_NUMBER }}.spdx.json + upload-artifact: true + + grype: + runs-on: ubuntu-22.04 + needs: [syft] + if: needs.syft.result == 'success' + permissions: + contents: read + security-events: write + + steps: + - name: Download SBOM + uses: actions/download-artifact@v7 + with: + name: poke-cli-sbom-${{ env.VERSION_NUMBER }}.spdx.json + path: /tmp + + - name: Scan SBOM + uses: anchore/scan-action@v7 + id: scan + with: + sbom: /tmp/poke-cli-sbom-${{ env.VERSION_NUMBER }}.spdx.json + fail-build: false + output-format: sarif + severity-cutoff: critical + + - name: Upload SARIF Report + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: ${{ steps.scan.outputs.sarif }} + + architecture-build: + runs-on: ubuntu-22.04 + needs: [gitleaks] + if: startsWith(github.ref, 'refs/tags/') && needs.gitleaks.result == 'success' + + strategy: + fail-fast: false + matrix: + platform: [linux/amd64, linux/arm64] + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Docker Meta + id: meta + uses: 'docker/metadata-action@v6' + with: + images: ${{ env.DOCKERHUB_REGISTRY_NAME }} + + - name: Set up QEMU + uses: 'docker/setup-qemu-action@v4' + + - name: Set up Docker Buildx + uses: 'docker/setup-buildx-action@v4' + + - name: Login to Docker Hub + uses: 'docker/login-action@v4' + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and Push by Digest + id: build + uses: 'docker/build-push-action@v7' + with: + context: . + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.DOCKERHUB_REGISTRY_NAME }},push-by-digest=true,name-canonical=true,push=true + + - name: Export Digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload Digest for AMD64 + if: matrix.platform == 'linux/amd64' + uses: actions/upload-artifact@v6 + with: + name: digests-amd64 + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + - name: Upload Digest for ARM64 + if: matrix.platform == 'linux/arm64' + uses: actions/upload-artifact@v6 + with: + name: digests-arm64 + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + create-manifest-and-push: + runs-on: ubuntu-22.04 + needs: [architecture-build] + if: needs.architecture-build.result == 'success' + + steps: + - name: Download Digests + uses: actions/download-artifact@v7 + with: + pattern: digests-* + path: /tmp/digests + merge-multiple: true + + - name: Set up Docker Buildx + uses: 'docker/setup-buildx-action@v4' + + - name: Docker meta + id: meta + uses: 'docker/metadata-action@v6' + with: + images: ${{ env.DOCKERHUB_REGISTRY_NAME }} + tags: ${{ env.VERSION_NUMBER }} + + - name: Login to Docker Hub + uses: 'docker/login-action@v4' + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create Manifest List and Push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.DOCKERHUB_REGISTRY_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY_NAME }}:${{ steps.meta.outputs.version }} + goreleaser: - needs: [build-poke-cache] + needs: [build-poke-cache, smoke-tests, rust-cache, gitleaks] runs-on: ubuntu-22.04 + permissions: + contents: write steps: - name: Checkout uses: actions/checkout@v6 @@ -97,7 +717,7 @@ jobs: path: prebuilt - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@v7 with: distribution: goreleaser-pro version: '~> v2' diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..29866ce --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,43 @@ +name: Security + +on: + pull_request: + types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + gosec: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Run Gosec Security Scanner + uses: securego/gosec@v2.25.0 + with: + args: '-severity medium -confidence medium ./...' + + bandit: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Run Bandit Security Scanner + run: | + uv tool run --from 'bandit[toml]' bandit \ + -r data_platform/pipelines \ + --severity-level medium \ + --confidence-level medium diff --git a/.goreleaser.yml b/.goreleaser.yml index 65194d8..2181b70 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -60,6 +60,9 @@ changelog: exclude: - "^docs:" - "^test:" + - "^ci:" + - "^chore:" + - "^build:" homebrew_casks: - name: poke-cli diff --git a/Dockerfile b/Dockerfile index cb90500..17022a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ FROM rust:1-alpine AS rust-build WORKDIR /build +# hadolint ignore=DL3018 RUN apk add --no-cache build-base COPY services/Cargo.toml services/Cargo.lock ./services/ @@ -26,6 +27,7 @@ RUN cargo build --release --manifest-path services/Cargo.toml --bin poke-cache FROM alpine:3.24 # Installing only necessary packages and remove them after use +# hadolint ignore=DL3018 RUN apk add --no-cache shadow && \ addgroup -S poke_group && adduser -S poke_user -G poke_group && \ sed -i 's/^root:.*/root:!*:0:0:root:\/root:\/sbin\/nologin/' /etc/passwd && \ diff --git a/cli.go b/cli.go index d524f7b..3b4015b 100644 --- a/cli.go +++ b/cli.go @@ -214,7 +214,7 @@ func runCLI(args []string) int { var exit = os.Exit func isInteractive() bool { - return term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd())) + return term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd())) // #nosec G115 } func saveConfig(cfg flags.Config) { diff --git a/cmd/types/damage_table.go b/cmd/types/damage_table.go index 3551351..1e357a1 100644 --- a/cmd/types/damage_table.go +++ b/cmd/types/damage_table.go @@ -40,7 +40,7 @@ func DamageTable(typesName string, endpoint string) (string, error) { out.WriteString(styling.StyleBold.Render("Damage Chart:")) out.WriteString("\n") - physicalWidth, _, _ := term.GetSize(uintptr(int(os.Stdout.Fd()))) + physicalWidth, _, _ := term.GetSize(uintptr(int(os.Stdout.Fd()))) // #nosec G115 doc := strings.Builder{} // Helper function to build list items diff --git a/cmd/utils/web.go b/cmd/utils/web.go index fdb03a8..150ed8f 100644 --- a/cmd/utils/web.go +++ b/cmd/utils/web.go @@ -22,13 +22,13 @@ func Open(url string) tea.Cmd { switch runtime.GOOS { case "windows": browserCmd = "cmd" - openCmd = exec.Command("cmd", "/c", "start", url) //nolint:gosec + openCmd = exec.Command("cmd", "/c", "start", url) // #nosec G204 case "darwin": browserCmd = "open" - openCmd = exec.Command("open", url) + openCmd = exec.Command("open", url) // #nosec G204 default: browserCmd = "xdg-open" - openCmd = exec.Command("xdg-open", url) + openCmd = exec.Command("xdg-open", url) // #nosec G204 } if _, err := exec.LookPath(browserCmd); err != nil { diff --git a/connections/cache.go b/connections/cache.go index a498764..4b19f73 100644 --- a/connections/cache.go +++ b/connections/cache.go @@ -71,7 +71,7 @@ func cachedFetch(url string) ([]byte, error) { warnNoCache() return directFetch(url) } - out, err := exec.Command(path, "get", url).Output() + out, err := exec.Command(path, "get", url).Output() // #nosec G204 if err != nil { return directFetch(url) } diff --git a/docs/Infrastructure_Guide/local-deployment.md b/docs/Infrastructure_Guide/local-deployment.md index b860674..ef6b12f 100644 --- a/docs/Infrastructure_Guide/local-deployment.md +++ b/docs/Infrastructure_Guide/local-deployment.md @@ -196,7 +196,7 @@ The above `yml` defines the structure for the raw `series` table from the `stagi Soda and its components needed for the project can be installed with `uv`: 1. Install Soda Core with PostgreSQL connector since Supabase uses PostgreSQL. - Other [connectors](https://github.com/sodadata/soda-core/blob/main/docs/installation.md#compatibility) can be used. + Other [connectors](https://github.com/sodadata/soda-core/blob/v3/docs/installation.md#compatibility) can be used. ```shell uv add soda-core-postgres ``` diff --git a/flags/config.go b/flags/config.go index b99698d..b0f9358 100644 --- a/flags/config.go +++ b/flags/config.go @@ -75,7 +75,7 @@ func Load() (Config, bool, error) { func LoadFrom(path string) (Config, bool, error) { cfg := Defaults() - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) // #nosec G304 if errors.Is(err, fs.ErrNotExist) { return cfg, true, nil } @@ -105,7 +105,7 @@ func SaveTo(path string, cfg Config) error { return err } dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0o755); err != nil { + if err := os.MkdirAll(dir, 0o750); err != nil { return err } tmp, err := os.CreateTemp(dir, "config-*.toml") diff --git a/styling/styling.go b/styling/styling.go index 0805755..e69a63d 100644 --- a/styling/styling.go +++ b/styling/styling.go @@ -110,7 +110,7 @@ func init() { } func HasDarkBackground() bool { - if !term.IsTerminal(int(os.Stdin.Fd())) || !term.IsTerminal(int(os.Stdout.Fd())) { + if !term.IsTerminal(int(os.Stdin.Fd())) || !term.IsTerminal(int(os.Stdout.Fd())) { // #nosec G115 return true } return lipgloss.HasDarkBackground(os.Stdin, os.Stdout)