diff --git a/.github/workflows/integrate.yaml b/.github/workflows/ci.yaml similarity index 53% rename from .github/workflows/integrate.yaml rename to .github/workflows/ci.yaml index 62c649d1..f41a1295 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/ci.yaml @@ -1,24 +1,55 @@ -# reusable workflow triggered by other actions name: CI +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: + pull_request: + schedule: + - cron: '0 8 * * TUE' + # Triggered on push by .github/workflows/release.yaml workflow_call: - secrets: - CHARMCRAFT_CREDENTIALS: - required: true + outputs: + artifact-prefix: + description: build_charm.yaml `artifact-prefix` output + value: ${{ jobs.build.outputs.artifact-prefix }} + charm-paths: + description: paths for all charms in this repo + value: ${{ jobs.get-charm-paths-channel.outputs.charm-paths }} + channel: + description: Charmhub channel the charms are released to + value: ${{ jobs.get-charm-paths-channel.outputs.charm-channel }} jobs: + get-charm-paths-channel: + name: Get charm paths and charmhub channel + runs-on: ubuntu-latest + outputs: + charm-paths: ${{ steps.get-charm-paths.outputs.charm-paths }} + charm-channel: ${{ steps.select-channel.outputs.name }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get paths for all charms in this repo + id: get-charm-paths + uses: canonical/kubeflow-ci/actions/get-charm-paths@main + - name: Select charmhub channel + uses: canonical/charming-actions/channel@2.6.2 + id: select-channel + lib-check: name: Check libraries + needs: + - get-charm-paths-channel strategy: matrix: - charm: - - tensorboard-controller - - tensorboards-web-app + charm: ${{ fromJSON(needs.get-charm-paths-channel.outputs.charm-paths) }} uses: canonical/charmed-kubeflow-workflows/.github/workflows/_quality-checks.yaml@main secrets: inherit with: - charm-path: ./charms/${{ matrix.charm }} + charm-path: ${{ matrix.charm }} lint: name: Lint Check @@ -56,22 +87,34 @@ jobs: terraform-checks: name: Terraform + needs: + - get-charm-paths-channel uses: canonical/charmed-kubeflow-workflows/.github/workflows/terraform-checks.yaml@main strategy: matrix: - charm: - - tensorboard-controller - - tensorboards-web-app + charm: ${{ fromJSON(needs.get-charm-paths-channel.outputs.charm-paths) }} with: - charm-path: ./charms/${{ matrix.charm }} + charm-path: ${{ matrix.charm }} # Skipping the Terraform apply check as the tensorboard-controller goes to Waiting status # instead of the expected Blocked or Active. This is currently a limitation of the # Terraform re-usable workflows in canonical/charmed-kubeflow-workflows # See https://github.com/canonical/charmed-kubeflow-workflows/issues/65 apply: false + + build: + strategy: + matrix: + charm: ${{ fromJSON(needs.get-charm-paths-channel.outputs.charm-paths) }} + name: Build charm | ${{ matrix.charm }} + needs: + - get-charm-paths-channel + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v29.0.0 + with: + path-to-charm-directory: ${{ matrix.charm }} integration: - name: Charm-specific Integration tests (microk8s) + name: Integration tests (microk8s) + needs: build runs-on: ubuntu-20.04 strategy: fail-fast: false @@ -80,18 +123,26 @@ jobs: steps: - name: Check out code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup operator environment uses: charmed-kubernetes/actions-operator@main with: provider: microk8s channel: 1.31-strict/stable juju-channel: 3.6/stable - charmcraft-channel: 3.x/stable microk8s-addons: dns hostpath-storage ingress metallb:10.64.140.43-10.64.140.49 + + - name: Download packed charm(s) + id: download-charms + timeout-minutes: 5 + uses: actions/download-artifact@v4 + with: + pattern: ${{ needs.build.outputs.artifact-prefix }}-* + merge-multiple: true + - run: | - sg snap_microk8s -c "tox -vve ${{ matrix.charm }}-integration" + sg snap_microk8s -c "tox -vve ${{ matrix.charm }}-integration -- --charm-path=${{ github.workspace }}/charms/${{ matrix.charm }}/${{ matrix.charm }}_ubuntu@20.04-amd64.charm" # Collect debug logs if failed - name: Dump Juju/K8s logs on failure @@ -103,6 +154,7 @@ jobs: deploy: name: Integration Test + needs: build runs-on: ubuntu-20.04 steps: - name: Check out code @@ -112,13 +164,20 @@ jobs: with: provider: microk8s channel: 1.31-strict/stable - charmcraft-channel: 3.x/stable juju-channel: 3.6/stable microk8s-addons: dns hostpath-storage ingress metallb:10.64.140.43-10.64.140.49 + - name: Download packed charm(s) + id: download-charms + timeout-minutes: 5 + uses: actions/download-artifact@v4 + with: + pattern: ${{ needs.build.outputs.artifact-prefix }}-* + merge-multiple: true + - name: Run test run: | - tox -e integration + tox -e integration -- --charms-path=${{ github.workspace }}/charms/ # On failure, capture debugging resources - name: Get all @@ -148,3 +207,21 @@ jobs: - name: Get tensorboards-web-app operator logs run: kubectl logs -n testing --tail 1000 -ljuju-operator=tensorboards-web-app if: failure() + + release: + strategy: + matrix: + charm: ${{ fromJSON(needs.get-charm-paths-channel.outputs.charm-paths) }} + name: Release charm to Charmhub branch | ${{ matrix.charm }} + if: ${{ github.event_name == 'pull_request' }} + needs: + - get-charm-paths-channel + - build + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v29.0.0 + with: + channel: ${{ needs.get-charm-paths-channel.outputs.charm-channel }} + artifact-prefix: ${{ needs.build.outputs.artifact-prefix }} + path-to-charm-directory: ${{ matrix.charm }} + create-git-tags: false + secrets: + charmhub-token: ${{ secrets.CHARMCRAFT_CREDENTIALS }} diff --git a/.github/workflows/get-charm-paths.sh b/.github/workflows/get-charm-paths.sh deleted file mode 100644 index 1110d59c..00000000 --- a/.github/workflows/get-charm-paths.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -x - -# Finds the charms in this repo, outputting them as JSON -# Will return one of: -# * the relative paths of the directories listed in `./charms`, if that directory exists -# * "./", if the root directory has a "metadata.yaml" file -# * otherwise, error -# -# Modified from: https://stackoverflow.com/questions/63517732/github-actions-build-matrix-for-lambda-functions/63736071#63736071 -CHARMS_DIR="./charms" -if [ -d "$CHARMS_DIR" ]; -then - CHARM_PATHS=$(find $CHARMS_DIR -maxdepth 1 -type d -not -path '*/\.*' -not -path "$CHARMS_DIR") -else - if [ -f "./metadata.yaml" ] - then - CHARM_PATHS="./" - else - echo "Cannot find valid charm directories - aborting" - exit 1 - fi -fi - -# Convert output to JSON string format -# { charm_paths: [...] } -CHARM_PATHS_LIST=$(echo "$CHARM_PATHS" | jq -c --slurp --raw-input 'split("\n")[:-1]') - -echo "Found CHARM_PATHS_LIST: $CHARM_PATHS_LIST" - -echo "::set-output name=CHARM_PATHS_LIST::$CHARM_PATHS_LIST" diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml deleted file mode 100644 index 3d7eef10..00000000 --- a/.github/workflows/on_pull_request.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: On Pull Request - -# On pull_request, we: -# * always publish to charmhub at latest/edge/branchname -# * always run tests - -on: - pull_request: - -jobs: - - tests: - name: Run Tests - uses: ./.github/workflows/integrate.yaml - secrets: inherit - - # publish runs in parallel with tests, as we always publish in this situation - publish-charm: - name: Publish Charm - uses: ./.github/workflows/publish.yaml - secrets: inherit diff --git a/.github/workflows/on_push.yaml b/.github/workflows/on_push.yaml deleted file mode 100644 index 31c42da3..00000000 --- a/.github/workflows/on_push.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: On Push - -# On push to a "special" branch, we: -# * always publish to charmhub at latest/edge/branchname -# * always run tests -# where a "special" branch is one of main or track/**, as -# by convention these branches are the source for a corresponding -# charmhub edge channel. - -on: - push: - branches: - - main - - track/** - -jobs: - - tests: - name: Run Tests - uses: ./.github/workflows/integrate.yaml - secrets: inherit - - # publish runs in series with tests, and only publishes if tests passes - publish-charm: - name: Publish Charm - needs: tests - uses: ./.github/workflows/publish.yaml - secrets: inherit diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml new file mode 100644 index 00000000..6f7e8bf1 --- /dev/null +++ b/.github/workflows/promote.yaml @@ -0,0 +1,31 @@ +# reusable workflow triggered manually +name: Promote charm to other tracks and channels + +on: + workflow_dispatch: + inputs: + destination-channel: + description: 'Destination Channel' + required: true + origin-channel: + description: 'Origin Channel' + required: true + charm-name: + description: 'Charm subdirectory name' + required: true + +jobs: + promote-charm: + name: Promote charm + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Release charm to channel + uses: canonical/charming-actions/release-charm@2.6.2 + with: + credentials: ${{ secrets.CHARMCRAFT_CREDENTIALS }} + github-token: ${{ secrets.GITHUB_TOKEN }} + destination-channel: ${{ github.event.inputs.destination-channel }} + origin-channel: ${{ github.event.inputs.origin-channel }} + tag-prefix: ${{ github.event.inputs.charm-name }} + charm-path: charms/${{ github.event.inputs.charm-name}} diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml deleted file mode 100644 index 93a15947..00000000 --- a/.github/workflows/publish.yaml +++ /dev/null @@ -1,94 +0,0 @@ -# reusable workflow for publishing all charms in this repo -name: Publish - -on: - workflow_call: - inputs: - source_branch: - description: Github branch from this repo to publish. If blank, will use the default branch - default: '' - required: false - type: string - secrets: - CHARMCRAFT_CREDENTIALS: - required: true - workflow_dispatch: - inputs: - destination_channel: - description: CharmHub channel to publish to - required: false - default: 'latest/edge' - type: string - source_branch: - description: Github branch from this repo to publish. If blank, will use the default branch - required: false - default: '' - type: string - -jobs: - get-charm-paths: - name: Generate the Charm Matrix - runs-on: ubuntu-20.04 - outputs: - charm_paths_list: ${{ steps.get-charm-paths.outputs.CHARM_PATHS_LIST }} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ inputs.source_branch }} - - name: Get paths for all charms in repo - id: get-charm-paths - run: bash .github/workflows/get-charm-paths.sh - - - publish-charm: - name: Publish Charm - runs-on: ubuntu-20.04 - needs: get-charm-paths - strategy: - fail-fast: false - matrix: - charm-path: ${{ fromJson(needs.get-charm-paths.outputs.charm_paths_list) }} - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ inputs.source_branch }} - - - name: Select charmhub channel - uses: canonical/charming-actions/channel@2.6.2 - id: select-channel - if: ${{ inputs.destination_channel == '' }} - - # Combine inputs from different sources to a single canonical value so later steps don't - # need logic for picking the right one - - name: Parse and combine inputs - id: parse-inputs - run: | - # destination_channel - destination_channel="${{ inputs.destination_channel || steps.select-channel.outputs.name }}" - echo "setting output of destination_channel=$destination_channel" - echo "::set-output name=destination_channel::$destination_channel" - - # tag_prefix - # if charm_path = ./ --> tag_prefix = '' (null) - # if charm_path != ./some-charm (eg: a charm in a ./charms dir) --> tag_prefix = 'some-charm' - if [ ${{ matrix.charm-path }} == './' ]; then - tag_prefix='' - else - tag_prefix=$(basename ${{ matrix.charm-path }} ) - fi - echo "setting output of tag_prefix=$tag_prefix" - echo "::set-output name=tag_prefix::$tag_prefix" - - - name: Upload charm to charmhub - uses: canonical/charming-actions/upload-charm@2.6.2 - with: - credentials: ${{ secrets.CHARMCRAFT_CREDENTIALS }} - github-token: ${{ secrets.GITHUB_TOKEN }} - charm-path: ${{ matrix.charm-path }} - channel: ${{ steps.parse-inputs.outputs.destination_channel }} - tag-prefix: ${{ steps.parse-inputs.outputs.tag_prefix }} - charmcraft-channel: 3.x/stable diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 57fc88dc..54a7cfec 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,31 +1,29 @@ -# reusable workflow triggered manually -name: Release charm to other tracks and channels +name: Release to Charmhub on: - workflow_dispatch: - inputs: - destination-channel: - description: 'Destination Channel' - required: true - origin-channel: - description: 'Origin Channel' - required: true - charm-name: - description: 'Charm subdirectory name' - required: true + push: + branches: + - main + - track/** jobs: - promote-charm: - name: Promote charm - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - name: Release charm to channel - uses: canonical/charming-actions/release-charm@2.6.2 - with: - credentials: ${{ secrets.CHARMCRAFT_CREDENTIALS }} - github-token: ${{ secrets.GITHUB_TOKEN }} - destination-channel: ${{ github.event.inputs.destination-channel }} - origin-channel: ${{ github.event.inputs.origin-channel }} - tag-prefix: ${{ github.event.inputs.charm-name }} - charm-path: charms/${{ github.event.inputs.charm-name}} + ci-tests: + uses: ./.github/workflows/ci.yaml + secrets: inherit + + release: + strategy: + matrix: + charm: ${{ fromJSON(needs.ci-tests.outputs.charm-paths) }} + name: Release charm | ${{ matrix.charm }} + needs: + - ci-tests + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v29.0.0 + with: + channel: ${{ inputs.destination_channel || needs.ci-tests.outputs.channel }} + artifact-prefix: ${{ needs.ci-tests.outputs.artifact-prefix }} + path-to-charm-directory: ${{ matrix.charm }} + secrets: + charmhub-token: ${{ secrets.CHARMCRAFT_CREDENTIALS }} + permissions: + contents: write # Needed to create git tags diff --git a/charms/tensorboard-controller/LICENSE b/charms/tensorboard-controller/LICENSE new file mode 100644 index 00000000..5823fae5 --- /dev/null +++ b/charms/tensorboard-controller/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Canonical Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/charms/tensorboard-controller/charmcraft.yaml b/charms/tensorboard-controller/charmcraft.yaml index a94153a9..b8a99f7d 100644 --- a/charms/tensorboard-controller/charmcraft.yaml +++ b/charms/tensorboard-controller/charmcraft.yaml @@ -1,20 +1,72 @@ -# Copyright 2023 Canonical Ltd. +# Copyright 2025 Canonical Ltd. # See LICENSE file for licensing details. - -type: "charm" -bases: - - build-on: - - name: "ubuntu" - channel: "20.04" - run-on: - - name: "ubuntu" - channel: "20.04" +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: charm +platforms: + ubuntu@20.04:amd64: +# Files implicitly created by charmcraft without a part: +# - dispatch (https://github.com/canonical/charmcraft/pull/1898) +# - manifest.yaml +# (https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/services/package.py#L259) +# Files implicitly copied/"primed" by charmcraft without a part: +# - actions.yaml, config.yaml, metadata.yaml +# (https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/services/package.py#L290-L293 +# https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/services/package.py#L156-L157) parts: - charm: - charm-python-packages: [setuptools, pip] # Fixes install of some packages - # Following lines are needed due to https://github.com/canonical/charmcraft/issues/1722 - build-snaps: [rustup] - build-packages: [pkg-config, libffi-dev, libssl-dev] + # "python-deps" part name is arbitrary; use for consistency + # (but could become a magic constant in the future, similar to "poetry-deps" + # https://github.com/canonical/craft-parts/pull/901) + python-deps: + plugin: nil + override-build: | + # Use environment variable instead of `--break-system-packages` to avoid failing on older + # versions of pip that do not recognize `--break-system-packages` + # `--user` needed (in addition to `--break-system-packages`) for Ubuntu >=24.04 + PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==24.3.1 # renovate: charmcraft-pip-latest + # "charm-python" part name is arbitrary; use for consistency + # Avoid using "charm" part name since that has special meaning to charmcraft + charm-python: + # By default, the `python` plugin creates/primes these directories: + # - lib, src + # (https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/parts/plugins/_python.py#L79-L81) + # - venv + # (https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/parts/plugins/_python.py#L100 + # https://github.com/canonical/craft-parts/blob/afb0d652eb330b6aaad4f40fbd6e5357d358de47/craft_parts/plugins/base.py#L270) + plugin: python + source: . + after: + - python-deps + python-requirements: [requirements.txt] + build-packages: + - libffi-dev # Needed to build Python dependencies with Rust from source + - libssl-dev # Needed to build Python dependencies with Rust from source + - pkg-config # Needed to build Python dependencies with Rust from source override-build: | - rustup default stable + # Workaround for https://github.com/canonical/charmcraft/issues/2068 + # rustup used to install rustc and cargo, which are needed to build Python dependencies with Rust from source + if [[ "$CRAFT_PLATFORM" == ubuntu@20.04:* || "$CRAFT_PLATFORM" == ubuntu@22.04:* ]] + then + snap install rustup --classic + else + apt-get install rustup -y + fi + + # If Ubuntu version < 24.04, rustup was installed from snap instead of from the Ubuntu + # archive—which means the rustup version could be updated at any time. Print rustup version + # to build log to make changes to the snap's rustup version easier to track + rustup --version + + # rpds-py (Python package) >=0.19.0 requires rustc >=1.76, which is not available in the + # Ubuntu 22.04 archive. Install rustc and cargo using rustup instead of the Ubuntu archive + rustup set profile minimal + rustup default 1.83.0 # renovate: charmcraft-rust-latest craftctl default + # Include requirements.txt in *.charm artifact for easier debugging + cp requirements.txt "$CRAFT_PART_INSTALL/requirements.txt" + # "files" part name is arbitrary; use for consistency + files: + plugin: dump + source: . + stage: + - LICENSE diff --git a/charms/tensorboard-controller/tests/integration/conftest.py b/charms/tensorboard-controller/tests/integration/conftest.py new file mode 100644 index 00000000..f4eda080 --- /dev/null +++ b/charms/tensorboard-controller/tests/integration/conftest.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +from _pytest.config.argparsing import Parser + + +def pytest_addoption(parser: Parser): + parser.addoption( + "--charm-path", + help="Path to charm file for performing tests on.", + ) diff --git a/charms/tensorboard-controller/tests/integration/test_charm.py b/charms/tensorboard-controller/tests/integration/test_charm.py index fe57b5ee..995448ad 100644 --- a/charms/tensorboard-controller/tests/integration/test_charm.py +++ b/charms/tensorboard-controller/tests/integration/test_charm.py @@ -69,17 +69,24 @@ def create_tensorboard(ops_test: OpsTest): @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed -async def test_build_and_deploy(ops_test: OpsTest): +async def test_build_and_deploy(ops_test: OpsTest, request): """Build the charm-under-test and deploy it together with related charms. Assert on the unit status before any relations/configurations take place. """ - # Build and deploy charm from local source folder - charm = await ops_test.build_charm(".") + # Build and deploy charm from local source folder or use + # a charm artefact passed using --charm-path + entity_url = ( + await ops_test.build_charm(".") + if not (entity_url := request.config.getoption("--charm-path")) + else entity_url + ) tb_controller_image = METADATA["resources"]["tensorboard-controller-image"]["upstream-source"] resources = {"tensorboard-controller-image": tb_controller_image} - await ops_test.model.deploy(charm, resources=resources, application_name=APP_NAME, trust=True) + await ops_test.model.deploy( + entity_url=entity_url, resources=resources, application_name=APP_NAME, trust=True + ) await ops_test.model.wait_for_idle( apps=[APP_NAME], status="waiting", raise_on_blocked=True, timeout=60 * 5 diff --git a/charms/tensorboards-web-app/LICENSE b/charms/tensorboards-web-app/LICENSE new file mode 100644 index 00000000..5823fae5 --- /dev/null +++ b/charms/tensorboards-web-app/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Canonical Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/charms/tensorboards-web-app/charmcraft.yaml b/charms/tensorboards-web-app/charmcraft.yaml index a94153a9..b8a99f7d 100644 --- a/charms/tensorboards-web-app/charmcraft.yaml +++ b/charms/tensorboards-web-app/charmcraft.yaml @@ -1,20 +1,72 @@ -# Copyright 2023 Canonical Ltd. +# Copyright 2025 Canonical Ltd. # See LICENSE file for licensing details. - -type: "charm" -bases: - - build-on: - - name: "ubuntu" - channel: "20.04" - run-on: - - name: "ubuntu" - channel: "20.04" +# Learn more about charmcraft.yaml configuration at: +# https://juju.is/docs/sdk/charmcraft-config +type: charm +platforms: + ubuntu@20.04:amd64: +# Files implicitly created by charmcraft without a part: +# - dispatch (https://github.com/canonical/charmcraft/pull/1898) +# - manifest.yaml +# (https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/services/package.py#L259) +# Files implicitly copied/"primed" by charmcraft without a part: +# - actions.yaml, config.yaml, metadata.yaml +# (https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/services/package.py#L290-L293 +# https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/services/package.py#L156-L157) parts: - charm: - charm-python-packages: [setuptools, pip] # Fixes install of some packages - # Following lines are needed due to https://github.com/canonical/charmcraft/issues/1722 - build-snaps: [rustup] - build-packages: [pkg-config, libffi-dev, libssl-dev] + # "python-deps" part name is arbitrary; use for consistency + # (but could become a magic constant in the future, similar to "poetry-deps" + # https://github.com/canonical/craft-parts/pull/901) + python-deps: + plugin: nil + override-build: | + # Use environment variable instead of `--break-system-packages` to avoid failing on older + # versions of pip that do not recognize `--break-system-packages` + # `--user` needed (in addition to `--break-system-packages`) for Ubuntu >=24.04 + PIP_BREAK_SYSTEM_PACKAGES=true python3 -m pip install --user --upgrade pip==24.3.1 # renovate: charmcraft-pip-latest + # "charm-python" part name is arbitrary; use for consistency + # Avoid using "charm" part name since that has special meaning to charmcraft + charm-python: + # By default, the `python` plugin creates/primes these directories: + # - lib, src + # (https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/parts/plugins/_python.py#L79-L81) + # - venv + # (https://github.com/canonical/charmcraft/blob/9ff19c328e23b50cc06f04e8a5ad4835740badf4/charmcraft/parts/plugins/_python.py#L100 + # https://github.com/canonical/craft-parts/blob/afb0d652eb330b6aaad4f40fbd6e5357d358de47/craft_parts/plugins/base.py#L270) + plugin: python + source: . + after: + - python-deps + python-requirements: [requirements.txt] + build-packages: + - libffi-dev # Needed to build Python dependencies with Rust from source + - libssl-dev # Needed to build Python dependencies with Rust from source + - pkg-config # Needed to build Python dependencies with Rust from source override-build: | - rustup default stable + # Workaround for https://github.com/canonical/charmcraft/issues/2068 + # rustup used to install rustc and cargo, which are needed to build Python dependencies with Rust from source + if [[ "$CRAFT_PLATFORM" == ubuntu@20.04:* || "$CRAFT_PLATFORM" == ubuntu@22.04:* ]] + then + snap install rustup --classic + else + apt-get install rustup -y + fi + + # If Ubuntu version < 24.04, rustup was installed from snap instead of from the Ubuntu + # archive—which means the rustup version could be updated at any time. Print rustup version + # to build log to make changes to the snap's rustup version easier to track + rustup --version + + # rpds-py (Python package) >=0.19.0 requires rustc >=1.76, which is not available in the + # Ubuntu 22.04 archive. Install rustc and cargo using rustup instead of the Ubuntu archive + rustup set profile minimal + rustup default 1.83.0 # renovate: charmcraft-rust-latest craftctl default + # Include requirements.txt in *.charm artifact for easier debugging + cp requirements.txt "$CRAFT_PART_INSTALL/requirements.txt" + # "files" part name is arbitrary; use for consistency + files: + plugin: dump + source: . + stage: + - LICENSE diff --git a/charms/tensorboards-web-app/tests/integration/conftest.py b/charms/tensorboards-web-app/tests/integration/conftest.py new file mode 100644 index 00000000..f4eda080 --- /dev/null +++ b/charms/tensorboards-web-app/tests/integration/conftest.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +from _pytest.config.argparsing import Parser + + +def pytest_addoption(parser: Parser): + parser.addoption( + "--charm-path", + help="Path to charm file for performing tests on.", + ) diff --git a/charms/tensorboards-web-app/tests/integration/test_charm.py b/charms/tensorboards-web-app/tests/integration/test_charm.py index 94949201..9a27fc68 100644 --- a/charms/tensorboards-web-app/tests/integration/test_charm.py +++ b/charms/tensorboards-web-app/tests/integration/test_charm.py @@ -21,15 +21,23 @@ @pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest): +async def test_build_and_deploy(ops_test: OpsTest, request): """Build and deploy the charm under test. Assert on the unit status before ingress relation is set up. """ - charm = await ops_test.build_charm(".") + # Build and deploy charm from local source folder or use + # a charm artefact passed using --charm-path + entity_url = ( + await ops_test.build_charm(".") + if not (entity_url := request.config.getoption("--charm-path")) + else entity_url + ) image_path = METADATA["resources"][f"{APP_NAME}-image"]["upstream-source"] resources = {f"{APP_NAME}-image": image_path} - await ops_test.model.deploy(charm, resources=resources, application_name=APP_NAME, trust=True) + await ops_test.model.deploy( + entity_url=entity_url, resources=resources, application_name=APP_NAME, trust=True + ) await ops_test.model.wait_for_idle(apps=[APP_NAME], status="blocked", timeout=60 * 5) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 00000000..0414f539 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# Copyright 2025 Canonical Ltd. +# See LICENSE file for licensing details. + +from _pytest.config.argparsing import Parser + + +def pytest_addoption(parser: Parser): + parser.addoption( + "--charms-path", + help="Path to directory where charm files are stored.", + ) diff --git a/tests/integration/test_charms.py b/tests/integration/test_charms.py index d78faf28..38104541 100644 --- a/tests/integration/test_charms.py +++ b/tests/integration/test_charms.py @@ -12,9 +12,15 @@ @pytest.mark.abort_on_fail -async def test_build_and_deploy_with_relations(ops_test: OpsTest): - tensorboard_controller = await ops_test.build_charm("charms/tensorboard-controller") - tensorboards_web_app = await ops_test.build_charm("charms/tensorboards-web-app") +async def test_build_and_deploy_with_relations(ops_test: OpsTest, request): + tc_app_name = TC_METADATA["name"] + twa_name = TWA_METADATA["name"] + if charms_path := request.config.getoption("--charms-path"): + tensorboard_controller = f"{charms_path}/{tc_app_name}/{tc_app_name}_ubuntu@20.04-amd64.charm" + tensorboards_web_app = f"{charms_path}/{twa_name}/{twa_name}_ubuntu@20.04-amd64.charm" + else: + tensorboard_controller = await ops_test.build_charm("charms/tensorboard-controller") + tensorboards_web_app = await ops_test.build_charm("charms/tensorboards-web-app") image_path = TC_METADATA["resources"]["tensorboard-controller-image"]["upstream-source"] resources = {"tensorboard-controller-image": image_path} @@ -27,8 +33,6 @@ async def test_build_and_deploy_with_relations(ops_test: OpsTest): istio_gateway = "istio-ingressgateway" istio_pilot = "istio-pilot" - tc_app_name = TC_METADATA["name"] - twa_name = TWA_METADATA["name"] await ops_test.model.deploy( entity_url="istio-gateway",