diff --git a/.github/workflows/functional-test-cloud.yaml b/.github/workflows/functional-test-cloud.yaml index 2613e5c616..a15b0c6e22 100644 --- a/.github/workflows/functional-test-cloud.yaml +++ b/.github/workflows/functional-test-cloud.yaml @@ -657,49 +657,6 @@ jobs: --set global.azureWorkloadIdentity.enabled=true \ --set global.aws.irsa.enabled=true - echo "*** Verify manifests are registered ***" - rm -f registermanifest_logs.txt - # Find the pod with container "ucp" - POD_NAME=$( - kubectl get pods -n radius-system \ - -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[*].name}{"\n"}{end}' \ - | grep "ucp" \ - | head -n1 \ - | cut -d" " -f1 - ) - echo "Found ucp pod: $POD_NAME" - - if [ -z "$POD_NAME" ]; then - echo "No pod with container 'ucp' found in namespace radius-system." - exit 1 - fi - - # Poll logs for up to iterations, 30 seconds each (upto 3 minutes total) - for i in {1..6}; do - kubectl logs "$POD_NAME" -n radius-system | tee registermanifest_logs.txt > /dev/null - - # Exit on error - if grep -qi "error" registermanifest_logs.txt; then - echo "Error found in ucp logs." - exit 1 - fi - - # Check for success - if grep -q "Successfully registered manifests" registermanifest_logs.txt; then - echo "Successfully registered manifests - message found." - break - fi - - echo "Logs not ready, waiting 30 seconds..." - sleep 30 - done - - # Final check to ensure success message was found - if ! grep -q "Successfully registered manifests" registermanifest_logs.txt; then - echo "Manifests not registered after 3 minutes." - exit 1 - fi - echo "*** Create workspace, group and environment for test ***" rad workspace create kubernetes rad group create kind-radius diff --git a/.github/workflows/functional-test-noncloud.yaml b/.github/workflows/functional-test-noncloud.yaml index 0ff7586d9b..a0af5c4844 100644 --- a/.github/workflows/functional-test-noncloud.yaml +++ b/.github/workflows/functional-test-noncloud.yaml @@ -290,49 +290,6 @@ jobs: echo "*** Installing Radius to Kubernetes ***" eval $RAD_COMMAND - echo "*** Verify manifests are registered ***" - rm -f registermanifest_logs.txt - # Find the pod with container "ucp" - POD_NAME=$( - kubectl get pods -n radius-system \ - -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[*].name}{"\n"}{end}' \ - | grep "ucp" \ - | head -n1 \ - | cut -d" " -f1 - ) - echo "Found ucp pod: $POD_NAME" - - if [ -z "$POD_NAME" ]; then - echo "No pod with container 'ucp' found in namespace radius-system." - exit 1 - fi - - # Poll logs for up to iterations, 30 seconds each (upto 3 minutes total) - for i in {1..6}; do - kubectl logs "$POD_NAME" -n radius-system | tee registermanifest_logs.txt > /dev/null - - # Exit on error - if grep -qi "error" registermanifest_logs.txt; then - echo "Error found in ucp logs." - exit 1 - fi - - # Check for success - if grep -q "Successfully registered manifests" registermanifest_logs.txt; then - echo "Successfully registered manifests - message found." - break - fi - - echo "Logs not ready, waiting 30 seconds..." - sleep 30 - done - - # Final check to ensure success message was found - if ! grep -q "Successfully registered manifests" registermanifest_logs.txt; then - echo "Manifests not registered after 3 minutes." - exit 1 - fi - echo "*** Create workspace, group and environment for test ***" rad workspace create kubernetes rad group create kind-radius diff --git a/build/docker.mk b/build/docker.mk index 0fe2268b30..7adcdcaa6b 100644 --- a/build/docker.mk +++ b/build/docker.mk @@ -17,7 +17,6 @@ DOCKER_REGISTRY?=$(shell whoami) DOCKER_TAG_VERSION?=latest IMAGE_SRC?=https://github.com/radius-project/radius -MANIFEST_DIR?=deploy/manifest/built-in-providers/self-hosted ##@ Docker Images @@ -107,17 +106,6 @@ APPS_MAP := ucpd:./deploy/images/ucpd \ testrp:./test/testrp \ magpiego:./test/magpiego -# copy_manifests copies the manifests to the output directory -.PHONY: copy-manifests -copy-manifests: - @if [ ! -d "$(MANIFEST_DIR)" ] || [ -z "$$(ls -A $(MANIFEST_DIR))" ]; then \ - echo "MANIFEST_DIR '$(MANIFEST_DIR)' does not exist or is empty"; \ - exit 1; \ - fi - @mkdir -p $(OUT_DIR)/manifest/built-in-providers/ - @echo "Copying manifests from $(MANIFEST_DIR) to $(OUT_DIR)/manifest/built-in-providers/" - @cp -v $(MANIFEST_DIR)/* $(OUT_DIR)/manifest/built-in-providers/ - # Function to extract the name and the directory of the Dockerfile from the app string define parseApp $(eval NAME := $(shell echo $(1) | cut -d: -f1)) @@ -144,7 +132,7 @@ DOCKER_PUSH_MULTI_TARGETS := $(foreach APP,$(APPS_MAP),$(eval $(call parseApp,$( # targets to build development images .PHONY: docker-build -docker-build: copy-manifests $(DOCKER_BUILD_TARGETS) ## Builds all Docker images. +docker-build: $(DOCKER_BUILD_TARGETS) ## Builds all Docker images. .PHONY: docker-push docker-push: $(DOCKER_PUSH_TARGETS) ## Pushes all Docker images (without building). @@ -152,7 +140,7 @@ docker-push: $(DOCKER_PUSH_TARGETS) ## Pushes all Docker images (without buildin # targets to build and push multi arch images. If you run this target in your machine, # ensure you have qemu and buildx installed by running make configure-buildx. .PHONY: docker-multi-arch-build -docker-multi-arch-build: copy-manifests $(DOCKER_BUILD_MULTI_TARGETS) ## Builds all docker images for multiple architectures. +docker-multi-arch-build: $(DOCKER_BUILD_MULTI_TARGETS) ## Builds all docker images for multiple architectures. .PHONY: docker-multi-arch-push -docker-multi-arch-push: copy-manifests $(DOCKER_PUSH_MULTI_TARGETS) ## Pushes all docker images for multiple architectures after building. +docker-multi-arch-push: $(DOCKER_PUSH_MULTI_TARGETS) ## Pushes all docker images for multiple architectures after building. diff --git a/cmd/ucpd/ucp-dev.yaml b/cmd/ucpd/ucp-dev.yaml index ae0da2ff5e..ddabe6bb0b 100644 --- a/cmd/ucpd/ucp-dev.yaml +++ b/cmd/ucpd/ucp-dev.yaml @@ -53,7 +53,7 @@ initialization: Microsoft.Resources: "http://localhost:5017" kind: "UCPNative" # This is the directory location which contains manifests to be registered. - manifestDirectory: "manifest/built-in-providers/" + manifestDirectory: "" identity: authMethod: default diff --git a/deploy/Chart/templates/ucp/configmaps.yaml b/deploy/Chart/templates/ucp/configmaps.yaml index 4b29bfdb47..2047e006a8 100644 --- a/deploy/Chart/templates/ucp/configmaps.yaml +++ b/deploy/Chart/templates/ucp/configmaps.yaml @@ -50,7 +50,7 @@ data: - id: "/planes/aws/aws" properties: kind: "AWS" - manifestDirectory: "/manifest/built-in-providers" + manifestDirectory: "" identity: authMethod: UCPCredential diff --git a/deploy/images/ucpd/Dockerfile b/deploy/images/ucpd/Dockerfile index b2243c39ad..44ff6e5be3 100644 --- a/deploy/images/ucpd/Dockerfile +++ b/deploy/images/ucpd/Dockerfile @@ -10,9 +10,6 @@ WORKDIR / # Copy the application binary for the specified architecture COPY ./linux_${TARGETARCH:-amd64}/release/ucpd / -# Copy the manifest files for the built-in providers -COPY ./manifest/built-in-providers/ /manifest/built-in-providers/ - # Set the user to non-root (65532:65532 is the default non-root user in distroless) USER 65532:65532 diff --git a/deploy/manifest/built-in-providers/dev/applications_core.yaml b/deploy/manifest/built-in-providers/applications_core.yaml similarity index 69% rename from deploy/manifest/built-in-providers/dev/applications_core.yaml rename to deploy/manifest/built-in-providers/applications_core.yaml index 48fa2c996c..d105acaf01 100644 --- a/deploy/manifest/built-in-providers/dev/applications_core.yaml +++ b/deploy/manifest/built-in-providers/applications_core.yaml @@ -1,40 +1,37 @@ name: Applications.Core -location: - global: - "http://localhost:8080" types: containers: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: [] applications: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: [] environments: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: [] gateways: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: [] secretStores: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: [] extenders: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] volumes: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: [] diff --git a/deploy/manifest/built-in-providers/dev/applications_dapr.yaml b/deploy/manifest/built-in-providers/applications_dapr.yaml similarity index 71% rename from deploy/manifest/built-in-providers/dev/applications_dapr.yaml rename to deploy/manifest/built-in-providers/applications_dapr.yaml index 57b4c9d6dc..3738e87643 100644 --- a/deploy/manifest/built-in-providers/dev/applications_dapr.yaml +++ b/deploy/manifest/built-in-providers/applications_dapr.yaml @@ -1,25 +1,22 @@ name: Applications.Dapr -location: - global: - "http://localhost:8080" types: configurationStores: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] pubSubBrokers: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] secretStores: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] stateStores: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/dev/applications_datastores.yaml b/deploy/manifest/built-in-providers/applications_datastores.yaml similarity index 70% rename from deploy/manifest/built-in-providers/dev/applications_datastores.yaml rename to deploy/manifest/built-in-providers/applications_datastores.yaml index 30c78a5641..b8eb7d65b7 100644 --- a/deploy/manifest/built-in-providers/dev/applications_datastores.yaml +++ b/deploy/manifest/built-in-providers/applications_datastores.yaml @@ -1,20 +1,17 @@ name: Applications.Datastores -location: - global: - "http://localhost:8080" types: mongoDatabases: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] sqlDatabases: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] redisCaches: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/dev/applications_messaging.yaml b/deploy/manifest/built-in-providers/applications_messaging.yaml similarity index 62% rename from deploy/manifest/built-in-providers/dev/applications_messaging.yaml rename to deploy/manifest/built-in-providers/applications_messaging.yaml index ba41b3a11b..cac03e8aa4 100644 --- a/deploy/manifest/built-in-providers/dev/applications_messaging.yaml +++ b/deploy/manifest/built-in-providers/applications_messaging.yaml @@ -1,10 +1,7 @@ name: Applications.Messaging -location: - global: - "http://localhost:8080" types: rabbitMQQueues: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/dev/microsoft_resources.yaml b/deploy/manifest/built-in-providers/dev/microsoft_resources.yaml deleted file mode 100644 index e780319a1c..0000000000 --- a/deploy/manifest/built-in-providers/dev/microsoft_resources.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: Microsoft.Resources -location: - global: - "http://localhost:5017" -types: - deployments: - apiVersions: - "2020-10-01": - schema: {} - "2022-09-01": - schema: {} - capabilities: [] diff --git a/deploy/manifest/built-in-providers/microsoft_resources.yaml b/deploy/manifest/built-in-providers/microsoft_resources.yaml new file mode 100644 index 0000000000..b2c24733bb --- /dev/null +++ b/deploy/manifest/built-in-providers/microsoft_resources.yaml @@ -0,0 +1,8 @@ +name: Microsoft.Resources +types: + deployments: + apiVersions: + "2025-01-01-preview": + schema: {} + capabilities: [] + diff --git a/deploy/manifest/built-in-providers/self-hosted/applications_core.yaml b/deploy/manifest/built-in-providers/self-hosted/applications_core.yaml deleted file mode 100644 index e7038d710d..0000000000 --- a/deploy/manifest/built-in-providers/self-hosted/applications_core.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: Applications.Core -location: - global: - "http://applications-rp.radius-system:5443" -types: - containers: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: [] - applications: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: [] - environments: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: [] - gateways: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: [] - secretStores: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: [] - extenders: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] - volumes: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: [] diff --git a/deploy/manifest/built-in-providers/self-hosted/applications_dapr.yaml b/deploy/manifest/built-in-providers/self-hosted/applications_dapr.yaml deleted file mode 100644 index 01e0400565..0000000000 --- a/deploy/manifest/built-in-providers/self-hosted/applications_dapr.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: Applications.Dapr -location: - global: - "http://applications-rp.radius-system:5443" -types: - configurationStores: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] - pubSubBrokers: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] - secretStores: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] - stateStores: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/self-hosted/applications_datastores.yaml b/deploy/manifest/built-in-providers/self-hosted/applications_datastores.yaml deleted file mode 100644 index 80a6b24a4b..0000000000 --- a/deploy/manifest/built-in-providers/self-hosted/applications_datastores.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: Applications.Datastores -location: - global: - "http://applications-rp.radius-system:5443" -types: - mongoDatabases: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] - sqlDatabases: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] - redisCaches: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/self-hosted/applications_messaging.yaml b/deploy/manifest/built-in-providers/self-hosted/applications_messaging.yaml deleted file mode 100644 index b00483559f..0000000000 --- a/deploy/manifest/built-in-providers/self-hosted/applications_messaging.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: Applications.Messaging -location: - global: - "http://applications-rp.radius-system:5443" -types: - rabbitMQQueues: - apiVersions: - "2023-10-01-preview": - schema: {} - capabilities: ["SupportsRecipes"] diff --git a/deploy/manifest/built-in-providers/self-hosted/microsoft_resources.yaml b/deploy/manifest/built-in-providers/self-hosted/microsoft_resources.yaml deleted file mode 100644 index 969a2e491e..0000000000 --- a/deploy/manifest/built-in-providers/self-hosted/microsoft_resources.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: Microsoft.Resources -location: - global: - "http://bicep-de.radius-system:6443" -types: - deployments: - apiVersions: - "2020-10-01": - schema: {} - "2022-09-01": - schema: {} - capabilities: [] diff --git a/pkg/cli/cmd/resourceprovider/create/testdata/valid.yaml b/pkg/cli/cmd/resourceprovider/create/testdata/valid.yaml index f2eee239d6..a6f26f7fb4 100644 --- a/pkg/cli/cmd/resourceprovider/create/testdata/valid.yaml +++ b/pkg/cli/cmd/resourceprovider/create/testdata/valid.yaml @@ -1,7 +1,4 @@ name: MyCompany.Resources -location: - global: - 'http://localhost:8080' types: testResources: apiVersions: diff --git a/pkg/cli/cmd/resourcetype/create/testdata/valid.yaml b/pkg/cli/cmd/resourcetype/create/testdata/valid.yaml index 621c3926c3..a6f26f7fb4 100644 --- a/pkg/cli/cmd/resourcetype/create/testdata/valid.yaml +++ b/pkg/cli/cmd/resourcetype/create/testdata/valid.yaml @@ -2,6 +2,6 @@ name: MyCompany.Resources types: testResources: apiVersions: - '2023-10-01-preview': + '2025-01-01-preview': schema: {} - capabilities: ["SupportsRecipes"] + capabilities: ["SupportsRecipes"] \ No newline at end of file diff --git a/pkg/cli/manifest/manifest.go b/pkg/cli/manifest/manifest.go index 9e112f0cf4..d7a8a5855e 100644 --- a/pkg/cli/manifest/manifest.go +++ b/pkg/cli/manifest/manifest.go @@ -21,9 +21,6 @@ type ResourceProvider struct { // Name is the resource provider name. This is also the namespace of the types defined by the resource provider. Name string `yaml:"name" validate:"required,resourceProviderNamespace"` - // Location is a map of location name to address in the resource provider. - Location map[string]string `yaml:"location,omitempty"` - // Types is a map of resource types in the resource provider. Types map[string]*ResourceType `yaml:"types" validate:"dive,keys,resourceType,endkeys,required"` } diff --git a/pkg/cli/manifest/manifest_test.go b/pkg/cli/manifest/manifest_test.go index 431ed1fc40..23c11de187 100644 --- a/pkg/cli/manifest/manifest_test.go +++ b/pkg/cli/manifest/manifest_test.go @@ -25,9 +25,6 @@ import ( func TestReadFileYAML(t *testing.T) { expected := &ResourceProvider{ Name: "MyCompany.Resources", - Location: map[string]string{ - "global": "http://localhost:8080", - }, Types: map[string]*ResourceType{ "testResources": { APIVersions: map[string]*ResourceTypeAPIVersion{ diff --git a/pkg/cli/manifest/registermanifest.go b/pkg/cli/manifest/registermanifest.go index 7ba5fdbe83..8f785def4b 100644 --- a/pkg/cli/manifest/registermanifest.go +++ b/pkg/cli/manifest/registermanifest.go @@ -38,21 +38,9 @@ func RegisterFile(ctx context.Context, clientFactory *v20231001preview.ClientFac return err } - var locationName string - var address string - - if resourceProvider.Location == nil { - locationName = v1.LocationGlobal - } else { - for locationName, address = range resourceProvider.Location { - // We support one location per resourceProvider - break - } - } - - logIfEnabled(logger, "Creating resource provider %s at location %s", resourceProvider.Name, locationName) + logIfEnabled(logger, "Creating resource provider %s", resourceProvider.Name) resourceProviderPoller, err := clientFactory.NewResourceProvidersClient().BeginCreateOrUpdate(ctx, planeName, resourceProvider.Name, v20231001preview.ResourceProviderResource{ - Location: to.Ptr(locationName), + Location: to.Ptr(v1.LocationGlobal), Properties: &v20231001preview.ResourceProviderProperties{}, }, nil) if err != nil { @@ -113,12 +101,8 @@ func RegisterFile(ctx context.Context, clientFactory *v20231001preview.ClientFac locationResource.Properties.ResourceTypes[resourceTypeName] = locationResourceType } - if address != "" { - locationResource.Properties.Address = to.Ptr(address) - } - - logIfEnabled(logger, "Creating location %s/%s/%s", resourceProvider.Name, locationName, address) - locationPoller, err := clientFactory.NewLocationsClient().BeginCreateOrUpdate(ctx, planeName, resourceProvider.Name, locationName, locationResource, nil) + logIfEnabled(logger, "Creating location %s/%s", resourceProvider.Name, v1.LocationGlobal) + locationPoller, err := clientFactory.NewLocationsClient().BeginCreateOrUpdate(ctx, planeName, resourceProvider.Name, v1.LocationGlobal, locationResource, nil) if err != nil { return err } @@ -183,21 +167,9 @@ func RegisterType(ctx context.Context, clientFactory *v20231001preview.ClientFac return err } - var locationName string - var address string - - if resourceProvider.Location == nil { - locationName = v1.LocationGlobal - } else { - for locationName, address = range resourceProvider.Location { - // We support one location per resourceProvider - break - } - } - resourceType, ok := resourceProvider.Types[typeName] if !ok { - return fmt.Errorf("type %s not found in manifest file %s", typeName, filePath) + return fmt.Errorf("Type %s not found in manifest file %s", typeName, filePath) } logIfEnabled(logger, "Creating resource type %s/%s", resourceProvider.Name, typeName) @@ -231,7 +203,7 @@ func RegisterType(ctx context.Context, clientFactory *v20231001preview.ClientFac } // get the existing location resource and update it with new resource type. We have to revisit this code once schema is finalized and validated. - locationResourceGetResponse, err := clientFactory.NewLocationsClient().Get(ctx, planeName, resourceProvider.Name, locationName, nil) + locationResourceGetResponse, err := clientFactory.NewLocationsClient().Get(ctx, planeName, resourceProvider.Name, v1.LocationGlobal, nil) if err != nil { return err } @@ -242,20 +214,15 @@ func RegisterType(ctx context.Context, clientFactory *v20231001preview.ClientFac } else { defaultAPIVersion = *resourceType.DefaultAPIVersion } - locationResource := locationResourceGetResponse.LocationResource - if address != "" { - locationResource.Properties.Address = to.Ptr(address) - } - locationResource.Properties.ResourceTypes[typeName] = &v20231001preview.LocationResourceType{ APIVersions: map[string]map[string]any{ defaultAPIVersion: {}, }, } - logIfEnabled(logger, "Updating location %s/%s with new resource type", resourceProvider.Name, locationName) - locationPoller, err := clientFactory.NewLocationsClient().BeginCreateOrUpdate(ctx, planeName, resourceProvider.Name, locationName, locationResource, nil) + logIfEnabled(logger, "Updating location %s/%s with new resource type", resourceProvider.Name, v1.LocationGlobal) + locationPoller, err := clientFactory.NewLocationsClient().BeginCreateOrUpdate(ctx, planeName, resourceProvider.Name, v1.LocationGlobal, locationResource, nil) if err != nil { return err } diff --git a/pkg/cli/manifest/registermanifest_test.go b/pkg/cli/manifest/registermanifest_test.go index 847939b8bc..8026c6b898 100644 --- a/pkg/cli/manifest/registermanifest_test.go +++ b/pkg/cli/manifest/registermanifest_test.go @@ -190,7 +190,7 @@ func TestRegisterType(t *testing.T) { resourceTypeName: "testResource5", filePath: "testdata/registerdirectory/resourceprovider-valid2.yaml", expectError: true, - expectedErrorMessage: "type testResource5 not found in manifest file testdata/registerdirectory/resourceprovider-valid2.yaml", + expectedErrorMessage: "Type testResource5 not found in manifest file testdata/registerdirectory/resourceprovider-valid2.yaml", expectedResourceProvider: "", expectedResourceTypeName: "", }, diff --git a/pkg/cli/manifest/testdata/missing-required-field.json b/pkg/cli/manifest/testdata/missing-required-field.json index 8a200cbec9..c6ef38a2f2 100644 --- a/pkg/cli/manifest/testdata/missing-required-field.json +++ b/pkg/cli/manifest/testdata/missing-required-field.json @@ -4,7 +4,7 @@ "apiVersions": { "2025-01-01-preview": { "schema": {}, - "capabilities": ["SupportsRecipes"] + "capabilities": ["Recipes"] } } } diff --git a/pkg/cli/manifest/testdata/registerdirectory/resourceprovider-valid2.yaml b/pkg/cli/manifest/testdata/registerdirectory/resourceprovider-valid2.yaml index 0b70586ad3..a2ffd310e7 100644 --- a/pkg/cli/manifest/testdata/registerdirectory/resourceprovider-valid2.yaml +++ b/pkg/cli/manifest/testdata/registerdirectory/resourceprovider-valid2.yaml @@ -1,7 +1,4 @@ name: MyCompany2.CompanyName2 -location: - global: - 'http://localhost:8080' types: testResource3: apiVersions: diff --git a/pkg/cli/manifest/testdata/valid.yaml b/pkg/cli/manifest/testdata/valid.yaml index f2eee239d6..a6f26f7fb4 100644 --- a/pkg/cli/manifest/testdata/valid.yaml +++ b/pkg/cli/manifest/testdata/valid.yaml @@ -1,7 +1,4 @@ name: MyCompany.Resources -location: - global: - 'http://localhost:8080' types: testResources: apiVersions: diff --git a/pkg/ucp/backend/controller/resourcegroups/trackedresourceprocess_test.go b/pkg/ucp/backend/controller/resourcegroups/trackedresourceprocess_test.go index 2d9deeeb14..9ee2929d5f 100644 --- a/pkg/ucp/backend/controller/resourcegroups/trackedresourceprocess_test.go +++ b/pkg/ucp/backend/controller/resourcegroups/trackedresourceprocess_test.go @@ -24,7 +24,6 @@ import ( v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" "github.com/radius-project/radius/pkg/armrpc/asyncoperation/controller" "github.com/radius-project/radius/pkg/components/database" - "github.com/radius-project/radius/pkg/to" "github.com/radius-project/radius/pkg/ucp/datamodel" "github.com/radius-project/radius/pkg/ucp/resources" "github.com/radius-project/radius/pkg/ucp/trackedresource" @@ -48,15 +47,10 @@ func Test_Run(t *testing.T) { id := resources.MustParse("/planes/test/local/resourceGroups/test-rg/providers/Applications.Test/testResources/my-resource") trackingID := trackedresource.IDFor(id) - data := datamodel.GenericResourceFromID(id, trackingID) - data.Properties.APIVersion = "2025-01-01" resourceTypeID, err := datamodel.ResourceTypeIDFromResourceID(id) require.NoError(t, err) - locationID, err := datamodel.ResourceProviderLocationIDFromResourceID(id, "global") - require.NoError(t, err) - plane := datamodel.RadiusPlane{ Properties: datamodel.RadiusPlaneProperties{ ResourceProviders: map[string]string{ @@ -64,42 +58,8 @@ func Test_Run(t *testing.T) { }, }, } - resourceGroup := &datamodel.ResourceGroup{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.RootScope(), - }, - }, - } - - resourceTypeResource := &datamodel.ResourceType{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - Name: "testResources", - ID: resourceTypeID.String(), - }, - }, - Properties: datamodel.ResourceTypeProperties{}, - } - - locationResource := &datamodel.Location{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - Name: "global", - ID: locationID.String(), - }, - }, - Properties: datamodel.LocationProperties{ - Address: to.Ptr("https://localhost:1234"), - ResourceTypes: map[string]datamodel.LocationResourceTypeConfiguration{ - "testResources": { - APIVersions: map[string]datamodel.LocationAPIVersionConfiguration{ - "2025-01-01": {}, - }, - }, - }, - }, - } + resourceGroup := datamodel.ResourceGroup{} + data := datamodel.GenericResourceFromID(id, trackingID) // Most of the heavy lifting is done by the updater. We just need to test that we're calling it correctly. t.Run("Success", func(t *testing.T) { @@ -110,21 +70,17 @@ func Test_Run(t *testing.T) { Return(&database.Object{Data: data}, nil).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), trackingID.PlaneScope(), gomock.Any()). + Get(gomock.Any(), "/planes/"+trackingID.PlaneNamespace(), gomock.Any()). Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) + Return(nil, &database.ErrNotFound{}).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), trackingID.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT(). - Get(gomock.Any(), locationResource.ID). - Return(&database.Object{Data: locationResource}, nil).Times(1) - result, err := pc.Run(testcontext.New(t), &controller.Request{ResourceID: trackingID.String()}) require.Equal(t, controller.Result{}, result) require.NoError(t, err) @@ -138,21 +94,17 @@ func Test_Run(t *testing.T) { Return(&database.Object{Data: data}, nil).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), trackingID.PlaneScope(), gomock.Any()). + Get(gomock.Any(), "/planes/"+trackingID.PlaneNamespace(), gomock.Any()). Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) + Return(nil, &database.ErrNotFound{}).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), trackingID.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT(). - Get(gomock.Any(), locationResource.ID). - Return(&database.Object{Data: locationResource}, nil).Times(1) - // Force a retry. updater.Result = &trackedresource.InProgressErr{} diff --git a/pkg/ucp/datamodel/radiusplane.go b/pkg/ucp/datamodel/radiusplane.go index 08243b1989..4c47a3d2a8 100644 --- a/pkg/ucp/datamodel/radiusplane.go +++ b/pkg/ucp/datamodel/radiusplane.go @@ -17,6 +17,8 @@ limitations under the License. package datamodel import ( + "strings" + v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" ) @@ -44,3 +46,15 @@ type RadiusPlane struct { func (p RadiusPlane) ResourceTypeName() string { return p.Type } + +// LookupResourceProvider checks if the input provider is in the list of configured providers. +func (plane RadiusPlane) LookupResourceProvider(key string) string { + var value string + for k, v := range plane.Properties.ResourceProviders { + if strings.EqualFold(k, key) { + value = v + break + } + } + return value +} diff --git a/pkg/ucp/frontend/controller/radius/proxy_test.go b/pkg/ucp/frontend/controller/radius/proxy_test.go index 108f543731..063105e599 100644 --- a/pkg/ucp/frontend/controller/radius/proxy_test.go +++ b/pkg/ucp/frontend/controller/radius/proxy_test.go @@ -29,7 +29,6 @@ import ( "github.com/radius-project/radius/pkg/armrpc/frontend/controller" "github.com/radius-project/radius/pkg/armrpc/rest" "github.com/radius-project/radius/pkg/components/database" - "github.com/radius-project/radius/pkg/to" "github.com/radius-project/radius/pkg/ucp/datamodel" "github.com/radius-project/radius/pkg/ucp/resources" "github.com/radius-project/radius/pkg/ucp/trackedresource" @@ -69,12 +68,11 @@ func createController(t *testing.T) (*ProxyController, *database.MockClient, *mo func Test_Run(t *testing.T) { id := resources.MustParse("/planes/test/local/resourceGroups/test-rg/providers/Applications.Test/testResources/my-resource") + // This test covers the legacy (pre-UDT) behavior for looking up the downstream URL. Update + // this when the old behavior is removed. resourceTypeID, err := datamodel.ResourceTypeIDFromResourceID(id) require.NoError(t, err) - locationID, err := datamodel.ResourceProviderLocationIDFromResourceID(id, "global") - require.NoError(t, err) - plane := datamodel.RadiusPlane{ Properties: datamodel.RadiusPlaneProperties{ ResourceProviders: map[string]string{ @@ -82,42 +80,7 @@ func Test_Run(t *testing.T) { }, }, } - resourceGroup := &datamodel.ResourceGroup{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - ID: id.RootScope(), - }, - }, - } - - resourceTypeResource := &datamodel.ResourceType{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - Name: "testResources", - ID: resourceTypeID.String(), - }, - }, - Properties: datamodel.ResourceTypeProperties{}, - } - - locationResource := &datamodel.Location{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - Name: "global", - ID: locationID.String(), - }, - }, - Properties: datamodel.LocationProperties{ - Address: to.Ptr("https://localhost:1234"), - ResourceTypes: map[string]datamodel.LocationResourceTypeConfiguration{ - "testResources": { - APIVersions: map[string]datamodel.LocationAPIVersionConfiguration{ - "2025-01-01": {}, - }, - }, - }, - }, - } + resourceGroup := datamodel.ResourceGroup{} t.Run("success (non-tracked)", func(t *testing.T) { p, databaseClient, _, roundTripper, _ := createController(t) @@ -135,21 +98,17 @@ func Test_Run(t *testing.T) { req := httptest.NewRequest(http.MethodGet, id.String()+"?api-version="+apiVersion, nil) databaseClient.EXPECT(). - Get(gomock.Any(), id.PlaneScope(), gomock.Any()). - Return(&database.Object{Data: plane}, nil).Times(1) + Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). + Return(nil, &database.ErrNotFound{}).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) + Get(gomock.Any(), "/planes/"+id.PlaneNamespace(), gomock.Any()). + Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), id.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT(). - Get(gomock.Any(), locationResource.ID). - Return(&database.Object{Data: locationResource}, nil).Times(1) - downstreamResponse := httptest.NewRecorder() downstreamResponse.WriteHeader(http.StatusOK) roundTripper.Response = downstreamResponse.Result() @@ -175,21 +134,17 @@ func Test_Run(t *testing.T) { req := httptest.NewRequest(http.MethodDelete, id.String()+"?api-version="+apiVersion, nil) databaseClient.EXPECT(). - Get(gomock.Any(), id.PlaneScope(), gomock.Any()). - Return(&database.Object{Data: plane}, nil).Times(1) + Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). + Return(nil, &database.ErrNotFound{}).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) + Get(gomock.Any(), "/planes/"+id.PlaneNamespace(), gomock.Any()). + Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), id.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT(). - Get(gomock.Any(), locationResource.ID). - Return(&database.Object{Data: locationResource}, nil).Times(1) - downstreamResponse := httptest.NewRecorder() downstreamResponse.WriteHeader(http.StatusOK) roundTripper.Response = downstreamResponse.Result() @@ -218,21 +173,17 @@ func Test_Run(t *testing.T) { req := httptest.NewRequest(http.MethodDelete, id.String()+"?api-version="+apiVersion, nil) databaseClient.EXPECT(). - Get(gomock.Any(), id.PlaneScope(), gomock.Any()). - Return(&database.Object{Data: plane}, nil).Times(1) + Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). + Return(nil, &database.ErrNotFound{}).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) + Get(gomock.Any(), "/planes/"+id.PlaneNamespace(), gomock.Any()). + Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), id.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT(). - Get(gomock.Any(), locationResource.ID). - Return(&database.Object{Data: locationResource}, nil).Times(1) - // Tracking entry created databaseClient.EXPECT(). Get(gomock.Any(), gomock.Any(), gomock.Any()). @@ -273,21 +224,17 @@ func Test_Run(t *testing.T) { req := httptest.NewRequest(http.MethodDelete, id.String()+"?api-version="+apiVersion, nil) databaseClient.EXPECT(). - Get(gomock.Any(), id.PlaneScope(), gomock.Any()). - Return(&database.Object{Data: plane}, nil).Times(1) + Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). + Return(nil, &database.ErrNotFound{}).Times(1) databaseClient.EXPECT(). - Get(gomock.Any(), resourceTypeID.String(), gomock.Any()). - Return(&database.Object{Data: resourceTypeResource}, nil).Times(1) + Get(gomock.Any(), "/planes/"+id.PlaneNamespace(), gomock.Any()). + Return(&database.Object{Data: plane}, nil).Times(1) databaseClient.EXPECT(). Get(gomock.Any(), id.RootScope(), gomock.Any()). Return(&database.Object{Data: resourceGroup}, nil).Times(1) - databaseClient.EXPECT(). - Get(gomock.Any(), locationResource.ID). - Return(&database.Object{Data: locationResource}, nil).Times(1) - // Tracking entry created existingEntry := &database.Object{ Data: &datamodel.GenericResource{ diff --git a/pkg/ucp/frontend/controller/resourcegroups/util.go b/pkg/ucp/frontend/controller/resourcegroups/util.go index 5d41849821..6c9ab112a3 100644 --- a/pkg/ucp/frontend/controller/resourcegroups/util.go +++ b/pkg/ucp/frontend/controller/resourcegroups/util.go @@ -123,7 +123,12 @@ func ValidateResourceType(ctx context.Context, client database.Client, id resour _, err = database.GetResource[datamodel.ResourceType](ctx, client, resourceTypeID.String()) if errors.Is(err, &database.ErrNotFound{}) { - return nil, &InvalidError{Message: fmt.Sprintf("resource type %q not found", id.Type())} + + // Return the error as-is to fallback to the legacy routing behavior. + return nil, err + + // Uncomment this when we remove the legacy routing behavior. + // return nil, &InvalidError{Message: fmt.Sprintf("resource type %q not found", id.Type())} } else if err != nil { return nil, fmt.Errorf("failed to fetch resource type %q: %w", id.Type(), err) } @@ -136,7 +141,12 @@ func ValidateResourceType(ctx context.Context, client database.Client, id resour location, err := database.GetResource[datamodel.Location](ctx, client, locationID.String()) if errors.Is(err, &database.ErrNotFound{}) { - return nil, &InvalidError{Message: fmt.Sprintf("location %q not found for resource provider %q", locationName, id.ProviderNamespace())} + + // Return the error as-is to fallback to the legacy routing behavior. + return nil, err + + // Uncomment this when we remove the legacy routing behavior. + // return nil, &InvalidError{Message: fmt.Sprintf("location %q not found for resource provider %q", locationName, id.ProviderNamespace())} } else if err != nil { return nil, fmt.Errorf("failed to fetch location %q: %w", locationID.String(), err) } @@ -220,6 +230,22 @@ func isOperationResourceType(id resources.ID) bool { return false } +// ValidateLegacyResourceProvider validates that the resource provider specified in the id exists. Returns InvalidError if the plane +// contains invalid data. +func ValidateLegacyResourceProvider(ctx context.Context, client database.Client, id resources.ID, plane *datamodel.RadiusPlane) (*url.URL, error) { + downstream := plane.LookupResourceProvider(id.ProviderNamespace()) + if downstream == "" { + return nil, &InvalidError{Message: fmt.Sprintf("resource provider %s not configured", id.ProviderNamespace())} + } + + downstreamURL, err := url.Parse(downstream) + if err != nil { + return nil, &InvalidError{Message: fmt.Sprintf("failed to parse downstream URL: %v", err.Error())} + } + + return downstreamURL, nil +} + // ValidateDownstream can be used to find and validate the downstream URL for a resource. // Returns NotFoundError for the case where the plane or resource group does not exist. // Returns InvalidError for cases where the data is invalid, like when the resource provider is not configured. @@ -229,11 +255,12 @@ func ValidateDownstream(ctx context.Context, client database.Client, id resource // - The plane exists // - The resource group exists // - The resource provider is configured .. either: + // - As part of the plane (legacy routing) // - As part of a resource provider resource (System.Resources/resourceProviders) (new/UDT routing) // // The plane exists. - _, err := ValidateRadiusPlane(ctx, client, id) + plane, err := ValidateRadiusPlane(ctx, client, id) if err != nil { return nil, err } @@ -246,7 +273,10 @@ func ValidateDownstream(ctx context.Context, client database.Client, id resource // If this returns success, it means the resource type is configured using new/UDT routing. downstreamURL, err := ValidateResourceType(ctx, client, id, location, apiVersion) - if err != nil { + if errors.Is(err, &database.ErrNotFound{}) { + // If the resource provider is not found, treat it like a legacy provider. + return ValidateLegacyResourceProvider(ctx, client, id, plane) + } else if err != nil { return nil, err } diff --git a/pkg/ucp/frontend/controller/resourcegroups/util_test.go b/pkg/ucp/frontend/controller/resourcegroups/util_test.go index 085f1de9b2..3b80d21bc3 100644 --- a/pkg/ucp/frontend/controller/resourcegroups/util_test.go +++ b/pkg/ucp/frontend/controller/resourcegroups/util_test.go @@ -409,3 +409,211 @@ func Test_ValidateDownstream(t *testing.T) { require.Nil(t, downstreamURL) }) } + +// This test validates the pre-UDT before where resource providers are registered as part of the plane. +// This can be deleted once the legacy routing behavior is removed. +func Test_ValidateDownstream_Legacy(t *testing.T) { + id, err := resources.ParseResource("/planes/radius/local/resourceGroups/test-group/providers/System.TestRP/testResources/name") + require.NoError(t, err) + + idWithoutResourceGroup, err := resources.Parse("/planes/radius/local/providers/System.TestRP/testResources") + require.NoError(t, err) + + resourceTypeID, err := datamodel.ResourceTypeIDFromResourceID(id) + require.NoError(t, err) + + downstream := "http://localhost:7443" + + plane := &datamodel.RadiusPlane{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.PlaneScope(), + }, + }, + Properties: datamodel.RadiusPlaneProperties{ + ResourceProviders: map[string]string{ + "System.TestRP": downstream, + }, + }, + } + + setup := func(t *testing.T) *database.MockClient { + ctrl := gomock.NewController(t) + return database.NewMockClient(ctrl) + } + + t.Run("success (resource group)", func(t *testing.T) { + resourceGroup := &datamodel.ResourceGroup{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.RootScope(), + }, + }, + } + + databaseClient := setup(t) + databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) + + expectedURL, err := url.Parse(downstream) + require.NoError(t, err) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) + require.NoError(t, err) + require.Equal(t, expectedURL, downstreamURL) + }) + + t.Run("success (non resource group)", func(t *testing.T) { + databaseClient := setup(t) + databaseClient.EXPECT().Get(gomock.Any(), idWithoutResourceGroup.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) + + expectedURL, err := url.Parse(downstream) + require.NoError(t, err) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, idWithoutResourceGroup, location, apiVersion) + require.NoError(t, err) + require.Equal(t, expectedURL, downstreamURL) + }) + + t.Run("plane not found", func(t *testing.T) { + databaseClient := setup(t) + databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(nil, &database.ErrNotFound{}).Times(1) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) + require.Error(t, err) + require.Equal(t, &NotFoundError{Message: "plane \"/planes/radius/local\" not found"}, err) + require.Nil(t, downstreamURL) + }) + + t.Run("plane retrieval failure", func(t *testing.T) { + databaseClient := setup(t) + + expected := fmt.Errorf("failed to fetch plane \"/planes/radius/local\": %w", errors.New("test error")) + databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(nil, errors.New("test error")).Times(1) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) + require.Error(t, err) + require.Equal(t, expected, err) + require.Nil(t, downstreamURL) + }) + + t.Run("resource group not found", func(t *testing.T) { + databaseClient := setup(t) + databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(nil, &database.ErrNotFound{}).Times(1) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) + require.Error(t, err) + require.Equal(t, &NotFoundError{Message: "resource group \"/planes/radius/local/resourceGroups/test-group\" not found"}, err) + require.Nil(t, downstreamURL) + }) + + t.Run("resource group err", func(t *testing.T) { + databaseClient := setup(t) + + databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(nil, errors.New("test error")).Times(1) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) + require.Error(t, err) + require.Equal(t, "failed to fetch resource group \"/planes/radius/local/resourceGroups/test-group\": test error", err.Error()) + require.Nil(t, downstreamURL) + }) + + t.Run("legacy resource provider not configured", func(t *testing.T) { + plane := &datamodel.RadiusPlane{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.PlaneScope(), + }, + }, + Properties: datamodel.RadiusPlaneProperties{ + ResourceProviders: map[string]string{}, + }, + } + + resourceGroup := &datamodel.ResourceGroup{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.RootScope(), + }, + }, + } + + databaseClient := setup(t) + databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) + require.Error(t, err) + require.Equal(t, &InvalidError{Message: "resource provider System.TestRP not configured"}, err) + require.Nil(t, downstreamURL) + }) + + t.Run("location not found", func(t *testing.T) { + plane := &datamodel.RadiusPlane{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.PlaneScope(), + }, + }, + Properties: datamodel.RadiusPlaneProperties{ + ResourceProviders: map[string]string{}, + }, + } + + resourceGroup := &datamodel.ResourceGroup{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.RootScope(), + }, + }, + } + + databaseClient := setup(t) + databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) + require.Error(t, err) + require.Equal(t, &InvalidError{Message: "resource provider System.TestRP not configured"}, err) + require.Nil(t, downstreamURL) + }) + + t.Run("resource provider invalid URL", func(t *testing.T) { + plane := &datamodel.RadiusPlane{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.PlaneScope(), + }, + }, + Properties: datamodel.RadiusPlaneProperties{ + ResourceProviders: map[string]string{ + "System.TestRP": "\ninvalid", + }, + }, + } + + resourceGroup := &datamodel.ResourceGroup{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: id.RootScope(), + }, + }, + } + + databaseClient := setup(t) + databaseClient.EXPECT().Get(gomock.Any(), id.PlaneScope()).Return(&database.Object{Data: plane}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), id.RootScope()).Return(&database.Object{Data: resourceGroup}, nil).Times(1) + databaseClient.EXPECT().Get(gomock.Any(), resourceTypeID.String()).Return(nil, &database.ErrNotFound{}).Times(1) + + downstreamURL, err := ValidateDownstream(testcontext.New(t), databaseClient, id, location, apiVersion) + require.Error(t, err) + require.Equal(t, &InvalidError{Message: "failed to parse downstream URL: parse \"\\ninvalid\": net/url: invalid control character in URL"}, err) + require.Nil(t, downstreamURL) + }) +} diff --git a/pkg/ucp/integrationtests/radius/proxy_test.go b/pkg/ucp/integrationtests/radius/proxy_test.go index 5c93754d61..5275034f31 100644 --- a/pkg/ucp/integrationtests/radius/proxy_test.go +++ b/pkg/ucp/integrationtests/radius/proxy_test.go @@ -39,11 +39,8 @@ const ( testRadiusPlaneID = "/planes/radius/test" testResourceNamespace = "System.Test" testResourceGroupID = testRadiusPlaneID + "/resourceGroups/test-rg" - testResourceProviderID = testRadiusPlaneID + "/providers/System.Resources/resourceproviders/System.Test" testResourceCollectionID = testResourceGroupID + "/providers/System.Test/testResources" testResourceID = testResourceCollectionID + "/test-resource" - resourceTypeURL = testResourceProviderID + "/resourcetypes/testResources" - locationID = testResourceProviderID + "/locations/global" assertTimeout = time.Second * 10 assertRetry = time.Second * 2 @@ -67,20 +64,14 @@ func Test_RadiusPlane_ResourceSync(t *testing.T) { ucp := testhost.Start(t) rp := testrp.Start(t) rp.Handler = testrp.SyncResource(t, ucp, testResourceGroupID) - address := to.Ptr("http://" + rp.Address()) + rps := map[string]*string{ - testResourceNamespace: address, + testResourceNamespace: to.Ptr("http://" + rp.Address()), } createRadiusPlane(ucp, rps) createResourceGroup(ucp, testResourceGroupID) - createResourceProvider(ucp) - - createResourceType(ucp, resourceTypeURL) - - createLocation(ucp, address) - message := "here is some test data" expectedTrackedResource := v20231001preview.GenericResource{ @@ -98,10 +89,7 @@ func Test_RadiusPlane_ResourceSync(t *testing.T) { body, err := json.Marshal(data) require.NoError(t, err) - response := ucp.MakeRequest(http.MethodGet, resourceTypeURL+"?api-version="+testrp.Version, nil) - response.EqualsStatusCode(http.StatusOK) - - response = ucp.MakeRequest(http.MethodPut, testResourceID+"?api-version="+testrp.Version, body) + response := ucp.MakeRequest(http.MethodPut, testResourceID+"?api-version="+testrp.Version, body) response.EqualsStatusCode(http.StatusOK) resource := &testrp.TestResource{} @@ -196,21 +184,14 @@ func Test_RadiusPlane_ResourceAsync(t *testing.T) { } rp.Handler = testrp.AsyncResource(t, ucp, testResourceGroupID, onPut, onDelete) - address := to.Ptr("http://" + rp.Address()) + rps := map[string]*string{ - testResourceNamespace: address, + testResourceNamespace: to.Ptr("http://" + rp.Address()), } - createRadiusPlane(ucp, rps) createResourceGroup(ucp, testResourceGroupID) - createResourceProvider(ucp) - - createResourceType(ucp, resourceTypeURL) - - createLocation(ucp, address) - message := "here is some test data" expectedTrackedResource := v20231001preview.GenericResource{ @@ -419,44 +400,3 @@ func createResourceGroup(ucp *testhost.TestHost, id string) { response := ucp.MakeTypedRequest(http.MethodPut, id+"?"+apiVersionParameter, body) response.EqualsStatusCode(http.StatusOK) } - -func createResourceProvider(ucp *testhost.TestHost) { - body := v20231001preview.ResourceProviderResource{ - Location: to.Ptr(v1.LocationGlobal), - Properties: &v20231001preview.ResourceProviderProperties{}, - } - response := ucp.MakeTypedRequest("PUT", testResourceProviderID+"?"+apiVersionParameter, body) - response.WaitForOperationComplete(nil) - response.EqualsStatusCode(http.StatusCreated) -} - -func createResourceType(ucp *testhost.TestHost, id string) { - body := v20231001preview.ResourceTypeResource{ - Properties: &v20231001preview.ResourceTypeProperties{ - DefaultAPIVersion: to.Ptr("2023-10-01-preview"), - }, - } - - response := ucp.MakeTypedRequest(http.MethodPut, id+"?"+apiVersionParameter, body) - response.WaitForOperationComplete(nil) - response.EqualsStatusCode(http.StatusCreated) -} - -func createLocation(server *testhost.TestHost, address *string) { - body := v20231001preview.LocationResource{ - Properties: &v20231001preview.LocationProperties{ - Address: address, - ResourceTypes: map[string]*v20231001preview.LocationResourceType{ - "testResources": { - APIVersions: map[string]map[string]any{ - "2023-10-01-preview": {}, - }, - }, - }, - }, - } - - response := server.MakeTypedRequest("PUT", locationID+"?"+apiVersionParameter, body) - response.WaitForOperationComplete(nil) - response.EqualsStatusCode(http.StatusCreated) -} diff --git a/pkg/ucp/integrationtests/resourceproviders/testdata/manifests/resourceprovider-valid1.yaml b/pkg/ucp/integrationtests/resourceproviders/testdata/manifests/resourceprovider-valid1.yaml index 02118b74c2..4523f045df 100644 --- a/pkg/ucp/integrationtests/resourceproviders/testdata/manifests/resourceprovider-valid1.yaml +++ b/pkg/ucp/integrationtests/resourceproviders/testdata/manifests/resourceprovider-valid1.yaml @@ -1,7 +1,4 @@ name: TestProvider.TestCompany -location: - global: - 'http://localhost:8080' types: testResourcesAbc: apiVersions: diff --git a/test/functional-portable/ucp/noncloud/resourceprovider_test.go b/test/functional-portable/ucp/noncloud/resourceprovider_test.go index 95f46fb63b..ba70e34263 100644 --- a/test/functional-portable/ucp/noncloud/resourceprovider_test.go +++ b/test/functional-portable/ucp/noncloud/resourceprovider_test.go @@ -38,7 +38,7 @@ func Test_ResourceProviderRegistration(t *testing.T) { manifestPath = "testdata/resourceprovider.yaml" resourceProviderName = "MyCompany.Resources" expectedResourceTypeName = "testResources" - expectedApiVersion = "2023-10-01-preview" + expectedApiVersion = "2025-01-01-preview" ) expectedData := map[string]any{ diff --git a/test/functional-portable/ucp/noncloud/testdata/resourceprovider.yaml b/test/functional-portable/ucp/noncloud/testdata/resourceprovider.yaml index 84dbc04431..c3348b0229 100644 --- a/test/functional-portable/ucp/noncloud/testdata/resourceprovider.yaml +++ b/test/functional-portable/ucp/noncloud/testdata/resourceprovider.yaml @@ -2,6 +2,6 @@ name: MyCompany.Resources types: testResources: apiVersions: - "2023-10-01-preview": + "2025-01-01-preview": schema: {} capabilities: ["SupportsRecipes"]