Skip to content

Commit 7c9617e

Browse files
TheRealJonclaude
andcommitted
Add ClusterExtension creation page for OLMv1 catalog
Implements a custom create page for ClusterExtension resources that pre-fills the YAML editor with operator details from the catalog. When a user clicks "Install" on an operator in the Extension Catalog, they are directed to a creation page with a YAML template pre-populated with the selected operator's package name, version, and catalog source. Changes: - Add CreateClusterExtension component with query parameter support - Add ClusterExtension and ClusterCatalog model definitions - Update catalog item install URL to include query parameters - Add console.resource/create extension for custom create page - Expose CreateClusterExtension component in package exports 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 5292d41 commit 7c9617e

File tree

6 files changed

+98
-3
lines changed

6 files changed

+98
-3
lines changed

frontend/packages/operator-lifecycle-manager-v1/console-extensions.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,21 @@
101101
"flags": {
102102
"required": ["TECH_PREVIEW"]
103103
}
104+
},
105+
{
106+
"type": "console.resource/create",
107+
"properties": {
108+
"model": {
109+
"group": "olm.operatorframework.io",
110+
"version": "v1",
111+
"kind": "ClusterExtension"
112+
},
113+
"component": {
114+
"$codeRef": "CreateClusterExtension"
115+
}
116+
},
117+
"flags": {
118+
"required": ["CLUSTER_EXTENSION_API"]
119+
}
104120
}
105121
]

frontend/packages/operator-lifecycle-manager-v1/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"exposedModules": {
99
"useCatalogItems": "src/hooks/useCatalogItems.ts",
1010
"useCatalogCategories": "src/hooks/useCatalogCategories.ts",
11-
"useOLMv1FlagProvider": "src/hooks/useOLMv1FlagProvider.ts"
11+
"useOLMv1FlagProvider": "src/hooks/useOLMv1FlagProvider.ts",
12+
"CreateClusterExtension": "src/components/cluster-extension/CreateClusterExtension.tsx"
1213
}
1314
}
1415
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as React from 'react';
2+
import { useLocation } from 'react-router-dom-v5-compat';
3+
import { CreateYAML } from '@console/internal/components/create-yaml';
4+
import { ClusterExtensionModel } from '../../models';
5+
6+
const CreateClusterExtension: React.FC = (props) => {
7+
const location = useLocation();
8+
const searchParams = new URLSearchParams(location.search);
9+
10+
// Get operator details from URL query parameters
11+
const packageName = searchParams.get('packageName');
12+
const version = searchParams.get('version');
13+
const catalog = searchParams.get('catalog');
14+
15+
// Generate the YAML template based on the operator details
16+
const template = React.useMemo(() => {
17+
return `apiVersion: ${ClusterExtensionModel.apiGroup}/${ClusterExtensionModel.apiVersion}
18+
kind: ${ClusterExtensionModel.kind}
19+
metadata:
20+
name: ${packageName || 'example'}
21+
spec:
22+
namespace: ${packageName || '<operator-namespace>'}
23+
serviceAccount:
24+
name: ${packageName ? `${packageName}-service-account` : '<service-account-name>'}
25+
source:
26+
sourceType: Catalog
27+
catalog:
28+
packageName: ${packageName || '<package-name>'}\n
29+
version : ${version || '<version>'}
30+
selector:
31+
matchLabels:
32+
olm.operatorframework.io/metadata.name: ${catalog || '<cluster-catalog-name>'}
33+
`;
34+
}, [packageName, version, catalog]);
35+
36+
return <CreateYAML {...(props as any)} template={template} />;
37+
};
38+
39+
export default CreateClusterExtension;
40+
41+
CreateClusterExtension.displayName = 'CreateClusterExtension';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { K8sKind } from '@console/internal/module/k8s';
2+
3+
export const ClusterExtensionModel: K8sKind = {
4+
label: 'ClusterExtension',
5+
labelKey: 'olm-v1~ClusterExtension',
6+
labelPlural: 'ClusterExtensions',
7+
labelPluralKey: 'olm-v1~ClusterExtensions',
8+
apiVersion: 'v1',
9+
apiGroup: 'olm.operatorframework.io',
10+
plural: 'clusterextensions',
11+
abbr: 'CE',
12+
namespaced: false,
13+
kind: 'ClusterExtension',
14+
id: 'clusterextension',
15+
crd: true,
16+
};
17+
18+
export const ClusterCatalogModel: K8sKind = {
19+
label: 'ClusterCatalog',
20+
labelKey: 'olm-v1~ClusterCatalog',
21+
labelPlural: 'ClusterCatalogs',
22+
labelPluralKey: 'olm-v1~ClusterCatalogs',
23+
apiVersion: 'v1',
24+
apiGroup: 'olm.operatorframework.io',
25+
plural: 'clustercatalogs',
26+
abbr: 'CC',
27+
namespaced: false,
28+
kind: 'ClusterCatalog',
29+
id: 'clustercatalog',
30+
crd: true,
31+
};

frontend/packages/operator-lifecycle-manager-v1/src/utils/catalog-item.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ export const normalizeCatalogItem: NormalizeExtensionCatalogItem = (item) => {
4242
);
4343
const tags = (categories ?? []).map((cat) => cat.toLowerCase().trim()).filter(Boolean);
4444
const source = getClusterCatalogSource(catalog);
45-
const installUrl = '/k8s/cluster/olm.operatorframework.io~v1~ClusterExtension/~new'; // TODO: build from model
45+
// Build install URL with query parameters for pre-filling the ClusterExtension YAML
46+
const installUrlParams = new URLSearchParams({
47+
packageName: name,
48+
...(version && { version }),
49+
catalog,
50+
});
51+
const installUrl = `/k8s/cluster/olm.operatorframework.io~v1~ClusterExtension/~new?${installUrlParams.toString()}`;
4652
return {
4753
attributes: {
4854
keywords,

frontend/public/module/k8s/k8s.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const getQN: (obj: K8sResourceKind) => string = (obj) => {
2727
export const getGroupVersionKind = (
2828
ref: GroupVersionKind | string,
2929
): [string, string, string] | undefined => {
30-
const parts = ref.split('~');
30+
const parts = ref?.split('~') ?? [];
3131
if (parts.length !== 3) {
3232
return undefined;
3333
}

0 commit comments

Comments
 (0)