From e8eb23530926ce8bca3f8ad273ddc5466687ce9e Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 4 Nov 2024 14:23:25 -0800 Subject: [PATCH] Add dynamic-rp boilerplate This change adds a new control-plane microservice - the "Dynamic RP". The dynamic-rp will be responsible for implementing the bulk of UDT functionality. The naming reflects that fact that dynamic-rp behaves *dynamically* based on the schema definitions provided by users. This change only contains the *base layer* for dynamic-rp and no user-visible functionality. Here's a brief list of what's included. - dynamic-rp binary - dynamic-rp Dockerfiles - dynamic-rp Kubernetes yaml - dynamic-rp configuration file - initialization and stubs for frontend and backend services - vscode debugging support - functional test integration The actual functionality for dynamic-rp will be added in a series of follow-up pull-requests. Signed-off-by: Ryan Nowak --- .github/scripts/release-verification.sh | 7 ++ .github/workflows/functional-test-cloud.yaml | 3 +- .../workflows/functional-test-noncloud.yaml | 1 + .github/workflows/long-running-azure.yaml | 3 +- .vscode/launch.json | 8 ++ build/build.mk | 1 + build/docker.mk | 1 + cmd/controller/controller-self-hosted.yaml | 2 +- cmd/dynamic-rp/cmd/root.go | 81 ++++++++++++ cmd/dynamic-rp/dynamicrp-dev.yaml | 43 +++++++ cmd/dynamic-rp/main.go | 23 ++++ cmd/ucpd/ucp-self-hosted-dev.yaml | 2 +- .../templates/controller/configmaps.yaml | 2 +- .../templates/dynamic-rp/configmaps.yaml | 58 +++++++++ .../templates/dynamic-rp/deployment.yaml | 98 +++++++++++++++ deploy/Chart/templates/dynamic-rp/rbac.yaml | 56 +++++++++ .../Chart/templates/dynamic-rp/service.yaml | 16 +++ .../templates/dynamic-rp/serviceaccount.yaml | 8 ++ deploy/Chart/values.yaml | 18 +++ deploy/images/dynamic-rp/Dockerfile | 25 ++++ deploy/images/dynamic-rp/Dockerfile.mariner | 23 ++++ pkg/dynamicrp/backend/service.go | 73 +++++++++++ pkg/dynamicrp/config.go | 88 +++++++++++++ pkg/dynamicrp/doc.go | 19 +++ pkg/dynamicrp/frontend/routes.go | 30 +++++ pkg/dynamicrp/frontend/service.go | 117 ++++++++++++++++++ pkg/dynamicrp/options.go | 111 +++++++++++++++++ pkg/dynamicrp/server/doc.go | 19 +++ pkg/dynamicrp/server/server.go | 76 ++++++++++++ 29 files changed, 1007 insertions(+), 5 deletions(-) create mode 100644 cmd/dynamic-rp/cmd/root.go create mode 100644 cmd/dynamic-rp/dynamicrp-dev.yaml create mode 100644 cmd/dynamic-rp/main.go create mode 100644 deploy/Chart/templates/dynamic-rp/configmaps.yaml create mode 100644 deploy/Chart/templates/dynamic-rp/deployment.yaml create mode 100644 deploy/Chart/templates/dynamic-rp/rbac.yaml create mode 100644 deploy/Chart/templates/dynamic-rp/service.yaml create mode 100644 deploy/Chart/templates/dynamic-rp/serviceaccount.yaml create mode 100644 deploy/images/dynamic-rp/Dockerfile create mode 100644 deploy/images/dynamic-rp/Dockerfile.mariner create mode 100644 pkg/dynamicrp/backend/service.go create mode 100644 pkg/dynamicrp/config.go create mode 100644 pkg/dynamicrp/doc.go create mode 100644 pkg/dynamicrp/frontend/routes.go create mode 100644 pkg/dynamicrp/frontend/service.go create mode 100644 pkg/dynamicrp/options.go create mode 100644 pkg/dynamicrp/server/doc.go create mode 100644 pkg/dynamicrp/server/server.go diff --git a/.github/scripts/release-verification.sh b/.github/scripts/release-verification.sh index ca0b404f65..2b2248cb4e 100755 --- a/.github/scripts/release-verification.sh +++ b/.github/scripts/release-verification.sh @@ -69,10 +69,12 @@ kind create cluster ./rad install kubernetes EXPECTED_APPCORE_RP_IMAGE="ghcr.io/radius-project/applications-rp:${EXPECTED_TAG_VERSION}" +EXPECTED_DYNAMIC_RP_IMAGE="ghcr.io/radius-project/dynamic-rp:${EXPECTED_TAG_VERSION}" EXPECTED_UCP_IMAGE="ghcr.io/radius-project/ucpd:${EXPECTED_TAG_VERSION}" EXPECTED_DE_IMAGE="ghcr.io/radius-project/deployment-engine:${EXPECTED_TAG_VERSION}" APPCORE_RP_IMAGE=$(kubectl describe pods -n radius-system -l control-plane=applications-rp | awk '/^.*Image:/ {print $2}') +DYNAMIC_RP_IMAGE=$(kubectl describe pods -n radius-system -l control-plane=dynamic-rp | awk '/^.*Image:/ {print $2}') UCP_IMAGE=$(kubectl describe pods -n radius-system -l control-plane=ucp | awk '/^.*Image:/ {print $2}') DE_IMAGE=$(kubectl describe pods -n radius-system -l control-plane=bicep-de | awk '/^.*Image:/ {print $2}') @@ -81,6 +83,11 @@ if [[ "${APPCORE_RP_IMAGE}" != "${EXPECTED_APPCORE_RP_IMAGE}" ]]; then exit 1 fi +if [[ "${DYNAMIC_RP_IMAGE}" != "${EXPECTED_DYNAMIC_RP_IMAGE}" ]]; then + echo "Error: Dynamic RP image: ${DYNAMIC_RP_IMAGE} does not match the desired image: ${EXPECTED_DYNAMIC_RP_IMAGE}." + exit 1 +fi + if [[ "${UCP_IMAGE}" != "${EXPECTED_UCP_IMAGE}" ]]; then echo "Error: UCP image: ${UCP_IMAGE} does not match the desired image: ${EXPECTED_UCP_IMAGE}." exit 1 diff --git a/.github/workflows/functional-test-cloud.yaml b/.github/workflows/functional-test-cloud.yaml index 6b1f5d5364..41be189ef2 100644 --- a/.github/workflows/functional-test-cloud.yaml +++ b/.github/workflows/functional-test-cloud.yaml @@ -275,6 +275,7 @@ jobs: * Bicep recipe location `${{ env.BICEP_RECIPE_REGISTRY }}/test/testrecipes/test-bicep-recipes/:${{ env.REL_VERSION }}` * Terraform recipe location `${{ env.TF_RECIPE_MODULE_SERVER_URL }}/.zip` (in cluster) * applications-rp test image location: `${{ env.CONTAINER_REGISTRY }}/applications-rp:${{ env.REL_VERSION }}` + * dynamic-rp test image location: `${{ env.CONTAINER_REGISTRY }}/dynamic-rp:${{ env.REL_VERSION }}` * controller test image location: `${{ env.CONTAINER_REGISTRY }}/controller:${{ env.REL_VERSION }}` * ucp test image location: `${{ env.CONTAINER_REGISTRY }}/ucpd:${{ env.REL_VERSION }}` * deployment-engine test image location: `${{ env.DE_IMAGE }}:${{ env.DE_TAG }}` @@ -647,7 +648,7 @@ jobs: echo "*** Installing Radius to Kubernetes ***" rad install kubernetes \ --chart ${{ env.RADIUS_CHART_LOCATION }} \ - --set rp.image=${{ env.CONTAINER_REGISTRY }}/applications-rp,rp.tag=${{ env.REL_VERSION }},controller.image=${{ env.CONTAINER_REGISTRY }}/controller,controller.tag=${{ env.REL_VERSION }},ucp.image=${{ env.CONTAINER_REGISTRY }}/ucpd,ucp.tag=${{ env.REL_VERSION }},de.image=${{ env.DE_IMAGE }},de.tag=${{ env.DE_TAG }},global.azureWorkloadIdentity.enabled=true + --set rp.image=${{ env.CONTAINER_REGISTRY }}/applications-rp,rp.tag=${{ env.REL_VERSION }},dynamicrp.image=${{ env.CONTAINER_REGISTRY }}/dynamic-rp,dynamicrp.tag=${{ env.REL_VERSION }}controller.image=${{ env.CONTAINER_REGISTRY }}/controller,controller.tag=${{ env.REL_VERSION }},ucp.image=${{ env.CONTAINER_REGISTRY }}/ucpd,ucp.tag=${{ env.REL_VERSION }},de.image=${{ env.DE_IMAGE }},de.tag=${{ env.DE_TAG }},global.azureWorkloadIdentity.enabled=true echo "*** Create workspace, group and environment for test ***" rad workspace create kubernetes diff --git a/.github/workflows/functional-test-noncloud.yaml b/.github/workflows/functional-test-noncloud.yaml index f16f6f7b1e..9397313d3b 100644 --- a/.github/workflows/functional-test-noncloud.yaml +++ b/.github/workflows/functional-test-noncloud.yaml @@ -290,6 +290,7 @@ jobs: RAD_COMMAND="rad install kubernetes \ --chart ${{ env.RADIUS_CHART_LOCATION }} \ --set rp.image=${{ env.LOCAL_REGISTRY_NAME }}:${{ env.LOCAL_REGISTRY_PORT }}/applications-rp,rp.tag=${{ env.REL_VERSION }} \ + --set dynamicrp.image=${{ env.LOCAL_REGISTRY_NAME }}:${{ env.LOCAL_REGISTRY_PORT }}/dynamic-rp,dynamicrp.tag=${{ env.REL_VERSION }} \ --set controller.image=${{ env.LOCAL_REGISTRY_NAME }}:${{ env.LOCAL_REGISTRY_PORT }}/controller,controller.tag=${{ env.REL_VERSION }} \ --set ucp.image=${{ env.LOCAL_REGISTRY_NAME }}:${{ env.LOCAL_REGISTRY_PORT }}/ucpd,ucp.tag=${{ env.REL_VERSION }} \ --set de.image=${{ env.DE_IMAGE }},de.tag=${{ env.DE_TAG }}" diff --git a/.github/workflows/long-running-azure.yaml b/.github/workflows/long-running-azure.yaml index 93b61e4424..ce30023e82 100644 --- a/.github/workflows/long-running-azure.yaml +++ b/.github/workflows/long-running-azure.yaml @@ -201,6 +201,7 @@ jobs: * Bicep recipe location `${{ env.BICEP_RECIPE_REGISTRY }}/test/testrecipes/test-bicep-recipes/:${{ steps.gen-id.outputs.REL_VERSION }}` * Terraform recipe location `${{ env.TF_RECIPE_MODULE_SERVER_URL }}/.zip` (in cluster) * applications-rp test image location: `${{ env.CONTAINER_REGISTRY }}/applications-rp:${{ steps.gen-id.outputs.REL_VERSION }}` + * dynamic-rp test image location: `${{ env.CONTAINER_REGISTRY }}/dynamic-rp:${{ steps.gen-id.outputs.REL_VERSION }}` * controller test image location: `${{ env.CONTAINER_REGISTRY }}/controller:${{ steps.gen-id.outputs.REL_VERSION }}` * ucp test image location: `${{ env.CONTAINER_REGISTRY }}/ucpd:${{ steps.gen-id.outputs.REL_VERSION }}` @@ -405,7 +406,7 @@ jobs: echo "*** Installing Radius to Kubernetes ***" rad install kubernetes --reinstall \ --chart ${{ env.RADIUS_CHART_LOCATION }} \ - --set rp.image=${{ env.CONTAINER_REGISTRY }}/applications-rp,rp.tag=${{ env.REL_VERSION }},controller.image=${{ env.CONTAINER_REGISTRY }}/controller,controller.tag=${{ env.REL_VERSION }},ucp.image=${{ env.CONTAINER_REGISTRY }}/ucpd,ucp.tag=${{ env.REL_VERSION }} + --set rp.image=${{ env.CONTAINER_REGISTRY }}/applications-rp,rp.tag=${{ env.REL_VERSION }},dynamicrp.image=${{ env.CONTAINER_REGISTRY }}/dynamic-rp,dynamicrp.tag=${{ env.REL_VERSION }},controller.image=${{ env.CONTAINER_REGISTRY }}/controller,controller.tag=${{ env.REL_VERSION }},ucp.image=${{ env.CONTAINER_REGISTRY }}/ucpd,ucp.tag=${{ env.REL_VERSION }} - name: Configure Radius test workspace run: | set -x diff --git a/.vscode/launch.json b/.vscode/launch.json index daf179be56..cc9a853f27 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -38,6 +38,13 @@ "env": { "RADIUS_ENV": "self-hosted" } + },{ + "name": "Launch Dynamic RP", + "type": "go", + "request": "launch", + "mode": "auto", + "preLaunchTask": "Build Radius (all)", + "program": "${workspaceFolder}/cmd/dynamic-rp/main.go", }, { "name": "Launch UCP", @@ -108,6 +115,7 @@ "name": "Launch Control Plane (all)", "configurations": [ "Launch UCP", + "Launch Dynamic RP", "Launch Applications RP", "Launch Controller", "Launch Deployment Engine", diff --git a/build/build.mk b/build/build.mk index 5f57604793..f173721c5b 100644 --- a/build/build.mk +++ b/build/build.mk @@ -119,6 +119,7 @@ GOARCHES := amd64 arm arm64 BINARIES := docgen:./cmd/docgen \ rad:./cmd/rad \ applications-rp:./cmd/applications-rp \ + dynamic-rp:./cmd/dynamic-rp \ ucpd:./cmd/ucpd \ controller:./cmd/controller \ testrp:./test/testrp \ diff --git a/build/docker.mk b/build/docker.mk index 3b161fd653..7adcdcaa6b 100644 --- a/build/docker.mk +++ b/build/docker.mk @@ -101,6 +101,7 @@ configure-buildx: # Define a target for each image with name and Dockerfile location APPS_MAP := ucpd:./deploy/images/ucpd \ applications-rp:./deploy/images/applications-rp \ + dynamic-rp:./deploy/images/dynamic-rp \ controller:./deploy/images/controller \ testrp:./test/testrp \ magpiego:./test/magpiego diff --git a/cmd/controller/controller-self-hosted.yaml b/cmd/controller/controller-self-hosted.yaml index fa77435b77..a3c4274dc5 100644 --- a/cmd/controller/controller-self-hosted.yaml +++ b/cmd/controller/controller-self-hosted.yaml @@ -15,7 +15,7 @@ server: # workerServer port specifies port set for Health Checks workerServer: - port: 3000 + port: 7073 ucp: kind: direct direct: diff --git a/cmd/dynamic-rp/cmd/root.go b/cmd/dynamic-rp/cmd/root.go new file mode 100644 index 0000000000..046ce4002d --- /dev/null +++ b/cmd/dynamic-rp/cmd/root.go @@ -0,0 +1,81 @@ +/* +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 cmd + +import ( + "context" + "fmt" + "os" + + "github.com/go-logr/logr" + "github.com/spf13/cobra" + runtimelog "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/radius-project/radius/pkg/armrpc/hostoptions" + "github.com/radius-project/radius/pkg/dynamicrp" + "github.com/radius-project/radius/pkg/dynamicrp/server" + "github.com/radius-project/radius/pkg/ucp/hosting" + "github.com/radius-project/radius/pkg/ucp/ucplog" +) + +var rootCmd = &cobra.Command{ + Use: "dynamic-rp", + Short: "Dyanamic Resource Provider server", + Long: `Server process for the Dynamic Resource Provider (dynamic-rp).`, + RunE: func(cmd *cobra.Command, args []string) error { + configFilePath := cmd.Flag("config-file").Value.String() + bs, err := os.ReadFile(configFilePath) + if err != nil { + return fmt.Errorf("failed to read config file %s: %w", configFilePath, err) + } + + config, err := dynamicrp.LoadConfig(bs) + if err != nil { + return fmt.Errorf("failed to parse config file %s: %w", configFilePath, err) + } + + options, err := dynamicrp.NewOptions(cmd.Context(), config) + if err != nil { + return err + } + + logger, flush, err := ucplog.NewLogger(ucplog.LoggerName, &options.Config.Logging) + if err != nil { + return err + } + defer flush() + + // Must set the logger before using controller-runtime. + runtimelog.SetLogger(logger) + + host, err := server.NewServer(options) + if err != nil { + return err + } + + ctx := logr.NewContext(cmd.Context(), logger) + + return hosting.RunWithInterrupts(ctx, host) + }, +} + +func Execute() { + // Let users override the configuration via `--config-file`. + rootCmd.Flags().String("config-file", fmt.Sprintf("dynamicrp-%s.yaml", hostoptions.Environment()), "The service configuration file.") + + cobra.CheckErr(rootCmd.ExecuteContext(context.Background())) +} diff --git a/cmd/dynamic-rp/dynamicrp-dev.yaml b/cmd/dynamic-rp/dynamicrp-dev.yaml new file mode 100644 index 0000000000..4794a02ce0 --- /dev/null +++ b/cmd/dynamic-rp/dynamicrp-dev.yaml @@ -0,0 +1,43 @@ +# This is an example of configuration file. +environment: + name: Dev + roleLocation: "global" +storageProvider: + provider: "apiserver" + apiserver: + context: '' + namespace: 'radius-testing' +queueProvider: + provider: "apiserver" + name: dynamic-rp + apiserver: + context: '' + namespace: 'radius-testing' +secretProvider: + provider: "kubernetes" +profilerProvider: + enabled: false + port: 6062 +metricsProvider: + prometheus: + enabled: false + path: "/metrics" + port: 9092 +server: + host: "0.0.0.0" + port: 8082 +workerServer: + maxOperationConcurrency: 10 + maxOperationRetryCount: 2 +ucp: + kind: direct + direct: + endpoint: "http://localhost:9000/apis/api.ucp.dev/v1alpha3" +logging: + level: "info" + json: false +bicep: + deleteRetryCount: 20 + deleteRetryDelaySeconds: 60 +terraform: + path: "/terraform" \ No newline at end of file diff --git a/cmd/dynamic-rp/main.go b/cmd/dynamic-rp/main.go new file mode 100644 index 0000000000..2f95e79deb --- /dev/null +++ b/cmd/dynamic-rp/main.go @@ -0,0 +1,23 @@ +/* +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 main + +import "github.com/radius-project/radius/cmd/dynamic-rp/cmd" + +func main() { + cmd.Execute() +} diff --git a/cmd/ucpd/ucp-self-hosted-dev.yaml b/cmd/ucpd/ucp-self-hosted-dev.yaml index d69c202251..4d26937dfe 100644 --- a/cmd/ucpd/ucp-self-hosted-dev.yaml +++ b/cmd/ucpd/ucp-self-hosted-dev.yaml @@ -61,7 +61,7 @@ metricsProvider: prometheus: enabled: false path: "/metrics" - port: 9090 + port: 9091 # Logging configuration logging: diff --git a/deploy/Chart/templates/controller/configmaps.yaml b/deploy/Chart/templates/controller/configmaps.yaml index 058223d41b..314213647b 100644 --- a/deploy/Chart/templates/controller/configmaps.yaml +++ b/deploy/Chart/templates/controller/configmaps.yaml @@ -23,7 +23,7 @@ data: queueProvider: provider: "apiserver" - name: "ucp" + name: "controller" apiserver: context: "" namespace: "radius-system" diff --git a/deploy/Chart/templates/dynamic-rp/configmaps.yaml b/deploy/Chart/templates/dynamic-rp/configmaps.yaml new file mode 100644 index 0000000000..31368b5db0 --- /dev/null +++ b/deploy/Chart/templates/dynamic-rp/configmaps.yaml @@ -0,0 +1,58 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: dynamic-rp-config + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: dynamic-rp + app.kubernetes.io/part-of: radius +data: + radius-self-host.yaml: |- + # Radius configuration file. + # See https://github.com/radius-project/radius/blob/main/docs/contributing/contributing-code/contributing-code-control-plane/configSettings.md for more information. + environment: + name: self-hosted + roleLocation: "global" + storageProvider: + provider: "apiserver" + apiserver: + context: "" + namespace: "radius-system" + queueProvider: + provider: "apiserver" + name: "dynamic-rp" + apiserver: + context: "" + namespace: "radius-system" + metricsProvider: + prometheus: + enabled: true + path: "/metrics" + port: 9092 + profilerProvider: + enabled: true + port: 6062 + secretProvider: + provider: kubernetes + server: + host: "0.0.0.0" + port: 8082 + workerServer: + maxOperationConcurrency: 10 + maxOperationRetryCount: 2 + ucp: + kind: kubernetes + logging: + level: "info" + json: true + {{- if and .Values.global.zipkin .Values.global.zipkin.url }} + tracerProvider: + serviceName: "dynamic-rp" + zipkin: + url: {{ .Values.global.zipkin.url }} + {{- end }} + bicep: + deleteRetryCount: 20 + deleteRetryDelaySeconds: 60 + terraform: + path: "/terraform" diff --git a/deploy/Chart/templates/dynamic-rp/deployment.yaml b/deploy/Chart/templates/dynamic-rp/deployment.yaml new file mode 100644 index 0000000000..ccdfd9837f --- /dev/null +++ b/deploy/Chart/templates/dynamic-rp/deployment.yaml @@ -0,0 +1,98 @@ +{{- $appversion := include "radius.versiontag" . }} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + control-plane: dynamic-rp + app.kubernetes.io/name: dynamic-rp + app.kubernetes.io/part-of: radius + name: dynamic-rp + namespace: {{ .Release.Namespace }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: dynamic-rp + template: + metadata: + labels: + control-plane: dynamic-rp + app.kubernetes.io/name: dynamic-rp + app.kubernetes.io/part-of: radius + {{- if eq .Values.global.azureWorkloadIdentity.enabled true }} + azure.workload.identity/use: "true" + {{- end }} + {{- if eq .Values.global.prometheus.enabled true }} + annotations: + prometheus.io/path: "{{ .Values.global.prometheus.path }}" + prometheus.io/port: "{{ .Values.global.prometheus.port }}" + prometheus.io/scrape: "{{ .Values.global.prometheus.enabled }}" + {{- end }} + spec: + serviceAccountName: dynamic-rp + containers: + - name: dynamic-rp + image: "{{ .Values.dynamicrp.image }}:{{ .Values.dynamicrp.tag | default $appversion }}" + args: + - --config-file=/etc/config/radius-self-host.yaml + env: + - name: SKIP_ARM + value: 'false' + - name: ARM_AUTH_METHOD + value: 'UCPCredential' + - name: RADIUS_ENV + value: 'self-hosted' + - name: K8S_CLUSTER + value: 'true' + {{- if .Values.global.rootCA.cert }} + - name: {{ .Values.global.rootCA.sslCertDirEnvVar }} + value: {{ .Values.global.rootCA.mountPath }} + {{- end}} + ports: + - containerPort: 5443 + name: dynamic-rp + protocol: TCP + {{- if eq .Values.global.prometheus.enabled true }} + - containerPort: {{ .Values.global.prometheus.port }} + name: metrics + protocol: TCP + {{- end }} + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: config-volume + mountPath: /etc/config + {{- if eq .Values.global.aws.irsa.enabled true }} + - name: aws-iam-token + mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount + {{- end }} + - name: terraform + mountPath: {{ .Values.dynamicrp.terraform.path }} + {{- if .Values.global.rootCA.cert }} + - name: {{ .Values.global.rootCA.volumeName }} + mountPath: {{ .Values.global.rootCA.mountPath }} + readOnly: true + {{- end }} + {{- if .Values.dynamicrp.resources }} + resources:{{ toYaml .Values.rp.resources | nindent 10 }} + {{- end }} + volumes: + - name: config-volume + configMap: + name: dynamic-rp-config + {{- if eq .Values.global.aws.irsa.enabled true }} + - name: aws-iam-token + projected: + sources: + - serviceAccountToken: + path: token + expirationSeconds: 86400 + audience: "sts.amazonaws.com" + {{- end }} + - name: terraform + emptyDir: {} + {{- if .Values.global.rootCA.cert }} + - name: {{ .Values.global.rootCA.volumeName }} + secret: + secretName: {{ .Values.global.rootCA.secretName }} + {{- end }} diff --git a/deploy/Chart/templates/dynamic-rp/rbac.yaml b/deploy/Chart/templates/dynamic-rp/rbac.yaml new file mode 100644 index 0000000000..67225ef6f7 --- /dev/null +++ b/deploy/Chart/templates/dynamic-rp/rbac.yaml @@ -0,0 +1,56 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: dynamic-rp + labels: + app.kubernetes.io/name: dynamic-rp + app.kubernetes.io/part-of: radius +rules: +# Adding coordination.k8s.io api group as Terraform need to access leases resource for backend initialization for state locking: https://developer.hashicorp.com/terraform/language/settings/backends/kubernetes. +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - get + - list + - patch + - update + +# Integration with UCP's API. +- apiGroups: + - api.ucp.dev + resources: + - '*' + verbs: + - '*' + +# Integration with data store and queues. +- apiGroups: + - ucp.dev + resources: + - resources + - queuemessages + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: dynamic-rp +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: dynamic-rp +subjects: +- kind: ServiceAccount + name: dynamic-rp + namespace: {{ .Release.Namespace }} diff --git a/deploy/Chart/templates/dynamic-rp/service.yaml b/deploy/Chart/templates/dynamic-rp/service.yaml new file mode 100644 index 0000000000..a2aa54f175 --- /dev/null +++ b/deploy/Chart/templates/dynamic-rp/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: dynamic-rp + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: dynamic-rp + app.kubernetes.io/part-of: radius +spec: + ports: + - port: 8082 + name: http + protocol: TCP + targetPort: 8082 + selector: + app.kubernetes.io/name: dynamic-rp diff --git a/deploy/Chart/templates/dynamic-rp/serviceaccount.yaml b/deploy/Chart/templates/dynamic-rp/serviceaccount.yaml new file mode 100644 index 0000000000..7b49ee5c2d --- /dev/null +++ b/deploy/Chart/templates/dynamic-rp/serviceaccount.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: dynamic-rp + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: dynamic-rp + app.kubernetes.io/part-of: radius diff --git a/deploy/Chart/values.yaml b/deploy/Chart/values.yaml index eb39f707ad..d750295570 100644 --- a/deploy/Chart/values.yaml +++ b/deploy/Chart/values.yaml @@ -64,6 +64,24 @@ ucp: limits: memory: "300Mi" +dynamicrp: + image: ghcr.io/radius-project/dynamic-rp + # Default tag uses Chart AppVersion. + # tag: latest + resources: + requests: + # request memory is the average memory usage + 10% buffer. + memory: "160Mi" + limits: + # limit is higher for dynamic-rp because the Terraform execution + # can spike memory usage. + memory: "500Mi" + bicep: + deleteRetryCount: 20 + deleteRetryDelaySeconds: 60 + terraform: + path: "/terraform" + rp: image: ghcr.io/radius-project/applications-rp # Default tag uses Chart AppVersion. diff --git a/deploy/images/dynamic-rp/Dockerfile b/deploy/images/dynamic-rp/Dockerfile new file mode 100644 index 0000000000..3eedb038d2 --- /dev/null +++ b/deploy/images/dynamic-rp/Dockerfile @@ -0,0 +1,25 @@ +# Use Alpine image to enable Git installation for Terraform module downloads. +# Switch to distroless when Terraform execution is moved to a separate container. +FROM alpine:3.20 + +ARG TARGETARCH + +# Install ca-certificates and Git (required for Terraform module downloads) +# Create non-root user in a single RUN command to minimize layers +RUN apk --no-cache add ca-certificates git && \ + addgroup -g 65532 rpuser && \ + adduser -u 65532 -G rpuser -s /bin/sh -D rpuser + +WORKDIR / + +# Copy the application binary +COPY ./linux_${TARGETARCH:-amd64}/release/dynamic-rp / + +# Set the user to non-root +USER rpuser + +# Expose the application port +EXPOSE 8080 + +# Set the entrypoint to the application binary +ENTRYPOINT ["/dynamic-rp"] diff --git a/deploy/images/dynamic-rp/Dockerfile.mariner b/deploy/images/dynamic-rp/Dockerfile.mariner new file mode 100644 index 0000000000..02aad31a2e --- /dev/null +++ b/deploy/images/dynamic-rp/Dockerfile.mariner @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/cbl-mariner/base/core:2.0 + +RUN yum -y install wget ca-certificates shadow-utils + +# Install libifxaudit +RUN wget https://packages.microsoft.com/centos/7/prod/libifxaudit-1.0-1525.x86_64.rpm && rpm -i libifxaudit-1.0-1525.x86_64.rpm + +WORKDIR /app + +RUN mkdir /app/config +COPY ./* /app/ + +RUN groupadd --gid 2000 radius + +RUN useradd --home "/nonexistent" --shell "/sbin/nologin" --gid radius --uid 1000 radius + +RUN chmod 770 /app/dynamic-rp +RUN chown radius.radius -R /app + +USER radius + +EXPOSE 8080 +ENTRYPOINT ["/app/dynamic-rp"] \ No newline at end of file diff --git a/pkg/dynamicrp/backend/service.go b/pkg/dynamicrp/backend/service.go new file mode 100644 index 0000000000..370563c80b --- /dev/null +++ b/pkg/dynamicrp/backend/service.go @@ -0,0 +1,73 @@ +/* +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 backend + +import ( + "context" + "fmt" + + "github.com/radius-project/radius/pkg/armrpc/asyncoperation/worker" + "github.com/radius-project/radius/pkg/armrpc/hostoptions" + "github.com/radius-project/radius/pkg/dynamicrp" +) + +// Service runs the backend for the dynamic-rp. +type Service struct { + worker.Service + options *dynamicrp.Options +} + +// NewService creates a new service to run the dynamic-rp backend. +func NewService(options *dynamicrp.Options) *Service { + return &Service{ + options: options, + Service: worker.Service{ + ProviderName: "dynamic-rp", + Options: hostoptions.HostOptions{ + Config: &hostoptions.ProviderConfig{ + Env: options.Config.Environment, + StorageProvider: options.Config.Storage, + SecretProvider: options.Config.Secrets, + QueueProvider: options.Config.Queue, + }, + }, + }, + } +} + +// Name returns the name of the service used for logging. +func (w *Service) Name() string { + return fmt.Sprintf("%s async worker", w.Service.ProviderName) +} + +// Run runs the service. +func (w *Service) Run(ctx context.Context) error { + err := w.Init(ctx) + if err != nil { + return err + } + + workerOptions := worker.Options{} + if w.options.Config.Worker.MaxOperationConcurrency != nil { + workerOptions.MaxOperationConcurrency = *w.options.Config.Worker.MaxOperationConcurrency + } + if w.options.Config.Worker.MaxOperationRetryCount != nil { + workerOptions.MaxOperationRetryCount = *w.options.Config.Worker.MaxOperationRetryCount + } + + return w.Start(ctx, workerOptions) +} diff --git a/pkg/dynamicrp/config.go b/pkg/dynamicrp/config.go new file mode 100644 index 0000000000..084c469c21 --- /dev/null +++ b/pkg/dynamicrp/config.go @@ -0,0 +1,88 @@ +/* +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 dynamicrp + +import ( + "bytes" + + "github.com/radius-project/radius/pkg/armrpc/hostoptions" + metricsprovider "github.com/radius-project/radius/pkg/metrics/provider" + profilerprovider "github.com/radius-project/radius/pkg/profiler/provider" + "github.com/radius-project/radius/pkg/trace" + ucpconfig "github.com/radius-project/radius/pkg/ucp/config" + "github.com/radius-project/radius/pkg/ucp/dataprovider" + queueprovider "github.com/radius-project/radius/pkg/ucp/queue/provider" + secretprovider "github.com/radius-project/radius/pkg/ucp/secret/provider" + "github.com/radius-project/radius/pkg/ucp/ucplog" + "gopkg.in/yaml.v3" +) + +// Config defines the configuration for the DynamicRP server. +type Config struct { + // Bicep configures properties for the Bicep recipe driver. + Bicep hostoptions.BicepOptions `yaml:"bicep"` + + // Environment is the configuration for the hosting environment. + Environment hostoptions.EnvironmentOptions `yaml:"environment"` + + // Logging is the configuration for the logging system. + Logging ucplog.LoggingOptions `yaml:"logging"` + + // Metrics is the configuration for the metrics endpoint. + Metrics metricsprovider.MetricsProviderOptions `yaml:"metricsProvider"` + + // Profiler is the configuration for the profiler endpoint. + Profiler profilerprovider.ProfilerProviderOptions `yaml:"profilerProvider"` + + // Queue is the configuration for the message queue. + Queue queueprovider.QueueProviderOptions `yaml:"queueProvider"` + + // Secrets is the configuration for the secret storage system. + Secrets secretprovider.SecretProviderOptions `yaml:"secretProvider"` + + // Server is the configuration for the HTTP server. + Server hostoptions.ServerOptions `yaml:"server"` + + // Storage is the configuration for the database used for storage. + Storage dataprovider.StorageProviderOptions `yaml:"storageProvider"` + + // Terraform configures properties for the Terraform recipe driver. + Terraform hostoptions.TerraformOptions `yaml:"terraform"` + + // Tracing is the configuration for the tracing system. + Tracing trace.Options `yaml:"tracerProvider"` + + // UCPConfig is the configuration for the connection to UCP. + UCP ucpconfig.UCPOptions `yaml:"ucp"` + + // Worker is the configuration for the backend worker server. + Worker hostoptions.WorkerServerOptions `yaml:"workerServer"` +} + +// LoadConfig loads a Config from bytes. +func LoadConfig(bs []byte) (*Config, error) { + decoder := yaml.NewDecoder(bytes.NewBuffer(bs)) + decoder.KnownFields(true) + + config := Config{} + err := decoder.Decode(&config) + if err != nil { + return nil, err + } + + return &config, nil +} diff --git a/pkg/dynamicrp/doc.go b/pkg/dynamicrp/doc.go new file mode 100644 index 0000000000..b491be2998 --- /dev/null +++ b/pkg/dynamicrp/doc.go @@ -0,0 +1,19 @@ +/* +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. +*/ + +// dynamicrp holds the implementation of the dynamic resource provider. The dynamicrp is responsible +// for managing the lifecycle of resources that are defined without their own resource provider implementation. +package dynamicrp diff --git a/pkg/dynamicrp/frontend/routes.go b/pkg/dynamicrp/frontend/routes.go new file mode 100644 index 0000000000..3988d532b3 --- /dev/null +++ b/pkg/dynamicrp/frontend/routes.go @@ -0,0 +1,30 @@ +/* +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 frontend + +import ( + "github.com/go-chi/chi/v5" + "github.com/radius-project/radius/pkg/validator" +) + +func (s *Service) registerRoutes(r *chi.Mux) error { + // Return ARM errors for invalid requests. + r.NotFound(validator.APINotFoundHandler()) + r.MethodNotAllowed(validator.APIMethodNotAllowedHandler()) + + return nil +} diff --git a/pkg/dynamicrp/frontend/service.go b/pkg/dynamicrp/frontend/service.go new file mode 100644 index 0000000000..f5bee9dbd9 --- /dev/null +++ b/pkg/dynamicrp/frontend/service.go @@ -0,0 +1,117 @@ +/* +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 frontend + +import ( + "context" + "fmt" + "net" + "net/http" + + "github.com/radius-project/radius/pkg/armrpc/servicecontext" + "github.com/radius-project/radius/pkg/dynamicrp" + "github.com/radius-project/radius/pkg/middleware" + "github.com/radius-project/radius/pkg/ucp/ucplog" + + "github.com/go-chi/chi/v5" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" +) + +func NewService(options *dynamicrp.Options) *Service { + return &Service{ + options: options, + } +} + +// Service implements the hosting.Service interface for the UCP frontend API. +type Service struct { + options *dynamicrp.Options +} + +// Name gets this service name. +func (s *Service) Name() string { + return "dynamic-rp api" +} + +// Initialize sets up the router, storage provider, secret provider, status manager, AWS config, AWS clients, +// registers the routes, configures the default planes, and sets up the http server with the appropriate middleware. It +// returns an http server and an error if one occurs. +func (s *Service) initialize(ctx context.Context) (*http.Server, error) { + r := chi.NewRouter() + + err := s.registerRoutes(r) + if err != nil { + return nil, fmt.Errorf("failed to register routes: %w", err) + } + + app := http.Handler(r) + + // Autodetect pathbase + app = servicecontext.ARMRequestCtx("", s.options.Config.Environment.RoleLocation)(app) + app = middleware.WithLogger(app) + + app = otelhttp.NewHandler( + middleware.NormalizePath(app), + "dynamic-rp", + otelhttp.WithMeterProvider(otel.GetMeterProvider()), + otelhttp.WithTracerProvider(otel.GetTracerProvider())) + + // TODO: This is the workaround to fix the high cardinality of otelhttp. + // Remove this once otelhttp middleware is fixed - https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3765 + app = middleware.RemoveRemoteAddr(app) + + server := &http.Server{ + Addr: fmt.Sprintf("%s:%d", s.options.Config.Server.Host, s.options.Config.Server.Port), + Handler: app, + BaseContext: func(ln net.Listener) context.Context { + return ctx + }, + } + + return server, nil +} + +// Run sets up a server to listen on a given address, and shuts it down when the context is done. It returns an +// error if the server fails to start or stops unexpectedly. +func (s *Service) Run(ctx context.Context) error { + logger := ucplog.FromContextOrDiscard(ctx) + server, err := s.initialize(ctx) + if err != nil { + return err + } + + // Handle shutdown based on the context + go func() { + <-ctx.Done() + // We don't care about shutdown errors + _ = server.Shutdown(ctx) + }() + + logger.Info(fmt.Sprintf("listening on: '%s'...", server.Addr)) + err = server.ListenAndServe() + if err == http.ErrServerClosed { + // We expect this, safe to ignore. + logger.Info("Server stopped...") + return nil + } else if err != nil { + return err + } + + logger.Info("Server stopped...") + return nil +} diff --git a/pkg/dynamicrp/options.go b/pkg/dynamicrp/options.go new file mode 100644 index 0000000000..ce18389617 --- /dev/null +++ b/pkg/dynamicrp/options.go @@ -0,0 +1,111 @@ +/* +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 dynamicrp + +import ( + "context" + "fmt" + + "github.com/radius-project/radius/pkg/armrpc/asyncoperation/statusmanager" + "github.com/radius-project/radius/pkg/armrpc/hostoptions" + "github.com/radius-project/radius/pkg/kubeutil" + "github.com/radius-project/radius/pkg/recipes/controllerconfig" + "github.com/radius-project/radius/pkg/sdk" + ucpconfig "github.com/radius-project/radius/pkg/ucp/config" + "github.com/radius-project/radius/pkg/ucp/dataprovider" + queueprovider "github.com/radius-project/radius/pkg/ucp/queue/provider" + secretprovider "github.com/radius-project/radius/pkg/ucp/secret/provider" + kube_rest "k8s.io/client-go/rest" +) + +// Options holds the configuration options and shared services for the server. +type Options struct { + // Config is the configuration for the server. + Config *Config + + // QueueProvider provides access to the message queue client. + QueueProvider *queueprovider.QueueProvider + + // Recipes is the configuration for the recipe subsystem. + Recipes *controllerconfig.RecipeControllerConfig + + // SecretProvider provides access to the secret storage system. + SecretProvider *secretprovider.SecretProvider + + // StatusManager implements operations on async operation statuses. + StatusManager statusmanager.StatusManager + + // StorageProvider provides access to the data storage system. + StorageProvider dataprovider.DataStorageProvider + + // UCP is the connection to UCP + UCP sdk.Connection +} + +// NewOptions creates a new Options instance from the given configuration. +func NewOptions(ctx context.Context, config *Config) (*Options, error) { + var err error + options := Options{ + Config: config, + } + + options.QueueProvider = queueprovider.New(config.Queue) + options.SecretProvider = secretprovider.NewSecretProvider(config.Secrets) + options.StorageProvider = dataprovider.NewStorageProvider(config.Storage) + + queueClient, err := options.QueueProvider.GetClient(ctx) + if err != nil { + return nil, err + } + + options.StatusManager = statusmanager.New(options.StorageProvider, queueClient, config.Environment.RoleLocation) + + var cfg *kube_rest.Config + cfg, err = kubeutil.NewClientConfig(&kubeutil.ConfigOptions{ + // TODO: Allow to use custom context via configuration. - https://github.com/radius-project/radius/issues/5433 + ContextName: "", + QPS: kubeutil.DefaultServerQPS, + Burst: kubeutil.DefaultServerBurst, + }) + if err != nil { + return nil, fmt.Errorf("failed to get kubernetes config: %w", err) + } + + options.UCP, err = ucpconfig.NewConnectionFromUCPConfig(&config.UCP, cfg) + if err != nil { + return nil, err + } + + // The recipe infrastructure is tied to corerp's dependencies, so we need to create it here. + recipes, err := controllerconfig.New(hostoptions.HostOptions{ + Config: &hostoptions.ProviderConfig{ + Bicep: config.Bicep, + Env: config.Environment, + Terraform: config.Terraform, + UCP: config.UCP, + }, + K8sConfig: cfg, + UCPConnection: options.UCP, + }) + if err != nil { + return nil, err + } + + options.Recipes = recipes + + return &options, nil +} diff --git a/pkg/dynamicrp/server/doc.go b/pkg/dynamicrp/server/doc.go new file mode 100644 index 0000000000..7d0951df61 --- /dev/null +++ b/pkg/dynamicrp/server/doc.go @@ -0,0 +1,19 @@ +/* +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. +*/ + +// server holds the server implementation for the dynamicrp. +// This package is the entry-point for interacting with the server. +package server diff --git a/pkg/dynamicrp/server/server.go b/pkg/dynamicrp/server/server.go new file mode 100644 index 0000000000..bb7e97963e --- /dev/null +++ b/pkg/dynamicrp/server/server.go @@ -0,0 +1,76 @@ +/* +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 server + +import ( + "time" + + "github.com/radius-project/radius/pkg/dynamicrp" + "github.com/radius-project/radius/pkg/dynamicrp/backend" + "github.com/radius-project/radius/pkg/dynamicrp/frontend" + metricsservice "github.com/radius-project/radius/pkg/metrics/service" + profilerservice "github.com/radius-project/radius/pkg/profiler/service" + "github.com/radius-project/radius/pkg/trace" + "github.com/radius-project/radius/pkg/ucp/data" + "github.com/radius-project/radius/pkg/ucp/dataprovider" + "github.com/radius-project/radius/pkg/ucp/hosting" +) + +const ( + HTTPServerStopTimeout = time.Second * 10 + ServiceName = "dynamic-rp" +) + +const UCPProviderName = "System.Resources" + +// NewServer creates a new hosting.Host instance with services for API, EmbeddedETCD, Metrics, Profiler and Backend (if +// enabled) based on the given Options. +func NewServer(options *dynamicrp.Options) (*hosting.Host, error) { + services := []hosting.Service{} + + // In-memory ETCD requires a service running in the process. + if options.Config.Storage.Provider == dataprovider.TypeETCD && + options.Config.Storage.ETCD.InMemory { + services = append(services, data.NewEmbeddedETCDService(data.EmbeddedETCDServiceOptions{ClientConfigSink: options.Config.Storage.ETCD.Client})) + } + + // Metrics is provided via a service. + if options.Config.Metrics.Prometheus.Enabled { + services = append(services, metricsservice.NewService(metricsservice.HostOptions{ + Config: &options.Config.Metrics, + })) + } + + // Profiling is provided via a service. + if options.Config.Profiler.Enabled { + services = append(services, profilerservice.NewService(profilerservice.HostOptions{ + Config: &options.Config.Profiler, + })) + } + + // Tracing is provided via a service. + if options.Config.Tracing.ServiceName != "" { + services = append(services, &trace.Service{Options: options.Config.Tracing}) + } + + services = append(services, frontend.NewService(options)) + services = append(services, backend.NewService(options)) + + return &hosting.Host{ + Services: services, + }, nil +}