Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert legacy docker tests from bash to golang #11357

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
82 changes: 82 additions & 0 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,85 @@ jobs:
. ./tests/docker/test-helpers
. ./tests/docker/test-run-${{ matrix.dtest }}
echo "Did test-run-${{ matrix.dtest }} pass $?"

build-go-tests:
name: "Build Go Tests"
runs-on: ubuntu-latest
outputs:
branch_name: ${{ steps.branch_step.outputs.BRANCH_NAME }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Go
uses: ./.github/actions/setup-go
- name: Build Go Tests
run: |
mkdir -p ./dist/artifacts
go test -c -ldflags="-w -s" -o ./dist/artifacts ./tests/docker/...
- name: Upload Go Tests
uses: actions/upload-artifact@v4
with:
name: docker-go-tests
path: ./dist/artifacts/*.test
compression-level: 9
retention-days: 1
# For upgrade and skew tests, we need to know the branch name this run is based off.
# Since this is predetermined, we can run this step before the docker-go job, saving time.
# For PRs we can use the base_ref (ie the target branch of the PR).
# For pushes to k3s-io/k3s, the branch_name is a valid ref, master or release-x.y.
# For pushes to a fork, we need to determine the branch name by finding the parent branch from git show-branch history.
- name: Determine branch name
id: branch_step
run: |
if [ ${{ github.repository }} = "k3s-io/k3s" ]; then
BRANCH_NAME=$(echo ${{ github.base_ref || github.ref_name }})
elif [ -z "${{ github.base_ref }}" ]; then
# We are in a fork, and need some git history to determine the branch name
git fetch origin --depth=100 +refs/heads/*:refs/remotes/origin/*
BRANCH_NAME=$(git show-branch -a 2> /dev/null | grep '\*' | grep -v `git rev-parse --abbrev-ref HEAD` | head -n1 | sed 's/.*\[\(.*\/\)\(.*\)\].*/\2/' | sed 's/[\^~].*//')
else
BRANCH_NAME=${{ github.base_ref }}
fi
echo "Branch Name is $BRANCH_NAME"
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT

docker-go:
needs: [build, build-go-tests]
name: Docker Tests In GO
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
dtest: [basics, bootstraptoken, cacerts, etcd, lazypull, skew, upgrade]
env:
BRANCH_NAME: ${{ needs.build-go-tests.outputs.branch_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: "Download K3s image"
uses: actions/download-artifact@v4
with:
name: k3s
path: ./dist/artifacts
- name: Load and set K3s image
run: |
docker image load -i ./dist/artifacts/k3s-image.tar
IMAGE_TAG=$(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep 'rancher/k3s')
echo "K3S_IMAGE=$IMAGE_TAG" >> $GITHUB_ENV
- name: Download Go Tests
uses: actions/download-artifact@v4
with:
name: docker-go-tests
path: ./dist/artifacts
- name: Run ${{ matrix.dtest }} Test
# Put the compied test binary back in the same place as the test source
run: |
chmod +x ./dist/artifacts/${{ matrix.dtest }}.test
mv ./dist/artifacts/${{ matrix.dtest }}.test ./tests/docker/${{ matrix.dtest }}/
cd ./tests/docker/${{ matrix.dtest }}
if [ ${{ matrix.dtest }} = "upgrade" ] || [ ${{ matrix.dtest }} = "skew" ]; then
./${{ matrix.dtest }}.test -k3sImage=$K3S_IMAGE -branch=$BRANCH_NAME
else
./${{ matrix.dtest }}.test -k3sImage=$K3S_IMAGE
fi
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ require (
go.etcd.io/etcd/server/v3 v3.5.16
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.27.0
golang.org/x/mod v0.20.0
golang.org/x/net v0.29.0
golang.org/x/sync v0.8.0
golang.org/x/sys v0.25.0
Expand Down Expand Up @@ -449,7 +450,6 @@ require (
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions scripts/test
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ docker ps
# Only run basic tests on non amd64 archs, we use GitHub Actions for amd64
if [ "$ARCH" != 'amd64' ]; then

export K3S_IMAGE="rancher/k3s:${VERSION_TAG}${SUFFIX}"
go test ./tests/docker/basics/basics_test.go -k3sImage="$K3S_IMAGE"
echo "Did go test basics $?"

. ./tests/docker/test-run-basics
echo "Did test-run-basics $?"

Expand Down
121 changes: 121 additions & 0 deletions tests/docker/basics/basics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package main

import (
"flag"
"fmt"
"os"
"strings"
"testing"

tester "github.com/k3s-io/k3s/tests/docker"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var k3sImage = flag.String("k3sImage", "", "The k3s image used to provision containers")
var config *tester.TestConfig

func Test_DockerBasic(t *testing.T) {
flag.Parse()
RegisterFailHandler(Fail)
RunSpecs(t, "Basic Docker Test Suite")
}

var _ = Describe("Basic Tests", Ordered, func() {

Context("Setup Cluster", func() {
It("should provision servers and agents", func() {
var err error
config, err = tester.NewTestConfig(*k3sImage)
Expect(err).NotTo(HaveOccurred())
Expect(config.ProvisionServers(1)).To(Succeed())
Expect(config.ProvisionAgents(1)).To(Succeed())
Eventually(func() error {
return tester.DeploymentsReady([]string{"coredns", "local-path-provisioner", "metrics-server", "traefik"}, config.KubeconfigFile)
}, "60s", "5s").Should(Succeed())
Eventually(func() error {
return tester.NodesReady(config.KubeconfigFile)
}, "40s", "5s").Should(Succeed())
})
})

Context("Verify Binaries and Images", func() {
It("has valid bundled binaries", func() {
for _, server := range config.Servers {
Expect(tester.VerifyValidVersion(server.Name, "kubectl")).To(Succeed())
Expect(tester.VerifyValidVersion(server.Name, "ctr")).To(Succeed())
Expect(tester.VerifyValidVersion(server.Name, "crictl")).To(Succeed())
}
})
It("has valid airgap images", func() {
Expect(config).To(Not(BeNil()))
err := VerifyAirgapImages(config.Servers)
Expect(err).NotTo(HaveOccurred())
})
})

Context("Use Local Storage Volume", func() {
It("should apply local storage volume", func() {
const volumeTestManifest = "../resources/volume-test.yaml"

// Apply the manifest
cmd := fmt.Sprintf("kubectl apply -f %s --kubeconfig=%s", volumeTestManifest, config.KubeconfigFile)
_, err := tester.RunCommand(cmd)
Expect(err).NotTo(HaveOccurred(), "failed to apply volume test manifest")
})
It("should validate local storage volume", func() {
Eventually(func() (bool, error) {
return tester.PodReady("volume-test", "kube-system", config.KubeconfigFile)
}, "20s", "5s").Should(BeTrue())
})
})
})

var failed bool
var _ = AfterEach(func() {
failed = failed || CurrentSpecReport().Failed()
})

var _ = AfterSuite(func() {
if config != nil && !failed {
config.Cleanup()
}
})

// VerifyAirgapImages checks for changes in the airgap image list
func VerifyAirgapImages(servers []tester.ServerConfig) error {
// This file is generated during the build packaging step
const airgapImageList = "../../../scripts/airgap/image-list.txt"

// Use a map to automatically handle duplicates
imageSet := make(map[string]struct{})
// Collect all images from containers
for _, server := range servers {
name := server.Name
cmd := fmt.Sprintf("docker exec %s crictl images -o json | jq -r '.images[].repoTags[0] | select(. != null)'", name)
output, err := tester.RunCommand(cmd)
Expect(err).NotTo(HaveOccurred(), "failed to execute crictl and jq: %v", err)

for _, line := range strings.Split(strings.TrimSpace(string(output)), "\n") {
if line != "" {
imageSet[line] = struct{}{}
}
}
}

// Convert map keys to slice
uniqueImages := make([]string, 0, len(imageSet))
for image := range imageSet {
uniqueImages = append(uniqueImages, image)
}

existing, err := os.ReadFile(airgapImageList)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to read airgap list file: %v", err)
}

// Sorting doesn't matter with ContainElements
existingImages := strings.Split(strings.TrimSpace(string(existing)), "\n")
Expect(existingImages).To(ContainElements(uniqueImages))
return nil
}
67 changes: 67 additions & 0 deletions tests/docker/bootstraptoken/bootstraptoken_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"flag"
"strings"
"testing"

tester "github.com/k3s-io/k3s/tests/docker"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var k3sImage = flag.String("k3sImage", "", "The k3s image used to provision containers")
var config *tester.TestConfig

func Test_DockerBootstrapToken(t *testing.T) {
flag.Parse()
RegisterFailHandler(Fail)
RunSpecs(t, "BoostrapToken Docker Test Suite")
}

var _ = Describe("Boostrap Token Tests", Ordered, func() {

Context("Setup Cluster", func() {
It("should provision servers", func() {
var err error
config, err = tester.NewTestConfig(*k3sImage)
Expect(err).NotTo(HaveOccurred())
Expect(config.ProvisionServers(1)).To(Succeed())
Eventually(func() error {
return tester.DeploymentsReady([]string{"coredns", "local-path-provisioner", "metrics-server", "traefik"}, config.KubeconfigFile)
}, "60s", "5s").Should(Succeed())
})
})

Context("Add Agent with Bootstrap token", func() {
var newSecret string
It("creates a bootstrap token", func() {
var err error
newSecret, err = tester.RunCmdOnDocker(config.Servers[0].Name, "k3s token create --ttl=5m --description=Test")
Expect(err).NotTo(HaveOccurred())
Expect(newSecret).NotTo(BeEmpty())
})
It("joins the agent with the new tokens", func() {
newSecret = strings.ReplaceAll(newSecret, "\n", "")
config.Secret = newSecret
Expect(config.ProvisionAgents(1)).To(Succeed())
Eventually(func(g Gomega) {
nodes, err := tester.ParseNodes(config.KubeconfigFile)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(nodes).To(HaveLen(2))
g.Expect(tester.NodesReady(config.KubeconfigFile)).To(Succeed())
}, "40s", "5s").Should(Succeed())
})
})
})

var failed bool
var _ = AfterEach(func() {
failed = failed || CurrentSpecReport().Failed()
})

var _ = AfterSuite(func() {
if config != nil && !failed {
config.Cleanup()
}
})
Loading
Loading