Repository: radius-project/radius

Issue:
Call to `rad bicep publish-extension ` where target exists results in misleading error
### Steps to reproduce

1. Create a yaml file for a bicep extension. A reference file for eg. deploy/manifest/built-in-providers/self-hosted/applications_core.yaml. Pls create different provider/type names. 
2. Run 
```
rad bicep publish-extension -f <path to .yaml file> --target br:<acr registry location>:label
```
3. bicep extension is successfully created.
4. rerun command in 2. above after a possible .yaml file update, we get error: 
```
The extension "br:<acr registry location>:label" already exists. Use --force to overwrite the existing extension.
```
5. Attempting to rerun the above command with additional --force argument results in error :
```
Error: unknown flag: --force
```

### Observed behavior

If a bicep extension is created using 
```
rad bicep-extension ..
```
to an existing target, we see that the user cannot overwrite the existing extension using --force

### Desired behavior

We should either:
1. Incorporate `--force ` flag into the command so that the user has a way to update the extension at an existing target location or 
2. Update the existing error message to ask the user to say delete the existing target and then rerun command.

### Workaround

User is able to push updated extension to target by first deleting the existing target and then rerunning the command.

### rad Version

v0.43.0

### Operating system

M1

### Additional context

_No response_

### Would you like to support us?

- [ ] Yes, I would like to support you

[AB#15217](https://dev.azure.com/azure-octo/e61041b4-555f-47ae-95b2-4f8ab480ea57/_workitems/edit/15217)

Hints from issue comments:
@radius-triage-bot: :wave: @lakshmimsft Thanks for filing this bug report. 

A project maintainer will review this report and get back to you soon. If you'd like immediate help troubleshooting, please visit our [Discord server](https://aka.ms/radius/discord).

_For more information on our triage process please visit our [triage overview](https://github.com/radius-project/radius/blob/main/docs/contributing/triage/triage-process.md)_

Solution patch:
diff --git a/.github/workflows/functional-test-noncloud.yaml b/.github/workflows/functional-test-noncloud.yaml
index abc553627..38454dd6b 100644
--- a/.github/workflows/functional-test-noncloud.yaml
+++ b/.github/workflows/functional-test-noncloud.yaml
@@ -145,7 +145,6 @@ jobs:
             samples-noncloud,
             ucp-noncloud,
             datastoresrp-noncloud,
-            dynamicrp-noncloud,
           ]
     runs-on: ${{ matrix.os }}
     env:
@@ -406,39 +405,6 @@ jobs:
           sudo mv ./bicep /usr/local/bin/bicep
           bicep --version
 
-      - name: Publish bicep types 
-        run: | 
-          bicep publish-extension ./hack/bicep-types-radius/generated/index.json --target br:${{ env.LOCAL_REGISTRY_SERVER }}:${{ env.LOCAL_REGISTRY_PORT }}/radius:${{ env.REL_VERSION == 'edge' && 'latest' || env.REL_VERSION }} --force     
-
-      - name: Publish UDT types 
-        run: |
-          export PATH=$GITHUB_WORKSPACE/bin:$PATH
-          which rad || { echo "cannot find rad"; exit 1; }
-          rad bicep publish-extension -f ./test/functional-portable/dynamicrp/noncloud/resources/testdata/usertypealpha.yaml --target br:${{ env.LOCAL_REGISTRY_SERVER }}:${{ env.LOCAL_REGISTRY_PORT }}/testresources:${{ env.REL_VERSION == 'edge' && 'latest' || env.REL_VERSION }}
-
-      - name: Generate test bicepconfig.json
-        run: |
-          if [[ "${{ env.REL_VERSION }}" == "edge" ]]; then
-            RADIUS_VERSION="latest"
-          else
-            RADIUS_VERSION="${{ env.REL_VERSION }}"
-          fi
-          cat <<EOF > ./test/bicepconfig.json
-          {
-            "experimentalFeaturesEnabled": {
-              "extensibility": true
-            },
-            "extensions": {
-              "radius": "br:${{ env.LOCAL_REGISTRY_SERVER }}:${{ env.LOCAL_REGISTRY_PORT }}/radius:$RADIUS_VERSION",
-              "aws": "br:${{ env.BICEP_TYPES_REGISTRY }}/aws:latest",
-              "testresources": "br:${{ env.LOCAL_REGISTRY_SERVER }}:${{ env.LOCAL_REGISTRY_PORT }}/testresources:$RADIUS_VERSION"
-            }
-          }
-          EOF
-
-          # Copy to the dynamicrp resources test directory
-          cp -f ./test/bicepconfig.json ./test/functional-portable/dynamicrp/noncloud/resources/bicepconfig.json
-
       - name: Publish Bicep Test Recipes
         run: |
           export PATH=$GITHUB_WORKSPACE/bin:$PATH
diff --git a/.github/workflows/long-running-azure.yaml b/.github/workflows/long-running-azure.yaml
index 9fb1048ea..0557a872c 100644
--- a/.github/workflows/long-running-azure.yaml
+++ b/.github/workflows/long-running-azure.yaml
@@ -569,15 +569,12 @@ jobs:
         run: |
           echo "FUNCTEST_OIDC_ISSUER=$(az aks show -n ${{ env.AKS_CLUSTER_NAME }} -g ${{ env.AKS_RESOURCE_GROUP }} --query "oidcIssuerProfile.issuerUrl" -otsv)" >> $GITHUB_ENV
       - name: Restore Bicep artifacts before running functional tests
-      # The exact files chosen to run the command can be changed, but we need 1 that uses the Radius extension, 1 that uses the AWS extension and 1 that uses UDT testresources
-      # so we can restore all Bicep artifacts before the tests start.
+      # The exact files chosen to run the command can be changed, but we need 1 that uses the Radius extension and 1 that uses the AWS extension so we can restore both Bicep artifacts before the tests start
         run: |
           # Restore Radius Bicep types
           bicep restore ./test/functional-portable/corerp/cloud/resources/testdata/corerp-azure-connection-database-service.bicep --force
           # Restore AWS Bicep types 
           bicep restore ./test/functional-portable/corerp/cloud/resources/testdata/aws-s3-bucket.bicep --force
-          # Restore UDT Test Bicep types
-          bicep restore ./test/functional-portable/dynamicrp/noncloud/resources/testdata/usertypealpha-recipe.bicep
       - name: Run functional tests
         run: |
           # Ensure rad cli is in path before running tests.
diff --git a/build/test.mk b/build/test.mk
index e1d1242f8..be17f6f6c 100644
--- a/build/test.mk
+++ b/build/test.mk
@@ -108,9 +108,6 @@ test-functional-datastoresrp: test-functional-datastoresrp-noncloud ## Runs all
 test-functional-datastoresrp-noncloud: ## Runs Datastores RP functional tests that do not require cloud resources
 	CGO_ENABLED=1 $(GOTEST_TOOL) ./test/functional-portable/datastoresrp/noncloud/... -timeout ${TEST_TIMEOUT} -v -parallel 3 $(GOTEST_OPTS)
 
-test-functional-dynamicrp-noncloud: ## Runs Dynamic RP functional tests that do not require cloud resources
-	CGO_ENABLED=1 $(GOTEST_TOOL) ./test/functional-portable/dynamicrp/noncloud/... -timeout ${TEST_TIMEOUT} -v -parallel 2 $(GOTEST_OPTS)
-	
 test-functional-samples: test-functional-samples-noncloud ## Runs all Samples functional tests
 
 test-functional-samples-noncloud: ## Runs Samples functional tests that do not require cloud resources
diff --git a/build/validate-bicep.sh b/build/validate-bicep.sh
index e033f32d1..f0ea3cd92 100755
--- a/build/validate-bicep.sh
+++ b/build/validate-bicep.sh
@@ -10,26 +10,20 @@ FILES=$(find . -type f -name "*.bicep")
 # Get the first bicep file with Radius and AWS extensions from the list to restore extensions
 FIRST_FILE_RAD=""
 FIRST_FILE_AWS=""
-FIRST_FILE_TESTRESOURCES=""
 for F in $FILES
 do
-    # Check if the file contains the string "extension radius"
+    # Check if the file contains the word "extension radius"
     if [ -z "$FIRST_FILE_RAD" ] && grep -q "extension radius" "$F"; then
         FIRST_FILE_RAD="$F"
     fi
-    # Check if the file contains the string "extension aws"
+    # Check if the file contains the word "extension aws"
     if [ -z "$FIRST_FILE_AWS" ] && grep -q "extension aws" "$F"; then
         FIRST_FILE_AWS="$F"
     fi
-    # Check if the file contains the string "extension testresources"
-    if [ -z "$FIRST_FILE_TESTRESOURCES" ] && grep -q "extension testresources" "$F"; then
-        FIRST_FILE_TESTRESOURCES="$F"
-    fi
-    # Break the loop if all three extensions are found
-    if [ -n "$FIRST_FILE_RAD" ] && [ -n "$FIRST_FILE_AWS" ] && [ -n "$FIRST_FILE_TESTRESOURCES" ]; then
+    # Break the loop if both files are found
+    if [ -n "$FIRST_FILE_RAD" ] && [ -n "$FIRST_FILE_AWS" ]; then
         break
     fi
-
 done
 
 # Restore the extensions once 
@@ -41,10 +35,6 @@ echo "running AWS: $BICEP_PATH build $FIRST_FILE_AWS"
 STDERR=$($BICEP_PATH build $FIRST_FILE_AWS --stdout 2>&1 1>/dev/null)
 echo "Restoring AWS extension with response: $STDERR..."
 
-echo "running TestResources: $BICEP_PATH build $FIRST_FILE_TESTRESOURCES"
-STDERR=$($BICEP_PATH build $FIRST_FILE_TESTRESOURCES --stdout 2>&1 1>/dev/null)
-echo "Restoring TestResources extension with response: $STDERR..."
-
 FAILURES=()
 WARNINGS=()
 for F in $FILES



Test patch:
diff --git a/test/functional-portable/dynamicrp/noncloud/resources/bicepconfig.json b/test/functional-portable/dynamicrp/noncloud/resources/bicepconfig.json
deleted file mode 100644
index 2062a0515..000000000
--- a/test/functional-portable/dynamicrp/noncloud/resources/bicepconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-	"experimentalFeaturesEnabled": {
-		"extensibility": true
-	},
-	"extensions": {
-		"radius": "br:biceptypes.azurecr.io/radius:latest",
-		"aws": "br:biceptypes.azurecr.io/aws:latest",
-		"testresources": "br:biceptypes.azurecr.io/testresources:latest"
-	}
-}
\ No newline at end of file
diff --git a/test/functional-portable/dynamicrp/noncloud/resources/dynamicrp_test.go b/test/functional-portable/dynamicrp/noncloud/resources/dynamicrp_test.go
deleted file mode 100644
index 2c7ac673e..000000000
--- a/test/functional-portable/dynamicrp/noncloud/resources/dynamicrp_test.go
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
-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"
-	"testing"
-
-	"github.com/radius-project/radius/test"
-	"github.com/radius-project/radius/test/radcli"
-	"github.com/radius-project/radius/test/rp"
-	"github.com/radius-project/radius/test/step"
-	"github.com/radius-project/radius/test/testutil"
-	"github.com/radius-project/radius/test/validation"
-	"github.com/stretchr/testify/require"
-)
-
-// Test_DynamicRP_Recipe tests creation of a resource using a user-defined type and its associated recipe.
-// The test consists of two main steps:
-//
-// 1. Resource Type Registration:
-//   - Registers a user-defined resource type "Test.Resources/userTypeAlpha"
-//   - Verifies the registration by checking if the resource type is listed in the CLI output
-//
-// 2. Resource Deployment:
-//   - Deploys a Bicep template that uses the registered resource type with a default recipe
-//   - Validates the creation of required Radius resources (environment and application)
-//   - Verifies the creation of Kubernetes objects (pod) in the specified namespace
-//   - Confirms the resource's status shows correct binding configuration
-func Test_DynamicRP_Recipe(t *testing.T) {
-	template := "testdata/usertypealpha-recipe.bicep"
-	name := "usertypealpha-recipe-app"
-	appNamespace := "default-usertypealpha-recipe"
-	resourceTypeName := "Test.Resources/userTypeAlpha"
-	filepath := "testdata/usertypealpha.yaml"
-	options := rp.NewRPTestOptions(t)
-	cli := radcli.NewCLI(t, options.ConfigFilePath)
-
-	test := rp.NewRPTest(t, name, []rp.TestStep{
-		{
-			// The first step in this test is to create/register a user-defined resource type using the CLI.
-			Executor: step.NewFuncExecutor(func(ctx context.Context, t *testing.T, options test.TestOptions) {
-				_, err := cli.ResourceProviderCreate(ctx, filepath)
-				require.NoError(t, err)
-			}),
-			SkipKubernetesOutputResourceValidation: true,
-			SkipObjectValidation:                   true,
-			SkipResourceDeletion:                   true,
-			PostStepVerify: func(ctx context.Context, t *testing.T, test rp.RPTest) {
-				output, err := cli.RunCommand(ctx, []string{"resource-type", "show", resourceTypeName, "--output", "json"})
-				require.NoError(t, err)
-				require.Contains(t, output, resourceTypeName)
-			},
-		},
-		{
-			// The next step is to deploy a bicep file using a default recipe for the resource type registered.
-			Executor: step.NewDeployExecutor(template, testutil.GetBicepRecipeRegistry(), testutil.GetBicepRecipeVersion()),
-			RPResources: &validation.RPResourceSet{
-				Resources: []validation.RPResource{
-					{
-						Name: "usertypealpha-recipe-env",
-						Type: validation.EnvironmentsResource,
-					},
-					{
-						Name: name,
-						Type: validation.ApplicationsResource,
-					},
-				},
-			},
-			K8sObjects: &validation.K8sObjectSet{
-				Namespaces: map[string][]validation.K8sObject{
-					appNamespace: {
-						validation.NewK8sPodForResource(name, "usertypealpha").ValidateLabels(false),
-					},
-				},
-			},
-			PostStepVerify: func(ctx context.Context, t *testing.T, test rp.RPTest) {
-				usertypealpha, err := test.Options.ManagementClient.GetResource(ctx, "Test.Resources/userTypeAlpha", "usertypealpha123")
-				require.NoError(t, err)
-				require.NotNil(t, usertypealpha)
-				status := usertypealpha.Properties["status"].(map[string]any)
-				binding := status["binding"].(map[string]interface{})
-				require.Equal(t, "8080", binding["port"].(string))
-			},
-		},
-	})
-
-	test.Test(t)
-}
diff --git a/test/functional-portable/dynamicrp/noncloud/resources/testdata/usertypealpha-recipe.bicep b/test/functional-portable/dynamicrp/noncloud/resources/testdata/usertypealpha-recipe.bicep
deleted file mode 100644
index 5ff4c21bd..000000000
--- a/test/functional-portable/dynamicrp/noncloud/resources/testdata/usertypealpha-recipe.bicep
+++ /dev/null
@@ -1,46 +0,0 @@
-extension testresources
-extension radius
-
-param registry string
-
-param version string
-
-@description('Specifies the location for resources.')
-param location string = 'global'
-
-resource env 'Applications.Core/environments@2023-10-01-preview' = {
-  name: 'usertypealpha-recipe-env'
-  location: location
-  properties: {
-    compute: {
-      kind: 'kubernetes'
-      resourceId: 'self'
-      namespace: 'usertypealpha-recipe-env'
-    }
-    recipes: {
-      'Test.Resources/userTypeAlpha': {
-        default: {
-          templateKind: 'bicep'
-          templatePath: '${registry}/test/testrecipes/test-bicep-recipes/dynamicrp_recipe:${version}'          
-        }
-      }
-    }
-  }
-}
-
-resource app 'Applications.Core/applications@2023-10-01-preview' = {
-  name: 'usertypealpha-recipe-app'
-  location: location
-  properties: {
-    environment: env.id
-  }
-}
-
-resource usertypealpha 'Test.Resources/userTypeAlpha@2023-10-01-preview' = {
-  name: 'usertypealpha123'
-  location: location
-  properties: {
-    application: app.id
-    environment: env.id
-  }
-}
diff --git a/test/functional-portable/dynamicrp/noncloud/resources/testdata/usertypealpha.yaml b/test/functional-portable/dynamicrp/noncloud/resources/testdata/usertypealpha.yaml
deleted file mode 100644
index ccabf2ffe..000000000
--- a/test/functional-portable/dynamicrp/noncloud/resources/testdata/usertypealpha.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Test.Resources
-types:
-  userTypeAlpha:
-    capabilities: ["SupportsRecipes"]
-    apiVersions:
-      "2023-10-01-preview":
-        schema:
-          type: "object"
-          properties:
-            application:
-              type: string
-              description: The resource ID of the application.
-            environment:
-              type: string
-              description: The resource ID of the environment.
-          required:
-            - application
-            - environment
\ No newline at end of file
diff --git a/test/radcli/cli.go b/test/radcli/cli.go
index 743f2153b..cbc20f9c5 100644
--- a/test/radcli/cli.go
+++ b/test/radcli/cli.go
@@ -287,18 +287,6 @@ func (cli *CLI) BicepPublish(ctx context.Context, file, target string) (string,
 	return cli.RunCommand(ctx, args)
 }
 
-// ResourceProviderCreate runs a command to create or update a resource provider and it's associated resource types from a manifest file.
-// It returns the output string or an error if the command fails.
-func (cli *CLI) ResourceProviderCreate(ctx context.Context, manifestFilePath string) (string, error) {
-	args := []string{
-		"resource-provider",
-		"create",
-		"--from-file",
-		manifestFilePath,
-	}
-	return cli.RunCommand(ctx, args)
-}
-
 // Version runs the version command and returns the output as a string, or an error if the command fails.
 func (cli *CLI) Version(ctx context.Context) (string, error) {
 	args := []string{
diff --git a/test/step/funcexecutor.go b/test/step/funcexecutor.go
deleted file mode 100644
index 5290e694a..000000000
--- a/test/step/funcexecutor.go
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-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 step
-
-import (
-	"context"
-	"testing"
-
-	"github.com/radius-project/radius/test"
-)
-
-var _ Executor = (*FuncExecutor)(nil)
-
-// FuncExecutor implements the Executor interface. It encapsulates a function to be executed in a test step
-// and includes a description.
-type FuncExecutor struct {
-	fn          func(ctx context.Context, t *testing.T, opts test.TestOptions)
-	Description string
-}
-
-// Execute runs the encapsulated function with the provided context, testing instance, and test options.
-func (f FuncExecutor) Execute(ctx context.Context, t *testing.T, opts test.TestOptions) {
-	f.fn(ctx, t, opts)
-}
-
-// NewFuncExecutor initializes and returns a new FuncExecutor with the given function and a default description.
-func NewFuncExecutor(fn func(ctx context.Context, t *testing.T, opts test.TestOptions)) FuncExecutor {
-	return FuncExecutor{fn: fn,
-		Description: "execute function in test step",
-	}
-}
-
-// GetDescription returns the description of the test step encapsulated by the FuncExecutor.
-func (f FuncExecutor) GetDescription() string {
-	return f.Description
-}
diff --git a/test/testrecipes/test-bicep-recipes/dynamicrp_recipe.bicep b/test/testrecipes/test-bicep-recipes/dynamicrp_recipe.bicep
deleted file mode 100644
index 3e25f4c59..000000000
--- a/test/testrecipes/test-bicep-recipes/dynamicrp_recipe.bicep
+++ /dev/null
@@ -1,55 +0,0 @@
-extension kubernetes with {
-  kubeConfig: ''
-  namespace: context.runtime.kubernetes.namespace
-} as kubernetes
-
-param context object
-
-resource usertypealpha 'apps/Deployment@v1' = {
-  metadata: {
-    name: 'usertypealpha-${uniqueString(context.resource.id)}'
-  }
-  spec: {
-    selector: {
-      matchLabels: {
-        app: 'usertypealpha'
-        resource: context.resource.name
-      }
-    }
-    template: {
-      metadata: {
-        labels: {
-          app: 'usertypealpha'
-          resource: context.resource.name
-        }
-      }
-      spec: {
-        containers: [
-          {
-            name: 'usertypealpha'
-            image: 'alpine:latest'
-            ports: [
-              {
-                containerPort: 8080
-              }
-            ]
-            command: ['/bin/sh']
-            args: ['-c', 'while true; do sleep 30; done']
-          }
-        ]
-      }
-    }
-  }
-}
-
-output result object = {
-  // This workaround is needed because the deployment engine omits Kubernetes resources from its output.
-  //
-  // Once this gap is addressed, users won't need to do this.
-  resources: [
-    '/planes/kubernetes/local/namespaces/${usertypealpha.metadata.namespace}/providers/apps/Deployment/${usertypealpha.metadata.name}'
-  ]
-  values: {
-    port: '8080'
-  }
-}