├── .github ├── .jira_sync_config.yaml ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug.yaml │ └── task.yaml ├── cluster.yaml ├── dependencies.yaml └── workflows │ ├── delete-aws-volumes.yaml │ ├── deploy-to-aks.yaml │ ├── deploy-to-eks.yaml │ ├── full-bundle-tests.yaml │ ├── get-images-and-scan.yaml │ ├── release-bundle-to-charmhub.yaml │ ├── run-tests-and-publish-bundle.yaml │ └── scan-images.yaml ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md ├── SECURITY.md ├── _conftest.py ├── charmcraft.yaml ├── docs ├── db-migration-guide.ipynb ├── db-migration-guide.md ├── examples │ ├── mnist.yaml │ └── object_detection.yaml ├── upgrade-guide-1.6-to-1.7.ipynb └── upgrade-guide-1.6-to-1.7.md ├── gcloud-publish-file.sh ├── overlays ├── ck-aws.yml ├── ck-gpu.yml └── ck.yml ├── pipeline-samples ├── README.md ├── condition.py ├── exit_handler.py ├── parallel_join.py ├── sequential.py └── xgboost_training_cm.py ├── poetry.lock ├── pyproject.toml ├── pytest.ini ├── releases ├── 1.10 │ ├── candidate │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml │ └── stable │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml ├── 1.4 │ ├── edge │ │ ├── kubeflow-lite │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ │ └── kubeflow │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ └── stable │ │ ├── kubeflow-lite │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml │ │ └── kubeflow │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml ├── 1.6 │ ├── beta │ │ ├── kubeflow-lite │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ │ └── kubeflow │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ ├── edge │ │ ├── kubeflow-lite │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ │ └── kubeflow │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ └── stable │ │ ├── kubeflow-lite │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml │ │ └── kubeflow │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml ├── 1.7 │ ├── beta │ │ └── kubeflow │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ ├── edge │ │ └── kubeflow │ │ │ ├── README.md │ │ │ ├── airgap │ │ │ ├── bundle-airgap.yaml │ │ │ └── podspec_script.sh │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ └── stable │ │ └── kubeflow │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml ├── 1.8 │ ├── beta │ │ └── kubeflow │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ ├── edge │ │ └── kubeflow │ │ │ ├── README.md │ │ │ ├── bundle.yaml │ │ │ └── charmcraft.yaml │ └── stable │ │ └── kubeflow │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml ├── 1.9 │ ├── edge │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml │ └── stable │ │ ├── README.md │ │ ├── bundle.yaml │ │ └── charmcraft.yaml └── latest │ ├── beta │ ├── README.md │ ├── bundle.yaml │ └── charmcraft.yaml │ └── edge │ ├── README.md │ ├── airgap │ ├── bundle-airgap.yaml │ └── podspec_script.sh │ ├── bundle.yaml │ └── charmcraft.yaml ├── requirements.txt ├── scripts ├── README.md ├── airgapped │ ├── README.md │ ├── __init__.py │ ├── deploy-1.8.sh │ ├── deploy-1.9.sh │ ├── images │ │ └── overview.png │ ├── prerequisites.sh │ ├── push-images-to-registry.py │ ├── requirements.txt │ ├── retag-images-to-cache.py │ ├── save-charms-to-tar.py │ ├── save-images-to-cache.py │ ├── save-images-to-tar.py │ └── utils.py ├── cli.py ├── delete_volumes.py ├── get_all_images.py ├── get_bundle_images_sbom.py ├── get_bundle_path.py ├── get_bundle_test_path.py ├── get_release_from_bundle_source.py ├── get_releases_affected.py ├── gh-actions │ ├── parse_versions.py │ └── set_eks_sysctl_config.sh └── requirements.txt ├── test-reference ├── README.md └── versions.json ├── test-requirements.txt ├── tests-bundle ├── 1.6 │ ├── __init__.py │ ├── helpers.py │ ├── requirements.txt │ └── test_1dot6.py ├── 1.7 │ ├── __init__.py │ ├── advanced_notebook.py.tmpl │ ├── conftest.py │ ├── helpers.py │ ├── requirements.txt │ ├── test_release_1-7.py │ └── test_tutorial.py ├── 1.8 │ ├── __init__.py │ ├── advanced_notebook.py.tmpl │ ├── conftest.py │ ├── helpers.py │ ├── requirements.txt │ ├── test_release_1-8.py │ └── test_tutorial.py ├── README.md ├── __init__.py └── conftest.py ├── tests ├── Dockerfile.mnist ├── Dockerfile.object_detection ├── __init__.py ├── airgapped │ ├── 1.8 │ │ ├── katib │ │ │ ├── README.md │ │ │ └── simple-pbt.yaml │ │ ├── knative │ │ │ ├── README.md │ │ │ └── helloworld.yaml │ │ ├── pipelines │ │ │ ├── README.md │ │ │ ├── kfp-airgapped.ipynb │ │ │ └── pipelines-runner │ │ │ │ ├── Dockerfile │ │ │ │ └── README.md │ │ ├── testing-images.txt │ │ └── training │ │ │ ├── README.md │ │ │ └── tfjob-simple.yaml │ ├── 1.9 │ │ ├── katib │ │ │ ├── README.md │ │ │ └── simple-pbt.yaml │ │ ├── knative │ │ │ ├── README.md │ │ │ └── helloworld.yaml │ │ ├── pipelines │ │ │ ├── README.md │ │ │ ├── kfp-airgapped.ipynb │ │ │ └── pipelines-runner │ │ │ │ ├── Dockerfile │ │ │ │ └── README.md │ │ ├── testing-images.txt │ │ └── training │ │ │ ├── README.md │ │ │ └── tfjob-simple.yaml │ ├── README.md │ ├── airgap.sh │ ├── ckf.sh │ ├── lxc │ │ ├── install-deps │ │ │ ├── ubuntu_20.04 │ │ │ └── ubuntu_22.04 │ │ └── microk8s.profile │ ├── lxd.profile │ ├── registry.sh │ ├── setup │ │ ├── lxd-docker-networking.sh │ │ ├── prerequisites.sh │ │ └── setup.sh │ └── utils.sh ├── dump-pipeline-logs.sh ├── dump-stdout.sh ├── integration │ └── test_bundle_deployment.py ├── kf_authentication.py ├── pipelines │ ├── artifacts │ │ └── pet.jpg │ ├── common.py │ ├── cowsay.py │ ├── jupyter.py │ ├── katib.py │ ├── mnist.py │ └── object_detection.py ├── profile_template.yaml ├── test_kubectl.py ├── test_pipelines.py └── test_selenium.py └── tox.ini /.github/.jira_sync_config.yaml: -------------------------------------------------------------------------------- 1 | settings: 2 | # Jira project key to create the issue in 3 | jira_project_key: "KF" 4 | 5 | # Dictionary mapping GitHub issue status to Jira issue status 6 | status_mapping: 7 | opened: Untriaged 8 | closed: done 9 | 10 | # (Optional) GitHub labels. Only issues with one of those labels will be synchronized. 11 | # If not specified, all issues will be synchronized 12 | labels: 13 | - bug 14 | - enhancement 15 | 16 | # (Optional) (Default: false) Add a new comment in GitHub with a link to Jira created issue 17 | add_gh_comment: true 18 | 19 | # (Optional) (Default: true) Synchronize issue description from GitHub to Jira 20 | sync_description: true 21 | 22 | # (Optional) (Default: true) Synchronize comments from GitHub to Jira 23 | sync_comments: false 24 | 25 | # (Optional) (Default: None) Parent Epic key to link the issue to 26 | epic_key: "KF-4805" 27 | 28 | # (Optional) Dictionary mapping GitHub issue labels to Jira issue types. 29 | # If label on the issue is not in specified list, this issue will be created as a Bug 30 | label_mapping: 31 | enhancement: Story 32 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | ** @canonical/kubeflow 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: > 8 | Thanks for taking the time to fill out this bug report! Before submitting your issue, please make 9 | sure you are using the latest version of the charms. If not, please switch to the newest revision prior to 10 | posting your report to make sure it's not already solved. 11 | - type: textarea 12 | id: bug-description 13 | attributes: 14 | label: Bug Description 15 | description: > 16 | If applicable, add screenshots to help explain your problem. If applicable, add screenshots to 17 | help explain the problem you are facing. 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: reproduction 22 | attributes: 23 | label: To Reproduce 24 | description: > 25 | Please provide a step-by-step instruction of how to reproduce the behavior. 26 | placeholder: | 27 | 1. `juju deploy ...` 28 | 2. `juju relate ...` 29 | 3. `juju status --relations` 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: environment 34 | attributes: 35 | label: Environment 36 | description: > 37 | We need to know a bit more about the context in which you run the charm. 38 | - Are you running Juju locally, on lxd, in multipass or on some other platform? 39 | - What track and channel you deployed the charm from (ie. `latest/edge` or similar). 40 | - Version of any applicable components, like the juju snap, the model controller, lxd, microk8s, and/or multipass. 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: logs 45 | attributes: 46 | label: Relevant Log Output 47 | description: > 48 | Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 49 | Fetch the logs using `juju debug-log --replay` and `kubectl logs ...`. Additional details available in the juju docs 50 | at https://juju.is/docs/olm/juju-logs 51 | render: shell 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: additional-context 56 | attributes: 57 | label: Additional Context 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.yaml: -------------------------------------------------------------------------------- 1 | name: Task 2 | description: File an enhancement proposal 3 | labels: "enhancement" 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: > 8 | Thanks for taking the time to fill out this enhancement 9 | proposal! Before submitting your issue, please make sure there 10 | isn't already a prior issue concerning this. If there is, 11 | please join that discussion instead. 12 | - type: textarea 13 | id: enhancement-proposal-context 14 | attributes: 15 | label: Context 16 | description: > 17 | Describe why we should work on this task/enhancement, as well as 18 | existing context we should be aware of 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: enhancement-proposal-what 23 | attributes: 24 | label: What needs to get done 25 | description: > 26 | Describe what needs to get done 27 | placeholder: | 28 | 1. Look into X 29 | 2. Implement Y 30 | 3. Create file Z 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: enhancement-proposal-dod 35 | attributes: 36 | label: Definition of Done 37 | description: > 38 | What are the requirements for the task to be considered done 39 | placeholder: | 40 | 1. We know how X works (spike) 41 | 2. Code is doing Y 42 | 3. Charm has functionality Z 43 | validations: 44 | required: true 45 | -------------------------------------------------------------------------------- /.github/cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eksctl.io/v1alpha5 2 | availabilityZones: 3 | - eu-central-1a 4 | - eu-central-1b 5 | cloudWatch: 6 | clusterLogging: {} 7 | iam: 8 | vpcResourceControllerPolicy: true 9 | withOIDC: false 10 | addons: 11 | - name: aws-ebs-csi-driver 12 | serviceAccountRoleARN: "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" 13 | kind: ClusterConfig 14 | kubernetesNetworkConfig: 15 | ipFamily: IPv4 16 | managedNodeGroups: 17 | - amiFamily: Ubuntu2204 18 | iam: 19 | withAddonPolicies: 20 | ebs: true 21 | instanceType: t2.2xlarge 22 | labels: 23 | alpha.eksctl.io/cluster-name: scrumptious-wardrobe-1684842095 24 | alpha.eksctl.io/nodegroup-name: ng-d06bd84e 25 | maxSize: 2 26 | minSize: 2 27 | name: ng-d06bd84e 28 | releaseVersion: "" 29 | ssh: 30 | allow: true 31 | tags: 32 | alpha.eksctl.io/nodegroup-name: ng-d06bd84e 33 | alpha.eksctl.io/nodegroup-type: managed 34 | volumeSize: 100 35 | metadata: 36 | name: kubeflow-test 37 | region: eu-central-1 38 | version: "1.26" 39 | -------------------------------------------------------------------------------- /.github/dependencies.yaml: -------------------------------------------------------------------------------- 1 | "1.8": 2 | K8S_VERSION: "1.29" 3 | JUJU_VERSION: "3.4" 4 | UATS_BRANCH: "track/1.8" 5 | "1.9": 6 | K8S_VERSION: "1.29" 7 | JUJU_VERSION: "3.6" 8 | UATS_BRANCH: "track/1.9" 9 | latest: 10 | K8S_VERSION: "1.31" 11 | JUJU_VERSION: "3.6" 12 | UATS_BRANCH: "main" 13 | -------------------------------------------------------------------------------- /.github/workflows/delete-aws-volumes.yaml: -------------------------------------------------------------------------------- 1 | name: Delete unattached (available) EBS volumes in all AWS regions 2 | on: 3 | workflow_dispatch: # This event allows manual triggering from the Github UI 4 | secrets: 5 | BUNDLE_KUBEFLOW_EKS_AWS_ACCESS_KEY_ID: 6 | required: true 7 | BUNDLE_KUBEFLOW_EKS_AWS_SECRET_ACCESS_KEY: 8 | required: true 9 | inputs: 10 | region: 11 | description: 'Insert the AWS Region name in which the script will delete unattached volumes. Running it with an empty region means that it will delete unattached volumes in the ALL available regions.' 12 | required: false 13 | default: '' 14 | workflow_call: 15 | secrets: 16 | BUNDLE_KUBEFLOW_EKS_AWS_ACCESS_KEY_ID: 17 | required: true 18 | BUNDLE_KUBEFLOW_EKS_AWS_SECRET_ACCESS_KEY: 19 | required: true 20 | inputs: 21 | region: 22 | description: 'Insert the AWS Region name in which the script will delete unattached volumes. Running it with an empty region means that it will delete unattached volumes in the ALL available regions.' 23 | required: false 24 | default: '' 25 | type: string 26 | 27 | jobs: 28 | delete-volumes: 29 | runs-on: ubuntu-24.04 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | 35 | - name: Configure AWS Credentials 36 | env: 37 | AWS_ACCESS_KEY_ID: ${{ secrets.BUNDLE_KUBEFLOW_EKS_AWS_ACCESS_KEY_ID }} 38 | AWS_SECRET_ACCESS_KEY: ${{ secrets.BUNDLE_KUBEFLOW_EKS_AWS_SECRET_ACCESS_KEY }} 39 | run: | 40 | aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID 41 | aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY 42 | aws configure set default.region eu-central-1 43 | 44 | - name: Install requirements 45 | run: | 46 | python -m pip install --upgrade pip 47 | pip install boto3 48 | pip install tenacity 49 | 50 | - name: Run delete volumes script 51 | run: python scripts/delete_volumes.py ${{ inputs.region }} 52 | -------------------------------------------------------------------------------- /.github/workflows/release-bundle-to-charmhub.yaml: -------------------------------------------------------------------------------- 1 | name: Publish bundle to Charmhub 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - releases/** 8 | 9 | jobs: 10 | 11 | get-releases-affected: 12 | name: Get releases affected 13 | runs-on: ubuntu-24.04 14 | outputs: 15 | releases_affected: ${{ steps.get-releases-affected.outputs.releases_affected_json }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | # FIXME: Skip due to security vulnerability in https://github.com/tj-actions/changed-files/issues/2463 22 | # - name: Get files changed 23 | # id: changed-files 24 | # uses: tj-actions/changed-files@v37 25 | 26 | # - name: Get releases affected 27 | # id: get-releases-affected 28 | # run: python scripts/get_releases_affected.py ${{ steps.changed-files.outputs.all_changed_files }} 29 | 30 | # run-tests-and-publish-bundle-for-releases-affected: 31 | # name: Run bundle tests and publish to Charmhub 32 | # needs: [get-releases-affected] 33 | # strategy: 34 | # fail-fast: false 35 | # matrix: 36 | # release: ${{ fromJson(needs.get-releases-affected.outputs.releases_affected) }} 37 | # uses: ./.github/workflows/run-tests-and-publish-bundle.yaml 38 | # with: 39 | # release: ${{ matrix.release }} 40 | # secrets: inherit 41 | -------------------------------------------------------------------------------- /.github/workflows/run-tests-and-publish-bundle.yaml: -------------------------------------------------------------------------------- 1 | name: Run bundle tests and publish to Charmhub 2 | on: 3 | workflow_call: 4 | inputs: 5 | release: 6 | description: Bundle release to run tests on and publish to Charmhub 7 | type: string 8 | required: true 9 | secrets: 10 | CHARMCRAFT_CREDENTIALS: 11 | required: true 12 | workflow_dispatch: 13 | inputs: 14 | release: 15 | description: Bundle release to run tests on and publish to Charmhub 16 | type: string 17 | required: true 18 | secrets: 19 | CHARMCRAFT_CREDENTIALS: 20 | required: true 21 | 22 | jobs: 23 | get-release-inputs: 24 | name: Get required inputs 25 | runs-on: ubuntu-24.04 26 | outputs: 27 | bundle_path: ${{ steps.bundle-path.outputs.bundle_path }} 28 | bundle_test_path: ${{ steps.bundle-test-path.outputs.bundle_test_path }} 29 | steps: 30 | - uses: actions/checkout@v3 31 | 32 | - name: Get bundle path for ${{ inputs.release }} 33 | id: bundle-path 34 | run: python scripts/get_bundle_path.py ${{ inputs.release }} 35 | 36 | - name: Get bundle test path for ${{ inputs.release }} 37 | id: bundle-test-path 38 | run: python scripts/get_bundle_test_path.py ${{ inputs.release }} 39 | 40 | 41 | # Commenting out since currently the tests are failing because of 42 | # https://github.com/canonical/oidc-gatekeeper-operator/issues/112 43 | #run-tests: 44 | #name: Run tests 45 | #needs: [get-release-inputs] 46 | #uses: ./.github/workflows/full-bundle-tests.yaml 47 | #with: 48 | #bundle-test-path: ${{ needs.get-release-inputs.outputs.bundle_test_path }} 49 | #bundle-source: --file ${{ needs.get-release-inputs.outputs.bundle_path }}/bundle.yaml 50 | 51 | publish-bundle-for-releases-affected: 52 | name: Publish bundle 53 | runs-on: ubuntu-24.04 54 | # we'll need to add back `run-tests` in the `needs` part, once we bring it back 55 | needs: [get-release-inputs] 56 | steps: 57 | - uses: actions/checkout@v3 58 | 59 | - name: Publish bundle release ${{ inputs.release }} 60 | uses: canonical/charming-actions/upload-bundle@2.6.3 61 | with: 62 | credentials: ${{ secrets.CHARMCRAFT_CREDENTIALS }} 63 | github-token: ${{ secrets.GITHUB_TOKEN }} 64 | bundle-path: ${{ needs.get-release-inputs.outputs.bundle_path }} 65 | channel: ${{ inputs.release }} 66 | -------------------------------------------------------------------------------- /.github/workflows/scan-images.yaml: -------------------------------------------------------------------------------- 1 | name: Scan images 2 | on: 3 | schedule: 4 | # every day at 1:12AM UTC 5 | - cron: '12 1 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | scan-images: 10 | name: Perform vulnerability scans 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | # specfy location of bundle(s) to be scanned 15 | bundle-path: 16 | - 1.8/stable/kubeflow 17 | - 1.9/stable 18 | - latest/edge 19 | uses: ./.github/workflows/get-images-and-scan.yaml 20 | with: 21 | bundle-directory: ${{ matrix.bundle-path }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__/ 3 | *.charm 4 | build/ 5 | .idea 6 | sel-screenshots/* 7 | geckodriver.log 8 | .vscode/* 9 | *.tar.gz 10 | 11 | # vim 12 | [._]*.s[a-v][a-z] 13 | !*.svg # comment out if you don't need vector files 14 | [._]*.sw[a-p] 15 | [._]s[a-rt-v][a-z] 16 | [._]ss[a-gi-z] 17 | [._]sw[a-p] 18 | 19 | # airgapped 20 | images.txt 21 | charms.txt 22 | retagged-images.txt 23 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @canonical/kubeflow 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | [![CharmHub Badge](https://charmhub.io/kubeflow/badge.svg)](https://charmhub.io/kubeflow) 3 | [![Release](https://github.com/canonical/bundle-kubeflow/actions/workflows/release-bundle-to-charmhub.yaml/badge.svg)](https://github.com/canonical/bundle-kubeflow/actions/workflows/release-bundle-to-charmhub.yaml) 4 | [![Scan Images](https://github.com/canonical/bundle-kubeflow/actions/workflows/scan-images.yaml/badge.svg)](https://github.com/canonical/bundle-kubeflow/actions/workflows/scan-images.yaml) 5 | [![EKS Tests](https://github.com/canonical/bundle-kubeflow/actions/workflows/deploy-to-eks.yaml/badge.svg)](https://github.com/canonical/bundle-kubeflow/actions/workflows/deploy-to-eks.yaml) 6 | [![AKS Tests](https://github.com/canonical/bundle-kubeflow/actions/workflows/deploy-to-aks.yaml/badge.svg)](https://github.com/canonical/bundle-kubeflow/actions/workflows/deploy-to-aks.yaml) 7 | 8 | ## Introduction 9 | 10 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 11 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 12 | on-prem, to public cloud and edge. 13 | 14 | A charm is a software package that includes an operator together with metadata that supports the 15 | integration of many operators in a coherent aggregated system. 16 | 17 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 18 | of Kubeflow. 19 | 20 | Visit [charmed-kubeflow.io][charmedkf] for more information. 21 | 22 | ## Install 23 | 24 | For any Kubernetes, follow the [installation instructions][install]. 25 | 26 | ## Testing 27 | 28 | To deploy this bundle and run tests locally, do the following: 29 | 30 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 31 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 32 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 33 | context. Do not create a namespace with the same name as the username, this will cause pipelines 34 | tests to fail. Beware of using `admin` as the dex-auth static-username as the tests attempt to 35 | create a profile and `admin` conflicts with an existing default profile. 36 | 37 | 1. Install test prerequisites: 38 | 39 | ```bash 40 | sudo snap install juju-wait --classic 41 | sudo snap install juju-kubectl --classic 42 | sudo snap install charmcraft --classic 43 | sudo apt update 44 | sudo apt install -y libssl-dev firefox-geckodriver 45 | sudo pip3 install tox 46 | sudo pip3 install -r requirements.txt 47 | ``` 48 | 49 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 50 | username and password you set in step (1) through environment variable or argument, for example: 51 | 52 | - full bundle (using command line arguments): 53 | ``` 54 | tox -e tests -- -m full --username user123@email.com --password user123 55 | ``` 56 | - lite bundle (using environment variables): 57 | ``` 58 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 59 | export KUBEFLOW_AUTH_PASSWORD=user1234 60 | tox -e tests -- -m lite 61 | ``` 62 | 63 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 64 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 65 | the selenium tests). 66 | 67 | ## Documentation 68 | 69 | Read the [official documentation][docs]. 70 | 71 | [charmedkf]: https://charmed-kubeflow.io/ 72 | [docs]: https://charmed-kubeflow.io/docs/ 73 | [install]: https://charmed-kubeflow.io/docs/install 74 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | ## Supported Versions 4 | 5 | The Charmed Kubeflow project releases with a cadence of ~6 months, supports the latest two minor versions of Kubeflow, and keeps up to date with the upstream project. Whenever a new version of Kubeflow is released, a new version of Charmed Kubeflow is also released, and the oldest version is dropped from support. Please also to [Supported versions](https://charmed-kubeflow.io/docs/supported-versions) for details on the actual versions. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | To report a security issue, file a [Private Security Report](https://github.com/canonical/bundle-kubeflow/security/advisories/new) with a description of the issue, the steps you took that led to the issue, affected versions, and, if known, mitigations for the issue. 10 | The [Ubuntu Security disclosure and embargo policy](https://ubuntu.com/security/disclosure-policy) contains more information about what you can expect when you contact us and what we expect from you. 11 | -------------------------------------------------------------------------------- /_conftest.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | 5 | # Use a custom parser that lets us require a variable from one of CLI or environment variable, 6 | # this way we can pass creds through CLI for local testing but via environment variables in CI 7 | class EnvDefault(argparse.Action): 8 | """Argument parser that accepts input from CLI (preferred) or an environment variable 9 | 10 | If a value is not specified in the CLI argument, the content of the environment variable 11 | named `envvar` is used. If this environment variable is also empty, the parser will fail 12 | citing a missing required argument 13 | 14 | Note this Action does not accept the `required` and `default` kwargs and will set them itself 15 | as appropriate. 16 | 17 | Modified from https://stackoverflow.com/a/10551190/5394584 18 | """ 19 | def __init__(self, option_strings, dest, envvar, **kwargs): 20 | # Determine the values for `required` and `default` based on whether defaults are available 21 | # from an environment variable 22 | required = False 23 | if not kwargs["default"]: 24 | if envvar: 25 | if envvar in os.environ: 26 | # An environment variable of this name exists, use that as a default 27 | kwargs["default"] = os.environ[envvar] 28 | else: 29 | # We have no default, require a value from the CLI 30 | required = True 31 | kwargs["default"] = None 32 | else: 33 | raise ValueError(f"EnvDefault requires non-null envvar, got '{envvar}'") 34 | self.envvar = envvar 35 | 36 | super(EnvDefault, self).__init__(option_strings, dest, required=required, **kwargs) 37 | 38 | def __call__(self, parser, namespace, values, option_string): 39 | # Actually set values to the destination arg in the namespace 40 | setattr(namespace, self.dest, values) 41 | 42 | 43 | def pytest_addoption(parser): 44 | parser.addoption("--proxy", action="store", help="Proxy to use") 45 | parser.addoption("--url", action="store", help="Kubeflow dashboard URL") 46 | parser.addoption("--headful", action="store_true", help="Juju model") 47 | 48 | username_envvar = "KUBEFLOW_AUTH_USERNAME" 49 | parser.addoption( 50 | "--username", 51 | action=EnvDefault, 52 | envvar=username_envvar, 53 | default="admin", 54 | help=f"Dex username (email address). Required, but can be passed either through CLI or " 55 | f"via environment variable '{username_envvar}", 56 | ) 57 | 58 | password_envvar = "KUBEFLOW_AUTH_PASSWORD" 59 | parser.addoption( 60 | "--password", 61 | action=EnvDefault, 62 | envvar=password_envvar, 63 | default="admin", 64 | help=f"Dex password. Required, but can be passed either through CLI or " 65 | f"via environment variable '{password_envvar}" 66 | ) 67 | -------------------------------------------------------------------------------- /charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /gcloud-publish-file.sh: -------------------------------------------------------------------------------- 1 | set -eu 2 | 3 | # This script is responsible for the following: 4 | # 1. Upload a file $ARTIFACT to a $GS_URL 5 | # 2. Creates a service account key from $GCLOUD_SA 6 | # 3. Creates a signed URL for the updoaded file with the SA key created 7 | # from the previous step 8 | # 9 | # The script expects that the user has 10 | # 1. logged in to the gcloud CLI 11 | # 2. selected in gcloud the project they want to use 12 | # 3. created a service account key, for pushing to bucket 13 | # 14 | # Some helper commands for the above are the following: 15 | # 16 | # gcloud auth login --no-launch-browser 17 | # gcloud projects list 18 | # gcloud config set project PROJECT_ID 19 | # gcloud iam service-accounts keys create \ 20 | # --iam-account=ckf-artifacts-storage-sa@thermal-creek-391110.iam.gserviceaccount.com 21 | # signing-sa-key.json \ 22 | # 23 | # For more information you can take a look on the following links 24 | # https://cloud.google.com/iam/docs/keys-create-delete#iam-service-account-keys-create-gcloud 25 | # https://cloud.google.com/storage/docs/access-control/signing-urls-with-helpers 26 | 27 | echo $FILE 28 | echo $GS_URL 29 | echo $GCLOUD_SA_KEY 30 | 31 | FILE_URL=$GS_URL/$(basename $FILE) 32 | 33 | echo "Copying \"$FILE\" to \"$GS_URL\"" 34 | gcloud storage cp -r $FILE $FILE_URL 35 | echo "Successfully uploaded!" 36 | 37 | echo "Creating signed url" 38 | gcloud storage sign-url \ 39 | --private-key-file=$GCLOUD_SA_KEY \ 40 | --duration=7d \ 41 | $FILE_URL 42 | -------------------------------------------------------------------------------- /overlays/ck-aws.yml: -------------------------------------------------------------------------------- 1 | description: Charmed Kubernetes overlay to add native AWS support. 2 | applications: 3 | aws-integrator: 4 | annotations: 5 | gui-x: "600" 6 | gui-y: "300" 7 | charm: cs:~containers/aws-integrator 8 | num_units: 1 9 | trust: true 10 | relations: 11 | - [aws-integrator, kubernetes-master] 12 | - [aws-integrator, kubernetes-worker] 13 | -------------------------------------------------------------------------------- /overlays/ck-gpu.yml: -------------------------------------------------------------------------------- 1 | applications: 2 | kubernetes-worker: 3 | to: ["0"] 4 | machines: 5 | 0: 6 | constraints: root-disk=100G instance-type=p2.xlarge 7 | -------------------------------------------------------------------------------- /overlays/ck.yml: -------------------------------------------------------------------------------- 1 | # Overlay for Charmed Kubernetes model 2 | applications: 3 | kubernetes-worker: 4 | constraints: cores=4 mem=4G root-disk=100G 5 | -------------------------------------------------------------------------------- /pipeline-samples/README.md: -------------------------------------------------------------------------------- 1 | Contains unbuilt pipeline samples. To build the samples, install `kfp` from 2 | pypi, then run this command: 3 | 4 | find pipeline-samples/ -name "*.py" -exec bash -c 'dsl-compile --py {} --output charms/pipelines-api/files/$(basename {} .py).yaml' \; 5 | 6 | Then, update these files as necessary: 7 | 8 | charms/pipelines-api/files/sample_config.json 9 | charms/pipelines-api/reactive/pipelines_api.py 10 | -------------------------------------------------------------------------------- /pipeline-samples/condition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2019 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import kfp 17 | from kfp import dsl 18 | 19 | 20 | def random_num_op(low, high): 21 | """Generate a random number between low and high.""" 22 | return dsl.ContainerOp( 23 | name='Generate random number', 24 | image='python:alpine3.6', 25 | command=['sh', '-c'], 26 | arguments=['python -c "import random; print(random.randint($0, $1))" | tee $2', str(low), str(high), '/tmp/output'], 27 | file_outputs={'output': '/tmp/output'} 28 | ) 29 | 30 | 31 | def flip_coin_op(): 32 | """Flip a coin and output heads or tails randomly.""" 33 | return dsl.ContainerOp( 34 | name='Flip coin', 35 | image='python:alpine3.6', 36 | command=['sh', '-c'], 37 | arguments=['python -c "import random; result = \'heads\' if random.randint(0,1) == 0 ' 38 | 'else \'tails\'; print(result)" | tee /tmp/output'], 39 | file_outputs={'output': '/tmp/output'} 40 | ) 41 | 42 | 43 | def print_op(msg): 44 | """Print a message.""" 45 | return dsl.ContainerOp( 46 | name='Print', 47 | image='alpine:3.6', 48 | command=['echo', msg], 49 | ) 50 | 51 | 52 | @dsl.pipeline( 53 | name='Conditional execution pipeline', 54 | description='Shows how to use dsl.Condition().' 55 | ) 56 | def flipcoin_pipeline(): 57 | flip = flip_coin_op() 58 | with dsl.Condition(flip.output == 'heads'): 59 | random_num_head = random_num_op(0, 9) 60 | with dsl.Condition(random_num_head.output > 5): 61 | print_op('heads and %s > 5!' % random_num_head.output) 62 | with dsl.Condition(random_num_head.output <= 5): 63 | print_op('heads and %s <= 5!' % random_num_head.output) 64 | 65 | with dsl.Condition(flip.output == 'tails'): 66 | random_num_tail = random_num_op(10, 19) 67 | with dsl.Condition(random_num_tail.output > 15): 68 | print_op('tails and %s > 15!' % random_num_tail.output) 69 | with dsl.Condition(random_num_tail.output <= 15): 70 | print_op('tails and %s <= 15!' % random_num_tail.output) 71 | 72 | 73 | if __name__ == '__main__': 74 | kfp.compiler.Compiler().compile(flipcoin_pipeline, __file__ + '.zip') 75 | -------------------------------------------------------------------------------- /pipeline-samples/exit_handler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2019 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | import kfp 18 | from kfp import dsl 19 | 20 | 21 | def gcs_download_op(url): 22 | return dsl.ContainerOp( 23 | name='GCS - Download', 24 | image='google/cloud-sdk:216.0.0', 25 | command=['sh', '-c'], 26 | arguments=['gsutil cat $0 | tee $1', url, '/tmp/results.txt'], 27 | file_outputs={ 28 | 'data': '/tmp/results.txt', 29 | } 30 | ) 31 | 32 | 33 | def echo_op(text, is_exit_handler=False): 34 | return dsl.ContainerOp( 35 | name='echo', 36 | image='library/bash:4.4.23', 37 | command=['sh', '-c'], 38 | arguments=['echo "$0"', text], 39 | ) 40 | 41 | 42 | @dsl.pipeline( 43 | name='Exit Handler', 44 | description='Downloads a message and prints it. The exit handler will run after the pipeline finishes (successfully or not).' 45 | ) 46 | def download_and_print(url='gs://ml-pipeline-playground/shakespeare1.txt'): 47 | """A sample pipeline showing exit handler.""" 48 | 49 | exit_task = echo_op('exit!') 50 | 51 | with dsl.ExitHandler(exit_task): 52 | download_task = gcs_download_op(url) 53 | echo_task = echo_op(download_task.output) 54 | 55 | 56 | if __name__ == '__main__': 57 | kfp.compiler.Compiler().compile(download_and_print, __file__ + '.zip') 58 | -------------------------------------------------------------------------------- /pipeline-samples/parallel_join.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2019 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | import kfp 18 | from kfp import dsl 19 | 20 | def gcs_download_op(url): 21 | return dsl.ContainerOp( 22 | name='GCS - Download', 23 | image='google/cloud-sdk:216.0.0', 24 | command=['sh', '-c'], 25 | arguments=['gsutil cat $0 | tee $1', url, '/tmp/results.txt'], 26 | file_outputs={ 27 | 'data': '/tmp/results.txt', 28 | } 29 | ) 30 | 31 | 32 | def echo2_op(text1, text2): 33 | return dsl.ContainerOp( 34 | name='echo', 35 | image='library/bash:4.4.23', 36 | command=['sh', '-c'], 37 | arguments=['echo "Text 1: $0"; echo "Text 2: $1"', text1, text2] 38 | ) 39 | 40 | 41 | @dsl.pipeline( 42 | name='Parallel pipeline', 43 | description='Download two messages in parallel and prints the concatenated result.' 44 | ) 45 | def download_and_join( 46 | url1='gs://ml-pipeline-playground/shakespeare1.txt', 47 | url2='gs://ml-pipeline-playground/shakespeare2.txt' 48 | ): 49 | """A three-step pipeline with first two running in parallel.""" 50 | 51 | download1_task = gcs_download_op(url1) 52 | download2_task = gcs_download_op(url2) 53 | 54 | echo_task = echo2_op(download1_task.output, download2_task.output) 55 | 56 | if __name__ == '__main__': 57 | kfp.compiler.Compiler().compile(download_and_join, __file__ + '.zip') 58 | -------------------------------------------------------------------------------- /pipeline-samples/sequential.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright 2019 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | import kfp 18 | from kfp import dsl 19 | 20 | 21 | def gcs_download_op(url): 22 | return dsl.ContainerOp( 23 | name='GCS - Download', 24 | image='google/cloud-sdk:216.0.0', 25 | command=['sh', '-c'], 26 | arguments=['gsutil cat $0 | tee $1', url, '/tmp/results.txt'], 27 | file_outputs={ 28 | 'data': '/tmp/results.txt', 29 | } 30 | ) 31 | 32 | 33 | def echo_op(text): 34 | return dsl.ContainerOp( 35 | name='echo', 36 | image='library/bash:4.4.23', 37 | command=['sh', '-c'], 38 | arguments=['echo "$0"', text] 39 | ) 40 | 41 | @dsl.pipeline( 42 | name='Sequential pipeline', 43 | description='A pipeline with two sequential steps.' 44 | ) 45 | def sequential_pipeline(url='gs://ml-pipeline-playground/shakespeare1.txt'): 46 | """A pipeline with two sequential steps.""" 47 | 48 | download_task = gcs_download_op(url) 49 | echo_task = echo_op(download_task.output) 50 | 51 | if __name__ == '__main__': 52 | kfp.compiler.Compiler().compile(sequential_pipeline, __file__ + '.zip') 53 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | target-version = ['py37'] 4 | skip-string-normalization = true 5 | exclude = "(venv|spawner.py|mnist.py|pipeline-samples)" 6 | 7 | [tool.poetry] 8 | name = "charmed-kubeflow" 9 | version = "1.0" 10 | description = "charmed-kubeflow.io" 11 | authors = [""] 12 | 13 | [tool.poetry.dependencies] 14 | python = ">=3.10" 15 | click = "^7" 16 | pyyaml = "^5.3" 17 | 18 | [tool.poetry.dev-dependencies] 19 | pytest = "^6" 20 | sh = "^1.14" 21 | kfp = "^1.4" 22 | selenium = "^3.141" 23 | flaky = "^3.7" 24 | charmcraft = "^0.9" 25 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --strict-markers 3 | markers = 4 | full: tests compatible with full bundle 5 | lite: tests compatible with lite bundle 6 | edge: tests compatible with edge bundle 7 | gpu: tests that require a GPU to run 8 | deploy: tests that deploy a bundle 9 | selenium: tests that require a selenium 10 | 11 | minversion = 6.0 12 | log_cli = true 13 | log_cli_level = INFO 14 | -------------------------------------------------------------------------------- /releases/1.10/candidate/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | ## Documentation 22 | 23 | Read the [official documentation][docs]. 24 | 25 | [charmedkf]: https://charmed-kubeflow.io/ 26 | [docs]: https://charmed-kubeflow.io/docs/ 27 | [install]: https://charmed-kubeflow.io/docs/install 28 | -------------------------------------------------------------------------------- /releases/1.10/candidate/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.10/stable/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | ## Documentation 22 | 23 | Read the [official documentation][docs]. 24 | 25 | [charmedkf]: https://charmed-kubeflow.io/ 26 | [docs]: https://charmed-kubeflow.io/docs/ 27 | [install]: https://charmed-kubeflow.io/docs/install 28 | -------------------------------------------------------------------------------- /releases/1.10/stable/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.4/edge/kubeflow-lite/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.4/edge/kubeflow-lite/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.4/edge/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.4/edge/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.4/stable/kubeflow-lite/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.4/stable/kubeflow-lite/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.4/stable/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.4/stable/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.6/beta/kubeflow-lite/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.6/beta/kubeflow-lite/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.6/beta/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.6/beta/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.6/edge/kubeflow-lite/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.6/edge/kubeflow-lite/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.6/edge/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.6/edge/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.6/stable/kubeflow-lite/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.6/stable/kubeflow-lite/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.6/stable/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.6/stable/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.7/beta/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.7/beta/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.7/edge/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.7/edge/kubeflow/airgap/podspec_script.sh: -------------------------------------------------------------------------------- 1 | # A script to deploy PodSpec charms, these cannot be included in the bundle definition due to https://github.com/canonical/bundle-kubeflow/issues/693 2 | juju deploy ./admission-webhook_a9c1b1d.charm --resource oci-image=172.17.0.2:5000/kubeflownotebookswg/poddefaults-webhook:v1.7.0 3 | juju deploy ./argo-controller_1b8dd06.charm --resource oci-image=172.17.0.2:5000/argoproj/workflow-controller:v3.3.9 --config executor-image=172.17.0.2:5000/charmedkubeflow/argoexec:v3.3.9_22.04_1 4 | juju deploy ./argo-server_08faf05.charm --resource oci-image=172.17.0.2:5000/argoproj/argocli:v3.3.9 5 | juju deploy ./jupyter-controller_a870440.charm --resource oci-image=172.17.0.2:5000/kubeflownotebookswg/notebook-controller:v1.7.0 6 | juju deploy ./katib-controller_564a127.charm \ 7 | --resource oci-image=172.17.0.2:5000/kubeflowkatib/katib-controller:v0.15.0 \ 8 | --config custom_images='{"default_trial_template": "172.17.0.2:5000/kubeflowkatib/mxnet-mnist:v0.15.0", "early_stopping__medianstop": "172.17.0.2:5000/kubeflowkatib/earlystopping-medianstop:v0.15.0", "enas_cpu_template": "172.17.0.2:5000/kubeflowkatib/enas-cnn-cifar10-cpu:v0.15.0", "metrics_collector_sidecar__stdout": "172.17.0.2:5000/kubeflowkatib/file-metrics-collector:v0.15.0", "metrics_collector_sidecar__file": "172.17.0.2:5000/kubeflowkatib/file-metrics-collector:v0.15.0", "metrics_collector_sidecar__tensorflow_event": "172.17.0.2:5000/kubeflowkatib/tfevent-metrics-collector:v0.15.0", "pytorch_job_template__master": "172.17.0.2:5000/kubeflowkatib/pytorch-mnist-cpu:v0.15.0", "pytorch_job_template__worker": "172.17.0.2:5000/kubeflowkatib/pytorch-mnist-cpu:v0.15.0", "suggestion__random": "172.17.0.2:5000/kubeflowkatib/suggestion-hyperopt:v0.15.0", "suggestion__tpe": "172.17.0.2:5000/kubeflowkatib/suggestion-hyperopt:v0.15.0", "suggestion__grid": "172.17.0.2:5000/kubeflowkatib/suggestion-optuna:v0.15.0", "suggestion__hyperband": "172.17.0.2:5000/kubeflowkatib/suggestion-hyperband:v0.15.0", "suggestion__bayesianoptimization": "172.17.0.2:5000/kubeflowkatib/suggestion-skopt:v0.15.0", "suggestion__cmaes": "172.17.0.2:5000/kubeflowkatib/suggestion-goptuna:v0.15.0", "suggestion__sobol": "172.17.0.2:5000/kubeflowkatib/suggestion-goptuna:v0.15.0", "suggestion__multivariate_tpe": "172.17.0.2:5000/kubeflowkatib/suggestion-optuna:v0.15.0", "suggestion__enas": "172.17.0.2:5000/kubeflowkatib/suggestion-enas:v0.15.0", "suggestion__darts": "172.17.0.2:5000/kubeflowkatib/suggestion-darts:v0.15.0", "suggestion__pbt": "172.17.0.2:5000/kubeflowkatib/suggestion-pbt:v0.15.0", }' 9 | juju deploy ./kfp-persistence_1b2dc2e.charm --resource oci-image=172.17.0.2:5000/charmedkubeflow/persistenceagent:2.0.0-alpha.7_22.04_1 10 | juju deploy ./kfp-profile-controller_a050a69.charm --resource oci-image=172.17.0.2:5000/python:3.7 11 | juju deploy ./kfp-schedwf_15ed6ef.charm --resource oci-image=172.17.0.2:5000/charmedkubeflow/scheduledworkflow:2.0.0-alpha.7_22.04_1 12 | juju deploy ./kfp-ui_f6f6fe4.charm --resource oci-image=172.17.0.2:5000/ml-pipeline/frontend:2.0.0-alpha.7 13 | juju deploy ./kfp-viewer_07fd1d4.charm --resource oci-image=172.17.0.2:5000/charmedkubeflow/viewer-crd-controller:2.0.0-alpha.7_22.04_1 14 | juju deploy ./kfp-viz_755ec1c.charm --resource oci-image=172.17.0.2:5000/charmedkubeflow/visualization-server:2.0.0-alpha.7_20.04_1 15 | juju deploy ./kubeflow-volumes_641d23c.charm --resource oci-image=172.17.0.2:5000/kubeflownotebookswg/volumes-web-app:v1.7.0 16 | juju deploy ./minio_eede92d.charm --resource oci-image=172.17.0.2:5000/minio/minio:RELEASE.2021-09-03T03-56-13Z 17 | juju deploy ./oidc-gatekeeper_29d375d.charm --resource oci-image=172.17.0.2:5000/arrikto/kubeflow/oidc-authservice:e236439 18 | juju deploy ./tensorboard-controller_63d7cbb.charm --resource oci-image=172.17.0.2:5000/kubeflownotebookswg/tensorboard-controller:v1.7.0 19 | juju deploy ./tensorboards-web-app_0faec72.charm --resource oci-image=172.17.0.2:5000/kubeflownotebookswg/tensorboards-web-app:v1.7.0 20 | 21 | juju relate argo-controller minio 22 | juju relate dex-auth:oidc-client oidc-gatekeeper:oidc-client 23 | juju relate istio-pilot:ingress kfp-ui:ingress 24 | juju relate istio-pilot:ingress kubeflow-volumes:ingress 25 | juju relate istio-pilot:ingress oidc-gatekeeper:ingress 26 | juju relate istio-pilot:ingress-auth oidc-gatekeeper:ingress-auth 27 | juju relate istio-pilot:ingress tensorboards-web-app:ingress 28 | juju relate istio-pilot:gateway-info tensorboard-controller:gateway-info 29 | juju relate kfp-profile-controller:object-storage minio:object-storage 30 | juju relate kfp-api:object-storage minio:object-storage 31 | juju relate kfp-ui:object-storage minio:object-storage 32 | juju relate kfp-api:kfp-api kfp-persistence:kfp-api 33 | juju relate kfp-api:kfp-api kfp-ui:kfp-api 34 | juju relate kfp-api:kfp-viz kfp-viz:kfp-viz 35 | -------------------------------------------------------------------------------- /releases/1.7/edge/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.7/stable/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 1. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 1. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/1.7/stable/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.8/beta/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | For any Kubernetes, follow the [installation instructions][install]. 20 | 21 | ## Testing 22 | 23 | To deploy this bundle and run tests locally, do the following: 24 | 25 | 1. Set up Kubernetes, Juju, and deploy Charmed Kubeflow bundle using the [installation guide](https://charmed-kubeflow.io/docs/install). 26 | 27 | 2. Run the Automated User Acceptance Tests following the [instructions in the README file](https://github.com/canonical/charmed-kubeflow-uats#run-the-tests). 28 | 29 | ## Documentation 30 | 31 | Read the [official documentation][docs]. 32 | 33 | [charmedkf]: https://charmed-kubeflow.io 34 | [docs]: https://charmed-kubeflow.io/docs 35 | [install]: https://charmed-kubeflow.io/docs/install 36 | -------------------------------------------------------------------------------- /releases/1.8/beta/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.8/edge/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | For any Kubernetes, follow the [installation instructions][install]. 20 | 21 | ## Testing 22 | 23 | To deploy this bundle and run tests locally, do the following: 24 | 25 | 1. Set up Kubernetes, Juju, and deploy Charmed Kubeflow bundle using the [installation guide](https://charmed-kubeflow.io/docs/install). 26 | 27 | 2. Run the Automated User Acceptance Tests following the [instructions in the README file](https://github.com/canonical/charmed-kubeflow-uats#run-the-tests). 28 | 29 | ## Documentation 30 | 31 | Read the [official documentation][docs]. 32 | 33 | [charmedkf]: https://charmed-kubeflow.io 34 | [docs]: https://charmed-kubeflow.io/docs 35 | [install]: https://charmed-kubeflow.io/docs/install 36 | -------------------------------------------------------------------------------- /releases/1.8/edge/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.8/stable/kubeflow/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | For any Kubernetes, follow the [installation instructions][install]. 20 | 21 | ## Testing 22 | 23 | To deploy this bundle and run tests locally, do the following: 24 | 25 | 1. Set up Kubernetes, Juju, and deploy Charmed Kubeflow bundle using the [installation guide](https://charmed-kubeflow.io/docs/install). 26 | 27 | 2. Run the Automated User Acceptance Tests following the [instructions in the README file](https://github.com/canonical/charmed-kubeflow-uats#run-the-tests). 28 | 29 | ## Documentation 30 | 31 | Read the [official documentation][docs]. 32 | 33 | [charmedkf]: https://charmed-kubeflow.io 34 | [docs]: https://charmed-kubeflow.io/docs 35 | [install]: https://charmed-kubeflow.io/docs/install 36 | -------------------------------------------------------------------------------- /releases/1.8/stable/kubeflow/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.9/edge/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | ## Documentation 22 | 23 | Read the [official documentation][docs]. 24 | 25 | [charmedkf]: https://charmed-kubeflow.io/ 26 | [docs]: https://charmed-kubeflow.io/docs/ 27 | [install]: https://charmed-kubeflow.io/docs/install 28 | -------------------------------------------------------------------------------- /releases/1.9/edge/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/1.9/stable/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | ## Documentation 22 | 23 | Read the [official documentation][docs]. 24 | 25 | [charmedkf]: https://charmed-kubeflow.io/ 26 | [docs]: https://charmed-kubeflow.io/docs/ 27 | [install]: https://charmed-kubeflow.io/docs/install 28 | -------------------------------------------------------------------------------- /releases/1.9/stable/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/latest/beta/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 2. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 3. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/latest/beta/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /releases/latest/edge/README.md: -------------------------------------------------------------------------------- 1 | # Kubeflow Operators 2 | 3 | ## Introduction 4 | 5 | Charmed Kubeflow is a full set of Kubernetes operators to deliver the 30+ applications and services 6 | that make up the latest version of Kubeflow, for easy operations anywhere, from workstations to 7 | on-prem, to public cloud and edge. 8 | 9 | A charm is a software package that includes an operator together with metadata that supports the 10 | integration of many operators in a coherent aggregated system. 11 | 12 | This technology leverages the Juju Operator Lifecycle Manager to provide day-0 to day-2 operations 13 | of Kubeflow. 14 | 15 | Visit [charmed-kubeflow.io][charmedkf] for more information. 16 | 17 | ## Install 18 | 19 | 20 | For any Kubernetes, follow the [installation instructions][install]. 21 | 22 | ## Testing 23 | 24 | To deploy this bundle and run tests locally, do the following: 25 | 26 | 1. Set up Kubernetes, Juju, and deploy the bundle you're interested in (`kubeflow` or 27 | `kubeflow-lite`) using the [installation guide](https://charmed-kubeflow.io/docs/install/). This 28 | must include populating the `.kube/config` file with your Kubernetes cluster as the active 29 | context. Do not create a namespace with the same name as the username, this will cause 30 | pipelines tests to fail. Beware of using `admin` as the dex-auth static-username as the tests 31 | attempt to create a profile and `admin` conflicts with an existing default profile. 32 | 2. Install test prerequisites: 33 | 34 | ```bash 35 | sudo snap install juju-wait --classic 36 | sudo snap install juju-kubectl --classic 37 | sudo snap install charmcraft --classic 38 | sudo apt update 39 | sudo apt install -y libssl-dev firefox-geckodriver 40 | sudo pip3 install tox 41 | sudo pip3 install -r requirements.txt 42 | ``` 43 | 44 | 3. Run tests on your bundle with tox. As many tests need authentication, make sure you pass the 45 | username and password you set in step (1) through environment variable or argument, for example: 46 | - full bundle (using command line arguments): 47 | ``` 48 | tox -e tests -- -m full --username user123@email.com --password user123 49 | ``` 50 | - lite bundle (using environment variables): 51 | ``` 52 | export KUBEFLOW_AUTH_USERNAME=user1234@email.com 53 | export KUBEFLOW_AUTH_PASSWORD=user1234 54 | tox -e tests -- -m lite 55 | ``` 56 | 57 | Subsets of the tests are also available using pytest's substring expression selector (e.g.: 58 | `tox -e tests -- -m full --username user123@email.com --password user123 -k 'selenium'` to run just 59 | the selenium tests). 60 | 61 | ## Documentation 62 | 63 | Read the [official documentation][docs]. 64 | 65 | [charmedkf]: https://charmed-kubeflow.io/ 66 | [docs]: https://charmed-kubeflow.io/docs/ 67 | [install]: https://charmed-kubeflow.io/docs/install 68 | -------------------------------------------------------------------------------- /releases/latest/edge/airgap/podspec_script.sh: -------------------------------------------------------------------------------- 1 | # A script to deploy PodSpec charms, these cannot be included in the bundle definition due to https://github.com/canonical/bundle-kubeflow/issues/693 2 | juju deploy ./argo-controller_980dd9f.charm --resource oci-image=172.17.0.2:5000/argoproj/workflow-controller:v3.3.10 --config executor-image=172.17.0.2:5000/argoproj/argoexec:v3.3.8 3 | juju deploy ./katib-controller_f371975.charm \ 4 | --resource oci-image=172.17.0.2:5000/kubeflowkatib/katib-controller:v0.16.0-rc.1 \ 5 | --config custom_images='{"default_trial_template": "172.17.0.2:5000/kubeflowkatib/mxnet-mnist:v0.16.0-rc.1", \ 6 | "early_stopping__medianstop": "172.17.0.2:5000/kubeflowkatib/earlystopping-medianstop:v0.16.0-rc.1", \ 7 | "enas_cpu_template": "172.17.0.2:5000/kubeflowkatib/enas-cnn-cifar10-cpu:v0.16.0-rc.1", \ 8 | "metrics_collector_sidecar__stdout": "172.17.0.2:5000/kubeflowkatib/file-metrics-collector:v0.16.0-rc.1", \ 9 | "metrics_collector_sidecar__file": "172.17.0.2:5000/kubeflowkatib/file-metrics-collector:v0.16.0-rc.1", \ 10 | "metrics_collector_sidecar__tensorflow_event": "172.17.0.2:5000/kubeflowkatib/tfevent-metrics-collector:v0.16.0-rc.1", \ 11 | "pytorch_job_template__master": "172.17.0.2:5000/kubeflowkatib/pytorch-mnist-cpu:v0.16.0-rc.1", \ 12 | "pytorch_job_template__worker": "172.17.0.2:5000/kubeflowkatib/pytorch-mnist-cpu:v0.16.0-rc.1", \ 13 | "suggestion__random": "172.17.0.2:5000/kubeflowkatib/suggestion-hyperopt:v0.16.0-rc.1", \ 14 | "suggestion__tpe": "172.17.0.2:5000/kubeflowkatib/suggestion-hyperopt:v0.16.0-rc.1", \ 15 | "suggestion__grid": "172.17.0.2:5000/kubeflowkatib/suggestion-optuna:v0.16.0-rc.1", \ 16 | "suggestion__hyperband": "172.17.0.2:5000/kubeflowkatib/suggestion-hyperband:v0.16.0-rc.1", \ 17 | "suggestion__bayesianoptimization": "172.17.0.2:5000/kubeflowkatib/suggestion-skopt:v0.16.0-rc.1", \ 18 | "suggestion__cmaes": "172.17.0.2:5000/kubeflowkatib/suggestion-goptuna:v0.16.0-rc.1", \ 19 | "suggestion__sobol": "172.17.0.2:5000/kubeflowkatib/suggestion-goptuna:v0.16.0-rc.1", \ 20 | "suggestion__multivariate_tpe": "172.17.0.2:5000/kubeflowkatib/suggestion-optuna:v0.16.0-rc.1", \ 21 | "suggestion__enas": "172.17.0.2:5000/kubeflowkatib/suggestion-enas:v0.16.0-rc.1", \ 22 | "suggestion__darts": "172.17.0.2:5000/kubeflowkatib/suggestion-darts:v0.16.0-rc.1", \ 23 | "suggestion__pbt": "172.17.0.2:5000/kubeflowkatib/suggestion-pbt:v0.16.0-rc.1", }' 24 | juju deploy ./kubeflow-volumes_2ee0a84.charm --resource oci-image=172.17.0.2:5000/kubeflownotebookswg/volumes-web-app:v1.7.0 25 | juju deploy ./minio_3ba39ff.charm --resource oci-image=172.17.0.2:5000/minio/minio:RELEASE.2021-09-03T03-56-13Z 26 | 27 | juju relate argo-controller minio 28 | juju relate istio-pilot:ingress kubeflow-volumes:ingress 29 | juju relate kubeflow-dashboard:links kubeflow-volumes:dashboard-links 30 | juju relate kfp-api:object-storage minio:object-storage 31 | juju relate kfp-profile-controller:object-storage minio:object-storage 32 | juju relate kfp-ui:object-storage minio:object-storage -------------------------------------------------------------------------------- /releases/latest/edge/charmcraft.yaml: -------------------------------------------------------------------------------- 1 | type: bundle 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | pyyaml==5.3.1 3 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Utility Script 2 | 3 | This directory contains helper scripts for Charmed Kubeflow, during CI and not only. 4 | 5 | ## Gather images used by a bundle 6 | 7 | You can get a list of all the OCI images used by the bundle by running the following command: 8 | ```bash 9 | pip3 install -r scripts/requirements.txt 10 | 11 | python3 scripts/get_all_images.py \ 12 | --append-images tests/airgapped/1.9/testing-images.txt \ 13 | releases/1.9/stable/bundle.yaml \ 14 | > images-all.txt 15 | ``` 16 | For Charmed Kubeflow 1.8, run 17 | ```bash 18 | pip3 install -r scripts/requirements.txt 19 | 20 | python3 scripts/get_all_images.py \ 21 | --append-images tests/airgapped/1.8/testing-images.txt \ 22 | releases/1.9/stable/kubeflow/bundle.yaml \ 23 | > images-all.txt 24 | ``` 25 | 26 | The script will gather the images in the following way: 27 | 1. For each `application` in the provided `bundle.yaml` file: 28 | 2. detect if it's owned by us or another team (by looking at the `_github_dependency_repo_name` and such metadata) 29 | 3. clone its repo, by looking at `_github_repo_name` and such metadata 30 | 4. If owned by another team: only parse it's `metadata.yaml` and look for `oci-resources` 31 | 5. If owned by us: run the `tools/get-images.sh` script the repo **must** have 32 | 6. If a repo does not have `tools/get-images.sh` (i.e. kubeflow-roles) then the script should skip the repo 33 | 7. If the `get-images.sh` script either fails (return code non zero) or has error logs then the script should **fail** 34 | 8. Aggregate the outputs of all `get-images.sh` scripts to one output 35 | 9. If user passed an argument `--append-images` then the script will amend a list of images we need for airgap testing 36 | 37 | 38 | ## Produce SBOM for images in a bundle 39 | 40 | ### Prerequisites 41 | 1. Install `syft` snap 42 | ``` 43 | sudo snap install syft --classic 44 | ``` 45 | 46 | 2. Install `docker` snap 47 | ``` 48 | sudo snap install docker 49 | ``` 50 | Setup `docker` snap to run as a normal user by following the snap [documentation](https://snapcraft.io/docker). 51 | 52 | 3. Create a venv and install python requirements. 53 | Note that python version 3.10 is required to run the script. 54 | ``` 55 | python3 -m venv venv 56 | source venv/bin/activate 57 | 58 | pip3 install -r scripts/requirements.txt 59 | ``` 60 | 61 | ### Run SBOM producing script 62 | You can get a list of all the SBOMs for the images used by the bundle by running the following command: 63 | ``` 64 | python3 scripts/get_bundle_images_sbom.py 65 | ``` 66 | For example for the 1.9 bundle, run: 67 | ``` 68 | python3 scripts/get_bundle_images_sbom.py releases/1.9/stable/bundle.yaml 69 | ``` 70 | 71 | > [!WARNING] 72 | > To produce the SBOMs of all images in the bundle (~100 images), the script can take up to a few hours depending on the network and processing resources. 73 | 74 | The script creates a compressed file under the repo's root with the name `images_SBOM.tar.gz`. The script will store all the SBOMs there. For each image, there will be the SBOM file formatted as `.spdx.json` inside the compressed file. 75 | -------------------------------------------------------------------------------- /scripts/airgapped/README.md: -------------------------------------------------------------------------------- 1 | # Airgap Utility Scripts 2 | 3 | This directory contains bash and python scripts that are useful for performing 4 | an airgapped installation. These scripts could either be used independently 5 | to create airgap artifacts or via our testing scripts. 6 | 7 | We'll document some use-case scenarios here for the different scripts. 8 | 9 | ## Prerequisites 10 | NOTE: All the commands are expected to be run from the root directory of the repo 11 | 12 | To use the scripts in this directory you'll need to install a couple of Python 13 | and Ubuntu packages on the host machine, driving the test (not the LXC machine 14 | that will contain the airgapped environment). 15 | ``` 16 | pip3 install -r scripts/airgapped/requirements.txt 17 | sudo apt install pigz 18 | sudo snap install docker 19 | sudo snap install yq 20 | sudo snap install jq 21 | ``` 22 | 23 | ## Get list of all images from a bundle definition 24 | 25 | Use the following script to get the list of all OCI images used by a bundle. 26 | This script makes the following assumptions: 27 | 1. Every charm in the bundle has a `_github_repo_name` metadata field, 28 | containing the repository name of the charm (the org is assumed to be 29 | canonical). 30 | 2. Every charm in the bundle has a `_github_repo_branch` metadata field, 31 | containing the branch of the source code. 32 | 3. There is a script called `tools/get_images.sh` in each repo that gathers 33 | the images for that repo. 34 | 35 | For Charmed Kubeflow 1.9, run: 36 | ```bash 37 | python3 scripts/get_all_images.py \ 38 | --append-images=tests/airgapped/1.9/testing-images.txt \ 39 | releases/1.9/stable/bundle.yaml \ 40 | > images.txt 41 | ``` 42 | 43 | For Charmed Kubeflow 1.8, run: 44 | ```bash 45 | python3 scripts/get_all_images.py \ 46 | --append-images=tests/airgapped/1.8/testing-images.txt \ 47 | releases/1.8/stable/kubeflow/bundle.yaml \ 48 | > images.txt 49 | ``` 50 | 51 | ## Pull images to docker cache 52 | 53 | We have a couple of scripts that are using `docker` commands to pull images, 54 | retag them and compress them in a final `tar.gz` file. Those scripts require 55 | that the images are already in docker's cache. This script pull a list of images 56 | provided by a txt file. 57 | 58 | ```bash 59 | python3 scripts/airgapped/save-images-to-cache.py images.txt 60 | ``` 61 | 62 | ## Retag images to cache 63 | 64 | In airgap environments users push their images in their own registries. So we'll 65 | need to rename prefixes like `docker.io` to the server that users would use. 66 | 67 | Note that this script will produce by default a `retagged-images.txt` file, 68 | containing the names of all re-tagged images. 69 | 70 | ```bash 71 | python3 scripts/airgapped/retag-images-to-cache.py images.txt 72 | ``` 73 | 74 | Or if you'd like to use a different prefix, i.e. `registry.example.com` 75 | ```bash 76 | python3 scripts/airgapped/retag-images-to-cache.py --new-registry=registry.example.com images.txt 77 | ``` 78 | 79 | ## Save images to tar 80 | 81 | Users will need to inject the OCI images in their registry in an airgap 82 | environment. For this we'll be preparing a `tar.gz` file with all OCI images. 83 | 84 | ```bash 85 | python3 scripts/airgapped/save-images-to-tar.py retagged-images.txt 86 | ``` 87 | 88 | ## Save charms to tar 89 | 90 | Users in an airgap env will need to deploy charms from local files. To assist this 91 | we'll use this script to create a `tar.gz` containing all the charms referenced 92 | in a bundle. 93 | 94 | ```bash 95 | BUNDLE_PATH=releases/1.7/stable/kubeflow/bundle.yaml 96 | 97 | python3 scripts/airgapped/save-charms-to-tar.py $BUNDLE_PATH 98 | ``` 99 | -------------------------------------------------------------------------------- /scripts/airgapped/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/bundle-kubeflow/8eeddb31e4daf34de6d5e58fdc00c74a9679753d/scripts/airgapped/__init__.py -------------------------------------------------------------------------------- /scripts/airgapped/images/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/bundle-kubeflow/8eeddb31e4daf34de6d5e58fdc00c74a9679753d/scripts/airgapped/images/overview.png -------------------------------------------------------------------------------- /scripts/airgapped/prerequisites.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 5 | 6 | echo "Installing dependencies..." 7 | pip3 install -r $SCRIPT_DIR/requirements.txt 8 | sudo apt update 9 | 10 | # Install and Setup Docker 11 | # we don't do `newgrp docker` here so not to open a new bash session 12 | # so that the rest of the scripts continue to execute. 13 | # See in the tests/airgapped README.md instructions for the docker user changes to take effect. 14 | echo "Installing Docker" 15 | sudo snap install docker 16 | sudo groupadd -f docker 17 | sudo usermod -aG docker $USER 18 | sudo snap disable docker 19 | sudo snap enable docker 20 | 21 | echo "Installing parsers" 22 | sudo snap install yq 23 | sudo snap install jq 24 | 25 | echo "Installing pigz for compression" 26 | sudo apt install pigz 27 | -------------------------------------------------------------------------------- /scripts/airgapped/push-images-to-registry.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | 4 | import docker 5 | 6 | from utils import get_images_list_from_file 7 | 8 | docker_client = docker.client.from_env() 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | if __name__ == "__main__": 14 | parser = argparse.ArgumentParser(description="Push images from list.") 15 | parser.add_argument("images") 16 | args = parser.parse_args() 17 | 18 | images_ls = get_images_list_from_file(args.images) 19 | images_len = len(images_ls) 20 | new_images_ls = [] 21 | for idx, image_nm in enumerate(images_ls): 22 | log.info("%s/%s", idx + 1, images_len) 23 | 24 | logging.info("Pushing image: %s", image_nm) 25 | docker_client.images.push(image_nm) 26 | 27 | log.info("Successfully pushed all images!") 28 | -------------------------------------------------------------------------------- /scripts/airgapped/requirements.txt: -------------------------------------------------------------------------------- 1 | docker 2 | requests 3 | PyYAML 4 | -------------------------------------------------------------------------------- /scripts/airgapped/retag-images-to-cache.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | 4 | import docker 5 | 6 | from utils import (delete_file_if_exists, get_images_list_from_file, 7 | get_or_pull_image) 8 | 9 | cli = docker.client.from_env() 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | SHA_TOKEN = "@sha256" 14 | 15 | 16 | def retag_image_with_sha(image): 17 | """Retag the image by using the sha value.""" 18 | log.info("Retagging image digest: %s", image) 19 | repo_digest = image.attrs["RepoDigests"][0] 20 | [repository_name, sha_value] = repo_digest.split("@sha256:") 21 | 22 | tagged_image = "%s:%s" % (repository_name, sha_value) 23 | log.info("Retagging to: %s", tagged_image) 24 | image.tag(tagged_image) 25 | 26 | log.info("Tagged image successfully: %s", tagged_image) 27 | return cli.images.get(tagged_image) 28 | 29 | 30 | def get_retagged_image_name(image_nm: str, new_registry: str) -> str: 31 | """Given an image name replace the repo and use sha as tag.""" 32 | if SHA_TOKEN in image_nm: 33 | log.info("Provided image has sha. Using it's value as tag.") 34 | image_nm = image_nm.replace(SHA_TOKEN, "") 35 | 36 | if len(image_nm.split("/")) == 1: 37 | # docker.io/library image, i.e. ubuntu:22.04 38 | return "%s/%s" % (new_registry, image_nm) 39 | 40 | if len(image_nm.split("/")) == 2: 41 | # classic docker.io image, i.e. argoproj/workflow-controller 42 | return "%s/%s" % (new_registry, image_nm) 43 | 44 | # There are more than 2 / in the image name. Replace first part 45 | # Example image: quay.io/metallb/speaker:v0.13.3 46 | _, image_nm = image_nm.split("/", 1) 47 | return "%s/%s" % (new_registry, image_nm) 48 | 49 | 50 | if __name__ == "__main__": 51 | parser = argparse.ArgumentParser(description="Retag list of images") 52 | parser.add_argument("images") 53 | parser.add_argument("--new-registry", default="172.17.0.2:5000") 54 | parser.add_argument("--retagged-images", default="retagged-images.txt") 55 | # The reason we are using this IP as new registry is because this will end 56 | # up being the IP of the Registry we'll run as a Container. We'll need to 57 | # do docker push <...> so we'll have to use the IP directly, or mess with 58 | # the environment's /etc/hosts file 59 | 60 | args = parser.parse_args() 61 | 62 | images_ls = get_images_list_from_file(args.images) 63 | images_len = len(images_ls) 64 | new_images_ls = [] 65 | for idx, image_nm in enumerate(images_ls): 66 | log.info("%s/%s", idx + 1, images_len) 67 | 68 | retagged_image_nm = get_retagged_image_name( 69 | image_nm, args.new_registry 70 | ) 71 | 72 | img = get_or_pull_image(image_nm) 73 | log.info("%s: Retagging to %s", image_nm, retagged_image_nm) 74 | img.tag(retagged_image_nm) 75 | 76 | new_images_ls.append(retagged_image_nm) 77 | 78 | log.info("Saving the produced list of images.") 79 | delete_file_if_exists(args.retagged_images) 80 | with open(args.retagged_images, "w+") as f: 81 | f.write("\n".join(new_images_ls)) 82 | 83 | log.info("Successfully saved list of images in '%s'", args.retagged_images) 84 | -------------------------------------------------------------------------------- /scripts/airgapped/save-charms-to-tar.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import subprocess 4 | 5 | import os 6 | import yaml 7 | 8 | import utils as airgap_utils 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | def download_bundle_charms(bundle: dict, no_zip: bool, 14 | skip_resource_dispatcher: bool, 15 | output_tar: str) -> None: 16 | """Given a bundle dict download all the charms using juju download.""" 17 | 18 | log.info("Downloading all charms...") 19 | applications = bundle.get("applications") 20 | for app in applications.values(): 21 | subprocess.run(["juju", "download", "--channel", app["channel"], 22 | app["charm"]]) 23 | 24 | # FIXME: https://github.com/canonical/bundle-kubeflow/issues/789 25 | if not skip_resource_dispatcher: 26 | log.info("Fetching charm of resource-dispatcher.") 27 | subprocess.run(["juju", "download", "--channel", "1.0/stable", 28 | "resource-dispatcher"]) 29 | 30 | if not no_zip: 31 | # python3 download_bundle_charms.py $BUNDLE_PATH --zip_all 32 | log.info("Creating the tar with all the charms...") 33 | cmd = "tar -cv --use-compress-program=pigz -f %s *.charm" % output_tar 34 | subprocess.run(cmd, shell=True) 35 | log.info("Created %s file will all charms.", output_tar) 36 | 37 | log.info("Removing downloaded charms...") 38 | airgap_utils.delete_files_with_extension(os.getcwd(), ".charm") 39 | 40 | 41 | if __name__ == "__main__": 42 | parser = argparse.ArgumentParser(description="Bundle Charms Downloader") 43 | parser.add_argument("--no-zip", action="store_true") 44 | parser.add_argument("--skip-resource-dispatcher", action="store_true") 45 | parser.add_argument("--output-tar", default="charms.tar.gz") 46 | parser.add_argument("bundle") 47 | args = parser.parse_args() 48 | log.info(args.no_zip) 49 | 50 | bundle_dict = {} 51 | with open(args.bundle, 'r') as file: 52 | bundle_dict = yaml.safe_load(file) 53 | 54 | airgap_utils.delete_file_if_exists(args.output_tar) 55 | download_bundle_charms(bundle_dict, args.no_zip, 56 | args.skip_resource_dispatcher, 57 | args.output_tar) 58 | -------------------------------------------------------------------------------- /scripts/airgapped/save-images-to-cache.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | 4 | import docker 5 | 6 | from utils import get_images_list_from_file, get_or_pull_image 7 | 8 | cli = docker.client.from_env() 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | if __name__ == "__main__": 14 | parser = argparse.ArgumentParser(description="Pull locally list of images") 15 | parser.add_argument("images") 16 | args = parser.parse_args() 17 | 18 | images_ls = get_images_list_from_file(args.images) 19 | images_len = len(images_ls) 20 | for idx, image in enumerate(images_ls): 21 | log.info("%s/%s", idx + 1, images_len) 22 | get_or_pull_image(image) 23 | -------------------------------------------------------------------------------- /scripts/airgapped/save-images-to-tar.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import os 4 | import subprocess 5 | 6 | import docker 7 | 8 | from utils import (delete_file_if_exists, get_images_list_from_file, 9 | get_or_pull_image) 10 | 11 | cli = docker.client.from_env() 12 | 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | def save_image(image_nm) -> str: 17 | """Given an Image object, save it as tar.""" 18 | get_or_pull_image(image_nm) 19 | file_name = "%s.tar" % image_nm 20 | file_name = file_name.replace("/", "-").replace(":", "-") 21 | if os.path.isfile(file_name): 22 | log.info("Tar '%s' already exists. Skipping...", file_name) 23 | return file_name 24 | 25 | log.info("%s: Saving image to tar '%s'.", image_nm, file_name) 26 | for i in range(10): 27 | # We've seen that sometimes we get socket timeouts. Try 10 times 28 | try: 29 | with open(file_name, "w+b") as f: 30 | subprocess.run(["docker", "save", image_nm], stdout=f) 31 | 32 | logging.info("%s: Saved image to tar '%s'", image_nm, file_name) 33 | return file_name 34 | except Exception as e: 35 | log.error("Failed to create tar file. Deleting tar '%s", file_name) 36 | log.error(e) 37 | log.info("Retrying %s/10 to store image to tar '%s'", 38 | i + 1, file_name) 39 | 40 | log.error("Tried 10 times to create tar '%s' and failed: %s", file_name) 41 | delete_file_if_exists(file_name) 42 | 43 | 44 | if __name__ == "__main__": 45 | parser = argparse.ArgumentParser(description="Create tar.gz from images") 46 | parser.add_argument("images") 47 | args = parser.parse_args() 48 | 49 | images_ls = get_images_list_from_file(args.images) 50 | images_len = len(images_ls) 51 | tar_files = [] 52 | for idx, image_nm in enumerate(images_ls): 53 | log.info("%s/%s", idx + 1, images_len) 54 | tar_file = save_image(image_nm) 55 | tar_files.append(tar_file) 56 | 57 | log.info("Creating final tar.gz file. Will take a while...") 58 | subprocess.run(["tar", "-cv", "--use-compress-program=pigz", 59 | "-f", "images.tar.gz", *tar_files]) 60 | log.info("Created the tar.gz file!") 61 | 62 | log.info("Deleting intermediate .tar files.") 63 | for file in tar_files: 64 | delete_file_if_exists(file) 65 | log.info("Deleted all .tar files.") 66 | -------------------------------------------------------------------------------- /scripts/airgapped/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import pathlib 4 | 5 | import docker 6 | 7 | cli = docker.client.from_env() 8 | 9 | LOG_FORMAT = "%(levelname)s \t| %(message)s" 10 | logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) 11 | 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | def delete_files_with_extension(dir_path, extension): 16 | """Delete all files in dir_path that have a specific file extension.""" 17 | dir_files = os.listdir(dir_path) 18 | for file in dir_files: 19 | if file.endswith(extension): 20 | os.remove(os.path.join(dir_path, file)) 21 | 22 | 23 | def delete_file_if_exists(file_name): 24 | """Delete the file name if it exists.""" 25 | pathlib.Path(file_name).unlink(missing_ok=True) 26 | 27 | 28 | def get_images_list_from_file(file_name: str) -> list[str]: 29 | """Given a file name with \n separated names return the list of names.""" 30 | try: 31 | with open(file_name, 'r') as file: 32 | images = file.read().splitlines() 33 | return images 34 | except FileNotFoundError: 35 | log.warn(f"File '{file_name}' not found.") 36 | return [] 37 | except Exception as e: 38 | log.error("An error occurred:", e) 39 | return [] 40 | 41 | 42 | def get_or_pull_image(image: str): 43 | """First try to get the image from local cache, and then pull.""" 44 | try: 45 | log.info("%s: Trying to get image from cache", image) 46 | img = cli.images.get(image) 47 | 48 | log.info("%s: Found image in cache", image) 49 | return img 50 | except docker.errors.ImageNotFound: 51 | log.info("%s: Couldn't find image in cache. Pulling it", image) 52 | img = cli.images.pull(image) 53 | 54 | log.info("%s: Pulled image", image) 55 | return img 56 | -------------------------------------------------------------------------------- /scripts/delete_volumes.py: -------------------------------------------------------------------------------- 1 | # Delete unattached EBS volumes (state=available) in all AWS regions 2 | # source: https://towardsthecloud.com/amazon-ec2-delete-unattached-ebs-volumes 3 | import boto3 4 | from tenacity import retry, stop_after_attempt, wait_fixed 5 | import sys 6 | 7 | @retry(stop=stop_after_attempt(3), wait=wait_fixed(2), reraise=True) 8 | def delete_volumes_in_region(region_name: str, count: int)-> int: 9 | try: 10 | ec2conn = boto3.resource("ec2", region_name = region_name) 11 | unattached_volumes = [ 12 | volume for volume in ec2conn.volumes.all() if (volume.state == "available") 13 | ] 14 | for volume in unattached_volumes: 15 | volume.delete() 16 | print(f"Deleted unattached volume {volume.id} in region {region_name}.") 17 | count = count + 1 18 | return count 19 | except Exception as e: 20 | print(f"Error: {e}") 21 | raise e 22 | 23 | def validate_region(region_name: str)-> bool: 24 | ec2 = boto3.client("ec2") 25 | regions = ec2.describe_regions()["Regions"] 26 | regions_names = list(map(lambda region: region["RegionName"],regions)) 27 | return region_name in regions_names 28 | 29 | def delete_volumes() -> None: 30 | count = 0 31 | if len(sys.argv)>1: 32 | region_name = sys.argv[1] 33 | if validate_region(region_name): 34 | count = delete_volumes_in_region(region_name, count) 35 | else: 36 | print("Region from input isn't being used in this AWS account.") 37 | raise Exception 38 | else: 39 | ec2 = boto3.client("ec2") 40 | for region in ec2.describe_regions()["Regions"]: 41 | region_name = region["RegionName"] 42 | count = delete_volumes_in_region(region_name, count) 43 | 44 | if count > 0: 45 | print(f"Deleted {count} unattached volumes.") 46 | else: 47 | print("No unattached volumes found for deletion.") 48 | 49 | delete_volumes() 50 | -------------------------------------------------------------------------------- /scripts/get_bundle_images_sbom.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import os 4 | from pathlib import Path 5 | import shutil 6 | import subprocess 7 | import tarfile 8 | 9 | from get_all_images import get_bundle_images 10 | 11 | SBOM_DIR = "images_SBOM" 12 | 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | def get_bundle_images_sbom(bundle_path: str) -> None: 17 | 18 | # get a list of images in the bundle 19 | bundle_images = get_bundle_images(bundle_path) 20 | 21 | log.info(f"Images gathered from the bundle: {bundle_images}") 22 | 23 | Path(SBOM_DIR).mkdir(parents=True, exist_ok=True) 24 | 25 | for image in bundle_images: 26 | log.info(f"Creating SBOM for {image}") 27 | try: 28 | subprocess.check_call( 29 | ['syft', 'scan', image, '-o', f'spdx-json={image}.spdx.json'], 30 | cwd=SBOM_DIR, 31 | ) 32 | except subprocess.CalledProcessError as e: 33 | log.error(f"Error scanning {image}: {e.output}") 34 | raise e 35 | 36 | sbom_files = os.listdir(SBOM_DIR) 37 | 38 | # Make .tar.gz from all SBOMs 39 | with tarfile.open(f"{SBOM_DIR}.tar.gz", "w:gz") as tar: 40 | for file in sbom_files: 41 | tar.add(f"{SBOM_DIR}/{file}") 42 | 43 | # Cleanup files 44 | shutil.rmtree(SBOM_DIR) 45 | 46 | 47 | def main(): 48 | parser = argparse.ArgumentParser(description="Get SBOMs for all images in a bundle.") 49 | parser.add_argument("bundle", help="the bundle.yaml file to be scanned.") 50 | 51 | args = parser.parse_args() 52 | 53 | get_bundle_images_sbom(args.bundle) 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /scripts/get_bundle_path.py: -------------------------------------------------------------------------------- 1 | # Get bundle path for specific release 2 | import re 3 | import sys 4 | import os 5 | 6 | 7 | def get_bundle_path_from_release() -> None: 8 | if len(sys.argv) <= 1: 9 | raise Exception("No release given as input.") 10 | 11 | release = sys.argv[1] 12 | bundle_is_latest = re.search("^latest/", release) 13 | if bundle_is_latest: 14 | bundle_path = "./releases/" + release + "/" 15 | else: 16 | bundle_path = "./releases/" + release + "/kubeflow/" 17 | 18 | # Check if file in bundle_path output exists 19 | # Expect the script to be executed from `bundle-kubeflow` repo directory 20 | if os.path.exists(bundle_path): 21 | print(f"Returning bundle_path={bundle_path}") 22 | with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: 23 | print(f'bundle_path={bundle_path}', file=fh) 24 | else: 25 | raise Exception(f"There is no file in path: {bundle_path}") 26 | 27 | 28 | get_bundle_path_from_release() 29 | -------------------------------------------------------------------------------- /scripts/get_bundle_test_path.py: -------------------------------------------------------------------------------- 1 | # Get bundle test path for specific release 2 | import os 3 | import sys 4 | 5 | # For new releases, add a release/tests mapping to this dictionary 6 | RELEASE_TESTS = { 7 | "1.6/beta": "./tests-bundle/1.6/", 8 | "1.6/edge": "./tests-bundle/1.6/", 9 | "1.6/stable": "./tests-bundle/1.6/", 10 | "1.7/beta": "./tests-bundle/1.7/", 11 | "1.7/edge": "./tests-bundle/1.7/", 12 | "1.7/stable": "./tests-bundle/1.7/", 13 | "1.8/beta": "./tests-bundle/1.8/", 14 | "1.8/edge": "./tests-bundle/1.8/", 15 | "1.8/stable": "./tests-bundle/1.8/", 16 | "latest/beta": "./tests-bundle/1.8/", 17 | "latest/edge": "./tests-bundle/1.8/", 18 | } 19 | 20 | 21 | def get_bundle_test_path_from_release() -> None: 22 | if len(sys.argv) <= 1: 23 | raise Exception("No release given as input.") 24 | 25 | release = sys.argv[1] 26 | 27 | if release not in RELEASE_TESTS.keys(): 28 | raise Exception( 29 | f"No tests available for {release} release. Please proceed to manually publish to Charmhub if needed." 30 | ) 31 | bundle_test_path = RELEASE_TESTS[release] 32 | 33 | # Check if file in bundle_test_path output exists 34 | # Expect the script to be executed from `bundle-kubeflow` repo directory 35 | if os.path.exists(bundle_test_path): 36 | print(f"Returning bundle_test_path={bundle_test_path}") 37 | with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: 38 | print(f'bundle_test_path={bundle_test_path}', file=fh) 39 | else: 40 | raise Exception(f"There is no directory in path: {bundle_test_path}") 41 | 42 | 43 | get_bundle_test_path_from_release() 44 | -------------------------------------------------------------------------------- /scripts/get_release_from_bundle_source.py: -------------------------------------------------------------------------------- 1 | # Get bundle test path for specific release 2 | import os 3 | import re 4 | import sys 5 | 6 | def get_release_from_bundle_source() -> None: 7 | if len(sys.argv) <= 1: 8 | raise Exception("No bundle source given as input.") 9 | 10 | bundle_source = sys.argv[1] 11 | # Bundle source input should be `--channel ` or `--file .yaml`` 12 | # e.g. --channel 1.8/stable or --file releases/1.8/stable/kubeflow/bundle.yaml 13 | bundle_source_starts_with_channel = re.search("^--channel", bundle_source) 14 | bundle_source_starts_with_file = re.search("^--file", bundle_source) 15 | 16 | try: 17 | if bundle_source_starts_with_channel: 18 | if re.search("^--channel=", bundle_source): 19 | substrings = bundle_source.split("=") 20 | else: 21 | substrings = bundle_source.split(" ") 22 | release=substrings[1] 23 | elif bundle_source_starts_with_file: 24 | substrings = bundle_source.split('/') 25 | track = substrings[1] 26 | risk = substrings[2] 27 | release = f"{track}/{risk}" 28 | print( 29 | f"Returning release={release}.") 30 | except: 31 | raise Exception("Bundle source doesn't have expected format.") 32 | 33 | with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: 34 | print(f'release={release}', file=fh) 35 | 36 | get_release_from_bundle_source() 37 | -------------------------------------------------------------------------------- /scripts/get_releases_affected.py: -------------------------------------------------------------------------------- 1 | # Extract from the files changed by this PR the releases/channels affected. 2 | import json 3 | import os 4 | import re 5 | import sys 6 | 7 | ACCEPTED_TRACKS = ["1.7", "1.8", "1.9", "latest"] 8 | ACCEPTED_RISKS = ["beta", "edge", "stable"] 9 | 10 | 11 | def get_releases_affected() -> None: 12 | releases_affected = set() 13 | 14 | for file_path in sys.argv: 15 | # check if string starts with the "releases" 16 | file_path_starts_with_releases = re.search("^releases", file_path) 17 | 18 | if file_path_starts_with_releases: 19 | directories = file_path.split('/') 20 | track = directories[1] 21 | risk = directories[2] 22 | 23 | if(track in ACCEPTED_TRACKS and risk in ACCEPTED_RISKS): 24 | release = f"{track}/{risk}" 25 | releases_affected.add(release) 26 | else: 27 | raise Exception( 28 | f"File {file_path} was changed in 'releases' directory but it's not part of a known release/channel.") 29 | 30 | releases_affected_json = json.dumps(list(releases_affected)) 31 | print( 32 | f"The following releases have been affected by this PR: {releases_affected_json}") 33 | with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: 34 | print(f'releases_affected_json={releases_affected_json}', file=fh) 35 | 36 | 37 | get_releases_affected() 38 | -------------------------------------------------------------------------------- /scripts/gh-actions/parse_versions.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | # Parse the versions given as a comma-separated list and return a JSON array 6 | def parse_versions(input_versions): 7 | # Remove whitespace between entries 8 | input_versions = input_versions.replace(" ", "") 9 | 10 | # Convert to JSON array 11 | json_array = json.dumps(input_versions.split(",")) 12 | return json_array 13 | 14 | if __name__ == "__main__": 15 | if len(sys.argv) < 2 or sys.argv[1] == "": 16 | raise Exception("No bundle versions given as input.") 17 | 18 | input_versions = sys.argv[1] 19 | json_array = parse_versions(input_versions) 20 | print(f"bundle_versions={json_array}") 21 | 22 | with open(os.environ['GITHUB_OUTPUT'], 'a') as output_file: 23 | output_file.write(f"bundle_versions={json_array}\n") 24 | 25 | -------------------------------------------------------------------------------- /scripts/gh-actions/set_eks_sysctl_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xe 3 | # This script checks that the sysctl are correctly set on an EKS cluster 4 | 5 | function check_sysctl(){ 6 | SSH_CMD=$1 7 | KEY=$2 8 | VALUE=$3 9 | 10 | CURRENT=$($SSH_CMD 'sudo sysctl -a 2>/dev/null' | grep "^${KEY}" | cut -d "=" -f 2 | xargs) 11 | 12 | if [ "$CURRENT" != "$VALUE" ] 13 | then 14 | echo "Failing check for ${KEY}: current ${CURRENT} expected: ${VALUE}" 15 | exit 1 16 | else 17 | echo "Check for ${KEY} passed successfully" 18 | fi 19 | } 20 | 21 | # Export all instances 22 | aws ec2 describe-instances \ 23 | --filters Name=instance-state-name,Values=running \ 24 | --output yaml > instances.yaml 25 | 26 | INTERNAL_DNS=$(kubectl get nodes -A | tail -n+2 | awk '{print $1}') 27 | 28 | for DNS in $INTERNAL_DNS; 29 | do 30 | # Figure out external dns 31 | DNS_QUERY=".Reservations[].Instances | flatten | map(select(.PrivateDnsName==\"${DNS}\"))[].PublicDnsName" 32 | echo $DNS_QUERY 33 | EXTERNAL_DNS=$(yq "$DNS_QUERY" instances.yaml) 34 | 35 | echo "==========================================" 36 | echo "Internal DNS: ${DNS}" 37 | echo "Using external DNS: ${EXTERNAL_DNS}" 38 | 39 | SSH_CMD="ssh -o StrictHostKeyChecking=no ubuntu@$EXTERNAL_DNS" 40 | 41 | # Setting the sysctl properties 42 | echo "Setting values" 43 | $SSH_CMD 'sudo sysctl fs.inotify.max_user_watches=655360' 44 | $SSH_CMD 'sudo sysctl fs.inotify.max_user_instances=1280' 45 | 46 | # To check the settings 47 | echo "Checking values" 48 | check_sysctl "$SSH_CMD" "fs.inotify.max_user_watches" "655360" 49 | check_sysctl "$SSH_CMD" "fs.inotify.max_user_instances" "1280" 50 | echo "==========================================" 51 | done 52 | 53 | rm instances.yaml 54 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | gitpython 2 | tenacity 3 | boto3 4 | click 5 | pyyaml 6 | -------------------------------------------------------------------------------- /test-reference/README.md: -------------------------------------------------------------------------------- 1 | # Test reference versions 2 | 3 | This directory contains a `versions.json` file that can be used as reference for combining versions of different components to test the Charmed Kubeflow bundle. 4 | 5 | It follows this schema: 6 | 7 | ```json 8 | { 9 | "ckf-version-risk-mlflow-version-risk": { 10 | "ckf":"", 11 | "mlflow": "", 12 | "k8s": { 13 | "charmed-kubernetes": { 14 | "min-supported": "", 15 | "max-supported": "" 16 | }, 17 | "microk8s": { 18 | "min-supported": "", 19 | "max-supported": "" 20 | }, 21 | "aks": { 22 | "min-supported": "", 23 | "max-supported": "" 24 | }, 25 | "eks": { 26 | "min-supported": "", 27 | "max-supported": "" 28 | } 29 | }, 30 | "juju": { 31 | "current": "", 32 | "future": "" 33 | } 34 | }, 35 | } 36 | ``` 37 | 38 | where, 39 | 40 | * `ckf` and `mlflow` define the versions of each bundle to deploy 41 | * A `k8s` substrate is a type of Kubernetes the team desires to test on (e.g. Microk8s, or AKS) 42 | * The `min-supported` is the smallest version the bundle should be tested on 43 | * The `max-supported` is the latest version the bundle should be tested on 44 | * The `current` juju version is the latest supported version of juju the MLOps team uses in the CI 45 | * The `future` juju version is the target version to be supported 46 | 47 | ## Usage 48 | 49 | Each object in "ckf-version-risk-mlflow-version-risk" can be combined to form the environment in which the bundle will be tested. For example `ckf 1.9/stable, mlflow 2.15/stable, on charmed-kubernetes 1.30 and juju 3.6/stable`. 50 | -------------------------------------------------------------------------------- /test-reference/versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "ckf-1.9-stable": { 3 | "ckf":"1.9/stable", 4 | "k8s": { 5 | "charmed-kubernetes": { 6 | "min-supported": "1.27", 7 | "max-supported": "1.30" 8 | }, 9 | "microk8s": { 10 | "min-supported": "1.27", 11 | "max-supported": "1.30" 12 | }, 13 | "aks": { 14 | "min-supported": "1.27", 15 | "max-supported": "1.30" 16 | }, 17 | "eks": { 18 | "min-supported": "1.27", 19 | "max-supported": "1.30" 20 | } 21 | }, 22 | "juju": { 23 | "current-edge": "3.6/edge", 24 | "current-stable": "3.6/stable", 25 | "future-beta": "4.0/beta" 26 | } 27 | }, 28 | "ckf-1.9-stable-mlflow-2.15-stable": { 29 | "ckf":"1.9/stable", 30 | "mlflow": "2.15/stable", 31 | "k8s": { 32 | "charmed-kubernetes": { 33 | "min-supported": "1.27", 34 | "max-supported": "1.30" 35 | }, 36 | "microk8s": { 37 | "min-supported": "1.27", 38 | "max-supported": "1.30" 39 | }, 40 | "aks": { 41 | "min-supported": "1.27", 42 | "max-supported": "1.30" 43 | }, 44 | "eks": { 45 | "min-supported": "1.27", 46 | "max-supported": "1.30" 47 | } 48 | }, 49 | "juju": { 50 | "current-edge": "3.6/edge", 51 | "current-stable": "3.6/stable", 52 | "future-beta": "4.0/beta" 53 | } 54 | }, 55 | "ckf-1.9-edge-mlflow-2.15-edge": { 56 | "ckf":"1.9/edge", 57 | "mlflow": "2.15/edge", 58 | "k8s": { 59 | "charmed-kubernetes": { 60 | "min-supported": "1.27", 61 | "max-supported": "1.30" 62 | }, 63 | "microk8s": { 64 | "min-supported": "1.27", 65 | "max-supported": "1.30" 66 | }, 67 | "aks": { 68 | "min-supported": "1.27", 69 | "max-supported": "1.30" 70 | }, 71 | "eks": { 72 | "min-supported": "1.27", 73 | "max-supported": "1.30" 74 | } 75 | }, 76 | "juju": { 77 | "current-edge": "3.6/edge", 78 | "current-stable": "3.6/stable", 79 | "future-beta": "4.0/beta" 80 | } 81 | }, 82 | "ckf-latest-beta": { 83 | "ckf":"latest/beta", 84 | "k8s": { 85 | "charmed-kubernetes": { 86 | "min-supported": "1.29", 87 | "max-supported": "1.32" 88 | }, 89 | "microk8s": { 90 | "min-supported": "1.29", 91 | "max-supported": "1.32" 92 | }, 93 | "aks": { 94 | "min-supported": "1.29", 95 | "max-supported": "1.32" 96 | }, 97 | "eks": { 98 | "min-supported": "1.29", 99 | "max-supported": "1.32" 100 | } 101 | }, 102 | "juju": { 103 | "current-edge": "3.6/edge", 104 | "current-stable": "3.6/stable", 105 | "future-beta": "4.0/beta" 106 | } 107 | }, 108 | "ckf-latest-beta-mlflow-2.15-stable": { 109 | "ckf":"latest/beta", 110 | "mlflow": "2.15/stable", 111 | "k8s": { 112 | "charmed-kubernetes": { 113 | "min-supported": "1.29", 114 | "max-supported": "1.32" 115 | }, 116 | "microk8s": { 117 | "min-supported": "1.29", 118 | "max-supported": "1.32" 119 | }, 120 | "aks": { 121 | "min-supported": "1.29", 122 | "max-supported": "1.32" 123 | }, 124 | "eks": { 125 | "min-supported": "1.29", 126 | "max-supported": "1.32" 127 | } 128 | }, 129 | "juju": { 130 | "current-edge": "3.6/edge", 131 | "current-stable": "3.6/stable", 132 | "future-beta": "4.0/beta" 133 | } 134 | }, 135 | "ckf-latest-edge-mlflow-latest-edge": { 136 | "ckf":"latest/edge", 137 | "mlflow": "latest/edge", 138 | "k8s": { 139 | "charmed-kubernetes": { 140 | "min-supported": "1.27", 141 | "max-supported": "1.30" 142 | }, 143 | "microk8s": { 144 | "min-supported": "1.27", 145 | "max-supported": "1.30" 146 | }, 147 | "aks": { 148 | "min-supported": "1.27", 149 | "max-supported": "1.30" 150 | }, 151 | "eks": { 152 | "min-supported": "1.27", 153 | "max-supported": "1.30" 154 | } 155 | }, 156 | "juju": { 157 | "current-edge": "3.6/edge", 158 | "current-stable": "3.6/stable", 159 | "future-beta": "4.0/beta" 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | flaky==3.7.0 3 | kfp==1.8.0 4 | lightkube<0.11.0 5 | pytest==6.2.3 6 | pyyaml==5.3.1 7 | selenium==3.141.0 8 | selenium-wire==4.3.1 9 | sh==1.14.1 10 | -------------------------------------------------------------------------------- /tests-bundle/1.6/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/bundle-kubeflow/8eeddb31e4daf34de6d5e58fdc00c74a9679753d/tests-bundle/1.6/__init__.py -------------------------------------------------------------------------------- /tests-bundle/1.6/helpers.py: -------------------------------------------------------------------------------- 1 | from lightkube.resources.core_v1 import Service 2 | 3 | 4 | def get_ingress_url(lightkube_client, model_name): 5 | gateway_svc = lightkube_client.get( 6 | Service, "istio-ingressgateway-workload", namespace=model_name 7 | ) 8 | 9 | public_url = f"http://{gateway_svc.status.loadBalancer.ingress[0].ip}.nip.io" 10 | return public_url 11 | -------------------------------------------------------------------------------- /tests-bundle/1.6/requirements.txt: -------------------------------------------------------------------------------- 1 | lightkube 2 | pytest 3 | pytest-operator 4 | kfp<2.0.0 5 | juju<3.0 -------------------------------------------------------------------------------- /tests-bundle/1.6/test_1dot6.py: -------------------------------------------------------------------------------- 1 | import shlex 2 | 3 | import pytest 4 | from helpers import get_ingress_url 5 | from pytest_operator.plugin import OpsTest 6 | 7 | USERNAME = "admin" 8 | PASSWORD = "secret" 9 | 10 | 11 | @pytest.mark.abort_on_fail 12 | @pytest.mark.deploy 13 | async def test_deploy_1dot6(ops_test: OpsTest, lightkube_client, deploy_cmd): 14 | print(f"Deploying bundle to {ops_test.model_full_name} using cmd '{deploy_cmd}'") 15 | rc, stdout, stderr = await ops_test.run(*shlex.split(deploy_cmd)) 16 | 17 | if rc != 0: 18 | raise Exception(f"Deploy failed with code: {rc}, \nstdout: {stdout}, \nstderr {stderr}") 19 | 20 | print("Waiting for bundle to be ready") 21 | await ops_test.model.wait_for_idle( 22 | status="active", 23 | raise_on_blocked=False, 24 | raise_on_error=False, 25 | timeout=6000, 26 | ) 27 | print("All applications are active") 28 | 29 | url = get_ingress_url(lightkube_client, ops_test.model_name) 30 | 31 | print("Update Dex and OIDC configs") 32 | await ops_test.model.applications["dex-auth"].set_config( 33 | {"public-url": url, "static-username": USERNAME, "static-password": PASSWORD} 34 | ) 35 | await ops_test.model.applications["oidc-gatekeeper"].set_config({"public-url": url}) 36 | 37 | await ops_test.model.wait_for_idle( 38 | status="active", 39 | raise_on_blocked=False, 40 | raise_on_error=True, 41 | timeout=600, 42 | ) 43 | -------------------------------------------------------------------------------- /tests-bundle/1.7/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/bundle-kubeflow/8eeddb31e4daf34de6d5e58fdc00c74a9679753d/tests-bundle/1.7/__init__.py -------------------------------------------------------------------------------- /tests-bundle/1.7/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | from datetime import datetime 4 | from pathlib import Path 5 | 6 | import pytest 7 | from selenium import webdriver 8 | 9 | from selenium.webdriver.firefox.options import Options 10 | from selenium.webdriver.firefox.service import Service 11 | from webdriver_manager.firefox import GeckoDriverManager 12 | 13 | DEBUG = os.environ.get("DEBUG_KF", False) 14 | 15 | 16 | @pytest.fixture(scope='session') 17 | def driver(request): 18 | """Set up webdriver fixture.""" 19 | options = Options() 20 | if not DEBUG: 21 | print("Running in headless mode") 22 | options.add_argument('--headless') 23 | options.add_argument('--disable-gpu') 24 | else: 25 | options.log.level = "trace" 26 | 27 | options.add_argument('--no-sandbox') 28 | options.add_argument('--disable-dev-shm-usage') 29 | options.binary_location = "/snap/bin/firefox" 30 | 31 | # must create path, 32 | # see https://github.com/mozilla/geckodriver/releases/tag/v0.31.0 33 | tmp_user = Path("~/tmp").expanduser() 34 | os.environ["TMPDIR"] = str(tmp_user) 35 | tmp_user.mkdir(parents=True, exist_ok=True) 36 | 37 | service = Service(GeckoDriverManager().install()) 38 | driver = webdriver.Firefox(options=options, service=service) 39 | driver.set_window_size(1920, 1080) 40 | driver.maximize_window() 41 | driver.implicitly_wait(10) 42 | 43 | yield driver 44 | driver.quit() 45 | 46 | 47 | @pytest.hookimpl(tryfirst=True, hookwrapper=True) 48 | def pytest_runtest_makereport(item, call): 49 | """Set up a hook to be able to check if a test has failed.""" 50 | # execute all other hooks to obtain the report object 51 | outcome = yield 52 | rep = outcome.get_result() 53 | 54 | # set a report attribute for each phase of a call, which can 55 | # be "setup", "call", "teardown" 56 | 57 | setattr(item, "rep_" + rep.when, rep) 58 | 59 | 60 | @pytest.fixture(scope="function") 61 | def failed_check(request): 62 | """Check if a test has failed and take a screenshot if it has.""" 63 | yield 64 | if request.node.rep_setup.passed: 65 | if request.node.rep_call.failed: 66 | driver = request.node.funcargs['driver'] 67 | take_screenshot(driver, request.node.name) 68 | print("executing test failed", request.node.nodeid) 69 | 70 | 71 | def take_screenshot(driver, node_name): 72 | time.sleep(1) 73 | Path("sel-screenshots").mkdir(parents=True, exist_ok=True) 74 | file_name = f'sel-screenshots/{node_name}_{datetime.today().strftime("%m-%d_%H-%M")}.png' 75 | print(f"Taking screenshot: {file_name}") 76 | driver.save_screenshot(file_name) 77 | -------------------------------------------------------------------------------- /tests-bundle/1.7/helpers.py: -------------------------------------------------------------------------------- 1 | from lightkube.resources.core_v1 import Service 2 | 3 | 4 | def get_ingress_url(lightkube_client, model_name): 5 | gateway_svc = lightkube_client.get( 6 | Service, "istio-ingressgateway-workload", namespace=model_name 7 | ) 8 | 9 | public_url = f"http://{gateway_svc.status.loadBalancer.ingress[0].ip}.nip.io" 10 | return public_url 11 | 12 | 13 | def from_minutes(minutes): 14 | return minutes * 60 15 | -------------------------------------------------------------------------------- /tests-bundle/1.7/requirements.txt: -------------------------------------------------------------------------------- 1 | lightkube 2 | pytest 3 | pytest-operator 4 | kfp<2.0.0 5 | juju<3.0.0 6 | selenium>=4.8.3 7 | webdriver_manager>=3.8.5 8 | -------------------------------------------------------------------------------- /tests-bundle/1.7/test_release_1-7.py: -------------------------------------------------------------------------------- 1 | import shlex 2 | 3 | import pytest 4 | from helpers import get_ingress_url, from_minutes 5 | from pytest_operator.plugin import OpsTest 6 | 7 | USERNAME = "admin" 8 | PASSWORD = "admin" 9 | 10 | 11 | @pytest.mark.abort_on_fail 12 | @pytest.mark.deploy 13 | async def test_deploy(ops_test: OpsTest, lightkube_client, deploy_cmd): 14 | print(f"Deploying bundle to {ops_test.model_full_name} using cmd '{deploy_cmd}'") 15 | rc, stdout, stderr = await ops_test.run(*shlex.split(deploy_cmd)) 16 | 17 | if rc != 0: 18 | raise Exception(f"Deploy failed with code: {rc}, \nstdout: {stdout}, \nstderr {stderr}") 19 | 20 | print("Waiting for bundle to be ready") 21 | apps = [ 22 | 'admission-webhook', 23 | 'argo-controller', 24 | 'argo-server', 25 | 'dex-auth', 26 | # 'istio-ingressgateway', # this is expected to wait for OIDC 27 | # 'istio-pilot', # this is expected to wait for OIDC 28 | 'jupyter-controller', 29 | 'jupyter-ui', 30 | 'katib-controller', 31 | 'katib-db', 32 | 'katib-db-manager', 33 | 'katib-ui', 34 | 'kfp-api', 35 | 'kfp-db', 36 | 'kfp-persistence', 37 | 'kfp-profile-controller', 38 | 'kfp-schedwf', 39 | 'kfp-ui', 40 | 'kfp-viewer', 41 | 'kfp-viz', 42 | 'knative-eventing', 43 | 'knative-operator', 44 | 'knative-serving', 45 | 'kserve-controller', 46 | 'kubeflow-dashboard', 47 | # due to https://github.com/canonical/kubeflow-profiles-operator/issues/117 48 | # 'kubeflow-profiles', 49 | 'kubeflow-roles', 50 | 'kubeflow-volumes', 51 | 'metacontroller-operator', 52 | 'minio', 53 | # 'oidc-gatekeeper', # this is expected to wait for public-url config 54 | 'seldon-controller-manager', 55 | # 'tensorboard-controller', # this is expected to wait for config 56 | 'tensorboards-web-app', 57 | 'training-operator', 58 | ] 59 | await ops_test.model.wait_for_idle( 60 | apps=apps, 61 | status="active", 62 | raise_on_blocked=False, 63 | raise_on_error=False, 64 | timeout=from_minutes(minutes=180), 65 | ) 66 | print("All applications are active") 67 | 68 | url = get_ingress_url(lightkube_client, ops_test.model_name) 69 | 70 | print("Update Dex and OIDC configs") 71 | await ops_test.model.applications["dex-auth"].set_config( 72 | {"public-url": url, "static-username": USERNAME, "static-password": PASSWORD} 73 | ) 74 | await ops_test.model.applications["oidc-gatekeeper"].set_config({"public-url": url}) 75 | 76 | # append apps since they should be configured now 77 | apps.append("oidc-gatekeeper") 78 | apps.append("istio-ingressgateway") 79 | apps.append("istio-pilot") 80 | await ops_test.model.wait_for_idle( 81 | apps=apps, 82 | status="active", 83 | raise_on_blocked=False, 84 | raise_on_error=False, 85 | timeout=from_minutes(minutes=100), 86 | ) 87 | 88 | print("dispatch istio config changed hook") 89 | rc, stdout, stderr = await ops_test.run( 90 | *shlex.split( 91 | 'juju run --unit istio-pilot/0 -- "export JUJU_DISPATCH_PATH=hooks/config-changed; ./dispatch"' 92 | ) 93 | ) 94 | 95 | if rc != 0: 96 | raise Exception(f"Dispatch failed with code: {rc}, \nstdout: {stdout}, \nstderr {stderr}") 97 | 98 | # now wait for all apps 99 | await ops_test.model.wait_for_idle( 100 | status="active", 101 | raise_on_blocked=False, 102 | raise_on_error=True, 103 | timeout=from_minutes(minutes=30), 104 | idle_period=from_minutes(minutes=3), 105 | ) 106 | 107 | 108 | @pytest.mark.deploy 109 | @pytest.mark.abort_on_fail 110 | async def test_profile_creation_action(ops_test: OpsTest): 111 | """Test that the create-profile action works. 112 | 113 | Also, this will allow to test selenium and skip welcome page in dashboard UI. 114 | """ 115 | action = await ops_test.model.applications["kubeflow-profiles"].units[0].run_action( 116 | "create-profile", profilename=USERNAME, username=USERNAME 117 | ) 118 | await action.wait() 119 | -------------------------------------------------------------------------------- /tests-bundle/1.8/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/bundle-kubeflow/8eeddb31e4daf34de6d5e58fdc00c74a9679753d/tests-bundle/1.8/__init__.py -------------------------------------------------------------------------------- /tests-bundle/1.8/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | from datetime import datetime 4 | from pathlib import Path 5 | 6 | import pytest 7 | from selenium import webdriver 8 | 9 | from selenium.webdriver.firefox.options import Options 10 | from selenium.webdriver.firefox.service import Service 11 | from webdriver_manager.firefox import GeckoDriverManager 12 | 13 | DEBUG = os.environ.get("DEBUG_KF", False) 14 | 15 | 16 | @pytest.fixture(scope='session') 17 | def driver(request): 18 | """Set up webdriver fixture.""" 19 | options = Options() 20 | if not DEBUG: 21 | print("Running in headless mode") 22 | options.add_argument('--headless') 23 | options.add_argument('--disable-gpu') 24 | else: 25 | options.log.level = "trace" 26 | 27 | options.add_argument('--no-sandbox') 28 | options.add_argument('--disable-dev-shm-usage') 29 | options.binary_location = "/snap/bin/firefox" 30 | 31 | # must create path, 32 | # see https://github.com/mozilla/geckodriver/releases/tag/v0.31.0 33 | tmp_user = Path("~/tmp").expanduser() 34 | os.environ["TMPDIR"] = str(tmp_user) 35 | tmp_user.mkdir(parents=True, exist_ok=True) 36 | 37 | service = Service(GeckoDriverManager().install()) 38 | driver = webdriver.Firefox(options=options, service=service) 39 | driver.set_window_size(1920, 1080) 40 | driver.maximize_window() 41 | driver.implicitly_wait(10) 42 | 43 | yield driver 44 | driver.quit() 45 | 46 | 47 | @pytest.hookimpl(tryfirst=True, hookwrapper=True) 48 | def pytest_runtest_makereport(item, call): 49 | """Set up a hook to be able to check if a test has failed.""" 50 | # execute all other hooks to obtain the report object 51 | outcome = yield 52 | rep = outcome.get_result() 53 | 54 | # set a report attribute for each phase of a call, which can 55 | # be "setup", "call", "teardown" 56 | 57 | setattr(item, "rep_" + rep.when, rep) 58 | 59 | 60 | @pytest.fixture(scope="function") 61 | def failed_check(request): 62 | """Check if a test has failed and take a screenshot if it has.""" 63 | yield 64 | if request.node.rep_setup.passed: 65 | if request.node.rep_call.failed: 66 | driver = request.node.funcargs['driver'] 67 | take_screenshot(driver, request.node.name) 68 | print("executing test failed", request.node.nodeid) 69 | 70 | 71 | def take_screenshot(driver, node_name): 72 | time.sleep(1) 73 | Path("sel-screenshots").mkdir(parents=True, exist_ok=True) 74 | file_name = f'sel-screenshots/{node_name}_{datetime.today().strftime("%m-%d_%H-%M")}.png' 75 | print(f"Taking screenshot: {file_name}") 76 | driver.save_screenshot(file_name) 77 | -------------------------------------------------------------------------------- /tests-bundle/1.8/helpers.py: -------------------------------------------------------------------------------- 1 | from lightkube.resources.core_v1 import Service 2 | 3 | 4 | def get_ingress_url(lightkube_client, model_name): 5 | gateway_svc = lightkube_client.get( 6 | Service, "istio-ingressgateway-workload", namespace=model_name 7 | ) 8 | 9 | public_url = f"http://{gateway_svc.status.loadBalancer.ingress[0].ip}.nip.io" 10 | return public_url 11 | 12 | 13 | def from_minutes(minutes): 14 | return minutes * 60 15 | -------------------------------------------------------------------------------- /tests-bundle/1.8/requirements.txt: -------------------------------------------------------------------------------- 1 | lightkube 2 | pytest 3 | pytest-operator 4 | kfp<2.0.0 5 | juju<4.0 6 | selenium>=4.8.3 7 | webdriver_manager>=3.8.5 8 | -------------------------------------------------------------------------------- /tests-bundle/1.8/test_release_1-8.py: -------------------------------------------------------------------------------- 1 | import shlex 2 | 3 | import pytest 4 | from helpers import get_ingress_url, from_minutes 5 | from pytest_operator.plugin import OpsTest 6 | 7 | USERNAME = "admin" 8 | PASSWORD = "admin" 9 | 10 | 11 | @pytest.mark.abort_on_fail 12 | @pytest.mark.deploy 13 | async def test_deploy(ops_test: OpsTest, lightkube_client, deploy_cmd): 14 | print(f"Deploying bundle to {ops_test.model_full_name} using cmd '{deploy_cmd}'") 15 | rc, stdout, stderr = await ops_test.run(*shlex.split(deploy_cmd)) 16 | 17 | if rc != 0: 18 | raise Exception(f"Deploy failed with code: {rc}, \nstdout: {stdout}, \nstderr {stderr}") 19 | 20 | print("Waiting for bundle to be ready") 21 | apps = [ 22 | 'admission-webhook', 23 | 'argo-controller', 24 | # 'dex-auth', # this is expected to wait for OIDC 25 | 'envoy', 26 | # 'istio-ingressgateway', # this is expected to wait for OIDC 27 | # 'istio-pilot', # this is expected to wait for OIDC 28 | 'jupyter-controller', 29 | 'jupyter-ui', 30 | 'katib-controller', 31 | 'katib-db', 32 | 'katib-db-manager', 33 | 'katib-ui', 34 | 'kfp-api', 35 | 'kfp-db', 36 | 'kfp-metadata-writer', 37 | 'kfp-persistence', 38 | 'kfp-profile-controller', 39 | 'kfp-schedwf', 40 | 'kfp-ui', 41 | 'kfp-viewer', 42 | 'kfp-viz', 43 | 'knative-eventing', 44 | 'knative-operator', 45 | 'knative-serving', 46 | 'kserve-controller', 47 | 'kubeflow-dashboard', 48 | # due to https://github.com/canonical/kubeflow-profiles-operator/issues/117 49 | # 'kubeflow-profiles', 50 | 'kubeflow-roles', 51 | 'kubeflow-volumes', 52 | 'metacontroller-operator', 53 | 'minio', 54 | 'mlmd', 55 | # 'oidc-gatekeeper', # this is expected to wait for public-url config 56 | 'seldon-controller-manager', 57 | # 'tensorboard-controller', # this is expected to wait for config 58 | 'tensorboards-web-app', 59 | 'training-operator', 60 | ] 61 | await ops_test.model.wait_for_idle( 62 | apps=apps, 63 | status="active", 64 | raise_on_blocked=False, 65 | raise_on_error=False, 66 | timeout=from_minutes(minutes=30), 67 | ) 68 | print("All applications are active") 69 | 70 | url = get_ingress_url(lightkube_client, ops_test.model_name) 71 | 72 | print("Update Dex and OIDC configs") 73 | await ops_test.model.applications["dex-auth"].set_config( 74 | {"public-url": url, "static-username": USERNAME, "static-password": PASSWORD} 75 | ) 76 | await ops_test.model.applications["oidc-gatekeeper"].set_config({"public-url": url}) 77 | 78 | # append apps since they should be configured now 79 | apps.append("dex-auth") 80 | apps.append("oidc-gatekeeper") 81 | apps.append("istio-ingressgateway") 82 | apps.append("istio-pilot") 83 | apps.append("kubeflow-profiles") 84 | apps.append("tensorboard-controller") 85 | await ops_test.model.wait_for_idle( 86 | apps=apps, 87 | status="active", 88 | raise_on_blocked=False, 89 | raise_on_error=False, 90 | timeout=from_minutes(minutes=30), 91 | ) 92 | 93 | if rc != 0: 94 | raise Exception(f"Dispatch failed with code: {rc}, \nstdout: {stdout}, \nstderr {stderr}") 95 | 96 | # now wait for all apps 97 | await ops_test.model.wait_for_idle( 98 | status="active", 99 | raise_on_blocked=False, 100 | raise_on_error=True, 101 | timeout=from_minutes(minutes=30), 102 | ) 103 | 104 | 105 | @pytest.mark.deploy 106 | @pytest.mark.abort_on_fail 107 | async def test_profile_creation_action(ops_test: OpsTest): 108 | """Test that the create-profile action works. 109 | 110 | Also, this will allow to test selenium and skip welcome page in dashboard UI. 111 | """ 112 | action = await ops_test.model.applications["kubeflow-profiles"].units[0].run_action( 113 | "create-profile", profilename=USERNAME, username=USERNAME 114 | ) 115 | await action.wait() 116 | -------------------------------------------------------------------------------- /tests-bundle/README.md: -------------------------------------------------------------------------------- 1 | # Tests Bundle 2 | 3 | This folder contains the full bundle tests for Kubeflow. These tests are designed to deploy Kubeflow and run various tests on the deployed Kubeflow, including Selenium tests for interacting with the Kubeflow UI. The long-term vision for this bundle is to add hundreds of tests to thoroughly test the entire Kubeflow bundle before deployment. 4 | 5 | ## Test Structure 6 | 7 | The tests are organized into directories based on the version of Kubeflow. Each version has its own suite of tests, although there may be similarities between versions. Minimally, each version's directory contains a Python file for deploying Kubeflow and one or more other files representing other tests. 8 | 9 | The test order is enforced to ensure that the deployment test runs and succeeds before further tests can run. This order is defined with the help of pytest markers, where the "deploy" tests are executed first. 10 | 11 | ## How to Run the Tests as a GitHub Action 12 | 13 | These steps assume that you are familiar with GitHub Actions and have the necessary permissions to trigger and run actions in the repository. 14 | 15 | To run the tests as a GitHub Action, follow these steps: 16 | 17 | 1. Trigger the full bundle tests [GitHub action](/../../actions/workflows/full-bundle-tests.yaml) by selecting `Run workflow`. 18 | 2. Provide the following inputs: 19 | 20 | - **Folder**: Path from the content root to the directory for the version of Kubeflow you want to test. Example: `tests-bundle/1.7`. 21 | 22 | - **Deployment Source**: The deployment source for `juju deploy`. This will either be a published channel e.g. `--channel=1.7/stable` or the path from the content root of an unpublished `bundle.yaml` file within the repo. 23 | 24 | 3. The GitHub action will start executing on Canonical's self-hosted runners and perform the following steps: 25 | 26 | - Set up the prerequisite installation and configuration for Kubeflow, including microk8s and Juju. 27 | - Run the bundle tests, which are Python tests, to deploy Kubeflow and perform further tests on the deployed Kubeflow. 28 | - Perform post-processing of the test results and gather information in case of failures. 29 | 30 | Please refer to the GitHub Action YAML file in the parent repository for more detailed information on the action setup and configuration. 31 | -------------------------------------------------------------------------------- /tests-bundle/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/bundle-kubeflow/8eeddb31e4daf34de6d5e58fdc00c74a9679753d/tests-bundle/__init__.py -------------------------------------------------------------------------------- /tests-bundle/conftest.py: -------------------------------------------------------------------------------- 1 | import lightkube 2 | import pytest 3 | from _pytest.config.argparsing import Parser 4 | 5 | 6 | def pytest_addoption(parser: Parser): 7 | parser.addoption( 8 | "--file", 9 | help="Path to bundle file to use as the template for tests. This must include all charms" 10 | "built by this bundle, where the locally built charms will replace those specified. " 11 | "This is useful for testing this bundle against different external dependencies. " 12 | "e.g. ./releases/1.6/kubeflow-bundle.yaml", 13 | ) 14 | 15 | parser.addoption( 16 | "--channel", 17 | help="Kubeflow channels, e.g. latest/stable, 1.6/beta", 18 | ) 19 | 20 | 21 | @pytest.fixture(scope="module") 22 | def lightkube_client(ops_test): 23 | yield lightkube.Client(namespace=ops_test.model_name, trust_env=False) 24 | 25 | 26 | @pytest.fixture(scope="module") 27 | def deploy_cmd(request, ops_test): 28 | if ops_test.model_name != "kubeflow": 29 | raise ValueError("kfp must be deployed to namespace kubeflow") 30 | 31 | bundle_file = request.config.getoption("file", default=None) 32 | channel = request.config.getoption("channel", default=None) 33 | 34 | if (not bundle_file and not channel) or (bundle_file and channel): 35 | raise ValueError("One of --file or --channel is required") 36 | 37 | model = ops_test.model_full_name 38 | if bundle_file: 39 | # pytest automatically prune path to relative paths without `./` 40 | # juju deploys requires `./` 41 | cmd = f"juju deploy ./{bundle_file} -m {model} --trust " 42 | elif channel: 43 | cmd = f"juju deploy kubeflow -m {model} --trust --channel {channel}" 44 | 45 | yield cmd 46 | -------------------------------------------------------------------------------- /tests/Dockerfile.mnist: -------------------------------------------------------------------------------- 1 | # docker build tests/ -f tests/Dockerfile.mnist -t upload.rocks.canonical.com:5000/kubeflow/examples/mnist-test:latest 2 | # docker push upload.rocks.canonical.com:5000/kubeflow/examples/mnist-test:latest 3 | 4 | FROM tensorflow/tensorflow:1.14.0-py3 5 | 6 | RUN pip install requests kubernetes 7 | -------------------------------------------------------------------------------- /tests/Dockerfile.object_detection: -------------------------------------------------------------------------------- 1 | FROM tensorflow/tensorflow:1.15.2-gpu-py3 2 | 3 | RUN apt update \ 4 | && apt install -y \ 5 | git \ 6 | ffmpeg libsm6 libxext6 \ 7 | wget \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN git clone https://github.com/tensorflow/models.git /models 11 | 12 | WORKDIR /models/research/object_detection 13 | 14 | RUN wget -O protobuf.zip https://github.com/google/protobuf/releases/download/v3.0.0/protoc-3.0.0-linux-x86_64.zip \ 15 | && unzip -p protobuf.zip bin/protoc > /usr/bin/protoc \ 16 | && chmod +x /usr/bin/protoc \ 17 | && cd ../ \ 18 | && protoc object_detection/protos/*.proto --python_out=. 19 | 20 | RUN pip3 install Cython contextlib2 matplotlib pillow lxml minio requests tf_slim scipy lvis 21 | 22 | ENV PYTHONPATH "${PYTHONPATH}:/models/research/:/models/research/slim" 23 | 24 | CMD ['python', 'object_detection/builders/model_builder_test.py'] 25 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/bundle-kubeflow/8eeddb31e4daf34de6d5e58fdc00c74a9679753d/tests/__init__.py -------------------------------------------------------------------------------- /tests/airgapped/1.8/katib/README.md: -------------------------------------------------------------------------------- 1 | # Testing Katib in airgapped 2 | 3 | This directory is dedicated to testing Katib in an airgapped environment. 4 | 5 | ## Prerequisites 6 | 7 | Prepare the airgapped environment and deploy CKF by following the steps in [Airgapped test scripts](https://github.com/canonical/bundle-kubeflow/tree/main/tests/airgapped#testing-airgapped-installation). 8 | 9 | Once you run the test scripts, the `kubeflowkatib/simple-pbt:v0.16.0` image used in the `simple-pbt` experiment will be included in your airgapped environment. It's specifically added in the [`get_all_images.py` script](../../../../scripts/get_all_images.py). 10 | 11 | ## How to test Katib in an Airgapped environment 12 | 1. Connect to the dashboard by visiting the IP of your airgapped VM. To get the IP run: 13 | ``` 14 | lxc ls | grep eth0 15 | ``` 16 | Look for the IP of the `airgapped-microk8s` instance. 17 | 18 | 2. Log in to the dashboard and create a Profile. 19 | 3. Go to `Experiments (AutoML)` tab from the dashboard sidebar. 20 | 4. Click `New Experiment` then `Edit and submit YAML`. 21 | 5. Paste the contents of the `simple-pbt.yaml` file found in this directory. 22 | 6. Create the Experiment, and monitor its status to check it is `Succeeded`. 23 | -------------------------------------------------------------------------------- /tests/airgapped/1.8/katib/simple-pbt.yaml: -------------------------------------------------------------------------------- 1 | # Source: katib/examples/v1beta1/hp-tuning/simple-pbt.yaml 2 | # This example is slightly modified from upstream to consume less resources 3 | # and disable istio sidecar. 4 | # There's a `modified` comment where we diverge from upstream. 5 | # When updating this file, make sure to keep those modifications. 6 | --- 7 | apiVersion: kubeflow.org/v1beta1 8 | kind: Experiment 9 | metadata: 10 | name: simple-pbt 11 | spec: 12 | maxTrialCount: 1 # modified 13 | parallelTrialCount: 1 # modified 14 | maxFailedTrialCount: 1 # modified 15 | resumePolicy: FromVolume 16 | objective: 17 | type: maximize 18 | goal: 0.99 19 | objectiveMetricName: Validation-accuracy 20 | algorithm: 21 | algorithmName: pbt 22 | algorithmSettings: 23 | - name: suggestion_trial_dir 24 | value: /var/log/katib/checkpoints/ 25 | - name: n_population 26 | value: '40' 27 | - name: truncation_threshold 28 | value: '0.2' 29 | parameters: 30 | - name: lr 31 | parameterType: double 32 | feasibleSpace: 33 | min: '0.0001' 34 | max: '0.02' 35 | step: '0.0001' 36 | trialTemplate: 37 | primaryContainerName: training-container 38 | trialParameters: 39 | - name: learningRate 40 | description: Learning rate for training the model 41 | reference: lr 42 | trialSpec: 43 | apiVersion: batch/v1 44 | kind: Job 45 | spec: 46 | template: 47 | spec: 48 | containers: 49 | - name: training-container 50 | image: 172.17.0.2:5000/kubeflowkatib/simple-pbt:v0.16.0 51 | command: 52 | - "python3" 53 | - "/opt/pbt/pbt_test.py" 54 | - "--epochs=20" 55 | - "--lr=${trialParameters.learningRate}" 56 | - "--checkpoint=/var/log/katib/checkpoints/" 57 | resources: # modified 58 | limits: # modified 59 | memory: "2Gi" # modified 60 | cpu: "1" # modified 61 | restartPolicy: Never 62 | metadata: # modified 63 | annotations: # modified 64 | sidecar.istio.io/inject: "false" # modified 65 | -------------------------------------------------------------------------------- /tests/airgapped/1.8/knative/README.md: -------------------------------------------------------------------------------- 1 | # Testing Knative in airgapped 2 | 3 | This directory is dedicated to testing Knative in an airgapped environment. 4 | 5 | ## Prerequisites 6 | 7 | Prepare the airgapped environment and deploy CKF by following the steps in [Airgapped test scripts](https://github.com/canonical/bundle-kubeflow/tree/main/tests/airgapped#testing-airgapped-installation). 8 | 9 | Once you run the test scripts, the `knative/helloworld-go` image used in the `helloworld` example will be included in your airgapped environment. It's specifically added in the [`get_all_images.py` script](../../../../scripts/get_all_images.py). 10 | 11 | ## How to test Knative in an Airgapped environment 12 | 1. Connect to the dashboard by visiting the IP of your airgapped VM. To get the IP run: 13 | ``` 14 | lxc ls | grep eth0 15 | ``` 16 | 2. Log in to the dashboard and create a Profile. 17 | 3. Apply the `helloworld.yaml` found in this directory to your Profile's Namespace: 18 | ``` 19 | kubectl apply -f ./helloworld.yaml -n 20 | ``` 21 | 4. Wait for the Knative Service to be `Ready` 22 | ``` 23 | kubectl get ksvc -n 24 | ``` 25 | Expected output: 26 | ``` 27 | NAME URL LATESTCREATED LATESTREADY READY REASON 28 | helloworld http://helloworld.admin.10.64.140.43.nip.io helloworld-00001 helloworld-00001 True 29 | ``` 30 | 5. Curl the Knative Service using the `URL` from the previous step 31 | ``` 32 | curl -L http://helloworld.admin.10.64.140.43.nip.io 33 | ``` 34 | Expected output: 35 | ``` 36 | Hello World! 37 | ``` 38 | -------------------------------------------------------------------------------- /tests/airgapped/1.8/knative/helloworld.yaml: -------------------------------------------------------------------------------- 1 | # This example is based on the [Knative Serving tutorial](https://knative.dev/docs/getting-started/first-service/#__tabbed_1_2) 2 | apiVersion: serving.knative.dev/v1 3 | kind: Service 4 | metadata: 5 | name: hello 6 | spec: 7 | template: 8 | metadata: 9 | labels: 10 | # Disable istio sidecar due to https://github.com/canonical/kserve-operators/issues/216 11 | sidecar.istio.io/inject : "false" 12 | spec: 13 | containers: 14 | - image: 172.17.0.2:5000/knative/helloworld-go:latest 15 | ports: 16 | - containerPort: 8080 17 | env: 18 | - name: TARGET 19 | value: "World" 20 | -------------------------------------------------------------------------------- /tests/airgapped/1.8/pipelines/README.md: -------------------------------------------------------------------------------- 1 | # Testing Pipelines in Airgapped 2 | 3 | ## The `kfp-airgapped-ipynb` Notebook 4 | To test Pipelines in Airgapped, we are using the Notebook in this directory. It contains the Data passing pipeline example, with the configuration of the Pipeline components to use the `pipelines-runner` [image](./pipelines-runner/README.md). 5 | 6 | The `pipelines-runner` image will be included in your airgapped environment given that you used the [Airgapped test scripts](../../README.md). It's specifically added in the [`get_all_images.py` script](../../../../scripts/airgapped/get_all_images.py). 7 | 8 | ## How to test Pipelines in an Airgapped environment 9 | 1. Prepare the airgapped environment and Deploy CKF by following the steps in [Airgapped test scripts](../../README.md). 10 | 2. Connect to the dashboard by visiting the IP of your airgapped VM. To get the IP run: 11 | ``` 12 | lxc ls | grep eth0 13 | ``` 14 | 3. Go to the `Notebooks` tab, create a new Notebook server and choose `jupyter-tensorflow-full` image from the dropdown. 15 | 4. Connect to the Notebook server and upload the `kfp-airgapped-ipynb` Notebook. 16 | 5. Run the Notebook. 17 | 6. Click on `Run details` from the output of the last cell in the Notebook to see the status of the Pipeline run. 18 | -------------------------------------------------------------------------------- /tests/airgapped/1.8/pipelines/pipelines-runner/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.9-alpine 2 | RUN pip install kfp==2.4.0 kfp-server-api==2.0.3 3 | CMD ["python3"] -------------------------------------------------------------------------------- /tests/airgapped/1.8/pipelines/pipelines-runner/README.md: -------------------------------------------------------------------------------- 1 | # `charmedkubeflow/pipelines-runner` image 2 | 3 | A simple image that contains the `kfp` and `kfp-server-api` packages installed on top of a `python` base image. 4 | The purpose of this image is to use it as the `base-image` for Pipelines components to enable running a Pipeline in an airgapped environment. 5 | 6 | ## How to publish the image 7 | 1. Build the image with the tag, for example for 1.8 we use the tag `ckf-1.8`: 8 | ``` 9 | docker build -t charmedkubeflow/pipelines-runner:ckf-1.8 . 10 | ``` 11 | 2. Login to docker as `charmedkubeflow` with `docker login` 12 | 3. Publish the image to Docker Hub 13 | ``` 14 | docker push charmedkubeflow/pipelines-runner:ckf-1.8 15 | ``` -------------------------------------------------------------------------------- /tests/airgapped/1.8/testing-images.txt: -------------------------------------------------------------------------------- 1 | charmedkubeflow/pipelines-runner:ckf-1.8 2 | docker.io/kubeflowkatib/simple-pbt:v0.16.0 3 | ghcr.io/knative/helloworld-go:latest 4 | gcr.io/kubeflow-ci/tf-mnist-with-summaries:1.0 5 | -------------------------------------------------------------------------------- /tests/airgapped/1.8/training/README.md: -------------------------------------------------------------------------------- 1 | # Testing training operator in airgapped 2 | 3 | This directory is dedicated to testing training operator in an airgapped environment. 4 | 5 | ## Prerequisites 6 | 7 | Prepare the airgapped environment and deploy CKF by following the steps in [Airgapped test scripts](../../README.md#testing-airgapped-installation). 8 | 9 | Once you run the test scripts, the `kubeflow-ci/tf-mnist-with-summaries:1.0` image used in the `tfjob-simple` training job will be included in your airgapped environment. It's specifically added in the [`get_all_images.py` script](../../../../scripts/get_all_images.py). 10 | 11 | ## How to test training operator in an Airgapped environment 12 | 1. Connect to the dashboard by visiting the IP of your airgapped VM. To get the IP run: 13 | ``` 14 | lxc ls | grep eth0 15 | ``` 16 | Look for the IP of the `airgapped-microk8s` instance. 17 | 18 | 2. Log in to the dashboard and create a Profile. 19 | 3. Apply the `tfjob-simple.yaml` found in this directory to your Profile's Namespace: 20 | ``` 21 | microk8s kubectl apply -f ./tfjob-simple.yaml -n 22 | ``` 23 | 4. Wait for the tfjob to be `Succeeded` 24 | ``` 25 | microk8s kubectl get tfjob -n 26 | ``` 27 | Expected output: 28 | ``` 29 | NAME STATE AGE 30 | tfjob-simple Succeeded 2m5s 31 | ``` 32 | -------------------------------------------------------------------------------- /tests/airgapped/1.8/training/tfjob-simple.yaml: -------------------------------------------------------------------------------- 1 | # Based on the upstream [TFJob example](https://github.com/kubeflow/training-operator/blob/964a6e836eedff11edfe79cc9f4e5b7c623cbe88/examples/tensorflow/image-classification/create-tfjob.ipynb) 2 | apiVersion: "kubeflow.org/v1" 3 | kind: TFJob 4 | metadata: 5 | name: tfjob-simple 6 | spec: 7 | tfReplicaSpecs: 8 | Worker: 9 | replicas: 2 10 | restartPolicy: OnFailure 11 | template: 12 | metadata: 13 | annotations: 14 | sidecar.istio.io/inject: "false" # disable istio sidecar injection to get tfjob running 15 | spec: 16 | containers: 17 | - name: tensorflow 18 | image: 172.17.0.2:5000/kubeflow-ci/tf-mnist-with-summaries:1.0 # the registry address is hardcoded to `172.17.0.2:5000` in the [test scripts](https://github.com/canonical/bundle-kubeflow/blob/main/tests/airgapped/registry.sh#L21) 19 | command: 20 | - "python" 21 | - "/var/tf_mnist/mnist_with_summaries.py" 22 | - "--fake_data=True" # uses dummy data to avoid pulling the dataset from the internet 23 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/katib/README.md: -------------------------------------------------------------------------------- 1 | # Testing Katib in airgapped 2 | 3 | This directory is dedicated to testing Katib in an airgapped environment. 4 | 5 | ## Prerequisites 6 | 7 | Prepare the airgapped environment and deploy CKF by following the steps in [Airgapped test scripts](https://github.com/canonical/bundle-kubeflow/tree/main/tests/airgapped#testing-airgapped-installation). 8 | 9 | Once you run the test scripts, the `kubeflowkatib/simple-pbt:v0.17.0` image used in the `simple-pbt` experiment will be included in your airgapped environment. It's specifically added in the [`get_all_images.py` script](../../../../scripts/airgapped/get_all_images.py). 10 | 11 | ## How to test Katib in an Airgapped environment 12 | 1. Connect to the dashboard by visiting the IP of your airgapped VM. To get the IP run: 13 | ``` 14 | lxc ls | grep eth0 15 | ``` 16 | Look for the IP of the `airgapped-microk8s` instance. 17 | 18 | 2. Log in to the dashboard and create a Profile. 19 | 3. Go to the `Katib Experiments` tab from the dashboard sidebar. 20 | 4. Click `New Experiment` then `Edit and submit YAML`. 21 | 5. Paste the contents of the `simple-pbt.yaml` file found in this directory. 22 | 6. Create the Experiment, and monitor its status to check it is `Succeeded`. 23 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/katib/simple-pbt.yaml: -------------------------------------------------------------------------------- 1 | # Source: katib/examples/v1beta1/hp-tuning/simple-pbt.yaml 2 | # This example is slightly modified from upstream to consume less resources 3 | # and disable istio sidecar. 4 | # There's a `modified` comment where we diverge from upstream. 5 | # When updating this file, make sure to keep those modifications. 6 | --- 7 | apiVersion: kubeflow.org/v1beta1 8 | kind: Experiment 9 | metadata: 10 | name: simple-pbt 11 | spec: 12 | maxTrialCount: 1 # modified 13 | parallelTrialCount: 1 # modified 14 | maxFailedTrialCount: 1 # modified 15 | resumePolicy: FromVolume 16 | objective: 17 | type: maximize 18 | goal: 0.99 19 | objectiveMetricName: Validation-accuracy 20 | algorithm: 21 | algorithmName: pbt 22 | algorithmSettings: 23 | - name: suggestion_trial_dir 24 | value: /var/log/katib/checkpoints/ 25 | - name: n_population 26 | value: '40' 27 | - name: truncation_threshold 28 | value: '0.2' 29 | parameters: 30 | - name: lr 31 | parameterType: double 32 | feasibleSpace: 33 | min: '0.0001' 34 | max: '0.02' 35 | step: '0.0001' 36 | trialTemplate: 37 | primaryContainerName: training-container 38 | trialParameters: 39 | - name: learningRate 40 | description: Learning rate for training the model 41 | reference: lr 42 | trialSpec: 43 | apiVersion: batch/v1 44 | kind: Job 45 | spec: 46 | template: 47 | spec: 48 | containers: 49 | - name: training-container 50 | image: 172.17.0.2:5000/kubeflowkatib/simple-pbt:v0.17.0 51 | command: 52 | - "python3" 53 | - "/opt/pbt/pbt_test.py" 54 | - "--epochs=20" 55 | - "--lr=${trialParameters.learningRate}" 56 | - "--checkpoint=/var/log/katib/checkpoints/" 57 | resources: # modified 58 | limits: # modified 59 | memory: "2Gi" # modified 60 | cpu: "1" # modified 61 | restartPolicy: Never 62 | metadata: # modified 63 | annotations: # modified 64 | sidecar.istio.io/inject: "false" # modified 65 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/knative/README.md: -------------------------------------------------------------------------------- 1 | # Testing Knative in airgapped 2 | 3 | This directory is dedicated to testing Knative in an airgapped environment. 4 | 5 | ## Prerequisites 6 | 7 | Prepare the airgapped environment and deploy CKF by following the steps in [Airgapped test scripts](https://github.com/canonical/bundle-kubeflow/tree/main/tests/airgapped#testing-airgapped-installation). 8 | 9 | Once you run the test scripts, the `knative/helloworld-go` image used in the `helloworld` example will be included in your airgapped environment. It's specifically added in the [`get_all_images.py` script](../../../../scripts/get_all_images.py). 10 | 11 | ## How to test Knative in an Airgapped environment 12 | 1. Connect to the dashboard by visiting the IP of your airgapped VM. To get the IP run: 13 | ``` 14 | lxc ls | grep eth0 15 | ``` 16 | 2. Log in to the dashboard and create a Profile. 17 | 3. Apply the `helloworld.yaml` found in this directory to your Profile's Namespace: 18 | ``` 19 | microk8s kubectl apply -f ./helloworld.yaml -n 20 | ``` 21 | 4. Wait for the Knative Service to be `Ready` 22 | ``` 23 | microk8s kubectl get ksvc -n 24 | ``` 25 | Expected output: 26 | ``` 27 | NAME URL LATESTCREATED LATESTREADY READY REASON 28 | hello http://hello.admin.10.64.140.43.nip.io hello-00001 hello-00001 True 29 | ``` 30 | 31 | 5. Curl the Knative Service using the `URL` from the previous step 32 | ``` 33 | curl -L http://hello.admin.10.64.140.43.nip.io 34 | ``` 35 | Expected output: 36 | ``` 37 | Hello World! 38 | ``` 39 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/knative/helloworld.yaml: -------------------------------------------------------------------------------- 1 | # This example is based on the [Knative Serving tutorial](https://knative.dev/docs/getting-started/first-service/#__tabbed_1_2) 2 | apiVersion: serving.knative.dev/v1 3 | kind: Service 4 | metadata: 5 | name: hello 6 | spec: 7 | template: 8 | metadata: 9 | labels: 10 | # Disable istio sidecar due to https://github.com/canonical/kserve-operators/issues/216 11 | sidecar.istio.io/inject : "false" 12 | spec: 13 | containers: 14 | - image: 172.17.0.2:5000/knative/helloworld-go:latest 15 | ports: 16 | - containerPort: 8080 17 | env: 18 | - name: TARGET 19 | value: "World" 20 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/pipelines/README.md: -------------------------------------------------------------------------------- 1 | # Testing Pipelines in Airgapped 2 | 3 | ## The `kfp-airgapped-ipynb` Notebook 4 | To test Pipelines in Airgapped, we are using the Notebook in this directory. It contains the Data passing pipeline example, with the configuration of the Pipeline components to use the `pipelines-runner` [image](./pipelines-runner/README.md). 5 | 6 | The `pipelines-runner` image will be included in your airgapped environment given that you used the [Airgapped test scripts](../README.md). It's specifically added in the [`get_all_images.py` script](../../../../scripts/get_all_images.py). 7 | 8 | ## How to test Pipelines in an Airgapped environment 9 | 1. Prepare the airgapped environment and Deploy CKF by following the steps in [Airgapped test scripts](../../README.md). 10 | 2. Connect to the dashboard by visiting the IP of your airgapped VM. To get the IP run: 11 | ``` 12 | lxc ls | grep eth0 13 | ``` 14 | 3. Go to the `Notebooks` tab, create a new Notebook server and choose `jupyter-tensorflow-full` image from the dropdown. 15 | 4. Connect to the Notebook server and upload the `kfp-airgapped-ipynb` Notebook. 16 | 5. Run the Notebook. 17 | 6. Click on `Run details` from the output of the last cell in the Notebook to see the status of the Pipeline run. 18 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/pipelines/pipelines-runner/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.9-alpine 2 | RUN pip install kfp==2.7.0 3 | CMD ["python3"] 4 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/pipelines/pipelines-runner/README.md: -------------------------------------------------------------------------------- 1 | # `charmedkubeflow/pipelines-runner` image 2 | 3 | A simple image that contains the `kfp` package installed on top of a `python` base image. 4 | The purpose of this image is to use it as the `base-image` for Pipelines components to enable running a Pipeline in an airgapped environment. 5 | 6 | ## How to publish the image 7 | 1. Build the image with the tag, for example for 1.9 we use the tag `ckf-1.9`: 8 | ``` 9 | docker build -t charmedkubeflow/pipelines-runner:ckf-1.9 . 10 | ``` 11 | 2. Login to docker as `charmedkubeflow` with `docker login` 12 | 3. Publish the image to Docker Hub 13 | ``` 14 | docker push charmedkubeflow/pipelines-runner:ckf-1.9 15 | ``` 16 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/testing-images.txt: -------------------------------------------------------------------------------- 1 | charmedkubeflow/pipelines-runner:ckf-1.9 2 | docker.io/kubeflowkatib/simple-pbt:v0.17.0 3 | ghcr.io/knative/helloworld-go:latest 4 | kubeflow/tf-mnist-with-summaries:latest 5 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/training/README.md: -------------------------------------------------------------------------------- 1 | # Testing training operator in airgapped 2 | 3 | This directory is dedicated to testing training operator in an airgapped environment. 4 | 5 | ## Prerequisites 6 | 7 | Prepare the airgapped environment and deploy CKF by following the steps in [Airgapped test scripts](../../README.md#testing-airgapped-installation). 8 | 9 | Once you run the test scripts, the `kubeflow/tf-mnist-with-summaries:latest` image used in the `tfjob-simple` training job will be included in your airgapped environment. It's specifically added in the [`get_all_images.py` script](../../../../scripts/get_all_images.py). 10 | 11 | ## How to test training operator in an Airgapped environment 12 | 1. Connect to the dashboard by visiting the IP of your airgapped VM. To get the IP run: 13 | ``` 14 | lxc ls | grep eth0 15 | ``` 16 | Look for the IP of the `airgapped-microk8s` instance. 17 | 18 | 2. Log in to the dashboard and create a Profile. 19 | 3. Apply the `tfjob-simple.yaml` found in this directory to your Profile's Namespace: 20 | ``` 21 | microk8s kubectl apply -f ./tfjob-simple.yaml -n 22 | ``` 23 | 4. Wait for the tfjob to be `Succeeded` 24 | ``` 25 | microk8s kubectl get tfjob -n 26 | ``` 27 | Expected output: 28 | ``` 29 | NAME STATE AGE 30 | tfjob-simple Succeeded 2m5s 31 | ``` 32 | -------------------------------------------------------------------------------- /tests/airgapped/1.9/training/tfjob-simple.yaml: -------------------------------------------------------------------------------- 1 | # Based on the upstream [TFJob example](https://github.com/kubeflow/training-operator/blob/964a6e836eedff11edfe79cc9f4e5b7c623cbe88/examples/tensorflow/image-classification/create-tfjob.ipynb) 2 | apiVersion: "kubeflow.org/v1" 3 | kind: TFJob 4 | metadata: 5 | name: tfjob-simple 6 | spec: 7 | tfReplicaSpecs: 8 | Worker: 9 | replicas: 2 10 | restartPolicy: OnFailure 11 | template: 12 | metadata: 13 | annotations: 14 | sidecar.istio.io/inject: "false" # disable istio sidecar injection to get tfjob running 15 | spec: 16 | containers: 17 | - name: tensorflow 18 | image: 172.17.0.2:5000/kubeflow/tf-mnist-with-summaries:latest # the registry address is hardcoded to `172.17.0.2:5000` in the [test scripts](https://github.com/canonical/bundle-kubeflow/blob/main/tests/airgapped/registry.sh#L21) 19 | command: 20 | - "python" 21 | - "/var/tf_mnist/mnist_with_summaries.py" 22 | - "--fake_data=True" # uses dummy data to avoid pulling the dataset from the internet 23 | -------------------------------------------------------------------------------- /tests/airgapped/ckf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This file includes helper functions for 4 | # 1. Fetching CKF artifacts (images,tars) 5 | # 2. Pushing the artifacts to the airgapped VM 6 | # 3. Initialising juju and preparing the model for CKF 7 | 8 | function create_images_tar() { 9 | local BUNDLE_PATH=$1 10 | local TESTING_IMAGES_PATH=$2 11 | 12 | if [ -f "images.tar.gz" ]; then 13 | echo "images.tar.gz exists. Will not recreate it." 14 | return 0 15 | fi 16 | 17 | pip3 install -r scripts/airgapped/requirements.txt 18 | 19 | echo "Generating list of images of Charmed Kubeflow" 20 | python3 scripts/get_all_images.py \ 21 | --append-images "$TESTING_IMAGES_PATH" \ 22 | "$BUNDLE_PATH" \ 23 | > images.txt 24 | 25 | echo "Using produced list to load it into our machine's docker cache" 26 | python3 scripts/airgapped/save-images-to-cache.py images.txt 27 | 28 | echo "Retagging in our machine's docker cache all the images of the list" 29 | python3 scripts/airgapped/retag-images-to-cache.py images.txt 30 | 31 | echo "Creating images.tar.gz file with all images defined in the retagged list" 32 | python3 scripts/airgapped/save-images-to-tar.py retagged-images.txt 33 | } 34 | 35 | function create_charms_tar() { 36 | local BUNDLE_PATH=$1 37 | 38 | if [ -f "charms.tar.gz" ]; then 39 | echo "charms.tar.gz exists. Will not recreate it." 40 | return 0 41 | fi 42 | 43 | python3 scripts/airgapped/save-charms-to-tar.py \ 44 | $BUNDLE_PATH 45 | } 46 | 47 | function copy_tars_to_airgapped_env() { 48 | local NAME="$1" 49 | 50 | echo "Pushing images.tar.gz..." 51 | lxc file push images.tar.gz "$NAME"/root/ 52 | 53 | echo "Pushing charms.tar.gz..." 54 | lxc file push charms.tar.gz "$NAME"/root/ 55 | lxc exec "$NAME" -- bash -c " 56 | mkdir charms 57 | tar -xzvf charms.tar.gz --directory charms 58 | rm charms.tar.gz 59 | " 60 | 61 | echo "Pushing retagged images list..." 62 | lxc file push retagged-images.txt "$NAME"/root/ 63 | 64 | echo "Pushed all artifacts successfully." 65 | } 66 | 67 | function install_juju() { 68 | container="$1" 69 | juju_channel="$2" 70 | 71 | lxc exec "$container" -- bash -c " 72 | snap install juju --channel ${juju_channel} 73 | sudo mkdir -p ~/.local/share/juju 74 | sudo juju bootstrap microk8s 75 | " 76 | 77 | } 78 | 79 | function init_juju_model() { 80 | container="$1" 81 | 82 | lxc exec "$container" -- bash -c " 83 | juju add-model kubeflow 84 | " 85 | } 86 | 87 | function fetch_ckf_charms() { 88 | local NAME=$1 89 | local BUNDLE_PATH=$2 90 | 91 | lxc exec "$NAME" -- bash -c " 92 | python3 scripts/airgapped/download_bundle_charms.py \ 93 | releases/1.7/stable/kubeflow/bundle.yaml \ 94 | --zip-all \ 95 | --delete-afterwards 96 | 97 | mkdir charms 98 | mv charms.tar.gz charms/ 99 | cd charms 100 | tar -xzvf charms.tar.gz 101 | " 102 | } 103 | -------------------------------------------------------------------------------- /tests/airgapped/lxc/install-deps/ubuntu_20.04: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export $(grep -v '^#' /etc/environment | xargs) 4 | export DEBIAN_FRONTEND=noninteractive 5 | 6 | apt-get update 7 | apt-get install python3-pip docker.io -y 8 | pip3 install pytest requests pyyaml sh 9 | # Attempting to address https://forum.snapcraft.io/t/lxd-refresh-cause-container-socket-error/8698 10 | # if core is to be installed by microk8s it fails 11 | snap install core18 | true 12 | -------------------------------------------------------------------------------- /tests/airgapped/lxc/install-deps/ubuntu_22.04: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export $(grep -v '^#' /etc/environment | xargs) 4 | export DEBIAN_FRONTEND=noninteractive 5 | 6 | apt-get update 7 | apt-get install python3-pip docker.io -y 8 | pip3 install pytest requests pyyaml sh 9 | # Attempting to address https://forum.snapcraft.io/t/lxd-refresh-cause-container-socket-error/8698 10 | # if core is to be installed by microk8s it fails 11 | snap install core20 | true 12 | -------------------------------------------------------------------------------- /tests/airgapped/lxc/microk8s.profile: -------------------------------------------------------------------------------- 1 | name: microk8s 2 | config: 3 | boot.autostart: "true" 4 | linux.kernel_modules: ip_vs,ip_vs_rr,ip_vs_wrr,ip_vs_sh,ip_tables,ip6_tables,netlink_diag,nf_nat,overlay,br_netfilter 5 | raw.lxc: | 6 | lxc.apparmor.profile=unconfined 7 | lxc.mount.auto=proc:rw sys:rw cgroup:rw 8 | lxc.cgroup.devices.allow=a 9 | lxc.cap.drop= 10 | security.nesting: "true" 11 | security.privileged: "true" 12 | description: "" 13 | devices: 14 | aadisable: 15 | path: /sys/module/nf_conntrack/parameters/hashsize 16 | source: /sys/module/nf_conntrack/parameters/hashsize 17 | type: disk 18 | aadisable2: 19 | path: /dev/kmsg 20 | source: /dev/kmsg 21 | type: unix-char 22 | aadisable3: 23 | path: /sys/fs/bpf 24 | source: /sys/fs/bpf 25 | type: disk 26 | aadisable4: 27 | path: /proc/sys/net/netfilter/nf_conntrack_max 28 | source: /proc/sys/net/netfilter/nf_conntrack_max 29 | type: disk 30 | -------------------------------------------------------------------------------- /tests/airgapped/lxd.profile: -------------------------------------------------------------------------------- 1 | config: {} 2 | networks: 3 | - config: 4 | ipv4.address: auto 5 | ipv6.address: auto 6 | description: "" 7 | name: lxdbr0 8 | type: "" 9 | project: default 10 | storage_pools: 11 | - config: 12 | size: 500GiB 13 | description: "" 14 | name: default 15 | driver: zfs 16 | profiles: 17 | - config: {} 18 | description: "" 19 | devices: 20 | eth0: 21 | name: eth0 22 | network: lxdbr0 23 | type: nic 24 | root: 25 | path: / 26 | pool: default 27 | type: disk 28 | name: default 29 | projects: [] 30 | cluster: null 31 | -------------------------------------------------------------------------------- /tests/airgapped/registry.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This file includes helper functions for: 4 | # 1. setting up a Docker Registry 5 | # 2. configuring microk8s to use this registry (with http) 6 | # 7 | # The scripts are aimed to be run with access to the internet. 8 | 9 | function setup_docker_registry() { 10 | local NAME=$1 11 | 12 | lxc exec "$NAME" -- bash -c " 13 | docker run -d \ 14 | -p 5000:5000 \ 15 | --restart=always \ 16 | --name registry \ 17 | -v /mnt/registry:/var/lib/registry \ 18 | registry:2.8.1 19 | 20 | echo '{ 21 | \"insecure-registries\" : [\"172.17.0.2:5000\"] 22 | } 23 | ' > /etc/docker/daemon.json 24 | 25 | systemctl restart docker 26 | " 27 | } 28 | 29 | 30 | function push_juju_images_to_registry() { 31 | local NAME=$1 32 | 33 | lxc exec "$NAME" -- bash -c " 34 | sudo microk8s ctr images pull docker.io/jujusolutions/charm-base:ubuntu-20.04 35 | sudo microk8s ctr images pull docker.io/jujusolutions/charm-base:ubuntu-22.04 36 | " 37 | } 38 | 39 | 40 | function push_images_tar_to_registry() { 41 | local NAME=$1 42 | 43 | lxc exec "$NAME" -- bash -c " 44 | echo \"Extracting images from tar\" 45 | mkdir images 46 | tar -xzvf images.tar.gz --directory images 47 | rm images.tar.gz 48 | 49 | echo \"Loading images into intermediate Docker client\" 50 | for img in images/*.tar; do docker load < \$img && rm \$img; done 51 | rmdir images 52 | 53 | echo \"Pushing images from local docker to Registry\" 54 | python3 scripts/airgapped/push-images-to-registry.py retagged-images.txt 55 | 56 | " 57 | 58 | echo "Pushing base charm images to registry" 59 | push_juju_images_to_registry "$NAME" 60 | } 61 | 62 | 63 | function configure_to_use_registry_mirror() { 64 | local NAME=$1 65 | 66 | lxc exec "$NAME" -- bash -c ' 67 | mkdir -p /var/snap/microk8s/current/args/certs.d/172.17.0.2:5000/ 68 | 69 | echo " 70 | [host.\"http://172.17.0.2:5000\"] 71 | capabilities = [\"pull\", \"resolve\"] 72 | " > /var/snap/microk8s/current/args/certs.d/172.17.0.2:5000/hosts.toml 73 | ' 74 | } 75 | -------------------------------------------------------------------------------- /tests/airgapped/setup/lxd-docker-networking.sh: -------------------------------------------------------------------------------- 1 | # https://documentation.ubuntu.com/lxd/en/latest/howto/network_bridge_firewalld/#prevent-connectivity-issues-with-lxd-and-docker 2 | 3 | echo "Configuring LXD and Docker networking" 4 | 5 | sudo iptables -I DOCKER-USER -i lxdbr0 -j ACCEPT 6 | sudo iptables -I DOCKER-USER -o lxdbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 7 | 8 | -------------------------------------------------------------------------------- /tests/airgapped/setup/prerequisites.sh: -------------------------------------------------------------------------------- 1 | JUJU_CHANNEL="${JUJU_CHANNEL:-3.4/stable}" 2 | 3 | echo "Installing pip" 4 | sudo apt update 5 | sudo apt -y install python3-pip 6 | 7 | echo "Installing Juju" 8 | sudo snap install juju --channel=$JUJU_CHANNEL --classic 9 | -------------------------------------------------------------------------------- /tests/airgapped/setup/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -xe 3 | 4 | cat tests/airgapped/lxd.profile | lxd init --preseed 5 | 6 | ./tests/airgapped/setup/prerequisites.sh 7 | ./scripts/airgapped/prerequisites.sh 8 | ./tests/airgapped/setup/lxd-docker-networking.sh 9 | 10 | pip3 install -r ./scripts/requirements.txt 11 | 12 | echo "Setup completed. Reboot your machine before running the tests for the docker commands to run without sudo." 13 | -------------------------------------------------------------------------------- /tests/airgapped/utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function create_machine() { 4 | local NAME=$1 5 | local DISTRO=$2 6 | if ! lxc profile show microk8s 7 | then 8 | lxc profile copy default microk8s 9 | fi 10 | lxc profile edit microk8s < tests/airgapped/lxc/microk8s.profile 11 | 12 | lxc launch -p default -p microk8s "$DISTRO" "$NAME" 13 | 14 | # Allow for the machine to boot and get an IP 15 | sleep 20 16 | tar cf - ./tests | lxc exec "$NAME" -- tar xvf - -C /root 17 | tar cf - ./scripts | lxc exec "$NAME" -- tar xvf - -C /root 18 | tar cf - ./releases | lxc exec "$NAME" -- tar xvf - -C /root 19 | DISTRO_DEPS_TMP="${DISTRO//:/_}" 20 | DISTRO_DEPS="${DISTRO_DEPS_TMP////-}" 21 | lxc exec "$NAME" -- /bin/bash "/root/tests/airgapped/lxc/install-deps/$DISTRO_DEPS" 22 | lxc exec "$NAME" -- pip3 install -r scripts/airgapped/requirements.txt 23 | lxc exec "$NAME" -- reboot 24 | sleep 20 25 | 26 | trap 'lxc delete '"${NAME}"' --force || true' EXIT 27 | if [ "$#" -ne 1 ] 28 | then 29 | lxc exec "$NAME" -- reboot 30 | sleep 20 31 | fi 32 | 33 | # inotify increase 34 | lxc exec "$NAME" -- sysctl fs.inotify.max_user_instances=1280 35 | lxc exec "$NAME" -- sysctl fs.inotify.max_user_watches=655360 36 | } 37 | 38 | function setup_tests() { 39 | DISTRO="${1-$DISTRO}" 40 | FROM_CHANNEL="${2-$FROM_CHANNEL}" 41 | TO_CHANNEL="${3-$TO_CHANNEL}" 42 | 43 | export DEBIAN_FRONTEND=noninteractive 44 | apt-get install python3-pip -y 45 | pip3 install -U pytest requests pyyaml sh 46 | apt-get install jq -y 47 | snap install kubectl --classic 48 | export ARCH=$(uname -m) 49 | export LXC_PROFILE="tests/airgapped/lxc/microk8s.profile" 50 | export BACKEND="lxc" 51 | export CHANNEL_TO_TEST=${TO_CHANNEL} 52 | } 53 | -------------------------------------------------------------------------------- /tests/dump-pipeline-logs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | pods=$(juju kubectl get pods -l workflows.argoproj.io/completed -o custom-columns=:metadata.name --no-headers) 6 | for pod in $pods; do 7 | containers=$(juju kubectl get pods -o jsonpath="{.spec.containers[*].name}" $pod) 8 | 9 | for container in $containers; do 10 | juju kubectl logs --timestamps $pod -c $container 11 | printf '\n' 12 | done 13 | printf '\n\n' 14 | done 15 | -------------------------------------------------------------------------------- /tests/dump-stdout.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | mkdir -p "$1" 6 | 7 | pods=$(microk8s kubectl get pods -n kubeflow -o custom-columns=:metadata.name --no-headers) 8 | for pod in $pods; do 9 | containers=$(microk8s kubectl get pods -n kubeflow -o jsonpath="{.spec.containers[*].name}" $pod || true) 10 | 11 | for container in $containers; do 12 | microk8s kubectl logs -n kubeflow --timestamps $pod -c $container > "$1"/kubeflow-$pod-$container.log || true 13 | done 14 | done 15 | -------------------------------------------------------------------------------- /tests/integration/test_bundle_deployment.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import json 3 | import os 4 | 5 | import aiohttp 6 | import lightkube 7 | import pytest 8 | import requests 9 | from pytest_operator.plugin import OpsTest 10 | from lightkube.resources.core_v1 import Service 11 | 12 | BUNDLE_NAME = "kubeflow" 13 | 14 | @pytest.fixture() 15 | def lightkube_client() -> lightkube.Client: 16 | client = lightkube.Client(field_manager=BUNDLE_NAME) 17 | return client 18 | 19 | @pytest.fixture 20 | def bundle_path() -> str: 21 | return os.environ.get("BUNDLE_PATH").replace("\"", "") 22 | 23 | class TestCharm: 24 | @pytest.mark.abort_on_fail 25 | async def test_bundle_deployment_works(self, ops_test: OpsTest, lightkube_client, bundle_path): 26 | subprocess.Popen(["juju", "deploy", bundle_path, "--trust"]) 27 | 28 | # Wait for istio-ingressgateway charm to be active and idle 29 | # This is required because later we'll try to fetch a response from the login url 30 | # using the ingress gateway service IP address (provided by the LoadBalancer) 31 | await ops_test.model.wait_for_idle( 32 | apps=["istio-ingressgateway"], 33 | status="active", 34 | raise_on_blocked=False, 35 | raise_on_error=False, 36 | timeout=1500, 37 | ) 38 | 39 | # To keep compatibility with CKF 1.8, the public-url configuration 40 | # must be set. For >=1.9 this is not required. 41 | # TODO: remove when CKF 1.8 falls out of support 42 | if "1.8" in bundle_path: 43 | await ops_test.model.wait_for_idle( 44 | apps=["oidc-gatekeeper"], 45 | status="blocked", 46 | raise_on_blocked=False, 47 | raise_on_error=False, 48 | timeout=1500, 49 | ) 50 | 51 | dex_svc_dns_name = "http://dex-auth.kubeflow.svc:5556" 52 | 53 | await ops_test.model.applications["dex-auth"].set_config({"public-url": dex_svc_dns_name}) 54 | await ops_test.model.applications["oidc-gatekeeper"].set_config({"public-url": dex_svc_dns_name}) 55 | 56 | # Wait for the whole bundle to become active and idle 57 | await ops_test.model.wait_for_idle( 58 | status="active", 59 | raise_on_blocked=False, 60 | raise_on_error=False, 61 | timeout=1500, 62 | ) 63 | 64 | url = get_public_url(lightkube_client, BUNDLE_NAME) 65 | result_status, result_text = await fetch_response(url) 66 | assert result_status == 200 67 | assert "Log in to Your Account" in result_text 68 | assert "Email Address" in result_text 69 | assert "Password" in result_text 70 | 71 | def get_public_url(lightkube_client: lightkube.Client, bundle_name: str): 72 | """Extracts public url from service istio-ingressgateway-workload for EKS deployment. 73 | As a next step, this could be generalized in order for the above test to run in MicroK8s as well. 74 | """ 75 | ingressgateway_svc = lightkube_client.get( 76 | Service, "istio-ingressgateway-workload", namespace=bundle_name 77 | ) 78 | address = ingressgateway_svc.status.loadBalancer.ingress[0].hostname or ingressgateway_svc.status.loadBalancer.ingress[0].ip 79 | public_url = f"http://{address}" 80 | return public_url 81 | 82 | async def fetch_response(url, headers=None): 83 | """Fetch provided URL and return pair - status and text (int, string).""" 84 | result_status = 0 85 | result_text = "" 86 | async with aiohttp.ClientSession() as session: 87 | async with session.get(url=url, headers=headers) as response: 88 | result_status = response.status 89 | result_text = await response.text() 90 | return result_status, str(result_text) 91 | -------------------------------------------------------------------------------- /tests/kf_authentication.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import logging 3 | import os 4 | 5 | import requests 6 | from urllib.parse import parse_qs 7 | 8 | logging.basicConfig(level=logging.DEBUG) 9 | 10 | 11 | def kubeflow_login(host, username=None, password=None): 12 | """Completes the dex/oidc login flow, returning the authservice_session cookie.""" 13 | host = host.rstrip('/') 14 | data = { 15 | 'login': username or os.getenv('KUBEFLOW_USERNAME', None), 16 | 'password': password or os.getenv('KUBEFLOW_PASSWORD', None), 17 | } 18 | 19 | if not data['login'] or not data['password']: 20 | raise ValueError( 21 | "Missing login credentials - credentials must be passed or defined" 22 | " in KUBEFLOW_USERNAME/KUBEFLOW_PASSWORD environment variables." 23 | ) 24 | 25 | # GET on host provides us a location header with the dex auth page 26 | # and state for this session 27 | response = requests.get(host, verify=False, allow_redirects=False) 28 | validate_response_status_code(response, [302], f"Failed to connect to host site '{host}'.") 29 | location = response.headers['location'] 30 | 31 | # Preserve only first portion of state if there are multiple, then rebuild 32 | # the dex_url 33 | state = parse_qs(location)['state'][0] 34 | location_prefix, _ = location.split('state=') 35 | location = location_prefix + f'state={state}' 36 | dex_url = location 37 | logging.debug(f"Redirected to dex_url of '{dex_url}'") 38 | 39 | # GET on the dex_url provides the dex auth login url we can use and a 40 | # request token 41 | response = requests.get(dex_url, verify=False, allow_redirects=False) 42 | validate_response_status_code(response, [302], f"Failed to connect to dex_url '{dex_url}'.") 43 | 44 | if response.status_code != 302: 45 | raise ValueError( 46 | f"Failed to connect to host site. " 47 | f"Got response {response.status_code}, expected 302" 48 | ) 49 | dex_login_partial_url = response.headers['location'] 50 | dex_login_url = f'{host}{dex_login_partial_url}' 51 | logging.debug(f"Got dex_login_url with request token of '{dex_login_url}") 52 | 53 | # Log in 54 | response = requests.post(dex_login_url, data=data, verify=False, allow_redirects=False) 55 | validate_response_status_code( 56 | response, [301, 303], f"Failed to log into dex - are your credentials correct?" 57 | ) 58 | dex_approval_partial_url = response.headers['location'] 59 | dex_approval_url = f'{host}{dex_approval_partial_url}' 60 | logging.debug(f"Got dex_approval_url of '{dex_approval_url}") 61 | 62 | # GET and return the authservice_session cookie 63 | response = requests.get(dex_approval_url, verify=False, allow_redirects=False) 64 | validate_response_status_code( 65 | response, [301, 303], f"Failed to connect to dex_approval_url '{dex_approval_url}'." 66 | ) 67 | authservice_partial_url = response.headers['location'] 68 | authservice_url = f"{host}{authservice_partial_url}" 69 | logging.debug(f"Got authservice_url of '{authservice_url}'") 70 | 71 | response = requests.get(authservice_url, verify=False, allow_redirects=False) 72 | validate_response_status_code( 73 | response, [301, 302], f"Failed to connect to authservice_url '{authservice_url}'." 74 | ) 75 | 76 | return response.cookies['authservice_session'] 77 | 78 | 79 | def validate_response_status_code(response, expected_codes: list, error_message: str = ""): 80 | """Validates the status code of a response, raising a ValueError with message""" 81 | if error_message: 82 | error_message += " " 83 | if response.status_code not in expected_codes: 84 | raise ValueError( 85 | f"{error_message}" 86 | f"Got response {response.status_code}, expected one of {expected_codes}" 87 | ) 88 | 89 | 90 | # For testing: 91 | if __name__ == "__main__": 92 | host = os.getenv("KUBEFLOW_HOST", "http://10.64.140.43.nip.io") 93 | # Infers username and password from KUBEFLOW_USERNAME, KUBEFLOW_PASSWORD 94 | authsession_cookie = kubeflow_login(host) 95 | print(authsession_cookie) 96 | -------------------------------------------------------------------------------- /tests/pipelines/artifacts/pet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canonical/bundle-kubeflow/8eeddb31e4daf34de6d5e58fdc00c74a9679753d/tests/pipelines/artifacts/pet.jpg -------------------------------------------------------------------------------- /tests/pipelines/common.py: -------------------------------------------------------------------------------- 1 | from kubernetes import client as k8s_client 2 | 3 | 4 | def attach_output_volume(op): 5 | """Attaches emptyDir volumes to container operations. 6 | 7 | See https://github.com/kubeflow/pipelines/issues/1654 8 | """ 9 | 10 | # Handle auto-generated pipeline metadata 11 | op.output_artifact_paths['mlpipeline-ui-metadata'] = '/tmp/outputs/mlpipeline-ui-metadata.json' 12 | op.output_artifact_paths['mlpipeline-metrics'] = '/tmp/outputs/mlpipeline-metrics.json' 13 | 14 | # Add somewhere to store regular output 15 | op.add_volume(k8s_client.V1Volume(name='volume', empty_dir=k8s_client.V1EmptyDirVolumeSource())) 16 | op.container.add_volume_mount(k8s_client.V1VolumeMount(name='volume', mount_path='/output')) 17 | 18 | # func_to_container_op wants to store outputs under /tmp/outputs 19 | op.add_volume( 20 | k8s_client.V1Volume(name='outputs', empty_dir=k8s_client.V1EmptyDirVolumeSource()) 21 | ) 22 | op.container.add_volume_mount( 23 | k8s_client.V1VolumeMount(name='outputs', mount_path='/tmp/outputs') 24 | ) 25 | 26 | return op 27 | -------------------------------------------------------------------------------- /tests/pipelines/cowsay.py: -------------------------------------------------------------------------------- 1 | from kfp import dsl 2 | from kubernetes import client as k8s_client 3 | 4 | 5 | def attach_output_volume(fun): 6 | """Attaches emptyDir volumes to container operations. 7 | 8 | See https://github.com/kubeflow/pipelines/issues/1654 9 | """ 10 | 11 | def inner(*args, **kwargs): 12 | op = fun(*args, **kwargs) 13 | op.output_artifact_paths = { 14 | 'mlpipeline-ui-metadata': '/output/mlpipeline-ui-metadata.json', 15 | 'mlpipeline-metrics': '/output/mlpipeline-metrics.json', 16 | } 17 | op.add_volume( 18 | k8s_client.V1Volume(name='volume', empty_dir=k8s_client.V1EmptyDirVolumeSource()) 19 | ) 20 | op.container.add_volume_mount(k8s_client.V1VolumeMount(name='volume', mount_path='/output')) 21 | 22 | return op 23 | 24 | return inner 25 | 26 | 27 | @attach_output_volume 28 | def fortune_task(url): 29 | """Get a random fortune.""" 30 | return dsl.ContainerOp( 31 | name='fortune', 32 | image='appropriate/curl', 33 | command=['sh', '-c'], 34 | arguments=['curl $0 | tee $1', url, '/output/fortune.txt'], 35 | file_outputs={'text': '/output/fortune.txt'}, 36 | ) 37 | 38 | 39 | @attach_output_volume 40 | def cow_task(text): 41 | """Have a cow say something""" 42 | return dsl.ContainerOp( 43 | name='cowsay', 44 | image='chuanwen/cowsay', 45 | command=['bash', '-c'], 46 | arguments=['/usr/games/cowsay "$0" | tee $1', text, '/output/cowsay.txt'], 47 | file_outputs={'text': '/output/cowsay.txt'}, 48 | ) 49 | 50 | 51 | @dsl.pipeline(name='Fortune Cow', description='Talk to a fortunate cow.') 52 | def cowsay_pipeline(url='https://helloacm.com/api/fortune/'): 53 | fortune = fortune_task(url) 54 | cowsay = cow_task(fortune.output) 55 | -------------------------------------------------------------------------------- /tests/pipelines/katib.py: -------------------------------------------------------------------------------- 1 | """Tests Katib""" 2 | 3 | from functools import partial 4 | from typing import NamedTuple 5 | 6 | from kfp import components, dsl 7 | 8 | func_to_container_op = partial( 9 | components.func_to_container_op, 10 | base_image='rocks.canonical.com:5000/kubeflow/examples/mnist-test:latest', 11 | ) 12 | 13 | 14 | @func_to_container_op 15 | def create_task(namespace: str, example: str) -> NamedTuple('Data', [('experiment_name', str)]): 16 | """""" 17 | 18 | from kubernetes import client, config 19 | from pprint import pprint 20 | import random 21 | import requests 22 | import string 23 | import yaml 24 | 25 | config.load_incluster_config() 26 | api = client.CustomObjectsApi() 27 | response = requests.get(example) 28 | response.raise_for_status() 29 | resource = yaml.safe_load(response.text) 30 | resource['metadata']['name'] += '-' + ''.join( 31 | random.choice(string.ascii_lowercase) for _ in range(6) 32 | ) 33 | resource['metadata']['namespace'] = 'admin' 34 | resource['spec']['maxTrialCount'] = 1 35 | resource['spec']['maxFailedTrialCount'] = 1 36 | resource['spec']['parallelTrialCount'] = 1 37 | 38 | pprint(resource) 39 | api.create_namespaced_custom_object( 40 | group="kubeflow.org", 41 | version="v1beta1", 42 | namespace="admin", 43 | plural="experiments", 44 | body=resource, 45 | ) 46 | 47 | return (resource['metadata']['name'],) 48 | 49 | 50 | @func_to_container_op 51 | def wait_task(namespace: str, experiment_name: str): 52 | """""" 53 | import requests 54 | import time 55 | import csv 56 | import json 57 | 58 | katib_ui = 'http://katib-ui.kubeflow.svc.cluster.local:8080/katib/fetch_hp_job_info/' 59 | 60 | for _ in range(120): 61 | response = requests.get( 62 | f'{katib_ui}?experimentName={experiment_name}&namespace={namespace}' 63 | ) 64 | 65 | try: 66 | response.raise_for_status() 67 | except requests.HTTPError as err: 68 | print("Got error while waiting for experiment to finish") 69 | print(err.response.content) 70 | print(err.response.headers) 71 | time.sleep(5) 72 | continue 73 | 74 | trials = csv.DictReader(json.loads(response.text).splitlines()) 75 | statuses = {t['trialName']: t['Status'] for t in trials} 76 | 77 | if statuses == {}: 78 | print("Waiting for jobs to be started.") 79 | elif set(statuses.values()) == {'Succeeded'}: 80 | print("All jobs completed successfully!") 81 | break 82 | elif 'Failed' in statuses.values(): 83 | print("Got failed status!") 84 | print(statuses) 85 | break 86 | else: 87 | print('Waiting for jobs to complete:') 88 | print(statuses) 89 | 90 | time.sleep(5) 91 | else: 92 | raise Exception("Waited too long for jobs to complete!") 93 | 94 | 95 | @func_to_container_op 96 | def delete_task(namespace: str, experiment_name: str): 97 | import requests 98 | 99 | katib_ui = 'http://katib-ui.kubeflow.svc.cluster.local:8080/katib/delete_experiment/' 100 | 101 | response = requests.get(f'{katib_ui}?experimentName={experiment_name}&namespace={namespace}') 102 | response.raise_for_status() 103 | 104 | 105 | @dsl.pipeline(name='Katib Test', description='Tests Katib') 106 | def katib_pipeline( 107 | namespace: str = 'admin', 108 | example: str = 'https://raw.githubusercontent.com/kubeflow/katib/5ed27d7/examples/v1beta1/early-stopping/median-stop.yaml', 109 | ): 110 | _create = create_task(namespace, example) 111 | # wait = wait_task(namespace, create.outputs['experiment_name']) 112 | # _delete = delete_task(namespace, create.outputs['experiment_name']).after(wait) 113 | -------------------------------------------------------------------------------- /tests/profile_template.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Canonical Ltd. 2 | # See LICENSE file for licensing details. 3 | 4 | apiVersion: kubeflow.org/v1 5 | kind: Profile 6 | metadata: 7 | name: {{ profile_name }} 8 | spec: 9 | owner: 10 | kind: User 11 | name: {{ username }} 12 | -------------------------------------------------------------------------------- /tests/test_kubectl.py: -------------------------------------------------------------------------------- 1 | """Runs tests by inspecting microk8s with kubectl.""" 2 | 3 | import pytest 4 | import yaml 5 | from sh import Command 6 | from pytest_operator.plugin import OpsTest 7 | 8 | try: 9 | from sh import juju_kubectl as kubectl 10 | except ImportError: 11 | kubectl = Command('kubectl').bake('-nkubeflow') 12 | 13 | 14 | def get_statuses(): 15 | """Gets names and statuses of all workload pods. 16 | 17 | Uses Juju 2.8 label first, and if that's empty, tries Juju 2.9 label 18 | """ 19 | 20 | pods = yaml.safe_load(kubectl.get('pods', '-ljuju-app', '-oyaml').stdout) 21 | 22 | if pods['items']: 23 | return {i['metadata']['labels']['juju-app']: i['status']['phase'] for i in pods['items']} 24 | else: 25 | pods = yaml.safe_load(kubectl.get('pods', '-lapp.kubernetes.io/name', '-oyaml').stdout) 26 | return { 27 | i['metadata']['labels']['app.kubernetes.io/name']: i['status']['phase'] 28 | for i in pods['items'] 29 | } 30 | 31 | 32 | @pytest.mark.full 33 | @pytest.mark.lite 34 | async def test_all_charms_running(ops_test: OpsTest): 35 | await ops_test.model.wait_for_idle( 36 | status="active", 37 | raise_on_blocked=True, 38 | timeout=300, 39 | ) 40 | 41 | 42 | @pytest.mark.full 43 | def test_crd_created_full(): 44 | crds = yaml.safe_load(kubectl.get('crd', '-oyaml').stdout) 45 | 46 | names = {i['metadata']['name'] for i in crds['items']} 47 | assert names.issuperset( 48 | { 49 | 'experiments.kubeflow.org', 50 | 'notebooks.kubeflow.org', 51 | 'poddefaults.kubeflow.org', 52 | 'profiles.kubeflow.org', 53 | 'scheduledworkflows.kubeflow.org', 54 | 'seldondeployments.machinelearning.seldon.io', 55 | 'servicerolebindings.rbac.istio.io', 56 | 'serviceroles.rbac.istio.io', 57 | 'suggestions.kubeflow.org', 58 | 'trials.kubeflow.org', 59 | 'viewers.kubeflow.org', 60 | 'workflows.argoproj.io', 61 | 'xgboostjobs.kubeflow.org', 62 | 'mxjobs.kubeflow.org', 63 | 'pytorchjobs.kubeflow.org', 64 | 'tfjobs.kubeflow.org', 65 | } 66 | ) 67 | 68 | 69 | @pytest.mark.lite 70 | def test_crd_created_lite(): 71 | crds = yaml.safe_load(kubectl.get('crd', '-oyaml').stdout) 72 | 73 | names = {i['metadata']['name'] for i in crds['items']} 74 | assert names.issuperset( 75 | { 76 | 'notebooks.kubeflow.org', 77 | 'poddefaults.kubeflow.org', 78 | 'profiles.kubeflow.org', 79 | 'scheduledworkflows.kubeflow.org', 80 | 'seldondeployments.machinelearning.seldon.io', 81 | 'servicerolebindings.rbac.istio.io', 82 | 'serviceroles.rbac.istio.io', 83 | 'viewers.kubeflow.org', 84 | 'workflows.argoproj.io', 85 | 'xgboostjobs.kubeflow.org', 86 | 'mxjobs.kubeflow.org', 87 | 'pytorchjobs.kubeflow.org', 88 | 'tfjobs.kubeflow.org', 89 | } 90 | ) 91 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = True 3 | envlist = lint, fmt, tests, full_bundle_tests, test_bundle_1.7, test_selenium_1.7, {test_bundle_deployment}-{1.7,1.8,1.9,latest} 4 | 5 | [vars] 6 | releases_test_path = {toxinidir}/tests-bundle/ 7 | test_path = {toxinidir}/tests 8 | 9 | [testenv] 10 | passenv = 11 | KUBECONFIG 12 | setenv = 13 | PYTHONPATH={toxinidir}:{toxinidir}/src 14 | PYTHONBREAKPOINT=ipdb.set_trace 15 | # Needed for juju cli to work correctly 16 | HOME={env:HOME} 17 | 18 | [testenv:lint] 19 | deps = 20 | black 21 | mdformat-gfm 22 | commands = 23 | black --check {toxinidir}/scripts/ {toxinidir}/tests/ 24 | mdformat --check --wrap=100 {toxinidir}/README.md 25 | 26 | [testenv:fmt] 27 | deps = 28 | black 29 | mdformat-gfm 30 | commands = 31 | black {toxinidir}/scripts/ {toxinidir}/tests/ {[vars]releases_test_path} 32 | mdformat --wrap=100 {toxinidir}/README.md 33 | 34 | [testenv:tests] 35 | setenv = 36 | # Needed to pass creds through environment variables 37 | passenv = 38 | KUBEFLOW_AUTH_USERNAME 39 | KUBEFLOW_AUTH_PASSWORD 40 | deps = 41 | -rrequirements.txt 42 | -rtest-requirements.txt 43 | pytest-operator 44 | commands = pytest -v --tb native --show-capture=no {posargs} {toxinidir}/tests/ 45 | 46 | [testenv:full_bundle_tests] 47 | # requires to specify following arguments: 48 | # --channel or --file .yaml 49 | # requires to specify following environment variables: 50 | # BUNDLE_TEST_PATH - path to the bundle test directory 51 | # 52 | # final execution command might look like: 53 | # BUNDLE_TEST_PATH=tests-bundle/1.7 tox -e full_bundle_tests -- --channel=1.7/stable 54 | commands = 55 | pytest -vs --tb native -m deploy --model kubeflow --keep-models {env:BUNDLE_TEST_PATH} {posargs} 56 | ; Disabled temporarily until Selenium tests are fixed in CI and 57 | ; https://github.com/canonical/bundle-kubeflow/issues/671 is closed 58 | ; pytest -vs --tb native -m selenium --model kubeflow {env:BUNDLE_TEST_PATH} {posargs} 59 | passenv = 60 | BUNDLE_TEST_PATH 61 | GH_TOKEN 62 | deps = 63 | -r {env:BUNDLE_TEST_PATH}/requirements.txt 64 | 65 | 66 | [testenv:test_bundle_1.7] 67 | commands = 68 | pytest -vs --tb native {[vars]releases_test_path}1.7 -m deploy --model kubeflow --channel 1.7/stable {posargs} 69 | 70 | deps = 71 | -r {[vars]releases_test_path}/1.7/requirements.txt 72 | description = Test bundles 73 | 74 | 75 | [testenv:test_selenium_1.7] 76 | commands = 77 | pytest -vs --tb native {[vars]releases_test_path}1.7 -m selenium --model kubeflow --channel 1.7/stable {posargs} 78 | 79 | deps = 80 | -r {[vars]releases_test_path}/1.7/requirements.txt 81 | description = Test bundles 82 | 83 | [testenv:test_bundle_deployment-{1.7,1.8,1.9,latest}] 84 | commands = 85 | pytest -v --tb native --asyncio-mode=auto {[vars]test_path}/integration/test_bundle_deployment.py --keep-models --log-cli-level=INFO -s {posargs} 86 | setenv = 87 | 1.7: BUNDLE_PATH = "./releases/1.7/stable/kubeflow/bundle.yaml" 88 | 1.8: BUNDLE_PATH = "./releases/1.8/stable/kubeflow/bundle.yaml" 89 | 1.9: BUNDLE_PATH = "./releases/1.9/stable/bundle.yaml" 90 | latest: BUNDLE_PATH = "./releases/latest/edge/bundle.yaml" 91 | deps = 92 | aiohttp 93 | lightkube 94 | pytest-operator 95 | tenacity 96 | ops>=2.3.0 97 | 1.7: juju<3.0.0 98 | 1.8: juju<4.0.0 99 | 1.9: juju<4.0.0 100 | latest: juju<4.0.0 101 | description = Test bundle deployment 102 | --------------------------------------------------------------------------------