From 1b7e89e41c548792f0562340a2101c9f8f1f8a06 Mon Sep 17 00:00:00 2001 From: Sergey Vasilyev Date: Sun, 29 Nov 2020 20:11:19 +0100 Subject: [PATCH] Implement the action initially --- .github/workflows/ci.yaml | 89 +++++++++++++++++ FUNDING.yml | 1 + LICENSE | 21 ++++ MAINTAINERS | 1 + README.md | 201 ++++++++++++++++++++++++++++++++++++++ action.sh | 104 ++++++++++++++++++++ action.yaml | 46 +++++++++ 7 files changed, 463 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 FUNDING.yml create mode 100644 LICENSE create mode 100644 MAINTAINERS create mode 100644 README.md create mode 100755 action.sh create mode 100644 action.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..52df91e --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,89 @@ +name: CI +on: + push: + branches: + - master + - release/** + pull_request: + branches: + - master + - release/** + workflow_dispatch: {} + +jobs: + + test-latest: + name: Install latest + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: ./ # normally: nolar/setup-k3d-k3s@v1 + - run: kubectl version + + test-specific: + strategy: + fail-fast: false + matrix: + k3s: [v1.17.14+k3s2, v1.17.14, v1.17, v1] + name: Install ${{ matrix.k3s }} + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: ./ # normally: nolar/setup-k3d-k3s@v1 + with: + version: ${{ matrix.k3s }} + github-token: ${{ secrets.GITHUB_TOKEN }} + - run: kubectl version + + test-outputs: + name: Check outputs + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: ./ # normally: nolar/setup-k3d-k3s@v1 + with: + version: latest + github-token: ${{ secrets.GITHUB_TOKEN }} + id: install + - run: echo K3S=${K3S} K8S=${K8S} + env: + K3S: ${{ steps.install.outputs.k3s-version }} + K8S: ${{ steps.install.outputs.k8s-version }} + + test-skip-readiness: + name: Skip readiness + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: ./ # normally: nolar/setup-k3d-k3s@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + skip-readiness: true + - run: kubectl version + + test-multi-cluster: + name: Multi-cluster + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: ./ # normally: nolar/setup-k3d-k3s@v1 + with: + version: v1.16 + k3d-name: 1-16 + - uses: ./ # normally: nolar/setup-k3d-k3s@v1 + with: + version: v1.18 + k3d-name: 1-18 + - run: kubectl version --context k3d-1-16 + - run: kubectl version --context k3d-1-18 + + test-custom-args: + name: Custom args + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: ./ # normally: nolar/setup-k3d-k3s@v1 + with: + version: v1.16 + k3d-args: --servers 2 --no-lb + - run: kubectl get nodes # there must be two of them diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..884e310 --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +github: nolar diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a052833 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Sergey Vasilyev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..a5c8e23 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1 @@ +Sergey Vasilyev diff --git a/README.md b/README.md new file mode 100644 index 0000000..2335699 --- /dev/null +++ b/README.md @@ -0,0 +1,201 @@ +# Setup K3d/K3s for GitHub Actions + +Install K3d/K3s and start a local Kubernetes cluster of a specific version. + +**K8s** is Kubernetes. +**K3s** is a lightweight K8s distribution. +**K3d** is a wrapper to run K3s in Docker. + +K3d/K3s are especially good for development and CI purposes, as it takes +only 20-30 seconds of time till the cluster is ready. For comparison, +Kind takes 1.5 minutes, Minikube takes 2 minutes till ready (as of Sep'2020). + + +## Quick start + +Start with the simplest way: + +```yaml +jobs: + some-job: + steps: + - uses: nolar/setup-k3d-k3s@v1 +``` + +Change versions with the verbose way: + +```yaml +jobs: + some-job: + steps: + - uses: nolar/setup-k3d-k3s@v1 + with: + version: v1.19 # E.g.: v1.19, v1.19.4, v1.19.4+k3s1 + github-token: ${{ secrets.GITHUB_TOKEN }} +``` + + +## Inputs + +### `version` + +**Required** version of Kubernetes and/or K3s -- either full or partial. + +The following notations are supported: + +* `v1.19.4+k3s1` +* `v1.19.4` +* `v1.19` +* `v1` +* `latest` + +Defaults to `latest`. + +Keep in mind that K3d dates back only to v1.16. +There are no 1.15 and older versions of K8s. + +When the version is partial, the latest detected one will be used, +as found in [K3s releases](https://github.com/rancher/k3s/releases), +according to the basic semantical sorting (i.e. not by time of releasing). + + +### `k3d-name` + +A name of the cluster to be created. + +By default (i.e. if no value is provided), K3d/K3s define their own name. +Usually it is `k3d-k3s-default`. + +Note: the name should not include the `k3d-` prefix, but must be used with it. +The `k3d-` prefix is enforced by K3d and cannot be disabled. + + +### `k3d-args` + +Additional args to pass to K3d. +See `k3d cluster create --help` for available flags. + + +### `github-token` + +A token for GitHub API, which is used to avoid rate limiting. + +The API is used to fetch the releases from the K3s repository. + +By default, or if it is empty, then the API is accessed anonymously, +which implies the limit of approximately 60 requests / 1 hour / 1 worker. + +Usage: + +```yaml +with: + github-token: ${{ secrets.GITHUB_TOKEN }} +``` + + +### `skip-readiness` + +Whether to return from the action as soon as possible, +possibly providing a cluster that is only partially ready. + +By default (`false`), the readiness is awaited by checking for some preselected +resources to appear (e.g., for a service account named "default"). + + +## Outputs + +### `k3s-version` + +The specific K3s version that was detected and used. E.g. `v1.19.4+k3s1`. + + +### `k8s-version` + +The specific K8s version that was detected and used. E.g. `v1.19.4`. + + +## Examples + +With the latest version of K3s/K8s: + +```yaml +steps: + - uses: nolar/setup-k3d-k3s@v1 +``` + +With the specific minor version of K8s, which implies the latest micro version +of K8s and the latest possible version of K3s: + +```yaml +steps: + - uses: nolar/setup-k3d-k3s@v1 + with: + version: v1.19 +``` + +With the very specific version of K3s: + +```yaml +steps: + - uses: nolar/setup-k3d-k3s@v1 + with: + version: v1.19.4+k3s1 +``` + +The partial versions enable the build matrices with only the essential +information in them, which in turn, makes it easier to configure GitHub +branch protection checks while the actual versions of tools are upgraded: + +```yaml +jobs: + some-job: + strategy: + fail-fast: false + matrix: + k8s: [ v1.19, v1.18, v1.17, v1.16 ] + name: K8s ${{ matrix.k8s }} + runs-on: ubuntu-20.04 + steps: + - uses: nolar/setup-k3d-k3s@v1 + with: + version: ${{ matrix.k8s }} +``` + +Multiple clusters in one job are possible, as long as there is enough memory +(note: `k3d-` prefix is enforced by K3d): + +```yaml +jobs: + some-job: + name: Multi-cluster + runs-on: ubuntu-20.04 + steps: + - uses: nolar/setup-k3d-k3s@v1 + with: + version: v1.16 + k3d-name: 1-16 + - uses: nolar/setup-k3d-k3s@v1 + with: + version: v1.18 + k3d-name: 1-18 + - run: kubectl version --context k3d-1-16 + - run: kubectl version --context k3d-1-18 +``` + +Custom args can be passed to K3d (and through it, to K3s & K8s): + +```yaml +jobs: + some-job: + name: Custom args + runs-on: ubuntu-20.04 + steps: + - uses: nolar/setup-k3d-k3s@v1 + with: + k3d-args: --servers 2 --no-lb + - run: kubectl get nodes # there must be two of them +``` + +For real-life examples, see: + +* https://github.com/nolar/kopf/actions (all "K3s…" jobs). diff --git a/action.sh b/action.sh new file mode 100755 index 0000000..08171a7 --- /dev/null +++ b/action.sh @@ -0,0 +1,104 @@ +#!/bin/bash +set -eu +#set -x # for debugging + +: ${GITHUB_API_URL:=https://api.github.com} +: ${VERSION:=latest} +: ${REPO:=rancher/k3s} + +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + authz=("-H" "Authorization: Bearer ${GITHUB_TOKEN}") +else + authz=() +fi + +# Fetch all K3s versions usable for the specified partial version. +# Even if the version is specific and complete, assume it is possibly partial. +curl --silent --fail "${authz[@]}" "${GITHUB_API_URL}/repos/${REPO}/releases?per_page=999" | + jq '.[] | select(.prerelease==false) | .tag_name' >/tmp/versions.txt + +echo "::group::All available K3s versions (unsorted)" +cat /tmp/versions.txt +echo "::endgroup::" + +# Sort the versions numerically, not lexographically: +# 0. Preserve the original name of the version. +# 1. Split the version nmame ("v1.19.4+k3s1") into parts (["1", "19", "4", "1"]). +# 2. Convert parts to numbers when possible ([1, 19, 4, 1]). +# 3. Sort numerically instead of lexographically. +# 4. Restore the original name of each version. +jq --slurp ' + [ .[] + | { original: ., + numeric: + . + | ltrimstr("v") + | split("(-|\\.|\\+k3s)"; "") + | [ .[] | (tonumber? // .) ] + } + ] + | sort_by(.numeric) + | reverse + | .[] + | .original + ' /tmp/sorted.txt + +echo "::group::All available K3s versions (newest on top)" +cat /tmp/sorted.txt +echo "::endgroup::" + +# The "latest" version is not directly exposed, but we hard-code its meaning. +if [[ "${VERSION}" == "latest" ]]; then + VERSION=$(head -n 1 /tmp/sorted.txt | jq -r) +fi + +# The select only those versions that match the requested one. +# Do not rely on the parsed forms of the versions -- they may miss some parts. +# Rely only on the actual name of the version. +# TODO: LATER: Handle release candidates: v1.18.2-rc3+k3s1 must be before v1.18.2+k3s1. +jq --slurp --arg version "${VERSION}" ' + .[] + | select((.|startswith($version + ".")) or + (.|startswith($version + "-")) or + (.|startswith($version + "+")) or + (.==$version)) + ' /tmp/matching.txt + +echo "::group::All matching K3s versions (newest on top)" +cat /tmp/matching.txt +echo "::endgroup::" + +# Validate that we could identify the version (even a very specific one). +if [[ ! -s /tmp/matching.txt ]]; then + echo "::error::No matching K3s versions were found." + exit 1 +fi + +# Get the best possible (i.e. the latest) version of K3s/K8s. +K3S=$(head -n 1 /tmp/matching.txt | jq -r) +K8S=${K3S%%+*} + +# Communicate back to GitHub Actions. +echo "::set-output name=k3s-version::${K3S}" +echo "::set-output name=k8s-version::${K8S}" + +# Install K3d and start a K3s cluster. It takes 20 seconds usually. +# Name & args can be empty or multi-value. For this, they are not quoted. +curl --silent --fail https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash +k3d cluster create ${K3D_NAME:-} --wait --image=rancher/k3s:"${K3S//+/-}" ${K3D_ARGS:-} + +# Install kubectl (for this action only, not for the whole job). +curl --silent --fail -L -o kubectl \ + https://storage.googleapis.com/kubernetes-release/release/"$K8S"/bin/linux/amd64/kubectl +chmod +x kubectl +sudo mv kubectl /usr/local/bin/ + +# Sometimes, the service account is not created immediately. Nice trick, but no: +# we need to wait until the cluster is fully ready before starting the tests. +if [[ -z "${SKIP_READINESS}" ]]; then + echo "::group::Waiting for cluster readiness" + while ! kubectl get serviceaccount default >/dev/null; do sleep 1; done + echo "::endgroup::" +else + echo "Skipping the readiness wait. The cluster can be not fully ready yet." +fi diff --git a/action.yaml b/action.yaml new file mode 100644 index 0000000..64ac53f --- /dev/null +++ b/action.yaml @@ -0,0 +1,46 @@ +name: Setup K3s/K3d +description: Setup a lightweight Kubernetes cluster with K3s/K3d +author: nolar +branding: + icon: cloud + color: yellow +inputs: + version: + description: Full or partial version of K3s, or `latest`. + required: true + default: latest + k3d-name: + description: Cluster name. + required: false + k3d-args: + description: Additional arguments to k3d cluster create. + required: false + github-token: + description: Optional GitHub token to overcome API rate limiting. + required: false + skip-readiness: + description: Skip waiting for full cluster readiness? + required: false +outputs: + k3s-version: + description: Actual version of K3s detected and used. + value: ${{ steps.main.outputs.k3s-version }} + k8s-version: + description: Actual version of K8s detected and used. + value: ${{ steps.main.outputs.k8s-version }} +runs: + using: composite + steps: + - id: main + shell: bash + run: ${{ github.action_path }}/action.sh + env: + VERSION: ${{ inputs.version }} + K3D_NAME: ${{ inputs.k3d-name }} + K3D_ARGS: ${{ inputs.k3d-args }} + GITHUB_TOKEN: ${{ inputs.github-token }} + SKIP_READINESS: ${{ inputs.skip-readiness && 'yes' || '' }} + + # Validate that everything is installed, is on PATH, and generally works. + - shell: bash + run: kubectl version