Repository: canonical/operator-workflows
Pull request #357: Add support for canonical k8s
Author: login: Thanhphan1147
Labels: name: trivial
Created: 2024-06-17T23:28:20Z | Merged: 2025-05-21T17:06:41Z

This PR adds an input `use-canonical-k8s` to the `integration-test` workflow to allow deploying juju applications on canonical-k8s. 

For now, this parameter **must be used** in conjuction with `provider: k8s` so that `action-operator` bootstraps canonical-k8s as a generic cluster when creating the juju controller. 

`action-operator` will add support for canonical-k8s in the future. Until then, this will be used as a workaround since it's needed to test workflows running on canonical-k8s.

### Checklist

- [x] The [contributing guide](https://github.com/canonical/is-charms-contributing-guide) was applied
- [x] The PR is tagged with appropriate label (`urgent`, `trivial`, `complex`)

<!-- Explanation for any unchecked items above -->



Diff:
diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml
index b2d98ac32..0365b0ab2 100644
--- a/.github/workflows/integration_test.yaml
+++ b/.github/workflows/integration_test.yaml
@@ -158,6 +158,10 @@ on:
           If this parameter is not specified, the default behavior is to use 'artifact' for pull requests 
           originating from forked repositories, and 'registry' for all other cases.
         default: ""
+      use-canonical-k8s:
+        type: boolean
+        description: (Experimental) Whether to use canonical k8s instead of microk8s
+        default: false
       working-directory:
         type: string
         description: The working directory for jobs
@@ -344,6 +348,7 @@ jobs:
       plan: ${{ needs.plan.outputs.plan }}
       pre-run-script: ${{ inputs.pre-run-script }}
       provider: ${{ inputs.provider }}
+      use-canonical-k8s: ${{ inputs.use-canonical-k8s }}
       registry: ghcr.io
       runs-on: ${{ inputs.runs-on || 'ubuntu-22.04' }}
       self-hosted-runner-arch: ${{ inputs.self-hosted-runner-arch }}
diff --git a/.github/workflows/integration_test_run.yaml b/.github/workflows/integration_test_run.yaml
index 47bc20d99..8d06af71f 100644
--- a/.github/workflows/integration_test_run.yaml
+++ b/.github/workflows/integration_test_run.yaml
@@ -120,6 +120,10 @@ on:
         type: string
         description: Target directory to do the Trivy testing
         default: "."
+      use-canonical-k8s:
+        type: boolean
+        description: (Experimental) Whether to use canonical k8s instead of microk8s
+        default: false
       working-directory:
         type: string
         description: The working directory for jobs
@@ -242,10 +246,35 @@ jobs:
         with:
           name: allure-fallback-results-${{ env.UNIQUE_ARTIFACT_SUFFIX }}
           path: ${{ inputs.working-directory }}allure-default/
+      - name: Setup Canonical k8s
+        if: inputs.use-canonical-k8s && inputs.provider == 'k8s'
+        id: canonical-k8s
+        run: |
+          sudo snap install k8s --channel=${{ inputs.channel }} --classic
+          
+          cat << EOF | sudo k8s bootstrap --file -
+          containerd-base-dir: /opt/containerd
+          EOF
+
+          # Note: the previous --file arguments override the default bootstrap options
+          #       resulting in the following not being enabled by default
+          sudo k8s enable network dns load-balancer local-storage gateway
+          sudo k8s status --wait-ready --timeout 5m
+
+          mkdir -p ~/.kube
+          sudo k8s config > ~/.kube/config
+          echo "kubeconfig=$(sudo k8s config | base64 -w 0)" >> $GITHUB_OUTPUT
+          
+          IPADDR=$(ip -4 -j route get 2.2.2.2 | jq -r '.[] | .prefsrc')
+          LB_FIRST_ADDR="$(echo "${IPADDR}" | awk -F'.' '{print $1,$2,$3,100}' OFS='.')"
+          LB_LAST_ADDR="$(echo "${IPADDR}" | awk -F'.' '{print $1,$2,$3,255}' OFS='.')"
+          LB_ADDR_RANGE="${LB_FIRST_ADDR}-${LB_LAST_ADDR}"
+          sudo k8s set load-balancer.cidrs=$LB_ADDR_RANGE load-balancer.enabled=true load-balancer.l2-mode=true
       - name: Setup operator environment
         uses: charmed-kubernetes/actions-operator@main
         with:
           provider: ${{ inputs.provider }}
+          credentials-yaml: ${{ steps.canonical-k8s.outputs.kubeconfig }}
           microk8s-addons: >-
             ${{
               ( 
@@ -355,7 +384,7 @@ jobs:
           echo "ARGS=$args" >> $GITHUB_ENV
       - name: Run k8s integration tests
         working-directory: ${{ inputs.working-directory }}
-        if: ${{ inputs.provider == 'microk8s' }}
+        if: ${{ inputs.provider == 'microk8s' }} || ${ inputs.provider == 'k8s' }
         run: |
           [ -n "${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME }}" ] && export ${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME }}="${{ secrets.INTEGRATION_TEST_SECRET_ENV_VALUE }}" || :
           [ -n "${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME_1 }}" ] && export ${{ vars.INTEGRATION_TEST_SECRET_ENV_NAME_1 }}="${{ secrets.INTEGRATION_TEST_SECRET_ENV_VALUE_1 }}" || :
diff --git a/.github/workflows/workflow_test.yaml b/.github/workflows/workflow_test.yaml
index 5c86dc5e3..ffe8cc31b 100644
--- a/.github/workflows/workflow_test.yaml
+++ b/.github/workflows/workflow_test.yaml
@@ -125,4 +125,20 @@ jobs:
           [ '${{ needs.publish.result }}' != 'failure' ] || (echo publish failed && false)
           [ '${{ needs.publish-artifact.result }}' != 'failure' ] || (echo publish failed && false)
           [ '${{ needs.allure-report.result }}' != 'failure' ] || (echo allure-report failed && false)
-
+  integration-canonical-k8s:
+    uses: ./.github/workflows/integration_test.yaml
+    secrets: inherit
+    with:
+      working-directory: "tests/workflows/integration/test-rock/"
+      trivy-image-config: "tests/workflows/integration/test-rock/trivy.yaml"
+      provider: k8s
+      extra-arguments: |
+        --kube-config=~/.kube/config
+      use-canonical-k8s: true
+      juju-channel: 3.5/stable
+      channel: edge
+      charmcraft-channel: latest/edge
+      # We need large runners to be able to deploy canonical k8s
+      self-hosted-runner: true
+      self-hosted-runner-image: noble
+      self-hosted-runner-label: large
diff --git a/docs/changelog.md b/docs/changelog.md
index 0cab94fdb..14e82527d 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -28,3 +28,9 @@ Each revision is versioned by the date of the revision.
 
 - Changelog added for tracking changes.
 - Added unique artifact name for zap-scan artifacts
+
+# 2025-05-15
+
+### Added
+
+- Added support for canonical-k8s via the `use-canonical-k8s: true` and `provider: k8s` parameter


Reviews:
user: login: arturo-seijas
body: It looks like some tests are filing. Can you double check?

state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-06-20T08:17:20Z
user: login: arturo-seijas
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2024-10-25T11:02:34Z
user: login: Thanhphan1147
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-10T17:52:00Z
user: login: arturo-seijas
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-02-10T17:54:47Z
user: login: javierdelapuente
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-11T16:39:46Z
user: login: arturo-seijas
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-11T17:08:16Z
user: login: yhaliaw
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T02:54:49Z
user: login: yanksyoon
body: would there be a test we could add to this change? LGTM!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-06T02:38:40Z
user: login: yanksyoon
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-16T07:40:49Z
user: login: seb4stien
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T08:28:06Z
user: login: Thanhphan1147
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T09:17:12Z
user: login: Thanhphan1147
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T09:19:19Z
user: login: seb4stien
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:43:27Z
user: login: arturo-seijas
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T13:56:51Z
user: login: Thanhphan1147
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:06:11Z
user: login: arturo-seijas
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T07:36:01Z

Review comments:
diff_hunk: @@ -171,10 +175,26 @@ jobs:
         )) || inputs.runs-on
       }}
     steps:
+      - name: Setup Canonical k8s
+        if: ${{ inputs.use-canonical-k8s }} == true
+        id: canonical-k8s
+        run: |
+          sudo snap install k8s --channel=1.30-classic/beta --classic
path: .github/workflows/integration_test_run.yaml
user: login: arturo-seijas
body: I think you can reuse the `channel` input here currently used for microk8s to be able to customise the snap channel
created_at: 2024-10-25T11:02:33Z
author_association: COLLABORATOR
diff_hunk: @@ -171,10 +175,26 @@ jobs:
         )) || inputs.runs-on
       }}
     steps:
+      - name: Setup Canonical k8s
+        if: ${{ inputs.use-canonical-k8s }} == true
+        id: canonical-k8s
+        run: |
+          sudo snap install k8s --channel=1.30-classic/beta --classic
path: .github/workflows/integration_test_run.yaml
user: login: Thanhphan1147
body: Thanks, I updated this to using {{ input.channel }}
created_at: 2025-02-10T17:52:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -174,6 +178,21 @@ jobs:
         )) || inputs.runs-on
       }}
     steps:
+      - name: Setup Canonical k8s
+        if: inputs.use-canonical-k8s && inputs.provider == 'k8s'
+        id: canonical-k8s
+        run: |
+          sudo snap install k8s --channel=${{ inputs.channel }} --classic
path: .github/workflows/integration_test_run.yaml
user: login: javierdelapuente
body: Maybe use another input variable for the channel? The default is latest/stable, and that does not exists for that snap.
created_at: 2025-02-11T16:18:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -174,6 +178,21 @@ jobs:
         )) || inputs.runs-on
       }}
     steps:
+      - name: Setup Canonical k8s
+        if: inputs.use-canonical-k8s && inputs.provider == 'k8s'
+        id: canonical-k8s
+        run: |
+          sudo snap install k8s --channel=${{ inputs.channel }} --classic
+          sudo k8s bootstrap
+          mkdir -p ~/.kube
+          sudo k8s config > ~/.kube/config
+          echo "kubeconfig=$(sudo k8s config | base64 -w 0)" >> $GITHUB_OUTPUT
+          
+          IPADDR=$(ip -4 -j route get 2.2.2.2 | jq -r '.[] | .prefsrc')
+          LB_FIRST_ADDR="$(echo "${IPADDR}" | awk -F'.' '{print $1,$2,$3,100}' OFS='.')"
path: .github/workflows/integration_test_run.yaml
user: login: javierdelapuente
body: I do not know much about this, but you will this work out of the box? You are in a subnet with other machines that may reply to arp requests for some of those ips. 
created_at: 2025-02-11T16:39:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -174,6 +178,21 @@ jobs:
         )) || inputs.runs-on
       }}
     steps:
+      - name: Setup Canonical k8s
+        if: inputs.use-canonical-k8s && inputs.provider == 'k8s'
+        id: canonical-k8s
+        run: |
+          sudo snap install k8s --channel=${{ inputs.channel }} --classic
path: .github/workflows/integration_test_run.yaml
user: login: arturo-seijas
body: That's not the best but I don't know if it's worth adding another option for this. I believe it might lead to confusion
created_at: 2025-02-11T17:08:16Z
author_association: COLLABORATOR
diff_hunk: @@ -158,6 +158,10 @@ on:
           If this parameter is not specified, the default behavior is to use 'artifact' for pull requests 
           originating from forked repositories, and 'registry' for all other cases.
         default: ""
+      use-canonical-k8s:
path: .github/workflows/integration_test.yaml
user: login: yhaliaw
body: It might good to document this added feature in the docs/changelog.md
created_at: 2025-03-24T02:54:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,6 +158,10 @@ on:
           If this parameter is not specified, the default behavior is to use 'artifact' for pull requests 
           originating from forked repositories, and 'registry' for all other cases.
         default: ""
+      use-canonical-k8s:
path: .github/workflows/integration_test.yaml
user: login: seb4stien
body: Why introducing a new variable as we already have "provider"?
created_at: 2025-05-21T08:28:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -174,6 +178,21 @@ jobs:
         )) || inputs.runs-on
       }}
     steps:
+      - name: Setup Canonical k8s
+        if: inputs.use-canonical-k8s && inputs.provider == 'k8s'
+        id: canonical-k8s
+        run: |
+          sudo snap install k8s --channel=${{ inputs.channel }} --classic
+          sudo k8s bootstrap
+          mkdir -p ~/.kube
+          sudo k8s config > ~/.kube/config
+          echo "kubeconfig=$(sudo k8s config | base64 -w 0)" >> $GITHUB_OUTPUT
+          
+          IPADDR=$(ip -4 -j route get 2.2.2.2 | jq -r '.[] | .prefsrc')
+          LB_FIRST_ADDR="$(echo "${IPADDR}" | awk -F'.' '{print $1,$2,$3,100}' OFS='.')"
path: .github/workflows/integration_test_run.yaml
user: login: Thanhphan1147
body: since this is a reusable workflow to run anywhere this is the simplest way I can think of to get a LB range, I'd assume cilium will at least try to see if the IP is available before taking it, so far from my testing it has been going fine, but I'll keep that in mind to see if this eventually becomes a problem.
created_at: 2025-05-21T09:17:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,6 +158,10 @@ on:
           If this parameter is not specified, the default behavior is to use 'artifact' for pull requests 
           originating from forked repositories, and 'registry' for all other cases.
         default: ""
+      use-canonical-k8s:
path: .github/workflows/integration_test.yaml
user: login: Thanhphan1147
body: the problem with `provider` is that it can also be used to bootstrap a generic k8s cluster (if you install your own k8s prior to running juju bootstrap for example), not just `canonical-k8s`. I'd imagine action-operator will support a `canonical-k8s` provider in the future but currently we need both `provider: k8s` and `use-canonical-k8s: true`
created_at: 2025-05-21T09:19:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -125,4 +125,20 @@ jobs:
           [ '${{ needs.publish.result }}' != 'failure' ] || (echo publish failed && false)
           [ '${{ needs.publish-artifact.result }}' != 'failure' ] || (echo publish failed && false)
           [ '${{ needs.allure-report.result }}' != 'failure' ] || (echo allure-report failed && false)
-
+  integration-canonical-k8s:
+    uses: ./.github/workflows/integration_test.yaml
+    secrets: inherit
+    with:
+      working-directory: "tests/workflows/integration/test-rock/"
+      trivy-image-config: "tests/workflows/integration/test-rock/trivy.yaml"
+      provider: k8s
+      extra-arguments: |
+        --kube-config=~/.kube/config
+      use-canonical-k8s: true
+      juju-channel: 3.5/stable
+      channel: edge
+      charmcraft-channel: latest/edge
+      # We need large runners to be able to deploy canonical k8s
+      self-hosted-runner: true
+      self-hosted-runner-image: noble
+      self-hosted-runner-label: large
path: .github/workflows/workflow_test.yaml
user: login: arturo-seijas
body: Can't you use a small one here?
created_at: 2025-05-21T13:56:51Z
author_association: COLLABORATOR
diff_hunk: @@ -125,4 +125,20 @@ jobs:
           [ '${{ needs.publish.result }}' != 'failure' ] || (echo publish failed && false)
           [ '${{ needs.publish-artifact.result }}' != 'failure' ] || (echo publish failed && false)
           [ '${{ needs.allure-report.result }}' != 'failure' ] || (echo allure-report failed && false)
-
+  integration-canonical-k8s:
+    uses: ./.github/workflows/integration_test.yaml
+    secrets: inherit
+    with:
+      working-directory: "tests/workflows/integration/test-rock/"
+      trivy-image-config: "tests/workflows/integration/test-rock/trivy.yaml"
+      provider: k8s
+      extra-arguments: |
+        --kube-config=~/.kube/config
+      use-canonical-k8s: true
+      juju-channel: 3.5/stable
+      channel: edge
+      charmcraft-channel: latest/edge
+      # We need large runners to be able to deploy canonical k8s
+      self-hosted-runner: true
+      self-hosted-runner-image: noble
+      self-hosted-runner-label: large
path: .github/workflows/workflow_test.yaml
user: login: Thanhphan1147
body: canonical-k8s requires a large runner I think otherwise bootstrapping of the cluster will fail 
created_at: 2025-05-21T17:06:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -125,4 +125,20 @@ jobs:
           [ '${{ needs.publish.result }}' != 'failure' ] || (echo publish failed && false)
           [ '${{ needs.publish-artifact.result }}' != 'failure' ] || (echo publish failed && false)
           [ '${{ needs.allure-report.result }}' != 'failure' ] || (echo allure-report failed && false)
-
+  integration-canonical-k8s:
+    uses: ./.github/workflows/integration_test.yaml
+    secrets: inherit
+    with:
+      working-directory: "tests/workflows/integration/test-rock/"
+      trivy-image-config: "tests/workflows/integration/test-rock/trivy.yaml"
+      provider: k8s
+      extra-arguments: |
+        --kube-config=~/.kube/config
+      use-canonical-k8s: true
+      juju-channel: 3.5/stable
+      channel: edge
+      charmcraft-channel: latest/edge
+      # We need large runners to be able to deploy canonical k8s
+      self-hosted-runner: true
+      self-hosted-runner-image: noble
+      self-hosted-runner-label: large
path: .github/workflows/workflow_test.yaml
user: login: arturo-seijas
body: It is not obvious for anyone reading the code. Please, next time add a comment explaining why a non obvious decision was made
created_at: 2025-05-22T07:36:01Z
author_association: COLLABORATOR

PR comments:
user: login: cbartz
created_at: 2024-11-14T07:23:46Z
body: @Thanhphan1147 what is the status here, please?
author_association: CONTRIBUTOR
user: login: nrobinaubertin
created_at: 2025-03-06T20:27:16Z
body: [CI](https://github.com/canonical/gateway-api-integrator-operator/pull/85) [is](https://github.com/canonical/gateway-api-integrator-operator/pull/86) [failing](https://github.com/canonical/gateway-api-integrator-operator/pull/84) for gateway-api. This is because the k8s cluster cannot be bootstrapped or because it is trying to install the stable channel which doesn't exist.
I write this comment as reference on those stalled PRs.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:32Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:40.117976

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:33Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:19.366965

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:33Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:39.748321

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:34Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:13.918761

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:35Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:32.286576

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:35Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:12.180864

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:36Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:47:14.890960

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:54Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:40.117976

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:54Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:19.366965

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:55Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:39.748321

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:56Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:13.918761

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:56Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:32.286576

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:57Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:44:12.180864

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-21T13:50:57Z
body: ## Test results for commit 88551741be16482f9acf143bf475dc4604898d9f

Test coverage for 88551741be16482f9acf143bf475dc4604898d9f

  ```
Name           Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------
src/charm.py       7      7      0      0     0%   8-23
----------------------------------------------------------
TOTAL              7      7      0      0     0%
```
  Static code analysis report

  ```
Run started:2025-05-21 13:47:14.890960

Test results:
	No issues identified.

Code scanned:
	Total lines of code: 52
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 0
		Medium: 0
		High: 0
Files skipped (0):
```
author_association: CONTRIBUTOR