diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index 4d74dd8..92f625d 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -38,7 +38,7 @@ import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
import { RequirePermission } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
-import { GetK8sOIDCTokenExtension } from './scaffolder/credentials';
+import { KubernetesClusterPickerExtension } from './scaffolder/kubernetesClusterPicker';
const app = createApp({
apis,
@@ -95,7 +95,7 @@ const routes = (
}>
-
+
} />
diff --git a/packages/app/src/scaffolder/credentials/extensions.ts b/packages/app/src/scaffolder/credentials/extensions.ts
deleted file mode 100644
index 8313c7b..0000000
--- a/packages/app/src/scaffolder/credentials/extensions.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import {scaffolderPlugin,} from '@backstage/plugin-scaffolder';
-import {createScaffolderFieldExtension} from "@backstage/plugin-scaffolder-react";
-import {GetK8sOIDCToken} from "./getOIDCToken";
-import {ClusterPickerSchema} from "./schema";
-
-export const GetK8sOIDCTokenExtension = scaffolderPlugin.provide(
- createScaffolderFieldExtension(
- {
- name: 'GetK8sOIDCToken',
- component: GetK8sOIDCToken,
- schema: ClusterPickerSchema,
- }
- )
-)
diff --git a/packages/app/src/scaffolder/credentials/getOIDCToken.tsx b/packages/app/src/scaffolder/credentials/getOIDCToken.tsx
deleted file mode 100644
index 9614852..0000000
--- a/packages/app/src/scaffolder/credentials/getOIDCToken.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import React, {useCallback, useEffect} from 'react';
-import FormControl from '@material-ui/core/FormControl';
-import {
- useApi, configApiRef, discoveryApiRef, oauthRequestApiRef
-} from "@backstage/core-plugin-api";
-import { kubernetesApiRef } from "@backstage/plugin-kubernetes";
-import { FormHelperText } from "@material-ui/core";
-import {Progress, Select} from "@backstage/core-components";
-import useAsync from "react-use/lib/useAsync";
-import {useTemplateSecrets} from "@backstage/plugin-scaffolder-react";
-import {ClusterPickerProps} from "./schema";
-import {OAuth2} from "@backstage/core-app-api";
-
-
-export const GetK8sOIDCToken = (props: ClusterPickerProps) => {
-
- const k8sApi = useApi(kubernetesApiRef)
- const { setSecrets, secrets } = useTemplateSecrets();
-
- const discoveryApi = useApi(discoveryApiRef)
- const oauthRequestApi = useApi(oauthRequestApiRef)
- const configApi = useApi(configApiRef)
-
- const {uiSchema, required} = props
- let {rawErrors} = props
- const {value: {clusters} = {clusters: []}, loading } = useAsync(
- async () => {
- const c = await k8sApi.getClusters()
- return {clusters: c.map(i => ({ label: i.name, value: i.name}))}
- }
- )
- if (!rawErrors) {
- rawErrors = []
- }
-
- const getToken = useCallback( async (clusterName: string) => {
- const {requestUserCredentials} = uiSchema?.['ui:options'] ?? {}
- if (!requestUserCredentials) {
- return;
- }
- const cs = await k8sApi.getClusters()
- const cluster = cs.find(c => {
- return c.name === clusterName
- })
- if (cluster?.oidcTokenProvider === undefined) {
- throw new Error("no oidc provider defined for this cluster")
- }
-
- const oidc = OAuth2.create({
- discoveryApi,
- oauthRequestApi,
- provider: {
- id: cluster.oidcTokenProvider,
- title: 'OIDC',
- icon: () => null,
- },
- environment: configApi.getOptionalString('auth.environment'),
- defaultScopes: ['openid', 'profile', 'email', 'groups'],
- })
- const token = await oidc.getIdToken()
-
- setSecrets({ [requestUserCredentials.secretKey]: token })
- }, [configApi, discoveryApi, k8sApi, oauthRequestApi, setSecrets, uiSchema]
- )
-
- useEffect(() => {
- const {requestUserCredentials} = uiSchema?.['ui:options'] ?? {}
- if (!requestUserCredentials?.secretKey || secrets[requestUserCredentials?.secretKey!]) {
- return
- }
-
- if (clusters.length) {
- getToken(clusters[0].value).catch(console.error)
- }
- }, [clusters, getToken, secrets, uiSchema])
-
- if (loading) {
- return ;
- }
-
- return (
- 0}
- >
-
- );
-};
-
-
-
diff --git a/packages/app/src/scaffolder/credentials/index.ts b/packages/app/src/scaffolder/credentials/index.ts
deleted file mode 100644
index 94d549d..0000000
--- a/packages/app/src/scaffolder/credentials/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export {GetK8sOIDCTokenExtension} from './extensions'
diff --git a/packages/app/src/scaffolder/credentials/schema.ts b/packages/app/src/scaffolder/credentials/schema.ts
deleted file mode 100644
index d243e89..0000000
--- a/packages/app/src/scaffolder/credentials/schema.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { z } from 'zod';
-import {makeFieldSchemaFromZod} from "@backstage/plugin-scaffolder";
-
-export const ClusterPickerFieldSchema = makeFieldSchemaFromZod(
- z.string(),
- z.object( {
- requestUserCredentials: z.object({
- secretKey: z.string().describe('Key used within the template secrets context to store the credential')
- }
- )
- .optional()
- .describe('If defined will request user credentials to auth against the cluster')
- })
-)
-
-export const ClusterPickerSchema = ClusterPickerFieldSchema.schema
-
-export type ClusterPickerProps = typeof ClusterPickerFieldSchema.type
-
-export type ClusterPickerUiOptions = typeof ClusterPickerFieldSchema.uiOptionsType
diff --git a/packages/app/src/scaffolder/kubernetesClusterPicker/KubernetesClusterPicker.tsx b/packages/app/src/scaffolder/kubernetesClusterPicker/KubernetesClusterPicker.tsx
new file mode 100644
index 0000000..94d7044
--- /dev/null
+++ b/packages/app/src/scaffolder/kubernetesClusterPicker/KubernetesClusterPicker.tsx
@@ -0,0 +1,87 @@
+import React, { useCallback, useMemo } from 'react';
+import FormControl from '@material-ui/core/FormControl';
+import { useApi } from '@backstage/core-plugin-api';
+import {
+ kubernetesApiRef,
+ kubernetesAuthProvidersApiRef,
+} from '@backstage/plugin-kubernetes';
+import { FormHelperText } from '@material-ui/core';
+import { Progress, Select } from '@backstage/core-components';
+import useAsync from 'react-use/lib/useAsync';
+import { useTemplateSecrets } from '@backstage/plugin-scaffolder-react';
+import { ClusterPickerProps } from './schema';
+
+export const KubernetesClusterPicker = (props: ClusterPickerProps) => {
+ const k8sApi = useApi(kubernetesApiRef);
+ const k8sAuthApi = useApi(kubernetesAuthProvidersApiRef);
+ const { setSecrets } = useTemplateSecrets();
+ const { uiSchema, required, onChange } = props;
+ const { rawErrors } = props ?? [];
+ const allowedClusters = useMemo(
+ () => uiSchema?.['ui:options']?.allowedClusters ?? [],
+ [uiSchema],
+ );
+
+ const getToken = useCallback(
+ async (clusterName: string) => {
+ const { requestUserCredentials } = uiSchema?.['ui:options'] ?? {};
+ if (!requestUserCredentials) {
+ return;
+ }
+ const cs = await k8sApi.getClusters();
+ const cluster = cs.find(c => {
+ return c.name === clusterName;
+ });
+ const { token } = await k8sAuthApi.getCredentials(cluster?.authProvider!);
+ if (token === undefined) {
+ return;
+ }
+ setSecrets({ [requestUserCredentials.secretKey]: token });
+ },
+ [k8sApi, k8sAuthApi, setSecrets, uiSchema],
+ );
+
+ const { value: { clusters } = { clusters: [] }, loading } = useAsync(
+ async () => {
+ const c = await k8sApi.getClusters();
+ const filteredClusters = c
+ .filter(i => {
+ if (allowedClusters.length === 0) {
+ return true;
+ }
+ return allowedClusters.includes(i.name);
+ })
+ .map(i => ({ label: i.name, value: i.name }));
+ if (filteredClusters.length) {
+ await getToken(filteredClusters[0].value);
+ onChange(filteredClusters[0].value);
+ }
+ return {
+ clusters: filteredClusters,
+ };
+ },
+ );
+
+ if (loading) {
+ return ;
+ }
+
+ return (
+ 0}
+ >
+
+ );
+};
diff --git a/packages/app/src/scaffolder/kubernetesClusterPicker/extensions.ts b/packages/app/src/scaffolder/kubernetesClusterPicker/extensions.ts
new file mode 100644
index 0000000..bad90c5
--- /dev/null
+++ b/packages/app/src/scaffolder/kubernetesClusterPicker/extensions.ts
@@ -0,0 +1,12 @@
+import { scaffolderPlugin } from '@backstage/plugin-scaffolder';
+import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
+import { KubernetesClusterPicker } from './KubernetesClusterPicker';
+import { ClusterPickerSchema } from './schema';
+
+export const KubernetesClusterPickerExtension = scaffolderPlugin.provide(
+ createScaffolderFieldExtension({
+ name: 'KubernetesClusterPicker',
+ component: KubernetesClusterPicker,
+ schema: ClusterPickerSchema,
+ }),
+);
diff --git a/packages/app/src/scaffolder/kubernetesClusterPicker/index.ts b/packages/app/src/scaffolder/kubernetesClusterPicker/index.ts
new file mode 100644
index 0000000..dbf85db
--- /dev/null
+++ b/packages/app/src/scaffolder/kubernetesClusterPicker/index.ts
@@ -0,0 +1 @@
+export { KubernetesClusterPickerExtension } from './extensions';
diff --git a/packages/app/src/scaffolder/kubernetesClusterPicker/schema.ts b/packages/app/src/scaffolder/kubernetesClusterPicker/schema.ts
new file mode 100644
index 0000000..e5dac30
--- /dev/null
+++ b/packages/app/src/scaffolder/kubernetesClusterPicker/schema.ts
@@ -0,0 +1,31 @@
+import { z } from 'zod';
+import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder';
+
+export const ClusterPickerFieldSchema = makeFieldSchemaFromZod(
+ z.string(),
+ z.object({
+ requestUserCredentials: z
+ .object({
+ secretKey: z
+ .string()
+ .describe(
+ 'Key used within the template secrets context to store the credential',
+ ),
+ })
+ .optional()
+ .describe(
+ 'If defined will request user credentials to auth against the cluster',
+ ),
+ allowedClusters: z
+ .array(z.string())
+ .optional()
+ .describe('List of allowed Kubernetes clusters'),
+ }),
+);
+
+export const ClusterPickerSchema = ClusterPickerFieldSchema.schema;
+
+export type ClusterPickerProps = typeof ClusterPickerFieldSchema.type;
+
+export type ClusterPickerUiOptions =
+ typeof ClusterPickerFieldSchema.uiOptionsType;