From 9069e35b426e314e086cafa0ad2e7cd54abb5e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Fri, 10 Jan 2025 12:04:05 +0000 Subject: [PATCH] WIP: Setup Automation --- .github/dependabot.yml | 20 ++ .github/workflows/calculate_dataset.yml | 329 ++++++++++++++++++++ README.md | 56 ++-- lib/openssf_compliance/badge.ex | 11 +- lib/openssf_compliance/hex.ex | 7 +- lib/openssf_compliance/score_card.ex | 8 +- priv/data/joined/2025-01-10.parquet | Bin 0 -> 9540 bytes priv/data/joined/2025-01-10.parquet.license | 6 + 8 files changed, 404 insertions(+), 33 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/calculate_dataset.yml create mode 100644 priv/data/joined/2025-01-10.parquet create mode 100644 priv/data/joined/2025-01-10.parquet.license diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..63bd121 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + github-actions: + applies-to: "version-updates" + patterns: + - "*" + - package-ecosystem: "mix" + directory: "/" + schedule: + interval: "weekly" + groups: + mix: + applies-to: "version-updates" + patterns: + - "*" diff --git a/.github/workflows/calculate_dataset.yml b/.github/workflows/calculate_dataset.yml new file mode 100644 index 0000000..2a35232 --- /dev/null +++ b/.github/workflows/calculate_dataset.yml @@ -0,0 +1,329 @@ +on: + schedule: + # Once a month at 15:27 (random time to not congest GitHub exactly at midnight) + - cron: "27 15 1 * *" + workflow_dispatch: + inputs: + dataset_name: + type: string + required: false + + # TODO: Remove + push: + branches: + - 'ci' + +name: "Calculate Dataset" + +permissions: + contents: read + +jobs: + define_name: + name: "Define Dataset Name" + + runs-on: ubuntu-latest + + outputs: + dataset_name: "${{ inputs.dataset_name || steps.current-date.outputs.DATASET_NAME }}" + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: "Get Current Date" + id: current-date + run: 'echo "DATASET_NAME=$(date --iso-8601)" >> $GITHUB_OUTPUT' + + fetch_projects: + name: "Fetch Projects" + + runs-on: ubuntu-latest + + needs: ["define_name"] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: "Checkout Code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: "Setup BEAM" + uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # v1.18.2 + id: setupBEAM + with: + version-file: .tool-versions + version-type: strict + + - name: "Cache Deps & Build" + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: | + _build + deps + key: mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}-${{ hashFiles('mix.exs') }} + restore-keys: | + mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}- + + - name: "Get Mix Dependencies" + run: mix deps.get + + - name: "Compile Project" + run: mix compile + + - name: "Fetch Hex.pm Projects" + run: mix openssf_compliance.fetch_projects "$DATASET_NAME" + env: + DATASET_NAME: "${{ needs.define_name.outputs.dataset_name }}" + HEX_API_KEY: "${{ secrets.HEX_API_KEY }}" + + - name: "Upload Project Artifact" + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: projects + path: priv/data/projects/* + + fetch_badges: + name: "Fetch Badges" + + runs-on: ubuntu-latest + + needs: ["define_name"] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: "Checkout Code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: "Setup BEAM" + uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # v1.18.2 + id: setupBEAM + with: + version-file: .tool-versions + version-type: strict + + - name: "Cache Deps & Build" + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: | + _build + deps + key: mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}-${{ hashFiles('mix.exs') }} + restore-keys: | + mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}- + + - name: "Get Mix Dependencies" + run: mix deps.get + + - name: "Compile Project" + run: mix compile + + - name: "Fetch Badge Projects" + run: mix openssf_compliance.fetch_badge_projects "$DATASET_NAME" + env: + DATASET_NAME: "${{ needs.define_name.outputs.dataset_name }}" + + - name: "Upload Badge Artifact" + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: badges + path: priv/data/badge/* + + fetch_scorecards: + name: "Fetch ScoreCards" + + runs-on: ubuntu-latest + + needs: ["define_name", "fetch_projects"] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: "Checkout Code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: "Setup BEAM" + uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # v1.18.2 + id: setupBEAM + with: + version-file: .tool-versions + version-type: strict + + - name: "Cache Deps & Build" + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: | + _build + deps + key: mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}-${{ hashFiles('mix.exs') }} + restore-keys: | + mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}- + + - name: "Get Mix Dependencies" + run: mix deps.get + + - name: "Compile Project" + run: mix compile + + - name: "Download Project Artifact" + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: projects + path: priv/data/projects/ + + - name: "Fetch ScoreCard Projects" + run: mix openssf_compliance.fetch_score_card_projects "$DATASET_NAME" + env: + DATASET_NAME: "${{ needs.define_name.outputs.dataset_name }}" + + - name: "Upload ScoreCard Artifact" + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: scorecards + path: priv/data/scorecard/* + + join_projects: + name: "Join Data" + + runs-on: ubuntu-latest + + needs: ["define_name", "fetch_projects", "fetch_badges", "fetch_scorecards"] + + permissions: + contents: write + id-token: write + attestations: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: "Checkout Code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: "Setup BEAM" + uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # v1.18.2 + id: setupBEAM + with: + version-file: .tool-versions + version-type: strict + + - name: "Cache Deps & Build" + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: | + _build + deps + key: mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}-${{ hashFiles('mix.exs') }} + restore-keys: | + mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}- + + - name: "Get Mix Dependencies" + run: mix deps.get + + - name: "Compile Project" + run: mix compile + + - name: "Download Project Artifact" + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: projects + path: priv/data/projects/ + + - name: "Download Badge Artifact" + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: badges + path: priv/data/badge/ + + - name: "Download ScoreCard Artifact" + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: scorecards + path: priv/data/scorecard/ + + - name: "Join Project Data" + run: mix openssf_compliance.join_projects "$DATASET_NAME" + env: + DATASET_NAME: "${{ needs.define_name.outputs.dataset_name }}" + + - name: "Attest data provenance" + uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + id: attest-docs-provenance + with: + subject-path: 'priv/data/joined/${{ needs.define_name.outputs.dataset_name }}.parquet*' + + - name: "Upload Joined Artifact" + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + with: + name: joined + path: priv/data/joined/* + + - name: "Comit new Dataset" + uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1 + with: + commit_message: "Add ${{ needs.define_name.outputs.dataset_name }} DataSet" + + print_stats: + name: "Print Stats" + + runs-on: ubuntu-latest + + needs: ["define_name", "join_projects"] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 + with: + egress-policy: audit + + - name: "Checkout Code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: "Setup BEAM" + uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # v1.18.2 + id: setupBEAM + with: + version-file: .tool-versions + version-type: strict + + - name: "Cache Deps & Build" + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: | + _build + deps + key: mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}-${{ hashFiles('mix.exs') }} + restore-keys: | + mix-${{ runner.os }}-${{ steps.setupBEAM.outputs.otp-version }}-${{ steps.setupBEAM.outputs.elixir-version }}- + + - name: "Get Mix Dependencies" + run: mix deps.get + + - name: "Compile Project" + run: mix compile + + - name: "Download Joined Artifact" + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: joined + path: priv/data/joined/ + + - name: "Calculate Stats" + run: mix openssf_compliance.stats "$DATASET_NAME" >> $GITHUB_STEP_SUMMARY + env: + DATASET_NAME: "${{ needs.define_name.outputs.dataset_name }}" diff --git a/README.md b/README.md index 1a1128c..de82665 100644 --- a/README.md +++ b/README.md @@ -20,32 +20,32 @@ be added to the `priv/additional_projects.tsv` file. ┌──────────────────────────────────┐ ┌─────────────────────────────────────────────┐ │File: priv/additional_projects.tsv│ │$ mix openssf_compliance.fetch_badge_projects│ └─────────────────┬────────────────┘ └───────────────────┬─────────────────────────┘ - │ │ -┌─────────────────▼─────────────────────┐ ┌───────────────────▼────────────────┐ -│$ mix openssf_compliance.fetch_projects│ │File: priv/data/badge/[NAME].parquet│ -└─────────────────┬─────────────────────┘ └───────────────────┬────────────────┘ - │ │ -┌─────────────────▼─────────────────────┐ │ -│File: priv/data/projects/[NAME].parquet│ │ -└─────────────────┬─────────────────────┘ │ - │ ┌──────────────────────────────────────────┘ -┌─────────────────▼───▼───────────────────────────┐ -│$ mix openssf_compliance.fetch_scorecard_projects│ -└─────────────────┬───────────────────────────────┘ - │ -┌─────────────────▼──────────────────────┐ -│File: priv/data/scorecard/[NAME].parquet│ -└─────────────────┬──────────────────────┘ - │ -┌─────────────────▼────────────────────┐ -│$ mix openssf_compliance.join_projects│ -└─────────────────┬────────────────────┘ - │ -┌─────────────────▼───────────────────┐ -│File: priv/data/joined/[NAME].parquet│ -└─────────────────┬───────────────────┘ - │ -┌─────────────────▼────────────┐ -│$ mix openssf_compliance.stats│ -└──────────────────────────────┘ + │ │ +┌─────────────────▼─────────────────────┐ ┌───────────────────▼────────────────┐ +│$ mix openssf_compliance.fetch_projects│ │File: priv/data/badge/[NAME].parquet│ +└─────────────────┬─────────────────────┘ └───────────────────┬────────────────┘ + │ │ +┌─────────────────▼─────────────────────┐ │ +│File: priv/data/projects/[NAME].parquet│ │ +└─────────────────┬─────────────────────┘ │ + │ | +┌─────────────────▼───────────────────────────────┐ | +│$ mix openssf_compliance.fetch_scorecard_projects│ | +└─────────────────┬───────────────────────────────┘ | + │ | +┌─────────────────▼──────────────────────┐ | +│File: priv/data/scorecard/[NAME].parquet│ | +└─────────────────┬──────────────────────┘ | + │ ┌──────────────────────────────────────────┘ +┌─────────────────▼────▼───────────────┐ +│$ mix openssf_compliance.join_projects│ +└─────────────────┬────────────────────┘ + │ +┌─────────────────▼───────────────────┐ +│File: priv/data/joined/[NAME].parquet│ +└─────────────────┬───────────────────┘ + │ +┌─────────────────▼────────────┐ +│$ mix openssf_compliance.stats│ +└──────────────────────────────┘ ``` \ No newline at end of file diff --git a/lib/openssf_compliance/badge.ex b/lib/openssf_compliance/badge.ex index 5f881c9..00a550f 100644 --- a/lib/openssf_compliance/badge.ex +++ b/lib/openssf_compliance/badge.ex @@ -23,10 +23,17 @@ defmodule OpenSSFCompliance.Badge do def load_projects do wait_timeout = ceil(@rate_limit_window / @rate_limit_anonymous) + page_stream = + wait_timeout + |> Stream.interval() + |> Stream.map(&(&1 + 1)) + # TODO: Remove + |> Stream.take(1) + OpenSSFCompliance.TaskSupervisor |> Task.Supervisor.async_stream( - Stream.interval(wait_timeout), - &load_page(&1 + 1), + page_stream, + &load_page/1, ordered: false, timeout: to_timeout(second: 30) ) diff --git a/lib/openssf_compliance/hex.ex b/lib/openssf_compliance/hex.ex index 6c6ddef..7a83717 100644 --- a/lib/openssf_compliance/hex.ex +++ b/lib/openssf_compliance/hex.ex @@ -21,6 +21,9 @@ defmodule OpenSSFCompliance.Hex do @spec load_packages() :: {:ok, Enumerable.t(package())} | {:error, term()} def load_packages do with {:ok, package_names} <- load_package_names() do + # TODO: Remove + package_names = Stream.take(package_names, 100) + load_all_package_details(package_names) end end @@ -80,11 +83,11 @@ defmodule OpenSSFCompliance.Hex do {200, _headers, %{ "meta" => %{"links" => links}, - "downloads" => %{"all" => total_downloads} + "downloads" => downloads }} -> package = Map.merge( - %{name: package_name, total_downloads: total_downloads}, + %{name: package_name, total_downloads: downloads["all"]}, find_package_repository(links, package_name) ) diff --git a/lib/openssf_compliance/score_card.ex b/lib/openssf_compliance/score_card.ex index af62c55..6034082 100644 --- a/lib/openssf_compliance/score_card.ex +++ b/lib/openssf_compliance/score_card.ex @@ -24,9 +24,15 @@ defmodule OpenSSFCompliance.ScoreCard do @spec load_projects(projects :: Enumerable.t(input_project())) :: Enumerable.t(project()) def load_projects(projects) do + project_stream = + projects + |> throttle() + # TODO: Remove + |> Stream.take(100) + OpenSSFCompliance.TaskSupervisor |> Task.Supervisor.async_stream( - throttle(projects), + project_stream, &load_project/1, ordered: false, timeout: to_timeout(second: 30) diff --git a/priv/data/joined/2025-01-10.parquet b/priv/data/joined/2025-01-10.parquet new file mode 100644 index 0000000000000000000000000000000000000000..8206e81fc8344872d662f24c9ac46990a3c423f7 GIT binary patch literal 9540 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 UlJZly23`7N9l(3cH2#0;zb;>z2mk;8 literal 0 HcmV?d00001 diff --git a/priv/data/joined/2025-01-10.parquet.license b/priv/data/joined/2025-01-10.parquet.license new file mode 100644 index 0000000..171ff85 --- /dev/null +++ b/priv/data/joined/2025-01-10.parquet.license @@ -0,0 +1,6 @@ +SPDX-FileCopyrightText: 2014-2025 Hex.pm Package Manager +SPDX-License-Identifier: CC-BY-3.0 +SPDX-FileCopyrightText: 2015-2025 OpenSSF Best Practices Badge +SPDX-License-Identifier: CC-BY-3.0 AND CDLA-Permissive-2.0 +SPDX-FileCopyrightText: 2015-2025 OpenSSF ScoreCard +SPDX-License-Identifier: CDLA-Permissive-2.0