diff --git a/cmpserver/plugin/plugin.go b/cmpserver/plugin/plugin.go
index e8cfb01ecb3e9..5bc621783ddc9 100644
--- a/cmpserver/plugin/plugin.go
+++ b/cmpserver/plugin/plugin.go
@@ -332,9 +332,9 @@ func (s *Service) GetParametersAnnouncement(stream apiclient.ConfigManagementPlu
}
func getParametersAnnouncement(ctx context.Context, appDir string, staticAnnouncements []Static, command Command) (*apiclient.ParametersAnnouncementResponse, error) {
- var staticParamAnnouncements []*apiclient.ParameterAnnouncement
+ var announcements []*apiclient.ParameterAnnouncement
for _, static := range staticAnnouncements {
- staticParamAnnouncements = append(staticParamAnnouncements, &apiclient.ParameterAnnouncement{
+ announcements = append(announcements, &apiclient.ParameterAnnouncement{
Name: static.Name,
Title: static.Title,
Tooltip: static.Tooltip,
@@ -347,19 +347,24 @@ func getParametersAnnouncement(ctx context.Context, appDir string, staticAnnounc
})
}
- stdout, err := runCommand(ctx, command, appDir, os.Environ())
- if err != nil {
- return nil, fmt.Errorf("error executing dynamic parameter output command: %s", err)
- }
+ if len(command.Command) > 0 {
+ stdout, err := runCommand(ctx, command, appDir, os.Environ())
+ if err != nil {
+ return nil, fmt.Errorf("error executing dynamic parameter output command: %s", err)
+ }
- var dynamicParamAnnouncements []*apiclient.ParameterAnnouncement
- err = json.Unmarshal([]byte(stdout), &dynamicParamAnnouncements)
- if err != nil {
- return nil, fmt.Errorf("error unmarshaling dynamic parameter output into ParametersAnnouncementResponse: %s", err)
+ var dynamicParamAnnouncements []*apiclient.ParameterAnnouncement
+ err = json.Unmarshal([]byte(stdout), &dynamicParamAnnouncements)
+ if err != nil {
+ return nil, fmt.Errorf("error unmarshaling dynamic parameter output into ParametersAnnouncementResponse: %s", err)
+ }
+
+ // dynamic goes first, because static should take precedence by being later.
+ announcements = append(dynamicParamAnnouncements, announcements...)
}
repoResponse := &apiclient.ParametersAnnouncementResponse{
- ParameterAnnouncements: append(staticParamAnnouncements, dynamicParamAnnouncements...),
+ ParameterAnnouncements: announcements,
}
return repoResponse, nil
}
diff --git a/cmpserver/plugin/plugin_test.go b/cmpserver/plugin/plugin_test.go
index 16e40aa20cef7..23f23bef07ad2 100644
--- a/cmpserver/plugin/plugin_test.go
+++ b/cmpserver/plugin/plugin_test.go
@@ -246,6 +246,20 @@ func Test_getParametersAnnouncement_empty_command(t *testing.T) {
assert.Equal(t, []*apiclient.ParameterAnnouncement{{Name: "static-a"}, {Name: "static-b"}}, res.ParameterAnnouncements)
}
+func Test_getParametersAnnouncement_no_command(t *testing.T) {
+ staticYAML := `
+- name: static-a
+- name: static-b
+`
+ static := &[]Static{}
+ err := yaml.Unmarshal([]byte(staticYAML), static)
+ require.NoError(t, err)
+ command := Command{}
+ res, err := getParametersAnnouncement(context.Background(), "", *static, command)
+ require.NoError(t, err)
+ assert.Equal(t, []*apiclient.ParameterAnnouncement{{Name: "static-a"}, {Name: "static-b"}}, res.ParameterAnnouncements)
+}
+
func Test_getParametersAnnouncement_static_and_dynamic(t *testing.T) {
staticYAML := `
- name: static-a
@@ -261,10 +275,10 @@ func Test_getParametersAnnouncement_static_and_dynamic(t *testing.T) {
res, err := getParametersAnnouncement(context.Background(), "", *static, command)
require.NoError(t, err)
expected := []*apiclient.ParameterAnnouncement{
- {Name: "static-a"},
- {Name: "static-b"},
{Name: "dynamic-a"},
{Name: "dynamic-b"},
+ {Name: "static-a"},
+ {Name: "static-b"},
}
assert.Equal(t, expected, res.ParameterAnnouncements)
}
@@ -278,3 +292,13 @@ func Test_getParametersAnnouncement_invalid_json(t *testing.T) {
assert.Error(t, err)
assert.Contains(t, err.Error(), "unexpected end of JSON input")
}
+
+func Test_getParametersAnnouncement_bad_command(t *testing.T) {
+ command := Command{
+ Command: []string{"exit"},
+ Args: []string{"1"},
+ }
+ _, err := getParametersAnnouncement(context.Background(), "", []Static{}, command)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "error executing dynamic parameter output command")
+}
diff --git a/test/cmp/plugin.yaml b/test/cmp/plugin.yaml
index 1184b715d284d..28c735290a4da 100644
--- a/test/cmp/plugin.yaml
+++ b/test/cmp/plugin.yaml
@@ -11,3 +11,17 @@ spec:
glob: "**/kustomization.yaml"
allowConcurrency: true
lockRepo: false
+ parameters:
+ static:
+ - name: string-param
+ string: value
+ - name: array-param
+ collectionType: array
+ array:
+ - value1
+ - value2
+ - name: map-param
+ collectionType: map
+ map:
+ key: value
+ key2: value2
diff --git a/ui/src/app/applications/components/application-parameters/application-parameters.tsx b/ui/src/app/applications/components/application-parameters/application-parameters.tsx
index 972783777f682..f1070c373db72 100644
--- a/ui/src/app/applications/components/application-parameters/application-parameters.tsx
+++ b/ui/src/app/applications/components/application-parameters/application-parameters.tsx
@@ -278,6 +278,36 @@ export const ApplicationParameters = (props: {
view: app.spec.source.plugin && (app.spec.source.plugin.env || []).map(i => `${i.name}='${i.value}'`).join(' '),
edit: (formApi: FormApi) =>
});
+ if (props.details.plugin.parametersAnnouncement) {
+ for (const announcement of props.details.plugin.parametersAnnouncement) {
+ const liveParam = app.spec.source.plugin.parameters?.find(param => param.name === announcement.name);
+ if (announcement.collectionType === undefined || announcement.collectionType === '' || announcement.collectionType === 'string') {
+ attributes.push({
+ title: announcement.title ?? announcement.name,
+ view: liveParam?.string || announcement.string,
+ edit: () => liveParam?.string || announcement.string
+ });
+ } else if (announcement.collectionType === 'array') {
+ attributes.push({
+ title: announcement.title ?? announcement.name,
+ view: (liveParam?.array || announcement.array || []).join(' '),
+ edit: () => (liveParam?.array || announcement.array || []).join(' ')
+ });
+ } else if (announcement.collectionType === 'map') {
+ const entries = concatMaps(announcement.map, liveParam?.map).entries();
+ attributes.push({
+ title: announcement.title ?? announcement.name,
+ view: Array.from(entries)
+ .map(([key, value]) => `${key}='${value}'`)
+ .join(' '),
+ edit: () =>
+ Array.from(entries)
+ .map(([key, value]) => `${key}='${value}'`)
+ .join(' ')
+ });
+ }
+ }
+ }
} else if (props.details.type === 'Directory') {
const directory = app.spec.source.directory || ({} as ApplicationSourceDirectory);
attributes.push({
@@ -344,3 +374,16 @@ export const ApplicationParameters = (props: {
/>
);
};
+
+// concatMaps merges two maps. Later args take precedence where there's a key conflict.
+function concatMaps(...maps: (Map | null)[]): Map {
+ const newMap = new Map();
+ for (const map of maps) {
+ if (map) {
+ for (const entry of Object.entries(map)) {
+ newMap.set(entry[0], entry[1]);
+ }
+ }
+ }
+ return newMap;
+}
diff --git a/ui/src/app/shared/models.ts b/ui/src/app/shared/models.ts
index fb704556ef749..1ac95fd60c13b 100644
--- a/ui/src/app/shared/models.ts
+++ b/ui/src/app/shared/models.ts
@@ -209,6 +209,14 @@ export interface EnvEntry {
export interface ApplicationSourcePlugin {
name: string;
env: EnvEntry[];
+ parameters?: Parameter[];
+}
+
+export interface Parameter {
+ name: string;
+ string?: string;
+ array?: string[];
+ map?: Map;
}
export interface JsonnetVar {
@@ -579,6 +587,19 @@ export interface KustomizeAppSpec {
export interface PluginAppSpec {
name: string;
env: EnvEntry[];
+ parametersAnnouncement?: ParameterAnnouncement[];
+}
+
+export interface ParameterAnnouncement {
+ name?: string;
+ title?: string;
+ tooltip?: string;
+ required?: boolean;
+ itemType?: string;
+ collectionType?: string;
+ string?: string;
+ array?: string[];
+ map?: Map;
}
export interface ObjectReference {