diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/import-environments.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/import-environments.ts new file mode 100644 index 00000000000..a69506e7696 --- /dev/null +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/import-environments.ts @@ -0,0 +1,30 @@ +import { Extension, ExtensionDeclaration } from '../types'; + +type ImageEnvironment = { + /** Environment variable key */ + key: string; + /** The input field's label */ + label: string; + /** Default value to use as a placeholder */ + defaultValue?: string; + /** Description of the environment variable */ + description?: string; +}; + +export type ImportEnvironment = ExtensionDeclaration< + 'dev-console.import/environment', + { + /** Name of the image stream to provide custom environment variables for */ + imageStreamName: string; + /** List of supported image stream tags */ + imageStreamTags: string[]; + /** List of environment variables */ + environments: ImageEnvironment[]; + } +>; + +// Type guards + +export const isImportEnvironment = (e: Extension): e is ImportEnvironment => { + return e.type === 'dev-console.import/environment'; +}; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/index.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/index.ts index ee284c51a1c..c2f277292b2 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/index.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/index.ts @@ -22,3 +22,4 @@ export * from './topology'; export * from './create-resource'; export * from './user-preferences'; export * from './horizontal-nav-tabs'; +export * from './import-environments'; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/schema/console-extensions.ts b/frontend/packages/console-dynamic-plugin-sdk/src/schema/console-extensions.ts index fc4c2988a76..bd429acc161 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/schema/console-extensions.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/schema/console-extensions.ts @@ -18,6 +18,7 @@ import { import { FeatureFlag, ModelFeatureFlag } from '../extensions/feature-flags'; import { FileUpload } from '../extensions/file-upload'; import { HorizontalNavTab } from '../extensions/horizontal-nav-tabs'; +import { ImportEnvironment } from '../extensions/import-environments'; import { HrefNavItem, ResourceNSNavItem, @@ -64,6 +65,7 @@ export type SupportedExtension = | YAMLTemplate | AddAction | AddActionGroup + | ImportEnvironment | ClusterGlobalConfig | HrefNavItem | ResourceNSNavItem diff --git a/frontend/packages/dev-console/console-extensions.json b/frontend/packages/dev-console/console-extensions.json index 86bb2e4be9b..63e5f643439 100644 --- a/frontend/packages/dev-console/console-extensions.json +++ b/frontend/packages/dev-console/console-extensions.json @@ -186,6 +186,21 @@ ] } }, + { + "type": "dev-console.import/environment", + "properties": { + "imageStreamName": "nodejs", + "imageStreamTags": ["16-ubi8", "14-ubi8", "14-ubi8-minimal", "12-ubi8", "latest"], + "environments": [ + { + "key": "NPM_RUN", + "label": "%devconsole~Run command%", + "description": "%devconsole~Optional parameter for Node.js applications to define the npm run .%", + "defaultValue": "start" + } + ] + } + }, { "type": "console.catalog/item-type", "properties": { diff --git a/frontend/packages/dev-console/integration-tests/features/addFlow/create-from-git.feature b/frontend/packages/dev-console/integration-tests/features/addFlow/create-from-git.feature index ea060b86be0..df0e28f66fe 100644 --- a/frontend/packages/dev-console/integration-tests/features/addFlow/create-from-git.feature +++ b/frontend/packages/dev-console/integration-tests/features/addFlow/create-from-git.feature @@ -203,3 +203,13 @@ Feature: Create Application from git form | git_url | | https://github.com/sclorg/httpd-ex.git | | https://github.com/sclorg/nginx-ex.git | + + @regression + Scenario: Provide custom build environments for nodejs git import + Given user is at Import from Git form + When user enters Git Repo URL as "https://github.com/sclorg/nodejs-ex" + And user enters run command for "NPM_RUN" as "build2" + And user enters Name as "nodejs-env" + And user clicks Create button on Add page + Then user will be redirected to Topology page + And user is able to navigate to Build #1 for deployment "nodejs-env" and see environment variable "NPM_RUN" in Environment tab of details page diff --git a/frontend/packages/dev-console/integration-tests/support/constants/global.ts b/frontend/packages/dev-console/integration-tests/support/constants/global.ts index 97ae2d29221..7bb86a73acc 100644 --- a/frontend/packages/dev-console/integration-tests/support/constants/global.ts +++ b/frontend/packages/dev-console/integration-tests/support/constants/global.ts @@ -41,6 +41,7 @@ export enum authenticationType { export enum resources { Deployments = 'Deployments', BuildConfigs = 'Build Configs', + Builds = 'Builds', Services = 'Services', ImageStreams = 'Image Streams', Routes = 'Routes', diff --git a/frontend/packages/dev-console/integration-tests/support/pages/add-flow/add-page.ts b/frontend/packages/dev-console/integration-tests/support/pages/add-flow/add-page.ts index c7292169c8c..47fe3bffca2 100644 --- a/frontend/packages/dev-console/integration-tests/support/pages/add-flow/add-page.ts +++ b/frontend/packages/dev-console/integration-tests/support/pages/add-flow/add-page.ts @@ -94,6 +94,13 @@ export const addPage = { } }, verifyCard: (cardName: string) => cy.get(cardTitle).should('contain.text', cardName), + setBuildEnvField: (envKey: string, value: string) => + cy + .get(`#form-input-image-imageEnv-${envKey}-field`) + .scrollIntoView() + .should('be.visible') + .clear() + .type(value), }; export const verifyAddPage = { diff --git a/frontend/packages/dev-console/integration-tests/support/step-definitions/common/addFlow.ts b/frontend/packages/dev-console/integration-tests/support/step-definitions/common/addFlow.ts index 558505bf598..5bb26b1b266 100644 --- a/frontend/packages/dev-console/integration-tests/support/step-definitions/common/addFlow.ts +++ b/frontend/packages/dev-console/integration-tests/support/step-definitions/common/addFlow.ts @@ -1,5 +1,12 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; -import { switchPerspective, devNavigationMenu, pageTitle, addOptions } from '../../constants'; +import { detailsPage } from '@console/cypress-integration-tests/views/details-page'; +import { + switchPerspective, + devNavigationMenu, + pageTitle, + addOptions, + resources, +} from '../../constants'; import { topologyPO } from '../../pageObjects'; import { gitPage, @@ -10,6 +17,8 @@ import { topologyHelper, perspective, navigateTo, + topologySidePane, + app, } from '../../pages'; Given('user is at Add page', () => { @@ -88,3 +97,20 @@ Then('user can see {string} card on the Add page', (cardName: string) => { When('user selects {string} card from add page', (cardName: string) => { addPage.selectCardFromOptions(cardName); }); + +When('user enters run command for {string} as {string}', (envKey: string, value: string) => { + addPage.setBuildEnvField(envKey, value); +}); + +Then( + 'user is able to navigate to Build #1 for deployment {string} and see environment variable {string} in Environment tab of details page', + (name: string, env: string) => { + topologyPage.clickOnNode(name); + topologySidePane.selectTab('Resources'); + topologySidePane.selectResource(resources.Builds, 'aut-addflow-git', `${name}-1`); + app.waitForLoad(); + detailsPage.selectTab('Environment'); + app.waitForLoad(); + cy.get(`input[data-test="pairs-list-name"][value="${env}"]`).should('exist'); + }, +); diff --git a/frontend/packages/dev-console/locales/en/devconsole.json b/frontend/packages/dev-console/locales/en/devconsole.json index d5aa4287149..f192cbcc40c 100644 --- a/frontend/packages/dev-console/locales/en/devconsole.json +++ b/frontend/packages/dev-console/locales/en/devconsole.json @@ -19,6 +19,8 @@ "Browse the catalog to discover and deploy operator managed services": "Browse the catalog to discover and deploy operator managed services", "Upload JAR file": "Upload JAR file", "Upload a JAR file from your local desktop to OpenShift": "Upload a JAR file from your local desktop to OpenShift", + "Run command": "Run command", + "Optional parameter for Node.js applications to define the npm run .": "Optional parameter for Node.js applications to define the npm run .", "Builder Images": "Builder Images", "Browse for container images that support a particular language or framework. Cluster administrators can customize the content made available in the catalog.": "Browse for container images that support a particular language or framework. Cluster administrators can customize the content made available in the catalog.", "**Builder Images** are container images that build source code for a particular language or framework.": "**Builder Images** are container images that build source code for a particular language or framework.", diff --git a/frontend/packages/dev-console/src/components/import/builder/BuilderImageEnvironments.tsx b/frontend/packages/dev-console/src/components/import/builder/BuilderImageEnvironments.tsx new file mode 100644 index 00000000000..661d7bd8f46 --- /dev/null +++ b/frontend/packages/dev-console/src/components/import/builder/BuilderImageEnvironments.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import { TextInputTypes } from '@patternfly/react-core'; +import { + ImportEnvironment, + isImportEnvironment, + useResolvedExtensions, +} from '@console/dynamic-plugin-sdk'; +import { InputField } from '@console/shared'; + +interface BuilderImageEnvironmentsProps { + name: string; + imageStreamName: string; + imageStreamTag: string; +} + +const BuilderImageEnvironments: React.FC = ({ + name, + imageStreamName, + imageStreamTag, +}) => { + const [environmentExtensions, resolved] = useResolvedExtensions( + isImportEnvironment, + ); + + const filteredExtensions = React.useMemo( + () => + environmentExtensions?.filter( + (e) => + e.properties.imageStreamName === imageStreamName && + e.properties.imageStreamTags.includes(imageStreamTag), + ), + [environmentExtensions, imageStreamName, imageStreamTag], + ); + + if (!resolved) { + return null; + } + return ( + <> + {filteredExtensions.map(({ properties }) => + properties.environments.map((env) => ( + + )), + )} + + ); +}; + +export default BuilderImageEnvironments; diff --git a/frontend/packages/dev-console/src/components/import/builder/BuilderImageTagSelector.tsx b/frontend/packages/dev-console/src/components/import/builder/BuilderImageTagSelector.tsx index f8475339174..cac0146b279 100644 --- a/frontend/packages/dev-console/src/components/import/builder/BuilderImageTagSelector.tsx +++ b/frontend/packages/dev-console/src/components/import/builder/BuilderImageTagSelector.tsx @@ -12,6 +12,7 @@ import { getPorts, } from '../../../utils/imagestream-utils'; import { useSafeK8s } from '../../../utils/safe-k8s-hook'; +import BuilderImageEnvironments from './BuilderImageEnvironments'; import ImageStreamInfo from './ImageStreamInfo'; export interface BuilderImageTagSelectorProps { @@ -79,6 +80,11 @@ const BuilderImageTagSelector: React.FC = ({ /> {imageTag && showImageInfo && } + ); }; diff --git a/frontend/packages/dev-console/src/components/import/import-submit-utils.ts b/frontend/packages/dev-console/src/components/import/import-submit-utils.ts index 2505adfa8e4..4ea00c2902a 100644 --- a/frontend/packages/dev-console/src/components/import/import-submit-utils.ts +++ b/frontend/packages/dev-console/src/components/import/import-submit-utils.ts @@ -169,7 +169,7 @@ export const createOrUpdateBuildConfig = ( application: { name: applicationName }, git: { url: repository, type: gitType, ref = 'master', dir: contextDir, secret: secretName }, docker: { dockerfilePath }, - image: { tag: selectedTag }, + image: { tag: selectedTag, imageEnv }, build: { env, triggers, strategy: buildStrategy }, labels: userLabels, } = formData; @@ -182,6 +182,11 @@ export const createOrUpdateBuildConfig = ( let buildStrategyData; let desiredContextDir = contextDir; + const customBuildEnvs = imageEnv + ? Object.keys(imageEnv) + .filter((k) => !!imageEnv[k]) + .map((k) => ({ name: k, value: imageEnv[k] })) + : []; switch (buildStrategy) { case 'Devfile': @@ -198,7 +203,7 @@ export const createOrUpdateBuildConfig = ( default: buildStrategyData = { sourceStrategy: { - env, + env: [...env, ...customBuildEnvs], from: { kind: 'ImageStreamTag', name: `${imageStreamName}:${selectedTag}`, diff --git a/frontend/packages/dev-console/src/components/import/import-types.ts b/frontend/packages/dev-console/src/components/import/import-types.ts index ec107cfa2b6..e43b7722b7c 100644 --- a/frontend/packages/dev-console/src/components/import/import-types.ts +++ b/frontend/packages/dev-console/src/components/import/import-types.ts @@ -146,6 +146,7 @@ export interface ImageData { tag: string; tagObj: object; ports: ContainerPort[]; + imageEnv?: { [key: string]: string }; } export interface ImageStreamImageData { diff --git a/frontend/packages/helm-plugin/integration-tests/support/step-definitions/common/common.ts b/frontend/packages/helm-plugin/integration-tests/support/step-definitions/common/common.ts index c09f1a0c4ad..106c74fa923 100644 --- a/frontend/packages/helm-plugin/integration-tests/support/step-definitions/common/common.ts +++ b/frontend/packages/helm-plugin/integration-tests/support/step-definitions/common/common.ts @@ -92,7 +92,7 @@ When('user switches to the {string} tab', (tab: string) => { }); When('user clicks on the link for the {string} of helm release', (resource: string) => { - topologySidePane.selectResource(resource, Cypress.env('NAMESPACE')); + topologySidePane.selectResource(resource, Cypress.env('NAMESPACE'), 'nodejs-release'); }); Given('user is at Add page', () => { diff --git a/frontend/packages/integration-tests-cypress/views/details-page.ts b/frontend/packages/integration-tests-cypress/views/details-page.ts index 40543836388..9acbb73af07 100644 --- a/frontend/packages/integration-tests-cypress/views/details-page.ts +++ b/frontend/packages/integration-tests-cypress/views/details-page.ts @@ -14,6 +14,11 @@ export const detailsPage = { }, isLoaded: () => cy.byTestID('skeleton-detail-view').should('not.exist'), breadcrumb: (breadcrumbIndex: number) => cy.byLegacyTestID(`breadcrumb-link-${breadcrumbIndex}`), + selectTab: (name: string) => { + cy.get(`a[data-test-id="horizontal-link-public~${name}"]`) + .should('exist') + .click(); + }, }; export namespace DetailsPageSelector { diff --git a/frontend/packages/topology/integration-tests/support/pages/topology/topology-side-pane-page.ts b/frontend/packages/topology/integration-tests/support/pages/topology/topology-side-pane-page.ts index 12c120568eb..70165aab475 100644 --- a/frontend/packages/topology/integration-tests/support/pages/topology/topology-side-pane-page.ts +++ b/frontend/packages/topology/integration-tests/support/pages/topology/topology-side-pane-page.ts @@ -125,31 +125,36 @@ export const topologySidePane = { cy.byTestActionID('Delete Application').should('be.visible'); cy.get(topologyPO.addToApplicationInContext).should('be.visible'); }, - selectResource: (opt: resources | string, namespace: string) => { + selectResource: (opt: resources | string, namespace: string, name: string) => { switch (opt) { case 'Deployments': case resources.Deployments: { - cy.get(`[href="/k8s/ns/${namespace}/deployments/nodejs-release"]`).click(); + cy.get(`[href="/k8s/ns/${namespace}/deployments/${name}"]`).click(); break; } case 'Build Configs': case resources.BuildConfigs: { - cy.get(`[href="/k8s/ns/${namespace}/buildconfigs/nodejs-release"]`).click(); + cy.get(`[href="/k8s/ns/${namespace}/buildconfigs/${name}"]`).click(); + break; + } + case 'Builds': + case resources.Builds: { + cy.get(`[href="/k8s/ns/${namespace}/builds/${name}]`).click(); break; } case 'Services': case resources.Services: { - cy.get(`[href="/k8s/ns/${namespace}/services/nodejs-release"]`).click(); + cy.get(`[href="/k8s/ns/${namespace}/services/${name}"]`).click(); break; } case 'Image Streams': case resources.ImageStreams: { - cy.get(`[href="/k8s/ns/${namespace}/imagestreams/nodejs-release"]`).click(); + cy.get(`[href="/k8s/ns/${namespace}/imagestreams/${name}"]`).click(); break; } case 'Routes': case resources.Routes: { - cy.get(`[href="/k8s/ns/${namespace}/routes/nodejs-release"]`).click(); + cy.get(`[href="/k8s/ns/${namespace}/routes/${name}"]`).click(); break; } default: {