diff --git a/.github/workflows/_charm-codeql-analysis.yml b/.github/workflows/_charm-codeql-analysis.yml new file mode 100644 index 00000000..c23f5b82 --- /dev/null +++ b/.github/workflows/_charm-codeql-analysis.yml @@ -0,0 +1,64 @@ +# Run a CodeQL analysis on a repository +name: "CodeQL" + +on: + workflow_call: + inputs: + charm-path: + type: string + required: false + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + 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'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + 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 Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + 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 + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/_charm-linting.yaml b/.github/workflows/_charm-linting.yaml new file mode 100644 index 00000000..33ba6f90 --- /dev/null +++ b/.github/workflows/_charm-linting.yaml @@ -0,0 +1,26 @@ +name: Linting + +on: + workflow_call: + inputs: + charm-path: + type: string + required: false + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install dependencies + run: python3 -m pip install tox + - name: Run linters + run: cd ${{ inputs.charm-path }} && tox -vve lint diff --git a/.github/workflows/_charm-quality-checks.yaml b/.github/workflows/_charm-quality-checks.yaml new file mode 100644 index 00000000..9a43373b --- /dev/null +++ b/.github/workflows/_charm-quality-checks.yaml @@ -0,0 +1,99 @@ +name: Quality Checks + +on: + workflow_call: + inputs: + charm-path: + type: string + required: false + provider: + type: string + description: "The provider to choose for either machine or k8s tests ('lxd' or 'microk8s')" + required: true + ip-range: + type: string + description: | + The IP range in the address pool for the load balancer to use. + It can be either a subnet(IP/mask) or a range (-) + required: false + default: "" + secrets: + CHARMHUB_TOKEN: + required: false +jobs: + check-secret: + name: Check the CHARMHUB_TOKEN secret + runs-on: ubuntu-latest + outputs: + defined: ${{ steps.check.outputs.defined }} + steps: + - id: check + env: + CHARMHUB_TOKEN: ${{ secrets.CHARMHUB_TOKEN }} + if: "${{ env.CHARMHUB_TOKEN != '' }}" + run: echo "defined=true" >> $GITHUB_OUTPUT + call-inclusive-naming-check: + # Issues with this workflow can be addressed by adding a .wokeignore in the repository root + name: Inclusive naming + uses: canonical-web-and-design/Inclusive-naming/.github/workflows/woke.yaml@main + with: + fail-on-error: "true" + lib-check: + name: Check libraries + runs-on: ubuntu-latest + needs: + - check-secret + if: needs.check-secret.outputs.defined == 'true' + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Check charm libraries # Make sure our charm libraries are updated + uses: canonical/charming-actions/check-libraries@2.4.0 + with: + credentials: "${{ secrets.CHARMHUB_TOKEN }}" + github-token: "${{ secrets.GITHUB_TOKEN }}" + charm-path: "${{ inputs.charm-path }}" + static-analysis: + name: Static Analysis + uses: ./.github/workflows/_charm-static-analysis.yaml@main + with: + charm-path: "${{ inputs.charm-path }}" + linting: + name: Linting + uses: ./.github/workflows/_charm-linting.yaml@main + with: + charm-path: "${{ inputs.charm-path }}" + unit-test: + name: Unit Tests + uses: ./.github/workflows/_charm-tests-unit.yaml@main + with: + charm-path: "${{ inputs.charm-path }}" + scenario-test: + name: Scenario Tests + uses: ./.github/workflows/_charm-tests-scenario.yaml@main + with: + charm-path: "${{ inputs.charm-path }}" + integration-test: + name: Integration Tests + needs: + - static-analysis + - linting + - unit-test + - scenario-test + uses: ./.github/workflows/_charm-tests-integration.yaml@main + with: + charm-path: "${{ inputs.charm-path }}" + provider: "${{ inputs.provider }}" + ip-range-start: ${{ inputs.ip-range-start }} + ip-range-end: ${{ inputs.ip-range-end }} + codeql: + name: CodeQL analysis + needs: + - static-analysis + - linting + - unit-test + uses: ./.github/workflows/_charm-codeql-analysis.yml@main + with: + charm-path: "${{ inputs.charm-path }}" diff --git a/.github/workflows/_charm-release.yaml b/.github/workflows/_charm-release.yaml new file mode 100644 index 00000000..e42d04a4 --- /dev/null +++ b/.github/workflows/_charm-release.yaml @@ -0,0 +1,97 @@ +name: Release charm to Edge + +on: + workflow_call: + inputs: + charm-path: + type: string + required: false + default: . + artifact: + description: "Name of artifact to download before building. Must contain the file artifact.tar.gz." + default: '' + required: false + type: string + secrets: + CHARMHUB_TOKEN: + required: true + +jobs: + build: + name: Build the charms + runs-on: ubuntu-22.04 + outputs: + charms: ${{ steps.builder.outputs.charms }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Setup LXD + uses: canonical/setup-lxd@v0.1.1 + with: + channel: latest/stable + - name: Download Artifact + uses: actions/download-artifact@v3 + id: download_artifact + with: + name: "${{ inputs.artifact }}" + if: ${{ inputs.artifact != '' }} + - name: Unpack Artifact + run: sudo apt-get update && sudo apt-get install tar && tar xf artifact.tar.gz + if: ${{ inputs.artifact != '' }} + - name: Build charm(s) + id: builder + run: | + sudo snap install jq + sudo snap install charmcraft --classic + charmcraft pack --project-dir ${{ inputs.charm-path }} + export CHARMS=$(ls ${{ inputs.charm-path }}/*.charm | jq -R -s -c 'split("\n")[:-1]') + echo "charms=$CHARMS" >> "$GITHUB_OUTPUT" + - name: Store charms + uses: actions/upload-artifact@v3 + with: + name: charms + path: ${{ inputs.charm-path }}/*.charm + - name: Step output + run: | + echo "${{ fromjson(steps.builder.outputs.charms) }} " + charm-output: + name: Charm List + runs-on: ubuntu-22.04 + needs: + - build + steps: + - name: Job output + run: | + echo job output: ${{ fromjson(needs.build.outputs.charms) }} + release-to-charmhub: + name: Release to CharmHub + runs-on: ubuntu-22.04 + needs: + - build + strategy: + matrix: + path: ${{ fromjson(needs.build.outputs.charms) }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Select charmhub channel + uses: canonical/charming-actions/channel@2.1.1 + id: channel + - name: Fetch charm artifacts + uses: actions/download-artifact@v3 + with: + name: charms + - name: Upload charm to charmhub + uses: canonical/charming-actions/upload-charm@2.4.0 + with: + credentials: "${{ secrets.CHARMHUB_TOKEN }}" + github-token: "${{ secrets.GITHUB_TOKEN }}" + channel: "${{ steps.channel.outputs.name }}" + built-charm-path: "${{ matrix.path }}" + # We set destructive mode to false, otherwise runner's OS would have to match + # charm's 'build-on' OS. + destructive-mode: false diff --git a/.github/workflows/_charm-static-analysis.yaml b/.github/workflows/_charm-static-analysis.yaml new file mode 100644 index 00000000..be3142f4 --- /dev/null +++ b/.github/workflows/_charm-static-analysis.yaml @@ -0,0 +1,42 @@ +name: Static Analysis + +on: + workflow_call: + inputs: + charm-path: + type: string + required: false + +jobs: + static-lib: + name: Static Analysis of Libs + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install dependencies + run: python3 -m pip install tox + - name: Run static analysis for /lib for 3.8 + run: cd ${{ inputs.charm-path }} && tox -vve static-lib + static-charm: + name: Static Analysis of Charm + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install dependencies + run: python3 -m pip install tox + - name: Run static analysis (charm) + run: cd ${{ inputs.charm-path }} && tox -vve static-charm diff --git a/.github/workflows/_charm-tests-integration.yaml b/.github/workflows/_charm-tests-integration.yaml new file mode 100644 index 00000000..e85b59fd --- /dev/null +++ b/.github/workflows/_charm-tests-integration.yaml @@ -0,0 +1,95 @@ +name: Integration Tests + +on: + workflow_call: + inputs: + charm-path: + type: string + required: false + provider: + type: string + description: "The provider to choose for either machine or k8s tests ('lxd' or 'microk8s')" + required: true + ip-range: + type: string + description: | + The IP range in the address pool for the load balancer to use. + It can be either a subnet(IP/mask) or a range (-) + required: false + default: "" + +# Default to bash +defaults: + run: + shell: bash + +jobs: + integration-test: + name: Integration Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Get IP range + run: | + if [ "${{ inputs.ip-range }}" == ""]; then + echo "IPRANGE=$(ip -4 -j route get 2.2.2.2 | jq -r '.[] | .prefsrc')/32" >> $GITHUB_ENV + else + echo "${{ inputs.ip-range }}" >> $GITHUB_ENV + fi + - name: Setup operator enviroment (machine) + if: inputs.provider == 'lxd' + uses: charmed-kubernetes/actions-operator@main + with: + juju-channel: 3.3/stable + provider: lxd + - name: Setup operator environment (k8s) + if: inputs.provider == 'microk8s' + uses: charmed-kubernetes/actions-operator@main + with: + juju-channel: 3.3/stable + provider: microk8s + channel: 1.26-strict/stable + microk8s-group: snap_microk8s + microk8s-addons: "hostpath-storage dns metallb:${{ env.IPRANGE }}" + - name: Run integration tests + run: cd ${{ inputs.charm-path }} && tox -vve integration + - name: Dump debug log + if: failure() + run: | + for m in $(juju models --format json | jq -r '.models[].name' | grep -v "admin/controller"); do juju debug-log -m $m --replay --ms --no-tail; done + exit 0 + - name: Dump pods and their logs + if: failure() + run: | + juju status --relations --storage + kubectl get pods \ + -A \ + -o=jsonpath='{range.items[*]}{.metadata.namespace} {.metadata.name}{"\n"}' \ + --sort-by=.metadata.namespace \ + | grep -v "^\s*$" \ + | while read namespace pod; do \ + kubectl -n $namespace describe pod $pod; \ + kubectl -n $namespace logs $pod \ + --all-containers=true \ + --tail=100; \ + done + exit 0 + - name: Dump deployments + if: failure() + run: | + kubectl describe deployments -A + exit 0 + - name: Dump replicasets + if: failure() + run: | + kubectl describe replicasets -A + exit 0 + - name: Dump charmcraft logs + if: failure() + uses: actions/upload-artifact@v3 + with: + name: charmcraft-logs + path: ~/.local/state/charmcraft/log/*.log diff --git a/.github/workflows/_charm-tests-scenario.yaml b/.github/workflows/_charm-tests-scenario.yaml new file mode 100644 index 00000000..293ceb3e --- /dev/null +++ b/.github/workflows/_charm-tests-scenario.yaml @@ -0,0 +1,26 @@ +name: Unit Tests + +on: + workflow_call: + inputs: + charm-path: + type: string + required: false + +jobs: + unit-tests: + name: Scenario tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install dependencies + run: python -m pip install tox + - name: Run tests + run: cd ${{ inputs.charm-path }} && tox -e scenario diff --git a/.github/workflows/_charm-tests-unit.yaml b/.github/workflows/_charm-tests-unit.yaml new file mode 100644 index 00000000..e6bc3503 --- /dev/null +++ b/.github/workflows/_charm-tests-unit.yaml @@ -0,0 +1,26 @@ +name: Unit Tests + +on: + workflow_call: + inputs: + charm-path: + type: string + required: false + +jobs: + unit-tests: + name: Unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Install dependencies + run: python -m pip install tox + - name: Run tests + run: cd ${{ inputs.charm-path }} && tox -e unit diff --git a/.github/workflows/_local-issue-sync.yaml b/.github/workflows/_local-issue-sync.yaml new file mode 100644 index 00000000..9d4217c7 --- /dev/null +++ b/.github/workflows/_local-issue-sync.yaml @@ -0,0 +1,11 @@ +name: Issues + +on: [issues] + +jobs: + update: + name: Update Issue + uses: ./.github/workflows/issues.yaml@main + secrets: inherit + with: + component: observability diff --git a/.github/workflows/_rock-build-test.yaml b/.github/workflows/_rock-build-test.yaml new file mode 100644 index 00000000..30a0ec9d --- /dev/null +++ b/.github/workflows/_rock-build-test.yaml @@ -0,0 +1,37 @@ +name: Build ROCKs + +on: + workflow_call: + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Determine any rockcraft.yaml changed in the PR + id: changed-files + uses: tj-actions/changed-files@v35 + with: + files: "**/rockcraft.yaml" + + - name: Setup LXD + if: steps.changed-files.outputs.any_changed + uses: canonical/setup-lxd@v0.1.0 + with: + channel: latest/stable + + - name: Install dependencies + if: steps.changed-files.outputs.any_changed + run: | + sudo snap install --classic --channel edge rockcraft + + - name: Build ROCK + if: steps.changed-files.outputs.any_changed + run: | + for file in ${{ steps.changed-files.outputs.all_changed_files }}; do + current_wd=$(pwd) && cd ${file%/*} + rockcraft pack + cd $current_wd + done diff --git a/.github/workflows/charm-promote.yaml b/.github/workflows/charm-promote.yaml new file mode 100644 index 00000000..6d7f685f --- /dev/null +++ b/.github/workflows/charm-promote.yaml @@ -0,0 +1,47 @@ +name: Promote Charm + +on: + workflow_call: + inputs: + charm-path: + description: "Path to the charm we want to promote. Defaults to the current working directory." + type: string + default: '.' + required: false + promotion: + type: string + required: true + secrets: + CHARMHUB_TOKEN: + required: true + +jobs: + promote: + name: Promote Charm + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set target channel + env: + PROMOTE_FROM: ${{ github.event.inputs.promotion }} + run: | + if [ "${PROMOTE_FROM}" == "edge -> beta" ]; then + echo "promote-from=edge" >> ${GITHUB_ENV} + echo "promote-to=beta" >> ${GITHUB_ENV} + elif [ "${PROMOTE_FROM}" == "beta -> candidate" ]; then + echo "promote-from=beta" >> ${GITHUB_ENV} + echo "promote-to=candidate" >> ${GITHUB_ENV} + elif [ "${PROMOTE_FROM}" == "candidate -> stable" ]; then + echo "promote-from=candidate" >> ${GITHUB_ENV} + echo "promote-to=stable" >> ${GITHUB_ENV} + fi + - name: Promote Charm + uses: canonical/charming-actions/release-charm@2.4.0 + with: + charm-path: ${{ inputs.charm-path }} + credentials: ${{ secrets.CHARMHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + destination-channel: latest/${{ env.promote-to }} + origin-channel: latest/${{ env.promote-from }} + charmcraft-channel: latest/stable diff --git a/.github/workflows/charm-pull-request.yaml b/.github/workflows/charm-pull-request.yaml new file mode 100644 index 00000000..a69fed00 --- /dev/null +++ b/.github/workflows/charm-pull-request.yaml @@ -0,0 +1,70 @@ +name: Pull Request + +on: + workflow_call: + inputs: + charm-path: + description: "Path to the charm we want to publish. Defaults to the current working directory." + default: '.' + required: false + type: string + provider: + description: "The provider to choose for either machine or k8s tests ('lxd' or 'microk8s')" + default: 'microk8s' + required: false + type: string + ip-range: + type: string + description: | + The IP range in the address pool for the load balancer to use. + It can be either a subnet(IP/mask) or a range (-) + required: false + default: "" + secrets: + CHARMHUB_TOKEN: + required: false +jobs: + ci-ignore: + name: Check against ignorelist + runs-on: ubuntu-latest + outputs: + any_modified: ${{ steps.echo-changes.outputs.any_modified }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} # To be compatible with PRs from forks + fetch-depth: 0 + + - name: Determine changed files in the PR + id: changed-files + uses: tj-actions/changed-files@v38 + with: + files_ignore: | + README.md + CONTRIBUTING.md + INTEGRATING.md + CODEOWNERS + LICENSE + icon.svg + .gitignore + .github/** + + - name: Echo changed files + id: echo-changes + run: | + echo "Changes made: ${{ steps.changed-files.outputs.any_modified }}" + echo "Modified files: ${{ steps.changed-files.outputs.all_modified_files }}" + echo "any_modified=${{ steps.changed-files.outputs.any_modified }}" >> $GITHUB_OUTPUT + + quality-checks: + name: Quality Checks + needs: + - ci-ignore + if: needs.ci-ignore.outputs.any_modified == 'true' + uses: ./.github/workflows/_charm-quality-checks.yaml@main + secrets: inherit + with: + charm-path: ${{ inputs.charm-path }} + provider: ${{ inputs.provider }} + ip-range: ${{ inputs.ip-range }} diff --git a/.github/workflows/charm-release.yaml b/.github/workflows/charm-release.yaml new file mode 100644 index 00000000..6e892f79 --- /dev/null +++ b/.github/workflows/charm-release.yaml @@ -0,0 +1,80 @@ +name: Release Charm + +on: + workflow_call: + inputs: + charm-path: + description: "Path to the charm we want to publish. Defaults to the current working directory." + default: '.' + required: false + type: string + artifact: + description: "Name of artifact to download before building. Must contain the file artifact.tar.gz." + default: '' + required: false + type: string + provider: + description: "The provider to choose for either machine or k8s tests ('lxd' or 'microk8s')" + default: 'microk8s' + required: false + type: string + ip-range: + type: string + description: | + The IP range in the address pool for the load balancer to use. + It can be either a subnet(IP/mask) or a range (-) + required: false + default: "" + secrets: + CHARMHUB_TOKEN: + required: true + OBSERVABILITY_NOCTUA_TOKEN: + required: true + +concurrency: + group: release + cancel-in-progress: true + +jobs: + quality-checks: + name: Quality Checks + uses: ./.github/workflows/_charm-quality-checks.yaml@main + secrets: inherit + with: + charm-path: "${{ inputs.charm-path }}" + provider: "${{ inputs.provider }}" + ip-range: "${{ inputs.ip-range }}" + release-charm: + name: Release Charm and Libraries + needs: + - quality-checks + uses: ./.github/workflows/_charm-release.yaml@main + secrets: inherit + with: + artifact: "${{ inputs.artifact }}" + charm-path: "${{ inputs.charm-path }}" + release-libs: + name: Release any bumped charm library + needs: + - quality-checks + runs-on: ubuntu-latest + steps: + - name: Checkout the source + uses: actions/checkout@v3 + with: + fetch-depth: 1 + path: charm + + - name: Release libraries + run: | + # Install Charmcraft + sudo snap install charmcraft --classic --channel latest/stable + cd $GITHUB_WORKSPACE/charm/${{ inputs.charm-path }} + # Get the libs folder name + libraries_folder=$(yq .name metadata.yaml | tr - _) + # For each library belonging to the charm, publish it + for lib in $(find lib/charms/$libraries_folder -type f | sed 's|lib/||' | sed 's/.py//' | sed 's|/|.|g'); do + charmcraft publish-lib $lib + done + env: + CHARMCRAFT_AUTH: "${{ secrets.CHARMHUB_TOKEN }}" diff --git a/.github/workflows/charm-update-libs.yaml b/.github/workflows/charm-update-libs.yaml new file mode 100644 index 00000000..a28e0c3a --- /dev/null +++ b/.github/workflows/charm-update-libs.yaml @@ -0,0 +1,52 @@ +name: Auto-update Charm Libraries +on: + workflow_call: + inputs: + charm-path: + description: "Path to the charm we want to publish. Defaults to the current working directory." + default: '.' + required: false + type: string + secrets: + CHARMHUB_TOKEN: + required: false + OBSERVABILITY_NOCTUA_TOKEN: + required: true + +permissions: + contents: write + pull-requests: write + +jobs: + update-lib: + name: Check libraries + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 # v3 + with: + fetch-depth: 0 + + - name: Fetch charm libraries + run: | + sudo snap install charmcraft --classic --channel latest/stable + cd ${{ inputs.charm-path }} + charmcraft fetch-lib + env: + CHARMCRAFT_AUTH: "${{ secrets.CHARMHUB_TOKEN }}" + + - name: Create a PR for local changes + uses: peter-evans/create-pull-request@v4.2.3 + id: cpr + with: + token: ${{ secrets.OBSERVABILITY_NOCTUA_TOKEN }} + commit-message: "chore: update charm libraries" + committer: "Github Actions " + author: "Github Actions " + title: "Update charm libraries" + body: | + Automated action to fetch latest version of charm libraries. The branch of this PR + will be wiped during the next check. Unless you really know what you're doing, you + most likely don't want to push any commits to this branch. + branch: "chore/auto-libs" + delete-branch: true diff --git a/.github/workflows/issues copy.yaml b/.github/workflows/issues copy.yaml new file mode 100644 index 00000000..949d6bbd --- /dev/null +++ b/.github/workflows/issues copy.yaml @@ -0,0 +1,59 @@ +name: Issues + +on: + workflow_call: + inputs: + component: + description: "Name of the 'component' for the JIRA issue" + required: true + type: string + +jobs: + update: + name: Update Issue + runs-on: ubuntu-latest + steps: + - name: Dump Github Context + run: | + echo "update=false" >> $GITHUB_ENV + if [ ${{ github.event_name }} != "issues" ]; then + echo "This action only operates on issues" + exit 0 + fi + echo "update=true" >> $GITHUB_ENV + - name: Determine action + run: | + if [ ${{ github.event.action }} == "opened" ]; then + echo "action=open" >> $GITHUB_ENV + fi + if [ ${{ github.event.action }} == "reopened" ]; then + echo "action=reopen" >> $GITHUB_ENV + fi + if [ ${{ github.event.action }} == "closed" ]; then + echo "action=close" >> $GITHUB_ENV + fi + - name: Determine type + run: | + if ${{ contains(github.event.*.labels.*.name, 'Type: Bug') }}; then + echo "type=bug" >> $GITHUB_ENV + else + echo "type=story" >> $GITHUB_ENV + fi + - name: Update + if: ${{ env.update == 'true' }} + env: + ID: ${{ github.event.issue.html_url }} + TITLE: ${{ github.event.issue.title }} + COMPONENT: ${{ inputs.component }} + DESCRIPTION: Opened by ${{ github.event.issue.user.login }}. + run: | + data=$(jq -n \ + --arg id "$ID" \ + --arg action "${{ env.action }}" \ + --arg title "$TITLE" \ + --arg description "$DESCRIPTION" \ + --arg component "$COMPONENT" \ + --arg type "${{ env.type }}" \ + '{data: {id: $id, action: $action, title: $title, description: $description, component: $component, type: $type}}') + + curl -X POST -H 'Content-type: application/json' --data "${data}" "${{ secrets.JIRA_URL }}" diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 5d23c2e1..6f4af2f5 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -8,8 +8,7 @@ on: jobs: pull-request: name: PR - uses: canonical/observability/.github/workflows/charm-pull-request.yaml@main + uses: ./.github/workflows/charm-pull-request.yaml@main secrets: inherit with: - ip-range-start: 10.64.140.43 - ip-range-end: 10.64.140.46 + ip-range: 10.64.140.43/31 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 10b355a1..e789e520 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,5 +10,4 @@ jobs: uses: canonical/observability/.github/workflows/charm-release.yaml@main secrets: inherit with: - ip-range-start: 10.64.140.43 - ip-range-end: 10.64.140.46 \ No newline at end of file + ip-range: 10.64.140.43/31 \ No newline at end of file diff --git a/.github/workflows/rock-pull-request.yaml b/.github/workflows/rock-pull-request.yaml new file mode 100644 index 00000000..f37faf12 --- /dev/null +++ b/.github/workflows/rock-pull-request.yaml @@ -0,0 +1,10 @@ +name: Pull Request +# quality checks to make sure the change won't break things + +on: + workflow_call: + +jobs: + test-build: + name: Test ROCK build + uses: ./.github/workflows/_rock-build-test.yaml@main diff --git a/.github/workflows/rock-release-dev.yaml b/.github/workflows/rock-release-dev.yaml new file mode 100644 index 00000000..e35961de --- /dev/null +++ b/.github/workflows/rock-release-dev.yaml @@ -0,0 +1,68 @@ +name: Build ROCK and release dev tag to GHCR + +on: + workflow_call: + inputs: + rock-name: + description: "Name of the application for which to build the ROCK" + required: true + type: string + secrets: + OBSERVABILITY_NOCTUA_TOKEN: + required: true + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Find the *latest* rockcraft.yaml + id: find-latest + run: | + latest_rockcraft_file=$(find $GITHUB_WORKSPACE -name "rockcraft.yaml" | sort -V | tail -n1) + rockcraft_dir=$(dirname ${latest_rockcraft_file#\./}) + echo "latest-dir=$rockcraft_dir" >> $GITHUB_OUTPUT + + - name: Setup LXD + uses: canonical/setup-lxd@v0.1.1 + with: + channel: latest/stable + + - name: Install dependencies + run: | + sudo snap install yq + sudo snap install --classic --channel edge rockcraft + + - name: Build ROCK + run: | + rockcraft_dir="${{ steps.find-latest.outputs.latest-dir }}" + current_wd=$(pwd) && cd $rockcraft_dir + rockcraft pack --verbose + cd $current_wd + cp $rockcraft_dir/${{ inputs.rock-name }}_*.rock $current_wd + digest=$(skopeo inspect oci-archive:$(realpath ./${{ inputs.rock-name }}_*.rock) --format '{{.Digest}}') + echo "digest=${digest#*:}" >> "$GITHUB_OUTPUT" + + - name: Upload ROCK to ghcr.io + run: | + sudo skopeo --insecure-policy copy oci-archive:$(realpath ./${{ inputs.rock-name }}_*.rock) docker://ghcr.io/canonical/${{ inputs.rock-name }}:dev --dest-creds "observability-noctua-bot:${{ secrets.OBSERVABILITY_NOCTUA_TOKEN }}" + + - name: Install Syft + run: | + curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin + + - name: Create SBOM + run: syft $(realpath ./${{ inputs.rock-name }}_*.rock) -o spdx-json=${{ inputs.rock-name }}.sbom.json + + - name: Upload SBOM + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.rock-name }}-sbom + path: "${{ inputs.rock-name}}.sbom.json" + - name: Upload locally built ROCK artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.rock-name }}-rock + path: "${{ inputs.rock-name }}_*.rock" diff --git a/.github/workflows/rock-release-oci-factory.yaml b/.github/workflows/rock-release-oci-factory.yaml new file mode 100644 index 00000000..7703e7ba --- /dev/null +++ b/.github/workflows/rock-release-oci-factory.yaml @@ -0,0 +1,127 @@ +name: Open a PR to OCI Factory when a new ROCK is merged to main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + workflow_call: + inputs: + rock-name: + description: "Name of the application for which to build the ROCK" + required: true + type: string + secrets: + OBSERVABILITY_NOCTUA_TOKEN: + required: true + +permissions: + contents: write + pull-requests: write + +jobs: + release-oci-factory: + name: Add the new ROCK + runs-on: ubuntu-latest + steps: + - name: Checkout the ROCK source + uses: actions/checkout@v3 + with: + fetch-depth: 2 + path: rock + + - name: Check for a new rockcraft.yaml from the latest commit + id: changed-files + uses: tj-actions/changed-files@v37 + with: + files: '**/rockcraft.yaml' + path: rock + + - name: Get the latest commit SHA + id: commit-sha + if: steps.changed-files.outputs.all_changed_and_modified_files != '' + run: | + cd $GITHUB_WORKSPACE/rock + commit_sha=$(git rev-parse HEAD) + cd $GITHUB_WORKSPACE + echo "commit_sha=$commit_sha" >> $GITHUB_OUTPUT + + - name: Sync the OCI Factory fork + id: fork-sync + if: steps.changed-files.outputs.all_changed_and_modified_files != '' + env: + GH_TOKEN: ${{ secrets.OBSERVABILITY_NOCTUA_TOKEN }} + run: gh repo sync --force observability-noctua-bot/oci-factory + + - name: Clone the fork + id: fork-clone + if: steps.changed-files.outputs.all_changed_and_modified_files != '' + uses: actions/checkout@v3 + with: + path: oci-factory + repository: observability-noctua-bot/oci-factory + token: ${{ secrets.OBSERVABILITY_NOCTUA_TOKEN }} + + - name: Update releases in image.yaml + id: update-releases + if: steps.changed-files.outputs.all_changed_and_modified_files != '' + run: | + all_tags="$(jq -r 'to_entries[] | .key' $GITHUB_WORKSPACE/oci-factory/oci/${{ inputs.rock-name }}/_releases.json | sed 's/-22.04//')" + today="$(date)" + echo "now_epoch=$(date -d now +%s)" >> $GITHUB_OUTPUT # to create a unique branch name on the fork + end_of_life="$(date -d "$today+1 year" +%Y-%m-%d)" + yq -i ".upload = []" $GITHUB_WORKSPACE/oci-factory/oci/${{ inputs.rock-name }}/image.yaml + for file in ${{ steps.changed-files.outputs.all_changed_and_modified_files }}; do + # For each ROCK version, build the `upload:` element for image.yaml as a json + # Example: {"source": "canonical/prometheus-rock", "commit": "...", ...} + patch_tag=""; minor_tag=""; major_tag="" + tag_json_format='"%s-22.04": {"end-of-life": "%sT00:00:00Z", "risks":["stable"]}' + # Parse the ROCK version from the rockcraft.yaml + rock_version=$(yq -r '.version' $GITHUB_WORKSPACE/rock/$file) + # Always tag with patch + patch_tag=$(printf "$tag_json_format" "$rock_version" "$end_of_life") + # If rock_version is the latest tag among the ones with equal major.minor, apply major.minor + rock_major_minor=$(echo $rock_version | sed -E "s/([0-9]+\.[0-9]+).*/\1/") + same_major_minor=$(printf "%s\n%s" "$all_tags" "$rock_version" | grep "$rock_major_minor") + if [[ $(echo "$same_major_minor" | sort -V | tail -n1) == "$rock_version" ]]; then + minor_tag=$(printf ",$tag_json_format" "$rock_major_minor" "$end_of_life") + fi + # If rock_version is the latest among the ones with equal major, apply major + rock_major=$(echo $rock_version | sed -E "s/([0-9]+).*/\1/") + same_major=$(printf "%s\n%s" "$all_tags" "$rock_version" | grep "$rock_major") + if [[ $(echo "$same_major" | sort -V | tail -n1) == "$rock_version" ]]; then + major_tag=$(printf ",$tag_json_format" "$rock_major" "$end_of_life") + fi + # Build the final JSON object to update image.yaml + rock_tags=$(printf '{%s%s%s}' "$patch_tag" "$minor_tag" "$major_tag") + upload_item_format='{"source":"%s","commit":"%s","directory":"%s","release":%s}' + upload_item=$(printf "$upload_item_format" \ + "canonical/${{ inputs.rock-name }}-rock" \ + "${{ steps.commit-sha.outputs.commit_sha }}" \ + "$rock_version" \ + "$rock_tags" \ + ) + yq -i ".upload += $upload_item" $GITHUB_WORKSPACE/oci-factory/oci/${{ inputs.rock-name }}/image.yaml + done + + - name: Commit to the fork + id: fork-commit + if: steps.changed-files.outputs.all_changed_and_modified_files != '' + uses: EndBug/add-and-commit@v9 + with: + add: 'oci/${{ inputs.rock-name }}/image.yaml' + cwd: './oci-factory' + message: 'chore: Add new ${{ inputs.rock-name }} releases' + new_branch: 'update-${{ steps.update-releases.outputs.now_epoch }}' + + - name: Open a PR from the fork to upstream + if: steps.changed-files.outputs.all_changed_and_modified_files != '' + id: upstream-pr + env: + GH_TOKEN: ${{ secrets.OBSERVABILITY_NOCTUA_TOKEN }} + run: | + cd $GITHUB_WORKSPACE/oci-factory + gh pr create --repo canonical/oci-factory \ + --head observability-noctua-bot:update-${{ steps.update-releases.outputs.now_epoch }} \ + --title "chore: Add new ${{ inputs.rock-name }} releases" \ + --body "This is an automatic PR opened by the Observability Noctua bot." diff --git a/.github/workflows/rock-update.yaml b/.github/workflows/rock-update.yaml new file mode 100644 index 00000000..76bedb1a --- /dev/null +++ b/.github/workflows/rock-update.yaml @@ -0,0 +1,132 @@ +# This action automatically creates a folder with the same name as the upstream version +name: Update ROCK on new releases of its source + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + workflow_call: + inputs: + rock-name: + description: "Name of the application for which to build the ROCK" + required: true + type: string + source-repo: + description: "Repository of the source application in 'org/repo' form" + required: true + type: string + check-go: + description: "Flag to check updates on the Go version" + default: false + required: false + type: boolean + update-script: + description: "Custom script to update external dependencies in rockcraft.yaml" + required: false + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + check-version: + name: Detect new releases + runs-on: ubuntu-latest + steps: + - name: Install dependencies + run: | + sudo snap install jq + sudo snap install yq + + - id: latest-release + name: Fetch version used in *latest* release + run: | + TAG=$(curl -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"\ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ inputs.source-repo }}/releases/latest \ + | jq -r 'select(.prerelease == false) | .tag_name') + echo "release=$TAG" >> $GITHUB_OUTPUT + + - name: Checkout the ROCK source + uses: actions/checkout@v3 + with: + path: main + + - id: check + name: Check if the release has already been added + if: steps.latest-release.outputs.release != '' + shell: bash + run: | + source_tag="${{ steps.latest-release.outputs.release }}" + version=${source_tag#"v"} + # Explicitly filter for specific ROCKs because we'd rather notice if a new ROCK has a different release schema + version=${version#"mimir-"} + if [ ! -f $GITHUB_WORKSPACE/main/$version/rockcraft.yaml ]; then + echo "version=$version" >> $GITHUB_OUTPUT + echo "release=${{steps.latest-release.outputs.release}}" >> $GITHUB_OUTPUT + echo "New upstream release ${{steps.latest-release.outputs.release}} found" + else + echo "No new upstream release found" + fi + + - name: Checkout application source for the Go version check + if: inputs.check-go && steps.check.outputs.release != '' + uses: actions/checkout@v3 + with: + repository: ${{ inputs.source-repo }} + ref: ${{ steps.check.outputs.release }} + path: application-src + + - name: Create a new rockcraft.yaml for the new application version + if: steps.check.outputs.release != '' + shell: bash + run: | + source_tag="${{ steps.check.outputs.release }}" + version="${{ steps.check.outputs.version }}" + latest_rockcraft_file=$(find $GITHUB_WORKSPACE/main/ -name "rockcraft.yaml" | sort -V | tail -n1) + cp -r "$(dirname $latest_rockcraft_file)" "$GITHUB_WORKSPACE/main/$version" + source_tag="$source_tag" \ + version="$version" \ + yq -i '.version = strenv(version) | .parts.${{ inputs.rock-name }}["source-tag"] = strenv(source_tag)' $GITHUB_WORKSPACE/main/$version/rockcraft.yaml + + - name: Update the Go version + if: inputs.check-go && steps.check.outputs.release != '' + shell: bash + run: | + version="${{ steps.check.outputs.version }}" + go_version=$(grep -Po "^go \K(\S+)" $GITHUB_WORKSPACE/application-src/go.mod) \ + # Delete the Go dependency and add the updated one + yq -i 'del(.parts.${{ inputs.rock-name }}.build-snaps.[] | select(. == "go/*"))' $GITHUB_WORKSPACE/main/$version/rockcraft.yaml + # Snap channels are named after major.minor only, so cut the go version to that format + go_major_minor=$(echo $go_version | sed -E "s/([0-9]+\.[0-9]+).*/\1/") + go_v="$go_major_minor" yq -i '.parts.${{ inputs.rock-name }}.build-snaps += "go/"+strenv(go_v)+"/stable"' $GITHUB_WORKSPACE/main/$version/rockcraft.yaml + + - name: Update other build dependencies + if: steps.check.outputs.release != '' && inputs.update-script != '' + shell: bash + run: | + version="${{ steps.check.outputs.version }}" + application_src=$GITHUB_WORKSPACE/application-src + rockcraft_yaml=$GITHUB_WORKSPACE/main/$version/rockcraft.yaml + cat > update-script.sh << EOF + ${{ inputs.update-script }} + EOF + source update-script.sh + + - name: Create a PR + if: steps.check.outputs.release != '' + uses: peter-evans/create-pull-request@v4.2.3 + with: + path: main + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore(deps): bump ${{ inputs.rock-name }} version to ${{ steps.check.outputs.release }}" + committer: "Github Actions " + author: "Github Actions " + title: "chore: add ROCK for ${{ inputs.rock-name }} ${{ steps.check.outputs.release }}" + body: Automated update to follow upstream [release](https://github.com/${{ inputs.source-repo }}/releases/tag/${{ steps.check.outputs.release }}) of ${{ inputs.rock-name }}. + branch: "chore/bump-version-to-${{ steps.check.outputs.release }}" + delete-branch: true