wizard: add GCP image sharing options

Adding GCP image sharing option according to the discussion on slack(https://redhat-internal.slack.com/archives/C03AZ0264LW/p1692789579814619) and mocks(https://issues.redhat.com/browse/HMS-2352).

In summary, within our GCP sources, we store the project ID. Images cannot be shared directly with a project ID, but they can be shared with a service account or a Google account. Consequently, to launch instances in GCP, users are not required to provide their Google account; instead, the images should be shared with the provisioning service account. This ensures that the provisioning team has access to the necessary images, as sharing them with individual users would not allow that.

After a thorough discussion, we have collectively decided to introduce an option. This option allows users to exclusively utilize the Launch service without the need to share the image with a Google account.
This commit is contained in:
Adi Abramovich 2023-09-10 12:27:08 +03:00 committed by Thomas Lavocat
parent 50b76751e7
commit a890dc5666
5 changed files with 103 additions and 41 deletions

View file

@ -122,20 +122,22 @@ const onSave = (values) => {
if (values['target-environment']?.gcp) {
let share = '';
switch (values['google-account-type']) {
case 'googleAccount':
share = `user:${values['google-email']}`;
break;
case 'serviceAccount':
share = `serviceAccount:${values['google-email']}`;
break;
case 'googleGroup':
share = `group:${values['google-email']}`;
break;
case 'domain':
share = `domain:${values['google-domain']}`;
break;
// no default
if (values['image_sharing'] === 'gcp-account') {
switch (values['google-account-type']) {
case 'googleAccount':
share = `user:${values['google-email']}`;
break;
case 'serviceAccount':
share = `serviceAccount:${values['google-email']}`;
break;
case 'googleGroup':
share = `group:${values['google-email']}`;
break;
case 'domain':
share = `domain:${values['google-domain']}`;
break;
// no default
}
}
const request = {
@ -148,15 +150,16 @@ const onSave = (values) => {
image_type: 'gcp',
upload_request: {
type: 'gcp',
options: {
share_with_accounts: [share],
},
options: {},
},
},
],
customizations,
};
if (share !== '') {
request.options = [share];
}
requests.push(request);
}

View file

@ -102,20 +102,48 @@ const googleCloudStep = {
name: 'google-cloud-text-component',
label: (
<p>
Your image will be uploaded to GCP and shared with the account you
provide below.
Select how to share your image. The image you create can be used to
launch instances on GCP, regardless of which method you select.
</p>
),
},
{
component: componentTypes.PLAIN_TEXT,
name: 'google-cloud-text-component',
label: (
<p>
<b>The shared image will expire within 14 days.</b> To permanently
access the image, copy it to your Google Cloud Platform account.
</p>
),
component: componentTypes.RADIO,
label: 'Select image sharing',
isRequired: true,
name: 'image-sharing',
initialValue: 'gcp-account',
autoFocus: true,
options: [
{
label: 'Share image with a Google account',
'data-testid': 'account-sharing',
autoFocus: true,
description: (
<p>
Your image will be uploaded to GCP and shared with the account you
provide below.
<b>The image expires in 14 days.</b> To keep permanent access to
your image, copy it to your GCP project.
</p>
),
value: 'gcp-account',
},
{
label: 'Share image with Red Hat Insights only',
'data-testid': 'image-sharing',
description: (
<p>
Your image will be uploaded to GCP and shared with Red Hat
Insights.
<b> The image expires in 14 days.</b> You cannot access or
recreate this image in your GCP project.
</p>
),
value: 'insights',
autoFocus: true,
},
],
},
{
component: 'radio-popover',
@ -137,6 +165,10 @@ const googleCloudStep = {
type: validatorTypes.REQUIRED,
},
],
condition: {
when: 'image-sharing',
is: 'gcp-account',
},
},
{
component: componentTypes.TEXT_FIELD,
@ -145,11 +177,16 @@ const googleCloudStep = {
type: 'text',
label: 'Principal (e.g. e-mail address)',
condition: {
or: [
{ when: 'google-account-type', is: 'googleAccount' },
{ when: 'google-account-type', is: 'serviceAccount' },
{ when: 'google-account-type', is: 'googleGroup' },
{ when: 'google-account-type', is: null },
and: [
{ when: 'image-sharing', is: 'gcp-account' },
{
or: [
{ when: 'google-account-type', is: 'googleAccount' },
{ when: 'google-account-type', is: 'serviceAccount' },
{ when: 'google-account-type', is: 'googleGroup' },
{ when: 'google-account-type', is: null },
],
},
],
},
isRequired: true,
@ -170,8 +207,10 @@ const googleCloudStep = {
type: 'text',
label: 'Domain',
condition: {
when: 'google-account-type',
is: 'domain',
and: [
{ when: 'image-sharing', is: 'gcp-account' },
{ when: 'google-account-type', is: 'domain' },
],
},
isRequired: true,
validate: [

View file

@ -12,7 +12,7 @@ import {
export const isGcpUploadRequestOptions = (
options: UploadRequest['options']
): options is GcpUploadRequestOptions => {
return (options as GcpUploadRequestOptions).share_with_accounts !== undefined;
return true;
};
export const isAwsUploadRequestOptions = (

View file

@ -458,7 +458,14 @@ describe('Step Upload to Google', () => {
test('clicking Next loads Registration', async () => {
await setUp();
await user.type(screen.getByTestId('input-google-email'), 'test@test.com');
const shareRadioButton = await screen.findByRole('radio', {
name: /share image with a google account/i,
});
await user.click(shareRadioButton);
const googleEmailInput = await screen.findByTestId('input-google-email');
await user.type(googleEmailInput, 'test@test.com');
await clickNext();
await screen.findByRole('textbox', {
@ -485,15 +492,22 @@ describe('Step Upload to Google', () => {
test('the google account id field is shown and required', async () => {
await setUp();
const accessKeyId = screen.getByTestId('input-google-email');
await waitFor(() => {
screen.getByTestId('account-sharing');
});
user.click(screen.getByTestId('account-sharing'));
const accessKeyId = await screen.findByTestId('input-google-email');
expect(accessKeyId).toHaveValue('');
expect(accessKeyId).toBeEnabled();
// expect(accessKeyId).toBeRequired(); // DDf does not support required value
});
test('the google email field must be a valid email', async () => {
await setUp();
await user.click(screen.getByTestId('account-sharing'));
await user.type(screen.getByTestId('input-google-email'), 'a');
expect(await getNextButton()).toHaveClass('pf-m-disabled');
expect(await getNextButton()).toBeDisabled();
@ -1033,7 +1047,11 @@ describe('Click through all steps', () => {
await user.type(screen.getByTestId('aws-account-id'), '012345678901');
await clickNext();
await user.click(screen.getByTestId('account-sharing'));
await user.type(screen.getByTestId('input-google-email'), 'test@test.com');
await user.click(await screen.findByTestId('image-sharing'));
await clickNext();
await user.click(screen.getByTestId('azure-radio-manual'));
@ -1391,10 +1409,8 @@ describe('Keyboard accessibility', () => {
await clickNext();
// Target environment google
const googleAccountRadio = screen.getByRole('radio', {
name: /google account/i,
});
expect(googleAccountRadio).toHaveFocus();
await user.click(screen.getByTestId('account-sharing'));
expect(screen.getByTestId('account-sharing')).toHaveFocus();
await user.type(screen.getByTestId('input-google-email'), 'test@test.com');
await clickNext();

View file

@ -36,6 +36,10 @@ failOnConsole({
) ||
errorMessage.includes(
"Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application."
) ||
// [2023-09] Suppresses an error that occurs on the GCP step of the Wizard.
errorMessage.includes(
'Warning: Cannot update a component (`ForwardRef(Field)`) while rendering a different component (`Radio`). To locate the bad setState() call inside `Radio`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render'
))
) {
// eslint-disable-next-line no-console