diff --git a/frontend/src/apis/job/api.ts b/frontend/src/apis/job/api.ts index 7036625cdd2..cb6ba656a3f 100644 --- a/frontend/src/apis/job/api.ts +++ b/frontend/src/apis/job/api.ts @@ -138,11 +138,17 @@ export interface ApiJob { */ pipeline_spec?: ApiPipelineSpec; /** - * Optional input field. Specify which resource this run belongs to. + * Optional input field. Specify which resource this job belongs to. * @type {Array} * @memberof ApiJob */ resource_references?: Array; + /** + * Optional input field. Specify which Kubernetes service account this job uses. + * @type {string} + * @memberof ApiJob + */ + service_account?: string; /** * * @type {string} diff --git a/frontend/src/apis/run/api.ts b/frontend/src/apis/run/api.ts index 40adca72be6..4fb09ec6a67 100644 --- a/frontend/src/apis/run/api.ts +++ b/frontend/src/apis/run/api.ts @@ -346,6 +346,12 @@ export interface ApiRun { * @memberof ApiRun */ resource_references?: Array; + /** + * Optional input field. Specify which Kubernetes service account this run uses. + * @type {string} + * @memberof ApiRun + */ + service_account?: string; /** * Output. The time that the run created. * @type {Date} diff --git a/frontend/src/pages/NewRun.test.tsx b/frontend/src/pages/NewRun.test.tsx index 0eb2ae7883a..9d1d0531bf7 100644 --- a/frontend/src/pages/NewRun.test.tsx +++ b/frontend/src/pages/NewRun.test.tsx @@ -130,6 +130,7 @@ describe('NewRun', () => { run: { id: 'some-mock-run-id', name: 'some mock run name', + service_account: 'pipeline-runner', pipeline_spec: { pipeline_id: 'original-run-pipeline-id', workflow_manifest: '{}', @@ -785,6 +786,25 @@ describe('NewRun', () => { expect(tree.state('runName')).toBe('Clone (2) of some run'); }); + it('uses service account in the original run', async () => { + const defaultRunDetail = newMockRunDetail(); + const runDetail = { + ...defaultRunDetail, + run: { + ...defaultRunDetail.run, + service_account: 'sa1', + }, + }; + const props = generateProps(); + props.location.search = `?${QUERY_PARAMS.cloneFromRun}=${runDetail.run!.id}`; + getRunSpy.mockImplementation(() => runDetail); + + tree = shallow(); + await TestUtils.flushPromises(); + + expect(tree.state('serviceAccount')).toBe('sa1'); + }); + it('uses the query param experiment ID over the one in the original run if an ID is present in both', async () => { const experiment = newMockExperiment(); const runDetail = newMockRunDetail(); @@ -1189,6 +1209,9 @@ describe('NewRun', () => { (tree.instance() as TestNewRun).handleChange('description')({ target: { value: 'test run description' }, }); + (tree.instance() as TestNewRun).handleChange('serviceAccount')({ + target: { value: 'service-account-name' }, + }); await TestUtils.flushPromises(); tree @@ -1205,6 +1228,7 @@ describe('NewRun', () => { pipeline_spec: { parameters: MOCK_PIPELINE.parameters, }, + service_account: 'service-account-name', resource_references: [ { key: { @@ -1259,6 +1283,7 @@ describe('NewRun', () => { pipeline_spec: { parameters: [{ name: 'testName', value: '{\n "test2": "value2"\n}' }], }, + service_account: '', resource_references: [ { key: { @@ -1351,6 +1376,7 @@ describe('NewRun', () => { pipeline_id: undefined, workflow_manifest: '{"metadata":{"name":"embedded"},"parameters":[]}', }, + service_account: 'pipeline-runner', resource_references: [], }); // TODO: verify route change happens @@ -1655,6 +1681,7 @@ describe('NewRun', () => { instance.handleChange('runName')({ target: { value: 'test run name' } }); instance.handleChange('description')({ target: { value: 'test run description' } }); + instance.handleChange('serviceAccount')({ target: { value: 'service-account-name' } }); await TestUtils.flushPromises(); tree @@ -1675,6 +1702,7 @@ describe('NewRun', () => { pipeline_spec: { parameters: MOCK_PIPELINE.parameters, }, + service_account: 'service-account-name', resource_references: [ { key: { diff --git a/frontend/src/pages/NewRun.tsx b/frontend/src/pages/NewRun.tsx index 0762086bbf3..08ad5ea74a2 100644 --- a/frontend/src/pages/NewRun.tsx +++ b/frontend/src/pages/NewRun.tsx @@ -57,12 +57,15 @@ import { Description } from '../components/Description'; import { NamespaceContext } from '../lib/KubeflowClient'; import { NameWithTooltip } from '../components/CustomTableNameColumn'; import { PredicateOp, ApiFilter } from '../apis/filter'; +import { HelpButton } from 'src/atoms/HelpButton'; +import { ExternalLink } from 'src/atoms/ExternalLink'; interface NewRunState { description: string; errorMessage: string; experiment?: ApiExperiment; experimentName: string; + serviceAccount: string; experimentSelectorOpen: boolean; isBeingStarted: boolean; isClone: boolean; @@ -116,6 +119,7 @@ export class NewRun extends Page<{ namespace?: string }, NewRunState> { description: '', errorMessage: '', experimentName: '', + serviceAccount: '', experimentSelectorOpen: false, isBeingStarted: false, isClone: false, @@ -176,6 +180,7 @@ export class NewRun extends Page<{ namespace?: string }, NewRunState> { description, errorMessage, experimentName, + serviceAccount, experimentSelectorOpen, isClone, isFirstRunInExperiment, @@ -499,6 +504,27 @@ export class NewRun extends Page<{ namespace?: string }, NewRunState> { }} /> +
+ This run will use the following Kubernetes service account.{' '} + + Note, the service account needs{' '} + + minimum permissions required by argo workflows + {' '} + and extra permissions the specific task requires. +
+ } + /> + + + {/* One-off/Recurring Run Type */}
Run Type
{isClone && {isRecurringRun ? 'Recurring' : 'One-off'}} @@ -910,6 +936,7 @@ export class NewRun extends Page<{ namespace?: string }, NewRunState> { let usePipelineFromRunLabel = ''; let name = ''; let pipelineVersionName = ''; + const serviceAccount = originalRun.service_account || ''; // Case 1: a legacy run refers to a pipeline without specifying version. const referencePipelineId = RunUtils.getPipelineId(originalRun); @@ -984,6 +1011,7 @@ export class NewRun extends Page<{ namespace?: string }, NewRunState> { usePipelineFromRunLabel, useWorkflowFromRun, workflowFromRun, + serviceAccount, }); this._validate(); @@ -1039,6 +1067,7 @@ export class NewRun extends Page<{ namespace?: string }, NewRunState> { : undefined, }, resource_references: references, + service_account: this.state.serviceAccount, }; if (this.state.isRecurringRun) { newRun = Object.assign(newRun, { diff --git a/frontend/src/pages/__snapshots__/NewRun.test.tsx.snap b/frontend/src/pages/__snapshots__/NewRun.test.tsx.snap index 1d470c4e656..c6b3f187646 100644 --- a/frontend/src/pages/__snapshots__/NewRun.test.tsx.snap +++ b/frontend/src/pages/__snapshots__/NewRun.test.tsx.snap @@ -378,6 +378,31 @@ exports[`NewRun arriving from pipeline details page indicates that a pipeline is value="" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> + +
@@ -873,6 +898,31 @@ exports[`NewRun changes the exit button's text if query params indicate this is value="some mock experiment name" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -1368,6 +1418,31 @@ exports[`NewRun changes title and form if the new run will recur, based on the r value="some mock experiment name" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -1874,6 +1949,31 @@ exports[`NewRun changes title and form to default state if the new run is a one- value="" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -2369,6 +2469,31 @@ exports[`NewRun fetches the associated pipeline if one is present in the query p value="" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -2862,6 +2987,31 @@ exports[`NewRun renders the new run page 1`] = ` value="some mock experiment name" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -3357,6 +3507,31 @@ exports[`NewRun starting a new recurring run includes additional trigger input f value="" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -3863,6 +4038,31 @@ exports[`NewRun starting a new run updates the pipeline params as user selects d value="some mock experiment name" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -4358,6 +4558,31 @@ exports[`NewRun starting a new run updates the pipeline params as user selects d value="some mock experiment name" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -4864,6 +5089,31 @@ exports[`NewRun starting a new run updates the pipeline params as user selects d value="some mock experiment name" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
@@ -5359,6 +5609,31 @@ exports[`NewRun updates the run's state with the associated experiment if one is value="some mock experiment name" variant="outlined" /> +
+ This run will use the following Kubernetes service account. + + + Note, the service account needs + + + minimum permissions required by argo workflows + + + and extra permissions the specific task requires. +
+ } + /> +
+
diff --git a/test/frontend-integration-test/helloworld.spec.js b/test/frontend-integration-test/helloworld.spec.js index c4e63637a43..4174008985c 100644 --- a/test/frontend-integration-test/helloworld.spec.js +++ b/test/frontend-integration-test/helloworld.spec.js @@ -103,6 +103,10 @@ describe('deploy helloworld sample run', () => { // Skip over "choose experiment" button browser.keys('Tab'); + // Skip over service account help button + browser.keys('Tab'); + // Skip over "service account" textbox + browser.keys('Tab'); // Skip over "Run Type" radio button browser.keys('Tab'); @@ -224,6 +228,10 @@ describe('deploy helloworld sample run', () => { // Skip over "choose experiment" button browser.keys('Tab'); + // Skip over service account help button + browser.keys('Tab'); + // Skip over "service account" textbox + browser.keys('Tab'); // Skip over "Run Type" radio button browser.keys('Tab');