chore: 🤖 gcp plugin creation in the UI (#2645)

gcp plugin creation and mirage changes
This commit is contained in:
Dharini Jeeva authored and Dharini committed 2025-01-10 14:18:48 -08:00
1 parent 520020f2c6
commit dfa62dd04b
11 files changed
+376 -28

No files matched your search

+3 -3
View File
@@ -95,7 +95,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
* @type {boolean}
*/
get isAWS() {
return this.compositeType === 'aws';
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AWS;
}
/**
@@ -103,7 +103,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
* @type {boolean}
*/
get isAzure() {
return this.compositeType === 'azure';
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE;
}
/**
@@ -111,7 +111,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
* @type {boolean}
*/
get isGCP() {
return this.compositeType === 'gcp';
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_GCP;
}
/**
+16 -3
View File
@@ -4,7 +4,12 @@
*/
import GeneratedHostSetModel from '../generated/models/host-set';
import { TYPES_HOST_CATALOG_PLUGIN } from './host-catalog';
import {
TYPES_HOST_CATALOG_PLUGIN,
TYPE_HOST_CATALOG_PLUGIN_GCP,
TYPE_HOST_CATALOG_PLUGIN_AWS,
TYPE_HOST_CATALOG_PLUGIN_AZURE,
} from './host-catalog';
export default class HostSetModel extends GeneratedHostSetModel {
// =attributes
@@ -40,14 +45,22 @@ export default class HostSetModel extends GeneratedHostSetModel {
* @type {boolean}
*/
get isAWS() {
return this.compositeType === 'aws';
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AWS;
}
/**
* Return if a host-set plugin is Azure or not.
*/
get isAzure() {
return this.compositeType === 'azure';
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE;
}
/**
* True if host catalog plugin type is GCP.
* @type {boolean}
*/
get isGCP() {
return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_GCP;
}
/**
+40 -10
View File
@@ -8,11 +8,23 @@ import { trait } from 'miragejs';
import permissions from '../helpers/permissions';
import { faker } from '@faker-js/faker';
import generateId from '../helpers/id';
import {
TYPE_HOST_CATALOG_PLUGIN_AZURE,
TYPE_HOST_CATALOG_PLUGIN_GCP,
TYPE_HOST_CATALOG_PLUGIN_AWS,
TYPE_HOST_CATALOG_STATIC,
TYPE_HOST_CATALOG_DYNAMIC,
TYPES_HOST_CATALOG,
} from 'api/models/host-catalog';
const types = ['static', 'plugin'];
// Represents known plugin types, except "foobar" which models the possibility
// of receiving an _unknown_ type, which the UI must handle gracefully.
const pluginTypes = ['aws', 'azure', 'foobar'];
const pluginTypes = [
TYPE_HOST_CATALOG_PLUGIN_GCP,
TYPE_HOST_CATALOG_PLUGIN_AWS,
TYPE_HOST_CATALOG_PLUGIN_AZURE,
'foobar',
];
let pluginTypeCounter = 1;
@@ -20,7 +32,7 @@ export default factory.extend({
id: () => generateId('hc_'),
// Cycle through available types
type: (i) => types[i % types.length],
type: (i) => TYPES_HOST_CATALOG[i % TYPES_HOST_CATALOG.length],
authorized_actions: () =>
permissions.authorizedActionsFor('host-catalog') || [
@@ -30,7 +42,7 @@ export default factory.extend({
'delete',
],
authorized_collection_actions: function () {
const isStatic = this.type === 'static';
const isStatic = this.type === TYPE_HOST_CATALOG_STATIC;
return {
// only static catalogs allow host create at this time
hosts: isStatic ? ['create', 'list'] : ['list'],
@@ -39,13 +51,16 @@ export default factory.extend({
},
worker_filter: function () {
if (this.type === 'plugin' && this.plugin?.name === 'aws') {
if (
this.type === TYPE_HOST_CATALOG_DYNAMIC &&
this.plugin?.name === TYPE_HOST_CATALOG_PLUGIN_AWS
) {
return `"${faker.word.noun()}" in "${faker.system.directoryPath()}"`;
}
},
plugin: function (i) {
if (this.type === 'plugin') {
if (this.type === TYPE_HOST_CATALOG_DYNAMIC) {
return {
id: `plugin-id-${i}`,
name: pluginTypes[pluginTypeCounter++ % pluginTypes.length],
@@ -56,35 +71,50 @@ export default factory.extend({
attributes() {
switch (this.plugin?.name) {
case 'aws':
case TYPE_HOST_CATALOG_PLUGIN_AWS:
return {
disable_credential_rotation: faker.datatype.boolean(),
region: `us-${faker.location.cardinalDirection()}-${faker.number.int(
9,
)}`,
};
case 'azure':
case TYPE_HOST_CATALOG_PLUGIN_AZURE:
return {
disable_credential_rotation: faker.datatype.boolean(),
tenant_id: faker.string.uuid(),
client_id: faker.string.uuid(),
subscription_id: faker.string.uuid(),
};
case TYPE_HOST_CATALOG_PLUGIN_GCP:
return {
disable_credential_rotation: faker.datatype.boolean(),
project_id: faker.string.uuid(),
client_email: faker.internet.email(),
target_service_account_id: faker.string.uuid(),
zone: `us-${faker.location.cardinalDirection()}${faker.number.int(
9,
)}-${faker.string.fromCharacters('abc')}`,
};
}
},
secrets() {
switch (this.plugin?.name) {
case 'aws':
case TYPE_HOST_CATALOG_PLUGIN_AWS:
return {
access_key_id: faker.string.nanoid(),
secret_access_key: faker.string.nanoid(),
};
case 'azure':
case TYPE_HOST_CATALOG_PLUGIN_AZURE:
return {
secret_id: faker.string.nanoid(),
secret_value: faker.string.nanoid(),
};
case TYPE_HOST_CATALOG_PLUGIN_GCP:
return {
private_key: faker.string.nanoid(),
private_key_id: faker.string.nanoid(),
};
}
},
withChildren: trait({
@@ -220,6 +220,7 @@ host-catalog:
aws: AWS
azure: Azure
plugin: Dynamic
gcp: GCP
credential:
label: Credential type
help: Select the credential type that you want to use.
@@ -240,6 +241,24 @@ host-catalog:
help: The client (application) ID of an Azure service principal that Boundary will use to authenticate and discover hosts from Azure.
worker_filter:
help: Insert a worker filter in order to route requests to the correct worker.
project_id:
label: Project ID
help: Project ID of the instances you want to add to host catalog.
zone:
label: Zone
help: Zone of the instances you want to add to host catalog.
client_email:
label: Client Email
help: The email address used to uniquely identify the service account.
target_service_account_id:
label: Target Service Account ID
help: The unique identifier for the service account that will be impersonated. This is only used when authenticating with service account impersonation.
private_key_id:
label: Private Key ID
help: The unique identifier of private key.
private_key:
label: Private Key
help: The private key used to obtain a OAuth 2.0 access token.
host-set:
title: Host Set
title_plural: Host Sets
@@ -398,7 +398,7 @@
name='disable_credential_rotation'
checked={{@model.disable_credential_rotation}}
disabled={{form.disabled}}
{{on 'change' (fn this.toggleDisableCredentialRotation @model)}}
{{on 'change' (fn @toggleDisableCredentialRotation @model)}}
as |F|
>
<F.Label>
@@ -26,13 +26,6 @@ export default class FormHostCatalogAwsComponent extends Component {
return TYPES_CREDENTIALS;
}
// =actions
@action
toggleDisableCredentialRotation(model) {
model.disable_credential_rotation = !model.disable_credential_rotation;
}
/**
* Allows to update the credential type
* @param type {string}
@@ -0,0 +1,261 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<Hds::Form::TextInput::Field
name='name'
@value={{@model.name}}
@isInvalid={{@model.errors.name}}
@isOptional={{true}}
readonly={{form.disabled}}
{{on 'input' (set-from-event @model 'name')}}
as |F|
>
<F.Label>{{t 'form.name.label'}}</F.Label>
<F.HelperText>{{t 'form.name.help'}}</F.HelperText>
{{#if @model.errors.name}}
<F.Error as |E|>
{{#each @model.errors.name as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::Textarea::Field
name='description'
@value={{@model.description}}
@isInvalid={{@model.errors.description}}
@isOptional={{true}}
readonly={{form.disabled}}
as |F|
>
<F.Label>{{t 'form.description.label'}}</F.Label>
<F.HelperText>{{t 'form.description.help'}}</F.HelperText>
{{#if @model.errors.description}}
<F.Error as |E|>
{{#each @model.errors.description as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::Textarea::Field>
{{#if @model.isNew}}
<Hds::Form::RadioCard::Group @name={{t 'form.type.label'}} as |G|>
<G.Legend>{{t 'form.type.label'}}</G.Legend>
{{#each @hostCatalogTypes as |hostCatalogType|}}
<G.RadioCard
@value={{hostCatalogType}}
@maxWidth='20rem'
@checked={{eq hostCatalogType @model.type}}
{{on 'input' (fn @changeType hostCatalogType)}}
as |R|
>
<R.Label>{{t
(concat 'resources.host-catalog.types.' hostCatalogType)
}}</R.Label>
<R.Description>{{t
(concat 'resources.host-catalog.help.' hostCatalogType)
}}</R.Description>
</G.RadioCard>
{{/each}}
</Hds::Form::RadioCard::Group>
<Hds::Form::RadioCard::Group
@name={{t 'form.plugin_type.label'}}
@alignment='center'
as |G|
>
<G.Legend>{{t 'titles.provider'}}</G.Legend>
<G.Legend>{{t 'titles.choose-a-provider'}}</G.Legend>
<G.HelperText>{{concat
(t 'descriptions.choose-a-provider')
}}</G.HelperText>
{{#each-in @mapResourceTypeWithIcon as |pluginType icon|}}
<G.RadioCard
@value={{pluginType}}
@maxWidth='20rem'
@checked={{eq pluginType @model.compositeType}}
{{on 'input' (fn @changeType pluginType)}}
as |R|
>
<R.Label>{{t
(concat 'resources.host-catalog.types.' pluginType)
}}</R.Label>
<R.Icon @name={{icon}} />
</G.RadioCard>
{{/each-in}}
</Hds::Form::RadioCard::Group>
{{else}}
<InfoField
@value={{t 'resources.host-catalog.types.gcp'}}
@icon='gcp-color'
readonly={{true}}
as |F|
>
<F.Label>{{t 'titles.provider'}}</F.Label>
<F.HelperText>{{t 'descriptions.provider'}}
</F.HelperText>
</InfoField>
{{/if}}
<Hds::Form::TextInput::Field
name='project_id'
@value={{@model.project_id}}
@isInvalid={{@model.errors.project_id}}
@isRequired={{true}}
readonly={{form.disabled}}
{{on 'input' (set-from-event @model 'project_id')}}
as |F|
>
<F.Label>{{t 'resources.host-catalog.form.project_id.label'}}</F.Label>
<F.HelperText>{{t
'resources.host-catalog.form.project_id.help'
}}</F.HelperText>
{{#if @model.errors.project_id}}
<F.Error as |E|>
{{#each @model.errors.project_id as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
name='zone'
@value={{@model.zone}}
@isInvalid={{@model.errors.zone}}
@isRequired={{true}}
readonly={{form.disabled}}
{{on 'input' (set-from-event @model 'zone')}}
as |F|
>
<F.Label>{{t 'resources.host-catalog.form.zone.label'}}</F.Label>
<F.HelperText>{{t 'resources.host-catalog.form.zone.help'}}</F.HelperText>
{{#if @model.errors.zone}}
<F.Error as |E|>
{{#each @model.errors.zone as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
name='client_email'
@value={{@model.client_email}}
@isInvalid={{@model.errors.client_email}}
@isOptional={{true}}
readonly={{form.disabled}}
{{on 'input' (set-from-event @model 'client_email')}}
as |F|
>
<F.Label>{{t 'resources.host-catalog.form.client_email.label'}}</F.Label>
<F.HelperText>{{t
'resources.host-catalog.form.client_email.help'
}}</F.HelperText>
{{#if @model.errors.client_email}}
<F.Error as |E|>
{{#each @model.errors.client_email as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
name='target_service_account_id'
@value={{@model.target_service_account_id}}
@isInvalid={{@model.errors.target_service_account_id}}
@isOptional={{true}}
readonly={{form.disabled}}
{{on 'input' (set-from-event @model 'target_service_account_id')}}
as |F|
>
<F.Label>{{t
'resources.host-catalog.form.target_service_account_id.label'
}}</F.Label>
<F.HelperText>{{t
'resources.host-catalog.form.target_service_account_id.help'
}}</F.HelperText>
{{#if @model.errors.target_service_account_id}}
<F.Error as |E|>
{{#each @model.errors.target_service_account_id as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::TextInput::Field
name='private_key_id'
@type='password'
@value={{@model.private_key_id}}
@isInvalid={{@model.errors.private_key_id}}
@isOptional={{true}}
readonly={{form.disabled}}
{{on 'input' (set-from-event @model 'private_key_id')}}
as |F|
>
<F.Label>{{t 'resources.host-catalog.form.private_key_id.label'}}</F.Label>
<F.HelperText>{{t
'resources.host-catalog.form.private_key_id.help'
}}</F.HelperText>
{{#if @model.errors.private_key_id}}
<F.Error as |E|>
{{#each @model.errors.private_key_id as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::TextInput::Field>
<Hds::Form::Textarea::Field
name='private_key'
@value={{@model.private_key}}
@isInvalid={{@model.errors.private_key}}
@isOptional={{true}}
readonly={{form.disabled}}
as |F|
>
<F.Label>{{t 'resources.host-catalog.form.private_key.label'}}</F.Label>
<F.HelperText>{{t
'resources.host-catalog.form.private_key.help'
}}</F.HelperText>
{{#if @model.errors.private_key}}
<F.Error as |E|>
{{#each @model.errors.private_key as |error|}}
<E.Message>{{error.message}}</E.Message>
{{/each}}
</F.Error>
{{/if}}
</Hds::Form::Textarea::Field>
<Hds::Form::Checkbox::Field
name='disable_credential_rotation'
checked={{@model.disable_credential_rotation}}
readonly={{form.disabled}}
{{on 'change' (fn @toggleDisableCredentialRotation @model)}}
as |F|
>
<F.Label>{{t 'form.disable_credential_rotation.label'}}</F.Label>
</Hds::Form::Checkbox::Field>
{{#if (can 'save model' @model)}}
<form.actions
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
{{/if}}
</Rose::Form>
@@ -12,4 +12,5 @@
changeCredentialType=@changeCredentialType
mapResourceTypeWithIcon=this.mapResourceTypeWithIcon
hostCatalogTypes=this.hostCatalogTypes
toggleDisableCredentialRotation=this.toggleDisableCredentialRotation
}}
@@ -4,6 +4,8 @@
*/
import Component from '@glimmer/component';
import { action } from '@ember/object';
import {
TYPES_HOST_CATALOG,
TYPES_HOST_CATALOG_PLUGIN,
@@ -24,4 +26,11 @@ export default class FormHostCatalogIndexComponent extends Component {
{},
);
}
// =actions
@action
toggleDisableCredentialRotation(model) {
model.disable_credential_rotation = !model.disable_credential_rotation;
}
}
@@ -4,8 +4,7 @@
*/
import Component from '@glimmer/component';
const types = ['aws', 'azure'];
import { TYPES_HOST_CATALOG_PLUGIN } from 'api/models/host-catalog';
export default class HostCatalogTypeComponent extends Component {
/**
* Display icons only for plugin compositeTypes.
@@ -13,7 +12,7 @@ export default class HostCatalogTypeComponent extends Component {
*/
get icon() {
return (
types.includes(this.args.model.compositeType) &&
TYPES_HOST_CATALOG_PLUGIN.includes(this.args.model.compositeType) &&
`${this.args.model.compositeType}-color`
);
}
@@ -45,6 +45,7 @@ module('Acceptance | host-catalogs | create', function (hooks) {
newStaticHostCatalog: null,
newAWSDynamicHostCatalog: null,
newAzureDynamicHostCatalog: null,
newGCPDynamicHostCatalog: null,
};
hooks.beforeEach(async function () {
@@ -81,6 +82,7 @@ module('Acceptance | host-catalogs | create', function (hooks) {
urls.newStaticHostCatalog = `${urls.newHostCatalog}?type=static`;
urls.newAWSDynamicHostCatalog = `${urls.newHostCatalog}?type=aws`;
urls.newAzureDynamicHostCatalog = `${urls.newHostCatalog}?type=azure`;
urls.newGCPDynamicHostCatalog = `${urls.newHostCatalog}?type=gcp`;
featuresService = this.owner.lookup('service:features');
// Generate resource counter
@@ -110,7 +112,7 @@ module('Acceptance | host-catalogs | create', function (hooks) {
assert.strictEqual(getHostCatalogCount(), count + 1);
});
test('Users can create new dynamic aws host catalogs with azure provider', async function (assert) {
test('Users can create new dynamic azure host catalogs with azure provider', async function (assert) {
const count = getHostCatalogCount();
await visit(urls.newAzureDynamicHostCatalog);
await fillIn(NAME_INPUT_SELECTOR, 'random string');
@@ -120,6 +122,18 @@ module('Acceptance | host-catalogs | create', function (hooks) {
assert.strictEqual(getHostCatalogCount(), count + 1);
});
test('Users can create new dynamic host catalogs with GCP provider ', async function (assert) {
const count = getHostCatalogCount();
await visit(urls.newGCPDynamicHostCatalog);
await fillIn(NAME_INPUT_SELECTOR, 'random string');
await fillIn(DESCRIPTION_INPUT_SELECTOR, 'random string');
await fillIn('[name="zone"]', 'random string');
await fillIn('[name="project_id"]', 'random string');
await fillIn('[name="client_email"]', 'random string');
await click(SAVE_BUTTON_SELECTOR);
assert.strictEqual(getHostCatalogCount(), count + 1);
});
test('Users can cancel creation of new static host catalogs', async function (assert) {
const count = getHostCatalogCount();
await visit(urls.newStaticHostCatalog);
@@ -138,6 +152,15 @@ module('Acceptance | host-catalogs | create', function (hooks) {
assert.strictEqual(getHostCatalogCount(), count);
});
test('Users can cancel creation of new dynamic host catalogs with GCP provider', async function (assert) {
const count = getHostCatalogCount();
await visit(urls.newGCPDynamicHostCatalog);
await fillIn(NAME_INPUT_SELECTOR, 'random string');
await click(CANCEL_BUTTON_SELECTOR);
assert.strictEqual(currentURL(), urls.hostCatalogs);
assert.strictEqual(getHostCatalogCount(), count);
});
test('Users can cancel creation of new dynamic host catalogs with Azure provider', async function (assert) {
const count = getHostCatalogCount();
await visit(urls.newAzureDynamicHostCatalog);