diff --git a/README.md b/README.md index 147cb5a..3a449c6 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -# run-k8s-job \ No newline at end of file +# Run Kubernetes Job Action + +Run an arbitrary docker image as a job on a Kubernetes cluster and report the output to stdout. + +## Why? + +For services/apps running on Kubernetes the `run-k8s-job` action allows you to define an arbitrary task as an explict step in a GitHub workflow, without having to deal with a lot of Kubernetes-specific details (you just need a Docker image). This can be useful for creating automated stage gates in a deployment pipeline, or kicking off any task that may be repeated based on the GitHub [events that trigger workflows](https://help.github.com/en/actions/reference/events-that-trigger-workflows). + +It's also not always entirely straightforward to get the output of a previously executed Kubernetes job. This action will grab job status and any logs and output them to the actions console. + +Some example uses might be: +- smoke/integration tests against a live environment +- load tests +- database migrations + + +## Required Inputs + +- `cluster-url` the base URL for your Kubernetes cluster. +- `cluster-token` the OAuth Bearer token for your Kubernetes cluster. +- `image` the docker image to be run as a job. The image must be publically accessible. +- `ca-file` the path to the root CA certificates (in PEM format) for establishing a TLS connection to the Kubernetes server. Note: this is not is not strictly a required input, but the step will fail if one is not provided and the `disable-tls` input is not explicitly set to false. It is **highly recommended** that a CA file be specified. + +## Optional Inputs +- `job-name` prefix for the auto-generated job name in Kubernetes. Defaults to the name of the repo. +- `namespace` the Kubernetes namespace where the job should run. Defaults to `default` +- `disable-tls` connect to the Kubernetes server insecurely (without TLS). Should only be used for testing purposes as it leaves the connection vulnerable to [man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). + + + + diff --git a/action.yml b/action.yml index 7cb778a..9882790 100644 --- a/action.yml +++ b/action.yml @@ -8,19 +8,24 @@ inputs: cluster-token: description: 'Kubernetes API authentication token' required: true - ca-file: - description: 'Path to the file containing the root CA cert for the kubernetes cluster' + image: + description: 'Name of the docker image in a remote repository (i.e. "debian")' required: true + ca-file: + description: 'Path to the file containing the root CA cert for the kubernetes API server' + required: false + job-name: + description: 'Name of the job (note: used only as a prefix for an auto-generated job name)' + required: false + default: ${{github.repository}}-job namespace: description: 'Kubernetes namespace where the job will run' - required: true - job-name: - description: 'Name of the job (note: used as a prefix only for an auto-generated job name)' required: false - default: ${{github.repository}} - image: - description: 'Name of the docker image in a remote repository (i.e. "debian")' - required: true + default: 'default' + disable-tls: + description: 'Connect to Kubernetes API server without TLS (not recommended)' + required: false + default: "false" runs: using: docker diff --git a/main.go b/main.go index ef2e918..6e8673d 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,8 @@ package main import ( "context" + "os" + "strconv" "time" "github.com/sethvargo/go-githubactions" @@ -19,16 +21,41 @@ func main() { image := action.GetInput("image") jobName := action.GetInput("job-name") caFilePath := action.GetInput("ca-file") + tlsFlag := action.GetInput("disable-tls") - action = action.WithFieldsMap(map[string]string{ - "job": jobName, - }) + if len(clusterURL) == 0 { + action.Fatalf("'cluster-url' is a required input but was empty") + } + + if len(token) == 0 { + action.Fatalf("'cluster-token' is a required input but was empty") + } + + if len(image) == 0 { + action.Fatalf("'image' is a required input but was empty") + } + + disableTLS, err := strconv.ParseBool(tlsFlag) + if err != nil { + action.Fatalf("'disable-tls input must be either 'true' or 'false', was %s", tlsFlag) + } + + if !disableTLS { + if len(caFilePath) == 0 { + action.Fatalf("you must either specify the file path to the root ca or explicitly disable tls using the 'disable-tls' input") + } + + if _, err := os.Stat(caFilePath); os.IsNotExist(err) { + action.Fatalf("could not locate file %s; please make sure the file is available in the runner's context", caFilePath) + } + } config, err := clientcmd.BuildConfigFromFlags(clusterURL, "") if err != nil { action.Fatalf("%v", err) } + config.Insecure = disableTLS config.CAFile = caFilePath config.BearerToken = token @@ -37,6 +64,10 @@ func main() { action.Fatalf("%v", err) } + action = action.WithFieldsMap(map[string]string{ + "job": jobName, + }) + runner := NewJobRunner(clientset.BatchV1().Jobs(namespace), clientset.CoreV1().Pods(namespace), 5*time.Second, action) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)