├── .github └── workflows │ └── test-action.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── entrypoint.sh ├── examples ├── arguments-parameters.yaml ├── coinflip.yaml └── whalesay.yaml ├── images └── argo-dashboard.png └── prebuild.Dockerfile /.github/workflows/test-action.yaml: -------------------------------------------------------------------------------- 1 | name: Integration Test 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | 8 | - name: checkout files in repo 9 | uses: actions/checkout@master 10 | 11 | - name: try-local-build 12 | uses: ./ 13 | with: 14 | argo_url: ${{ secrets.ARGO_URI }} 15 | application_credentials: ${{ secrets.APPLICATION_CREDENTIALS }} 16 | project_id: ${{ secrets.PROJECT_ID }} 17 | location_zone: ${{ secrets.LOCATION_ZONE }} 18 | cluster_name: ${{ secrets.CLUSTER_NAME }} 19 | workflow_yaml_path: "examples/coinflip.yaml" 20 | parameter_file_path: "examples/arguments-parameters.yaml" 21 | 22 | - name: Prebuild Image 23 | run: | 24 | cd $GITHUB_WORKSPACE 25 | echo ${PASSWORD} | docker login -u $USERNAME --password-stdin 26 | docker build -t hamelsmu/gke-argo -f prebuild.Dockerfile . 27 | docker push hamelsmu/gke-argo 28 | env: 29 | USERNAME: ${{ secrets.DOCKER_USERNAME }} 30 | PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 31 | 32 | - name: pull from Docker 33 | id: argo 34 | uses: machine-learning-apps/gke-argo@master 35 | with: 36 | argo_url: ${{ secrets.ARGO_URI }} 37 | application_credentials: ${{ secrets.APPLICATION_CREDENTIALS }} 38 | project_id: ${{ secrets.PROJECT_ID }} 39 | location_zone: ${{ secrets.LOCATION_ZONE }} 40 | cluster_name: ${{ secrets.CLUSTER_NAME }} 41 | workflow_yaml_path: "examples/coinflip.yaml" 42 | parameter_file_path: "examples/arguments-parameters.yaml" 43 | 44 | - name: test argo outputs 45 | run: echo "Argo URL $WORKFLOW_URL" 46 | env: 47 | WORKFLOW_URL: ${{ steps.argo.outputs.WORKFLOW_URL }} 48 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hamelsmu/gke-argo 2 | 3 | ENTRYPOINT ["/entrypoint.sh"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Actions Status](https://github.com/machine-learning-apps/gke-argo/workflows/Integration%20Test/badge.svg) 2 | 3 | ## This Action Submits Workflows To [Argo](https://argoproj.github.io/) Running on [GKE](https://cloud.google.com/kubernetes-engine/) 4 | 5 | For a cloud-agnostic version of this action, look [here](https://github.com/machine-learning-apps/actions-argo) 6 | 7 | The purpose of this action is to allow automatic testing of [Argo Workflows](https://argoproj.github.io/argo) from GitHub for Kubernetes cluster running on GCP. 8 | 9 | This action is a mechanism you can leverage to accomplish [CI/CD of Machine Learning](https://blog.paperspace.com/ci-cd-for-machine-learning-ai/). This Action facilitates instantiating model training runs on the compute of your choice running on K8s, specifically on [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/). 10 | 11 | What are Argo Workflows? 12 | 13 | From [the docs](https://argoproj.github.io/docs/argo/readme.html): 14 | 15 | - Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. Argo Workflows is implemented as a Kubernetes CRD (Custom Resource Definition). 16 | 17 | - Define workflows where each step in the workflow is a container. 18 | Model multi-step workflows as a sequence of tasks or capture the dependencies between tasks using a graph (DAG). 19 | - Easily run compute intensive jobs for machine learning or data processing in a fraction of the time using Argo Workflows on Kubernetes. 20 | - Run CI/CD pipelines natively on Kubernetes without configuring complex software development products. 21 | 22 | ## Usage 23 | 24 | ### Example Workflow That Uses This Action 25 | 26 | This action is the third step in the below example: `Submit Argo Deployment` 27 | 28 | ```yaml 29 | name: ML Workflow Via Actions 30 | on: 31 | pull_request: 32 | types: 33 | - labeled 34 | 35 | jobs: 36 | gke-auth: 37 | name: Argo Submit 38 | runs-on: ubuntu-latest 39 | steps: 40 | 41 | # Copy the contents of the current branch into the Actions context 42 | - name: Copy Repo Files 43 | uses: actions/checkout@master 44 | 45 | # This Step Sets the Variable ARGO_TEST_RUN='True' if an open PR is labeled with `argo/run-test` 46 | - name: Filter For PR Label 47 | id: validate 48 | run: python gke-argo-action/validate_payload.py 49 | 50 | # The workflow is submitted to Argo only if ARGO_TEST_RUN='True' 51 | - name: Submit Argo Deployment 52 | id: argo 53 | if: steps.validate.outputs.ARGO_TEST_RUN == 'True' 54 | uses: machine-learning-apps/gke-argo@master #reference this Action 55 | with: # most of the inputs below are used to obtain authentication credentials for GKE 56 | ARGO_URL: ${{ secrets.ARGO_URI }} 57 | APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} 58 | PROJECT_ID: ${{ secrets.GCLOUD_PROJECT_ID }} 59 | LOCATION_ZONE: "us-west1-a" 60 | CLUSTER_NAME: "github-actions-demo" 61 | WORKFLOW_YAML_PATH: argo/nlp-model.yaml # the argo workflow file relative to the repo's root. 62 | PARAMETER_FILE_PATH: argo/arguments-parameters.yml # optional parameter file. This can be built dynamically inside the action or appended to from an existing file in the repo. 63 | 64 | # A comment is made on the PR with the URL to the Argo dashboard for the run. 65 | - name: PR Comment - Argo Workflow URL 66 | if: steps.validate.outputs.ARGO_TEST_RUN == 'True' 67 | run: bash gke-argo-action/pr_comment.sh "The workflow can be viewed at $WORKFLOW_URL" 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | ISSUE_NUMBER: ${{ steps.validate.outputs.ISSUE_NUMBER }} 71 | WORKFLOW_URL: ${{ steps.argo.outputs.WORKFLOW_URL }} 72 | ``` 73 | 74 | ### Mandatory Inputs 75 | 76 | 1. `ARGO_URL`: The endpoint where your Argo UI is hosted. This is used to build the link for dashboard of unique runs. 77 | 2. `APPLICATION_CREDENTIALS`: base64 encoded GCP application credentials (https://cloud.google.com/sdk/docs/authorizing) 78 | 3. `PROJECT_ID`: Name of the GCP Project where the GKE K8s cluster resides. 79 | 4. `LOCATION_ZONE`: The location-zone where your GKE K8s cluster resides, for example, `us-west1-a` 80 | 5. `CLUSTER_NAME`: The name of your GKE K8s cluster 81 | 6. `WORKFLOW_YAML_PATH`: The full path name including the filename of the YAML file that describes the workflow you want to run on Argo. This should be relative to the root of the GitHub repository where the Action is triggered. 82 | 83 | ### Optional Inputs 84 | 85 | 1. `PARAMETER_FILE_PATH`: Parameter file that allows you to change variables in your workflow file. One common use for this file in an Action is to append additional arguments with the output of other Actions. For more dicussion on parameter files, see [the Argo docs](https://argoproj.github.io/docs/argo/examples/readme.html). 86 | 2. `SHA`: Normally, this action uses the system environment variable `GITHUB_SHA` to construct the run name for the Argo workflow. However, you can override this if you supply this value. 87 | 88 | ### Outputs 89 | 90 | You can reference the outputs of an action using [expression syntax](https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions), as illustrated in the last step in the example Action workflow above. 91 | 92 | 1. `WORKFLOW_URL`: URL that is a link to the dashboard for the current run in Argo. The dashboard looks like this: 93 | 94 | > ![alt text](images/argo-dashboard.png) 95 | 96 | ## Keywords 97 | MLOps, Machine Learning, Data Science 98 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'gke-argo' 2 | description: Trigger an Argo Workflow on GKE 3 | author: Hamel Husain 4 | inputs: 5 | argo_url: 6 | description: The endpoint where your Argo UI is hosted. This is used to build the link for dashboard of unique runs. 7 | required: true 8 | application_credentials: 9 | description: base64 encoded GCP application credentials (https://cloud.google.com/sdk/docs/authorizing) 10 | require: true 11 | project_id: 12 | description: name of the GCP Project where the GKE K8s cluster resides. 13 | require: true 14 | location_zone: 15 | description: The location-zone where your GKE K8s cluster resides, for example `us-west1-a` 16 | require: true 17 | cluster_name: 18 | description: The name of your GKE K8s cluster 19 | require: true 20 | workflow_yaml_path: 21 | description: The full path name including the filename of the YAML file that describes the workflow you want to run on Argo. This should be relative to the root of the GitHub repository where the Action is triggered. 22 | require: true 23 | parameter_file_path: 24 | description: Parameter file that allows you to change variables in your workflow file. 25 | require: false 26 | default: "" 27 | sha: 28 | description: Supply a SHA for the run-id, otherwise uses the $GITHUB_SHA 29 | require: false 30 | default: "" 31 | outputs: 32 | WORKFLOW_URL: 33 | description: URL that is a link to the current run in Argo. 34 | branding: 35 | color: 'blue' 36 | icon: 'upload-cloud' 37 | runs: 38 | using: 'docker' 39 | image: 'docker://hamelsmu/gke-argo' 40 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #This file retrieves GKE credentials and submits an Argo Workflow on K8s 4 | 5 | set -e 6 | 7 | ############ Helper Functions ############ 8 | 9 | function check_env() { 10 | if [ -z $(eval echo "\$$1") ]; then 11 | echo "Variable $1 not found. Exiting..." 12 | exit 1 13 | fi 14 | } 15 | 16 | function check_file_exists() { 17 | if [ ! -f $1 ]; then 18 | echo "File $1 was not found" 19 | echo "Here are the contents of the current directory:" 20 | ls 21 | exit 1 22 | fi 23 | } 24 | 25 | function yaml_get_generateName { 26 | local prefix=$2 27 | local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034') 28 | sed -ne "s|^\($s\):|\1|" \ 29 | -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \ 30 | -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 | 31 | awk -F$fs '{ 32 | indent = length($1)/2; 33 | vname[indent] = $2; 34 | for (i in vname) {if (i > indent) {delete vname[i]}} 35 | if (length($3) > 0) { 36 | vn=""; for (i=0; i /tmp/account.json 84 | 85 | # Use gcloud CLI to retrieve k8s authentication 86 | gcloud auth activate-service-account --key-file=/tmp/account.json 87 | gcloud config set project "$INPUT_PROJECT_ID" 88 | gcloud container clusters get-credentials "$INPUT_CLUSTER_NAME" --zone "$INPUT_LOCATION_ZONE" --project "$INPUT_PROJECT_ID" 89 | 90 | ############ Instantiate Argo Workflow ############ 91 | 92 | # If the optional argument PARAMETER_FILE_PATH is supplied, add additional -f argument to `argo submit` command 93 | if [ ! -z "$INPUT_PARAMETER_FILE_PATH" ]; then 94 | echo "Parameter file path provided: $INPUT_PARAMETER_FILE_PATH" 95 | check_file_exists $INPUT_PARAMETER_FILE_PATH 96 | PARAM_FILE_CMD="-f $INPUT_PARAMETER_FILE_PATH" 97 | else 98 | PARAM_FILE_CMD="" 99 | fi 100 | 101 | # Execute the command 102 | ARGO_CMD="argo submit $INPUT_WORKFLOW_YAML_PATH --name $WORKFLOW_NAME $PARAM_FILE_CMD" 103 | echo "executing command: $ARGO_CMD" 104 | eval $ARGO_CMD 105 | 106 | #emit the outputs 107 | echo "::set-output name=WORKFLOW_URL::$INPUT_ARGO_URL/$WORKFLOW_NAME" -------------------------------------------------------------------------------- /examples/arguments-parameters.yaml: -------------------------------------------------------------------------------- 1 | message: this is a message coming from the parameters file! 2 | message-heads: (from the params file) seems like you got heads! 3 | message-tails: (from the params file) tails it is! 4 | log-level: WARNING -------------------------------------------------------------------------------- /examples/coinflip.yaml: -------------------------------------------------------------------------------- 1 | # The coinflip example combines the use of a script result, 2 | # along with conditionals, to take a dynamic path in the 3 | # workflow. In this example, depending on the result of the 4 | # first step, 'flip-coin', the template will either run the 5 | # 'heads' step or the 'tails' step. 6 | apiVersion: argoproj.io/v1alpha1 7 | kind: Workflow 8 | metadata: 9 | generateName: coinflip- 10 | spec: 11 | entrypoint: coinflip 12 | arguments: 13 | parameters: 14 | - name: log-level 15 | value: INFO 16 | templates: 17 | - name: coinflip 18 | steps: 19 | - - name: flip-coin 20 | template: flip-coin 21 | - - name: heads 22 | template: heads 23 | when: "{{steps.flip-coin.outputs.result}} == heads" 24 | arguments: 25 | parameters: 26 | - name: heads-message 27 | value: "hello1" 28 | - name: tails 29 | template: tails 30 | when: "{{steps.flip-coin.outputs.result}} == tails" 31 | arguments: 32 | parameters: 33 | - name: tails-message 34 | value: "hello1" 35 | 36 | - name: flip-coin 37 | script: 38 | image: python:alpine3.6 39 | env: 40 | - name: LOG_LEVEL 41 | value: "{{workflow.parameters.log-level}}" 42 | command: [python] 43 | source: | 44 | import random 45 | result = "heads" if random.randint(0,1) == 0 else "tails" 46 | print(result) 47 | 48 | - name: heads 49 | inputs: 50 | parameters: 51 | - name: heads-message 52 | container: 53 | image: alpine:3.6 54 | command: [sh, -c, echo] 55 | args: ["{{inputs.parameters.heads-message}}"] 56 | 57 | - name: tails 58 | inputs: 59 | parameters: 60 | - name: tails-message 61 | container: 62 | image: alpine:3.6 63 | command: [sh, -c, echo] 64 | args: ["{{inputs.parameters.tails-message}}"] 65 | -------------------------------------------------------------------------------- /examples/whalesay.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Workflow 3 | metadata: 4 | generateName: hello-world-parameters- 5 | spec: 6 | # invoke the whalesay template with 7 | # "hello world" as the argument 8 | # to the message parameter 9 | entrypoint: whalesay 10 | arguments: 11 | parameters: 12 | - name: message 13 | value: hello world 14 | 15 | templates: 16 | - name: whalesay 17 | inputs: 18 | parameters: 19 | - name: message # parameter declaration 20 | container: 21 | # run cowsay with that message input parameter as args 22 | image: docker/whalesay 23 | command: [cowsay] 24 | args: ["{{inputs.parameters.message}}"] -------------------------------------------------------------------------------- /images/argo-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machine-learning-apps/gke-argo/9c1d2c768b128ac36a2bb6a2dd7f92d12a81647e/images/argo-dashboard.png -------------------------------------------------------------------------------- /prebuild.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM google/cloud-sdk:alpine 2 | RUN curl -sLO https://github.com/argoproj/argo/releases/download/v2.12.5/argo-linux-amd64.gz 3 | RUN gunzip argo-linux-amd64.gz 4 | RUN chmod +x argo-linux-amd64 5 | RUN mv ./argo-linux-amd64 /usr/local/bin/argo 6 | ADD entrypoint.sh /entrypoint.sh 7 | RUN chmod +x /entrypoint.sh 8 | 9 | ENTRYPOINT ["/entrypoint.sh"] 10 | --------------------------------------------------------------------------------