Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The following steps must be done by one of the [Gateway API maintainers][gateway
in the upcoming steps.
- Use `git` to cherry-pick all relevant PRs into your branch.
- Update `pkg/consts/consts.go` with the new semver tag and any updates to the API review URL.
- Update regex spec.validations.expression in `config/crd/standard/gateway.networking.k8s.io_vap_safeupgrades.yaml` to match older versions.
- Run the following command `BASE_REF=vmajor.minor.patch make generate` which
will update generated docs with the correct version info. (Note that you can't
test with these YAMLs yet as they contain references to elements which wont
Expand All @@ -63,6 +64,7 @@ The following steps must be done by one of the [Gateway API maintainers][gateway
- Cut a `release-major.minor` branch that we can tag things in as needed.
- Check out the `release-major.minor` release branch locally.
- Update `pkg/consts/consts.go` with the new semver tag and any updates to the API review URL.
- Update regex spec.validations.expression in `config/crd/standard/gateway.networking.k8s.io_vap_safeupgrades.yaml` to match older versions.
- Run the following command `BASE_REF=vmajor.minor.patch make generate` which
will update generated docs with the correct version info. (Note that you can't
test with these YAMLs yet as they contain references to elements which wont
Expand Down
1 change: 1 addition & 0 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ resources:
- standard/gateway.networking.k8s.io_httproutes.yaml
- standard/gateway.networking.k8s.io_referencegrants.yaml
- standard/gateway.networking.k8s.io_backendtlspolicies.yaml
- standard/gateway.networking.k8s.io_vap_safeupgrades.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions hack/delete-crds.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ for TYPE in ${RESOURCES}; do
kubectl delete ${TYPE} --all
kubectl delete crd/${TYPE}
done

kubectl delete ValidatingAdmissionPolicy/safe-upgrades.gateway.networking.k8s.io
kubectl delete ValidatingAdmissionPolicyBinding/safe-upgrades.gateway.networking.k8s.io
135 changes: 110 additions & 25 deletions tests/crd/crd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ limitations under the License.
package crd_test

import (
"bytes"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -70,40 +74,105 @@ func TestCRDValidation(t *testing.T) {
crdChannel = requestedCRDChannel
}

t.Run("should be able to start test environment", func(_ *testing.T) {
testEnv = &envtest.Environment{
Scheme: scheme,
ErrorIfCRDPathMissing: true,
DownloadBinaryAssets: true,
DownloadBinaryAssetsVersion: k8sVersion,
CRDInstallOptions: envtest.CRDInstallOptions{
Paths: []string{
filepath.Join("..", "..", "config", "crd", crdChannel),
},
CleanUpAfterUse: true,
testEnv = &envtest.Environment{
Scheme: scheme,
ErrorIfCRDPathMissing: true,
DownloadBinaryAssets: true,
DownloadBinaryAssetsVersion: k8sVersion,
CRDInstallOptions: envtest.CRDInstallOptions{
Paths: []string{
filepath.Join("..", "..", "..", "config", "crd", crdChannel),
},
}

_, err = testEnv.Start()
if err != nil {
panic(fmt.Sprintf("Error initializing test environment: %v", err))
}
})
CleanUpAfterUse: true,
},
}

_, err = testEnv.Start()
t.Cleanup(func() {
require.NoError(t, testEnv.Stop())
})
require.NoError(t, err, "Error initializing test environment")

// Setup kubectl and kubeconfig
kubectlLocation = testEnv.ControlPlane.KubectlPath
require.NotEmpty(t, kubectlLocation, "Error initializing Kubectl")

t.Run("should be able to set kubectl and kubeconfig and connect to the cluster", func(t *testing.T) {
kubectlLocation = testEnv.ControlPlane.KubectlPath
require.NotEmpty(t, kubectlLocation)
Comment on lines -97 to -99
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for removing this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put this logic in the parent test so that you could run targeted tests without issue. for example "make test.crds-validation GO_TEST_FLAGS="-run TestCRDValidation/safeupgrades_VAP_should_validate_correctly"". would not work as well as a few other tests because it depended on logic in this test.

kubeconfigLocation = fmt.Sprintf("%s/kubeconfig", filepath.Dir(kubectlLocation))
err = os.WriteFile(kubeconfigLocation, testEnv.KubeConfig, 0o600)
require.NoError(t, err, "Error initializing kubeconfig")

kubeconfigLocation = fmt.Sprintf("%s/kubeconfig", filepath.Dir(kubectlLocation))
require.NoError(t, os.WriteFile(kubeconfigLocation, testEnv.KubeConfig, 0o600))
apiResources, err := executeKubectlCommand(t, kubectlLocation, kubeconfigLocation, []string{"api-resources"})
require.NoError(t, err)
require.Contains(t, apiResources, "gateway.networking.k8s.io/v1")

t.Run("safeupgrades VAP should validate correctly", func(t *testing.T) {
if crdChannel == "experimental" {
t.Skipf("skipping safeupgrades VAP")
}

apiResources, err := executeKubectlCommand(t, kubectlLocation, kubeconfigLocation, []string{"api-resources"})
output, err := executeKubectlCommand(t, kubectlLocation, kubeconfigLocation,
[]string{"apply", "--server-side", "--wait", "-f", filepath.Join("..", "..", "..", "config", "crd", "standard", "gateway.networking.k8s.io_vap_safeupgrades.yaml")})
require.NoError(t, err)
require.Contains(t, apiResources, "gateway.networking.k8s.io/v1")

// Even though --wait is applied I noticed a race condition that causes tests to fail.
time.Sleep(time.Second)

t.Run("should be able to install standard CRDs", func(t *testing.T) {
output, err = executeKubectlCommand(t, kubectlLocation, kubeconfigLocation,
[]string{"apply", "--server-side", "--wait", "-f", filepath.Join("..", "..", "..", "config", "crd", "standard")})
require.NoError(t, err)
})

t.Run("should not be able to install k8s.io experimental CRDs", func(t *testing.T) {
t.Cleanup(func() {
output, err = executeKubectlCommand(t, kubectlLocation, kubeconfigLocation,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should you assert/require that an error does not happen here as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also shouldn't this cleanup be on "crd/standard"? (or am I missing something here?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback. I didn't assert the error here because failing to delete in cleanup should not invalidate a successful test IMO. Though it would make sense to add an assert in cleanup for the test "should be able to install x-k8s.io experimental CRDs" and I would update the name to "should be able to install and remove x-k8s.io experimental CRDs". But for the other 2 if there is no error and enter clean up there is nothing to delete anyways so throwing an error would be a false negative. Tho since I'm not using output or err I should rename to _, _. But to your point if it is failing to cleanup we probably want to see that in some form. To me it would make since to have a separate test for testing delete.

I didn't put a cleanup on crd/standard because the following test is to test installing experimental on top of standard. So it makes since to not clean up the first test to have it persist for the following tests. But ultimately the VAP doesn't care so it doesn't really matter.

[]string{"delete", "--wait", "-f", filepath.Join("..", "..", "..", "config", "crd", "experimental", "*.k8s.*")})
})

output, err = executeKubectlCommand(t, kubectlLocation, kubeconfigLocation,
[]string{"apply", "--server-side", "--wait", "-f", filepath.Join("..", "..", "..", "config", "crd", "experimental", "*.k8s.*")})
require.Error(t, err)
assert.Contains(t, output, "Error from server (Invalid)")
assert.Contains(t, output, "ValidatingAdmissionPolicy 'safe-upgrades.gateway.networking.k8s.io' with binding 'safe-upgrades.gateway.networking.k8s.io' denied request")

// Check that --api-group has no invalid crd's
output, err = executeKubectlCommand(t, kubectlLocation, kubeconfigLocation, []string{"describe", "CustomResourceDefinition"})
require.NoError(t, err)
assert.NotContains(t, output, "gateway.networking.k8s.io/channel: experimental", "output contains 'gateway.networking.k8s.io/channel: experimental'")
})

t.Run("should be able to install x-k8s.io experimental CRDs", func(t *testing.T) {
t.Cleanup(func() {
output, err = executeKubectlCommand(t, kubectlLocation, kubeconfigLocation,
[]string{"delete", "--wait", "-f", filepath.Join("..", "..", "..", "config", "crd", "experimental", "*.x-k8s.*")})
})

output, err = executeKubectlCommand(t, kubectlLocation, kubeconfigLocation,
[]string{"apply", "--server-side", "--wait", "-f", filepath.Join("..", "..", "..", "config", "crd", "experimental", "*.x-k8s.*")})
require.NoError(t, err)
})

t.Run("should not be able to install CRDs with an older version", func(t *testing.T) {
t.Cleanup(func() {
output, err = executeKubectlCommand(t, kubectlLocation, kubeconfigLocation,
[]string{"delete", "--wait", "-f", filepath.Join("..", "..", "..", "config", "crd", "standard", "gateway.networking.k8s.io_httproutes.yaml")})
})

// Read test crd into []byte
httpCrd, err := os.ReadFile(filepath.Join("..", "..", "..", "config", "crd", "standard", "gateway.networking.k8s.io_httproutes.yaml"))
require.NoError(t, err)

// do replace on gateway.networking.k8s.io/bundle-version: v1.4.0
re := regexp.MustCompile(`gateway\.networking\.k8s\.io\/bundle-version: \S*`)
sub := []byte("gateway.networking.k8s.io/bundle-version: v1.3.0")
oldCrd := re.ReplaceAll(httpCrd, sub)

// supply crd to stdin of cmd and kubectl apply -f -
output, err = executeKubectlCommandStdin(t, kubectlLocation, kubeconfigLocation, bytes.NewReader(oldCrd), []string{"apply", "-f", "-"})

require.Error(t, err)
assert.Contains(t, output, "ValidatingAdmissionPolicy 'safe-upgrades.gateway.networking.k8s.io' with binding 'safe-upgrades.gateway.networking.k8s.io' denied request")
})
})

t.Run("should be able to install standard examples", func(t *testing.T) {
Expand Down Expand Up @@ -155,6 +224,22 @@ func executeKubectlCommand(t *testing.T, kubectl, kubeconfig string, args []stri
return string(output), err
}

func executeKubectlCommandStdin(t *testing.T, kubectl, kubeconfig string, stdin io.Reader, args []string) (string, error) {
t.Helper()

cacheDir := filepath.Dir(kubeconfig)
args = append([]string{"--cache-dir", cacheDir}, args...)

cmd := exec.Command(kubectl, args...)
cmd.Env = []string{
fmt.Sprintf("KUBECONFIG=%s", kubeconfig),
}
cmd.Stdin = stdin

output, err := cmd.CombinedOutput()
return string(output), err
}

func getInvalidExamplesFiles(t *testing.T, crdChannel string) ([]string, error) {
t.Helper()

Expand Down