diff --git a/test/functional-portable/cli/noncloud/env_deploy_test.go b/test/functional-portable/cli/noncloud/env_deploy_test.go index dba790be1f2..5291bf96648 100644 --- a/test/functional-portable/cli/noncloud/env_deploy_test.go +++ b/test/functional-portable/cli/noncloud/env_deploy_test.go @@ -38,41 +38,22 @@ func Test_DeployEnvironmentTemplate(t *testing.T) { t.Cleanup(cancel) options := rp.NewRPTestOptions(t) + cli := newCLIWithoutDefaultEnvironment(t, options) - // Create a custom config file without a default environment to test the scenario. - - // Get the connection details from the existing workspace - connectionKind := options.Workspace.Connection["kind"] - connectionContext := options.Workspace.Connection["context"] - - // Create a temporary config file with workspace that has NO default environment - tempConfigFile, err := os.CreateTemp("", "rad-test-config-*.yaml") - require.NoError(t, err, "Failed to create temp config file") - defer os.Remove(tempConfigFile.Name()) - - // Build config YAML with workspace but NO environment field - configYAML := fmt.Sprintf(`workspaces: - default: test-workspace - items: - test-workspace: - connection: - kind: %v - context: %v - `, connectionKind, connectionContext) - - _, err = tempConfigFile.WriteString(configYAML) - require.NoError(t, err, "Failed to write config file") - err = tempConfigFile.Close() - require.NoError(t, err, "Failed to close config file") - - // Use CLI with the custom config that has no default environment - cli := radcli.NewCLI(t, tempConfigFile.Name()) - - // Generate a unique resource group name to avoid conflicts with parallel tests - uniqueGroupName := fmt.Sprintf("test-deploy-env-group-%s", uuid.New().String()) + cwd, err := os.Getwd() + require.NoError(t, err) + + // Generate a unique suffix so the resource group and Kubernetes namespace do + // not collide with parallel, repeated, or stale runs against the same cluster. + uniqueSuffix := uuid.New().String() + uniqueGroupName := fmt.Sprintf("test-deploy-env-group-%s", uniqueSuffix) envName := "test-deploy-env" + envNamespace := fmt.Sprintf("default-test-deploy-env-%s", uniqueSuffix) // Ensure cleanup even if test fails + t.Cleanup(func() { + deleteKubernetesNamespace(context.Background(), t, options, envNamespace) + }) t.Cleanup(func() { // Try to delete the test group if it still exists // Ignore errors as the group might have been successfully deleted @@ -84,20 +65,20 @@ func Test_DeployEnvironmentTemplate(t *testing.T) { err = cli.GroupCreate(ctx, uniqueGroupName) require.NoError(t, err, "Failed to create resource group") + createKubernetesNamespace(ctx, t, options, envNamespace) + // Get the template file path - cwd, err := os.Getwd() - require.NoError(t, err) templateFilePath := filepath.Join(cwd, "testdata/corerp-env-deploy-test.bicep") // Deploy the environment template without specifying --environment flag t.Logf("Deploying environment template to group: %s without --environment flag", uniqueGroupName) - err = cli.DeployWithGroup(ctx, templateFilePath, "", "", uniqueGroupName) + err = cli.DeployWithGroup(ctx, templateFilePath, "", "", uniqueGroupName, "envNamespace="+envNamespace) require.NoError(t, err, "Failed to deploy environment template") // Verify environment was created successfully t.Logf("Verifying environment was created: %s", envName) showOpts := radcli.ShowOptions{Group: uniqueGroupName} - output, err := cli.ResourceShow(ctx, "Applications.Core/environments", envName, showOpts) + output, err := cli.ResourceShow(ctx, "Radius.Core/environments", envName, showOpts) require.NoError(t, err, "Failed to show environment %s", envName) require.Contains(t, output, envName, "Environment %s should exist", envName) diff --git a/test/functional-portable/cli/noncloud/namespace_helpers_test.go b/test/functional-portable/cli/noncloud/namespace_helpers_test.go new file mode 100644 index 00000000000..5b772b14648 --- /dev/null +++ b/test/functional-portable/cli/noncloud/namespace_helpers_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource_test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/radius-project/radius/pkg/cli/kubernetes" + "github.com/radius-project/radius/test/radcli" + "github.com/radius-project/radius/test/rp" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func newCLIWithoutDefaultEnvironment(t *testing.T, options rp.RPTestOptions) *radcli.CLI { + t.Helper() + + tempConfigFile, err := os.CreateTemp(t.TempDir(), "rad-test-config-*.yaml") + require.NoError(t, err, "Failed to create temp config file") + + configYAML := fmt.Sprintf(`workspaces: + default: test-workspace + items: + test-workspace: + connection: + kind: %v + context: %v + `, options.Workspace.Connection["kind"], options.Workspace.Connection["context"]) + + _, err = tempConfigFile.WriteString(configYAML) + require.NoError(t, err, "Failed to write config file") + err = tempConfigFile.Close() + require.NoError(t, err, "Failed to close config file") + + return radcli.NewCLI(t, tempConfigFile.Name()) +} + +func createKubernetesNamespace(ctx context.Context, t *testing.T, options rp.RPTestOptions, namespace string) { + t.Helper() + + client, err := rp.DeploymentTargetK8sClient(options) + require.NoError(t, err, "failed to build deployment-target Kubernetes client") + require.NoError(t, kubernetes.EnsureNamespace(ctx, client, namespace), "failed to create namespace %s", namespace) +} + +func deleteKubernetesNamespace(ctx context.Context, t *testing.T, options rp.RPTestOptions, namespace string) { + t.Helper() + + client, err := rp.DeploymentTargetK8sClient(options) + if err != nil { + t.Logf("Warning: Failed to build deployment-target Kubernetes client: %v", err) + return + } + + err = client.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + t.Logf("Warning: Failed to delete namespace %s: %v", namespace, err) + } +} diff --git a/test/functional-portable/cli/noncloud/testdata/corerp-env-deploy-test.bicep b/test/functional-portable/cli/noncloud/testdata/corerp-env-deploy-test.bicep index f4c937bc861..202b4dc18ea 100644 --- a/test/functional-portable/cli/noncloud/testdata/corerp-env-deploy-test.bicep +++ b/test/functional-portable/cli/noncloud/testdata/corerp-env-deploy-test.bicep @@ -1,12 +1,15 @@ extension radius -resource env 'Applications.Core/environments@2023-10-01-preview' = { +@description('Specifies the Kubernetes namespace where the environment deploys recipe resources.') +param envNamespace string = 'default-test-deploy-env' + +resource env 'Radius.Core/environments@2025-08-01-preview' = { name: 'test-deploy-env' properties: { - compute: { - kind: 'kubernetes' - resourceId: 'self' - namespace: 'default-test-deploy-env' + providers: { + kubernetes: { + namespace: envNamespace + } } } } diff --git a/test/rp/rptest.go b/test/rp/rptest.go index 03681bbed7a..6df143c4d87 100644 --- a/test/rp/rptest.go +++ b/test/rp/rptest.go @@ -264,7 +264,7 @@ func NewPreviewEnvPreSetup(testName string, workspaceScope string, kubernetesNam // namespace named after the test (ct.Name) is auto-created by CreateInitialResources, so when // the preview environment uses a different namespace we must create it here before the CLI // call, otherwise env creation fails with "Namespace '' does not exist". - nsClient, err := test.deploymentTargetK8sClient() + nsClient, err := DeploymentTargetK8sClient(test.Options) require.NoError(t, err, "failed to build deployment-target Kubernetes client") require.NoError(t, kubernetes.EnsureNamespace(ctx, nsClient, kubernetesNamespace), "failed to ensure preview environment namespace exists") @@ -331,7 +331,7 @@ func K8sSecretResource(namespace, name, secretType string, kv ...any) unstructur // CreateInitialResources creates a namespace and creates initial resources from the InitialResources field of the // RPTest struct. It returns an error if either of these operations fail. func (ct RPTest) CreateInitialResources(ctx context.Context) error { - nsClient, err := ct.deploymentTargetK8sClient() + nsClient, err := DeploymentTargetK8sClient(ct.Options) if err != nil { return fmt.Errorf("failed to build deployment-target Kubernetes client: %w", err) } @@ -341,8 +341,8 @@ func (ct RPTest) CreateInitialResources(ctx context.Context) error { } for _, r := range ct.InitialResources { - if err := kubernetes.EnsureNamespace(ctx, ct.Options.K8sClient, r.GetNamespace()); err != nil { - return fmt.Errorf("failed to create namespace %s: %w", ct.Name, err) + if err := kubernetes.EnsureNamespace(ctx, nsClient, r.GetNamespace()); err != nil { + return fmt.Errorf("failed to create namespace %s: %w", r.GetNamespace(), err) } if err := ct.Options.Client.Create(ctx, &r); err != nil { return fmt.Errorf("failed to create resource %#v: %w", r, err) @@ -352,7 +352,7 @@ func (ct RPTest) CreateInitialResources(ctx context.Context) error { return nil } -// deploymentTargetK8sClient returns the Kubernetes client for the cluster that an +// DeploymentTargetK8sClient returns the Kubernetes client for the cluster that an // application's resources deploy to. In multi-cluster runs the test sets // RADIUS_TEST_EXTERNAL_KUBECONFIG to the external (workload) cluster's kubeconfig; // this mirrors the RADIUS_TARGET_KUBECONFIG contract Radius itself honors, so the @@ -360,10 +360,10 @@ func (ct RPTest) CreateInitialResources(ctx context.Context) error { // to and the control-plane cluster is not populated with per-application // namespaces. When the variable is unset (single-cluster runs) the control-plane // client is returned unchanged. -func (ct RPTest) deploymentTargetK8sClient() (k8sclient.Interface, error) { +func DeploymentTargetK8sClient(options RPTestOptions) (k8sclient.Interface, error) { kubeconfigPath := os.Getenv(externalKubeconfigEnvVar) if kubeconfigPath == "" { - return ct.Options.K8sClient, nil + return options.K8sClient, nil } config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)