diff --git a/.github/workflows/_e2e_tests.yaml b/.github/workflows/_e2e_tests.yaml index 65fe8a1fcf..cfe9309966 100644 --- a/.github/workflows/_e2e_tests.yaml +++ b/.github/workflows/_e2e_tests.yaml @@ -134,6 +134,25 @@ jobs: with: password: ${{ secrets.PULP_PASSWORD }} + - name: split image and tag + id: split + env: + KONG: ${{ inputs.kong-image }} + CONTROLLER: ${{ inputs.controller-image }} + run: | + if [ "${{ inputs.kong-image }}" != "" ]; then + export kong_image=$(echo ${{ inputs.kong-image }} | awk '{split($0,a,":"); print a[1]}') + export kong_tag=$(echo ${{ inputs.kong-image }} | awk '{split($0,a,":"); print a[2]}') + echo "kong-image=$kong_image" >> $GITHUB_OUTPUT + echo "kong-tag=$kong_tag" >> $GITHUB_OUTPUT + fi + if [ "${{ inputs.kic-image }}" != "" ]; then + export kic_image=$(echo ${{ inputs.kic-image }} | awk '{split($0,a,":"); print a[1]}') + export kic_tag=$(echo ${{ inputs.kic-image }} | awk '{split($0,a,":"); print a[2]}') + echo "kic-image=$kic_image" >> $GITHUB_OUTPUT + echo "kic-tag=$kic_tag" >> $GITHUB_OUTPUT + fi + # We need to pull the Gateway image locally if loading local image was specified. # This is a "workaround" of the fact that we bind the env variable - responsible for # indicating whether we'd like to load the images - for both controller @@ -149,9 +168,11 @@ jobs: env: E2E_TEST_RUN: ${{ matrix.test }} KONG_CLUSTER_VERSION: ${{ matrix.kubernetes-version }} - TEST_KONG_CONTROLLER_IMAGE_OVERRIDE: ${{ inputs.kic-image }} + TEST_CONTROLLER_IMAGE: ${{ steps.split.outputs.kic-image }} + TEST_CONTROLLER_TAG: ${{ steps.split.outputs.kic-tag }} TEST_KONG_LOAD_IMAGES: ${{ inputs.load-local-image }} - TEST_KONG_IMAGE_OVERRIDE: ${{ inputs.kong-image }} + TEST_KONG_IMAGE: ${{ steps.split.outputs.kong-image }} + TEST_KONG_TAG: ${{ steps.split.outputs.kong-tag }} TEST_KONG_KONNECT_ACCESS_TOKEN: ${{ secrets.K8S_TEAM_KONNECT_ACCESS_TOKEN }} KONG_LICENSE_DATA: ${{ steps.license.outputs.license }} GOTESTSUM_JUNITFILE: "e2e-${{ matrix.test }}${{ matrix.kubernetes-version }}-tests.xml" @@ -199,6 +220,21 @@ jobs: with: password: ${{ secrets.PULP_PASSWORD }} + - name: split image and tag + id: split + env: + KONG: ${{ inputs.kong-image }} + CONTROLLER: ${{ inputs.controller-image }} + run: | + if [ "${{ inputs.kong-image }}" != "" ]; then + export kong_image=$(echo ${{ inputs.kong-image }} | awk '{split($0,a,":"); print a[1]}') + export kong_tag=$(echo ${{ inputs.kong-image }} | awk '{split($0,a,":"); print a[2]}') + echo "kong-image=$kong_image" >> $GITHUB_OUTPUT + echo "kong-tag=$kong_tag" >> $GITHUB_OUTPUT + fi + # see the https://github.com/Kong/kubernetes-testing-framework/issues/587 TODO below + # if we add local image GKE support, we probably need to split it into components here + - name: run ${{ matrix.test }} run: make test.e2e env: @@ -206,13 +242,15 @@ jobs: # therefore we need to use the nightly one. # TODO: Once we have a way to load images into GKE, we can use the local image. # KTF issue that should enable it: https://github.com/Kong/kubernetes-testing-framework/issues/587 - TEST_KONG_CONTROLLER_IMAGE_OVERRIDE: "kong/nightly-ingress-controller:nightly" - TEST_KONG_IMAGE_OVERRIDE: ${{ inputs.kong-image }} + TEST_CONTROLLER_IMAGE: "kong/nightly-ingress-controller" + TEST_CONTROLLER_TAG: "nightly" + TEST_KONG_IMAGE: ${{ steps.split.outputs.kong-image }} + TEST_KONG_TAG: ${{ steps.split.outputs.kong-tag }} TEST_KONG_EFFECTIVE_VERSION: ${{ inputs.kong-effective-version }} KONG_LICENSE_DATA: ${{ steps.license.outputs.license }} TEST_KONG_KONNECT_ACCESS_TOKEN: ${{ secrets.K8S_TEAM_KONNECT_ACCESS_TOKEN }} KONG_CLUSTER_VERSION: ${{ matrix.kubernetes-version }} - KONG_TEST_CLUSTER_PROVIDER: gke + KONG_CLUSTER_PROVIDER: gke E2E_TEST_RUN: ${{ matrix.test }} GOTESTSUM_JUNITFILE: "e2e-gke-${{ matrix.test }}-${{ matrix.kubernetes-version }}-tests.xml" GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} @@ -270,12 +308,34 @@ jobs: with: password: ${{ secrets.PULP_PASSWORD }} + - name: split image and tag + id: split + env: + KONG: ${{ inputs.kong-image }} + CONTROLLER: ${{ inputs.controller-image }} + run: | + if [ "${{ inputs.kong-image }}" != "" ]; then + export kong_image=$(echo ${{ inputs.kong-image }} | awk '{split($0,a,":"); print a[1]}') + export kong_tag=$(echo ${{ inputs.kong-image }} | awk '{split($0,a,":"); print a[2]}') + echo "kong-image=$kong_image" >> $GITHUB_OUTPUT + echo "kong-tag=$kong_tag" >> $GITHUB_OUTPUT + fi + if [ "${{ inputs.kic-image }}" != "" ]; then + export kic_image=$(echo ${{ inputs.kic-image }} | awk '{split($0,a,":"); print a[1]}') + export kic_tag=$(echo ${{ inputs.kic-image }} | awk '{split($0,a,":"); print a[2]}') + echo "kic-image=$kic_image" >> $GITHUB_OUTPUT + echo "kic-tag=$kic_tag" >> $GITHUB_OUTPUT + fi + + - name: run Istio tests run: make test.istio env: - TEST_KONG_CONTROLLER_IMAGE_OVERRIDE: ${{ inputs.kic-image }} + TEST_CONTROLLER_IMAGE: ${{ steps.split.outputs.kic-image }} + TEST_CONTROLLER_TAG: ${{ steps.split.outputs.kic-tag }} TEST_KONG_LOAD_IMAGES: ${{ inputs.load-local-image }} - TEST_KONG_IMAGE_OVERRIDE: ${{ inputs.kong-image }} + TEST_KONG_IMAGE: ${{ steps.split.outputs.kong-image }} + TEST_KONG_TAG: ${{ steps.split.outputs.kong-tag }} KONG_LICENSE_DATA: ${{ steps.license.outputs.license }} KONG_CLUSTER_VERSION: ${{ matrix.kind }} ISTIO_VERSION: ${{ matrix.istio }} diff --git a/test/e2e/environment.go b/test/e2e/environment.go deleted file mode 100644 index b7196a6fb5..0000000000 --- a/test/e2e/environment.go +++ /dev/null @@ -1,37 +0,0 @@ -package e2e - -import "os" - -var ( - // controllerImageOverride is the controller image to use in lieu of the default. - controllerImageOverride = os.Getenv("TEST_KONG_CONTROLLER_IMAGE_OVERRIDE") - - // imageLoad is a boolean flag that indicates whether the controller and kong images should be loaded into the cluster. - imageLoad = os.Getenv("TEST_KONG_LOAD_IMAGES") - - // kongImageOverride is the Kong image to use in lieu of the default. - kongImageOverride = os.Getenv("TEST_KONG_IMAGE_OVERRIDE") - kongImagePullUsername = os.Getenv("TEST_KONG_PULL_USERNAME") - kongImagePullPassword = os.Getenv("TEST_KONG_PULL_PASSWORD") - - // KONG_TEST_CLUSTER is to be filled when an already existing cluster should be used - // in tests. It should be in a `:` format. - // It takes precedence over KONG_TEST_CLUSTER_PROVIDER. - existingCluster = os.Getenv("KONG_TEST_CLUSTER") - - // KONG_TEST_CLUSTER_PROVIDER is to be filled when a cluster of a given kind should - // be created in tests. It can be either `gke` or `kind`. - // It's not used when KONG_TEST_CLUSTER is set. - clusterProvider = os.Getenv("KONG_TEST_CLUSTER_PROVIDER") - - // githubServerURL, githubRepo, githubRunID are used to locate the run of github wokflow. - // See: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables - githubServerURL = os.Getenv("GITHUB_SERVER_URL") - githubRepo = os.Getenv("GITHUB_REPOSITORY") - githubRunID = os.Getenv("GITHUB_RUN_ID") -) - -// shouldLoadImages tells whether the controller and kong images should be loaded into the cluster. -func shouldLoadImages() bool { - return imageLoad == "true" -} diff --git a/test/e2e/features_test.go b/test/e2e/features_test.go index d2be62cb97..7b5ec9070b 100644 --- a/test/e2e/features_test.go +++ b/test/e2e/features_test.go @@ -96,9 +96,11 @@ func TestWebhookUpdate(t *testing.T) { require.NoError(t, err) addons := []clusters.Addon{} addons = append(addons, metallb.New()) - if shouldLoadImages() { - if b, err := loadimage.NewBuilder().WithImage(controllerImageOverride); err == nil { + if testenv.ClusterLoadImages() == "true" { + if b, err := loadimage.NewBuilder().WithImage(testenv.ControllerImageTag()); err == nil { addons = append(addons, b.Build()) + } else { + require.NoError(t, err) } } builder := environments.NewBuilder().WithExistingCluster(cluster).WithAddons(addons...) diff --git a/test/e2e/helpers_test.go b/test/e2e/helpers_test.go index c221aa1d3c..f7a72d31cf 100644 --- a/test/e2e/helpers_test.go +++ b/test/e2e/helpers_test.go @@ -115,9 +115,9 @@ func setupE2ETest(t *testing.T, addons ...clusters.Addon) (context.Context, envi func getEnvironmentBuilder(ctx context.Context, t *testing.T) (*environments.Builder, error) { t.Helper() - if existingCluster == "" { - t.Logf("no existing cluster provided, creating a new one for %q type", clusterProvider) - switch clusterProvider { + if testenv.ExistingClusterName() == "" { + t.Logf("no existing cluster provided, creating a new one for %q type", testenv.ClusterProvider()) + switch testenv.ClusterProvider() { case string(gke.GKEClusterType): t.Log("creating a GKE cluster builder") return createGKEBuilder(t) @@ -127,9 +127,9 @@ func getEnvironmentBuilder(ctx context.Context, t *testing.T) (*environments.Bui } } - clusterParts := strings.Split(existingCluster, ":") + clusterParts := strings.Split(testenv.ExistingClusterName(), ":") if len(clusterParts) < 2 { - return nil, fmt.Errorf("expected existing cluster in format :, got %s", existingCluster) + return nil, fmt.Errorf("expected existing cluster in format :, got %s", testenv.ExistingClusterName()) } clusterType, clusterName := clusterParts[0], clusterParts[1] @@ -171,8 +171,8 @@ func createKINDBuilder(t *testing.T) *environments.Builder { clusterBuilder = clusterBuilder.WithClusterVersion(clusterVersion) } builder := environments.NewBuilder().WithClusterBuilder(clusterBuilder).WithAddons(metallb.New()) - if shouldLoadImages() { - builder = builder.WithAddons(buildImageLoadAddon(t, controllerImageOverride, kongImageOverride)) + if testenv.ClusterLoadImages() == "true" { + builder = builder.WithAddons(buildImageLoadAddon(t, testenv.ControllerImageTag(), testenv.KongImageTag())) } return builder } @@ -184,8 +184,8 @@ func createExistingKINDBuilder(t *testing.T, name string) (*environments.Builder builder = builder.WithExistingCluster(cluster) builder = builder.WithAddons(metallb.New()) - if shouldLoadImages() { - builder = builder.WithAddons(buildImageLoadAddon(t, controllerImageOverride, kongImageOverride)) + if testenv.ClusterLoadImages() == "true" { + builder = builder.WithAddons(buildImageLoadAddon(t, testenv.ControllerImageTag(), testenv.KongImageTag())) } return builder, nil } @@ -237,7 +237,7 @@ type ManifestDeploy struct { Path string // SkipTestPatches is a flag that controls whether to apply standard test patches (e.g. replace controller - // image when TEST_KONG_CONTROLLER_IMAGE_OVERRIDE set, etc.) to the manifests before deploying them. + // image when TEST_CONTROLLER_IMAGE set, etc.) to the manifests before deploying them. SkipTestPatches bool // AdditionalSecrets is a list of additional secrets to create before deploying the manifest. @@ -723,7 +723,7 @@ func buildImageLoadAddon(t *testing.T, images ...string) clusters.Addon { func createKongImagePullSecret(ctx context.Context, t *testing.T, env environments.Environment) { t.Helper() - if kongImagePullUsername == "" || kongImagePullPassword == "" { + if testenv.KongPullUsername() == "" || testenv.KongPullPassword() == "" { return } kubeconfigFilename := getTemporaryKubeconfig(t, env) @@ -733,8 +733,8 @@ func createKongImagePullSecret(ctx context.Context, t *testing.T, env environmen ctx, "kubectl", "--kubeconfig", kubeconfigFilename, "create", "secret", "docker-registry", secretName, - "--docker-username="+kongImagePullUsername, - "--docker-password="+kongImagePullPassword, + "--docker-username="+testenv.KongPullUsername(), + "--docker-password="+testenv.KongPullPassword(), ) out, err := cmd.CombinedOutput() require.NoError(t, err, "command output: "+string(out)) @@ -792,18 +792,18 @@ func getTemporaryKubeconfig(t *testing.T, env environments.Environment) string { func runOnlyOnKindClusters(t *testing.T) { t.Helper() - existingClusterIsKind := strings.Split(existingCluster, ":")[0] == string(kind.KindClusterType) + existingClusterIsKind := strings.Split(testenv.ExistingClusterName(), ":")[0] == string(kind.KindClusterType) if existingClusterIsKind { return } - clusterProviderIsKind := clusterProvider == string(kind.KindClusterType) + clusterProviderIsKind := testenv.ClusterProvider() == string(kind.KindClusterType) if clusterProviderIsKind { return } - clusterProviderUnspecified := clusterProvider == "" - existingClusterUnspecified := existingCluster == "" + clusterProviderUnspecified := testenv.ClusterProvider() == "" + existingClusterUnspecified := testenv.ExistingClusterName() == "" if clusterProviderUnspecified && existingClusterUnspecified { return } diff --git a/test/e2e/konnect_test.go b/test/e2e/konnect_test.go index d8563b085d..e52069acfc 100644 --- a/test/e2e/konnect_test.go +++ b/test/e2e/konnect_test.go @@ -35,6 +35,7 @@ import ( "github.com/kong/kubernetes-ingress-controller/v2/internal/konnect/roles" "github.com/kong/kubernetes-ingress-controller/v2/test/helpers/certificate" "github.com/kong/kubernetes-ingress-controller/v2/test/internal/helpers" + "github.com/kong/kubernetes-ingress-controller/v2/test/internal/testenv" ) const ( @@ -176,9 +177,9 @@ func generateTestKonnectControlPlaneDescription(t *testing.T) string { t.Helper() desc := fmt.Sprintf("control plane for test %s", t.Name()) - if githubServerURL != "" && githubRepo != "" && githubRunID != "" { + if testenv.GithubServerURL() != "" && testenv.GithubRepo() != "" && testenv.GithubRunID() != "" { githubRunURL := fmt.Sprintf("%s/%s/actions/runs/%s", - githubServerURL, githubRepo, githubRunID) + testenv.GithubServerURL(), testenv.GithubRepo(), testenv.GithubRunID()) desc += ", github workflow run " + githubRunURL } diff --git a/test/e2e/utils_test.go b/test/e2e/utils_test.go index 22d43a6818..d4490a082f 100644 --- a/test/e2e/utils_test.go +++ b/test/e2e/utils_test.go @@ -5,6 +5,7 @@ package e2e import ( "bytes" "context" + "errors" "fmt" "io" "net" @@ -16,6 +17,8 @@ import ( "testing" "time" + "github.com/blang/semver/v4" + "github.com/kong/go-kong/kong" "github.com/kong/kubernetes-testing-framework/pkg/clusters/types/gke" "github.com/kong/kubernetes-testing-framework/pkg/environments" "github.com/phayes/freeport" @@ -26,6 +29,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/kong/kubernetes-ingress-controller/v2/test/internal/testenv" ) const ( @@ -147,27 +152,62 @@ func getTestManifest(t *testing.T, baseManifestPath string, skipTestPatches bool t.Logf("failed patching controller liveness (%v), using default manifest %v", err, baseManifestPath) return manifestsReader } + } return manifestsReader } -// patchGatewayImageFromEnv will optionally replace a default controller image in manifests with `kongImageOverride` -// if it's set. +// extractVersionFromImage extracts semver of image from image tag. If tag is not given, +// or is not in a semver format, it returns an error. +// for example: kong/kubernetes-ingress-controller:2.9.3 => semver.Version{Major:2,Minor:9,Patch:3}. +// +//lint:ignore U1000 retained for future use +func extractVersionFromImage(imageName string) (semver.Version, error) { + split := strings.Split(imageName, ":") + if len(split) < 2 { + return semver.Version{}, fmt.Errorf("could not parse override image '%s', expected : format", imageName) + } + // parse version from image tag, like kong/kubernetes-ingress-controller:2.9.3 => 2.9.3 + tag := split[len(split)-1] + v, err := semver.ParseTolerant(tag) + if err != nil { + return semver.Version{}, fmt.Errorf("failed to parse version from image tag %s: %w", tag, err) + } + return v, nil +} + +// skipTestIfControllerVersionBelow skips the test case if version of override KIC image is +// below the minVersion. +// if the override KIC image is not set, it assumes that the latest image is used, so it never skips +// the test if override image is not given. +// +//lint:ignore U1000 retained for future use +func skipTestIfControllerVersionBelow(t *testing.T, minVersion semver.Version) { + if testenv.ControllerImageTag() == "" { + return + } + v, err := extractVersionFromImage(testenv.ControllerImageTag()) + // assume using latest version if failed to extract version from image tag. + if err != nil { + t.Logf("could not extract version from controller image: %v, assume using the latest version", err) + return + } + if v.LE(minVersion) { + t.Skipf("skipped the test because version of KIC %s is below the minimum version %s", + v.String(), minVersion.String()) + } +} + +// patchGatewayImageFromEnv will optionally replace a default controller image in manifests with env overrides. func patchGatewayImageFromEnv(t *testing.T, manifestsReader io.Reader) (io.Reader, error) { t.Helper() - if kongImageOverride != "" { - t.Logf("replace kong image with %s", kongImageOverride) - split := strings.Split(kongImageOverride, ":") - if len(split) < 2 { - return nil, fmt.Errorf("invalid image name '%s', expected : format", kongImageOverride) - } - repo := strings.Join(split[0:len(split)-1], ":") - tag := split[len(split)-1] - manifestsReader, err := patchKongImage(manifestsReader, repo, tag) + if testenv.KongImageTag() != "" { + t.Logf("replace kong image with %s", testenv.KongImageTag()) + manifestsReader, err := patchKongImage(manifestsReader, testenv.KongImage(), testenv.KongTag()) if err != nil { - return nil, fmt.Errorf("failed patching override image '%v'", kongImageOverride) + return nil, fmt.Errorf("failed patching override image '%v'", testenv.KongImageTag()) } return manifestsReader, nil } @@ -176,30 +216,15 @@ func patchGatewayImageFromEnv(t *testing.T, manifestsReader io.Reader) (io.Reade return manifestsReader, nil } -// splitImageRepoTag splits repo and tag from given image name, like kong:3.4.0 => kong, 3.4.0. -func splitImageRepoTag(image string) (string, string, error) { - split := strings.Split(image, ":") - if len(split) < 2 { - return "", "", fmt.Errorf("could not parse override image '%v', expected : format", image) - } - repo := strings.Join(split[0:len(split)-1], ":") - tag := split[len(split)-1] - return repo, tag, nil -} - -// patchControllerImageFromEnv will optionally replace a default controller image in manifests with `controllerImageOverride` +// patchControllerImageFromEnv will optionally replace a default controller image in manifests with env override // if it's set. func patchControllerImageFromEnv(t *testing.T, manifestReader io.Reader) (io.Reader, error) { t.Helper() - if controllerImageOverride != "" { - repo, tag, err := splitImageRepoTag(controllerImageOverride) + if testenv.ControllerImageTag() != "" { + manifestReader, err := patchControllerImage(manifestReader, testenv.ControllerImage(), testenv.ControllerTag()) if err != nil { - return nil, err - } - manifestReader, err = patchControllerImage(manifestReader, repo, tag) - if err != nil { - return nil, fmt.Errorf("failed patching override image '%v': %w", controllerImageOverride, err) + return nil, fmt.Errorf("failed patching override image '%v': %w", testenv.ControllerImageTag(), err) } return manifestReader, nil } @@ -208,6 +233,22 @@ func patchControllerImageFromEnv(t *testing.T, manifestReader io.Reader) (io.Rea return manifestReader, nil } +// getKongVersionFromOverrideTag parses Kong version from env effective version or override tag. The effective version +// takes precedence. +// +//lint:ignore U1000 retained for future use +func getKongVersionFromOverrideTag() (kong.Version, error) { + if kongEffectiveVersion := testenv.KongEffectiveVersion(); kongEffectiveVersion != "" { + return kong.ParseSemanticVersion(kongEffectiveVersion) + } + + if testenv.KongImageTag() == "" { + return kong.Version{}, errors.New("No Kong tag provided") + } + + return kong.ParseSemanticVersion(testenv.KongTag()) +} + // getKongProxyIP takes a Service with Kong proxy ports and returns and its IP, or fails the test if it cannot. func getKongProxyIP(ctx context.Context, t *testing.T, env environments.Environment) string { t.Helper() diff --git a/test/internal/testenv/testenv.go b/test/internal/testenv/testenv.go index 2f3976338e..b6078dc2c6 100644 --- a/test/internal/testenv/testenv.go +++ b/test/internal/testenv/testenv.go @@ -41,6 +41,33 @@ func KongTag() string { return os.Getenv("TEST_KONG_TAG") } +// KongImageTag is the combined Kong image and tag if both are set, or empty string if not. +func KongImageTag() string { + if KongImage() != "" && KongTag() != "" { + return fmt.Sprintf("%s:%s", KongImage(), KongTag()) + } + return "" +} + +// ControllerImage is the Kong image to use in lieu of the default. +func ControllerImage() string { + return os.Getenv("TEST_CONTROLLER_IMAGE") +} + +// ControllerTag is the Kong image tag to use in tests. +func ControllerTag() string { + return os.Getenv("TEST_CONTROLLER_TAG") +} + +// ControllerImageTag is the combined Controller image and tag if both are set, or empty string if not. +func ControllerImageTag() string { + if ControllerImage() != "" && ControllerTag() != "" { + return fmt.Sprintf("%s:%s", ControllerImage(), ControllerTag()) + } + return "" +} + +// KongEffectiveVersion is the effective semver of kong gateway. // KongEffectiveVersion is the effective semver of kong gateway. // When testing against "nightly" image of kong gateway, we need to set the effective version for parsing semver in chart templates. func KongEffectiveVersion() string { @@ -96,6 +123,33 @@ func ClusterVersion() string { return os.Getenv("KONG_CLUSTER_VERSION") } +// ClusterProvider indicates the Kubernetes cluster provider. +func ClusterProvider() string { + return os.Getenv("KONG_CLUSTER_PROVIDER") +} + +// ClusterLoadImages loads images into test clusters when set. +func ClusterLoadImages() string { + return os.Getenv("TEST_KONG_LOAD_IMAGES") +} + +// See: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + +// GithubServerURL returns a Github server URL from a Github Actions environment. +func GithubServerURL() string { + return os.Getenv("GITHUB_SERVER_URL") +} + +// GithubRepo returns a Github repository from a Github Actions environment. +func GithubRepo() string { + return os.Getenv("GITHUB_REPOSITORY") +} + +// GithubRunID returns a Github run ID from a Github Actions environment. +func GithubRunID() string { + return os.Getenv("GITHUB_RUN_ID") +} + // ControllerFeatureGates contains the feature gates that should be enabled // for test runs in the controller. // If none specified, we fall back to default values.