From dfa62dd04be5b4227e61563c4884d9f6b8342fe4 Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Wed, 8 Jan 2025 11:49:01 -0800 Subject: [PATCH] =?UTF-8?q?chore:=20=F0=9F=A4=96=20gcp=20plugin=20creation?= =?UTF-8?q?=20in=20the=20UI=20(#2645)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gcp plugin creation and mirage changes --- addons/api/addon/models/host-catalog.js | 6 +- addons/api/addon/models/host-set.js | 19 +- addons/api/mirage/factories/host-catalog.js | 50 +++- addons/core/translations/resources/en-us.yaml | 19 ++ .../form/host-catalog/aws/index.hbs | 2 +- .../components/form/host-catalog/aws/index.js | 7 - .../form/host-catalog/gcp/index.hbs | 261 ++++++++++++++++++ .../components/form/host-catalog/index.hbs | 1 + .../app/components/form/host-catalog/index.js | 9 + .../host-catalog-type-badge/index.js | 5 +- .../acceptance/host-catalogs/create-test.js | 25 +- 11 files changed, 376 insertions(+), 28 deletions(-) create mode 100644 ui/admin/app/components/form/host-catalog/gcp/index.hbs diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js index 78e3ec6d6..f220fd56e 100644 --- a/addons/api/addon/models/host-catalog.js +++ b/addons/api/addon/models/host-catalog.js @@ -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; } /** diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index c5895021d..9239dc811 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -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; } /** diff --git a/addons/api/mirage/factories/host-catalog.js b/addons/api/mirage/factories/host-catalog.js index 1d394004d..d35cc8b07 100644 --- a/addons/api/mirage/factories/host-catalog.js +++ b/addons/api/mirage/factories/host-catalog.js @@ -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({ diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index dff49b81e..38b7147ba 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -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 diff --git a/ui/admin/app/components/form/host-catalog/aws/index.hbs b/ui/admin/app/components/form/host-catalog/aws/index.hbs index bc397c645..05f500d2d 100644 --- a/ui/admin/app/components/form/host-catalog/aws/index.hbs +++ b/ui/admin/app/components/form/host-catalog/aws/index.hbs @@ -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| > diff --git a/ui/admin/app/components/form/host-catalog/aws/index.js b/ui/admin/app/components/form/host-catalog/aws/index.js index 0c60c07a6..1385ea0ee 100644 --- a/ui/admin/app/components/form/host-catalog/aws/index.js +++ b/ui/admin/app/components/form/host-catalog/aws/index.js @@ -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} diff --git a/ui/admin/app/components/form/host-catalog/gcp/index.hbs b/ui/admin/app/components/form/host-catalog/gcp/index.hbs new file mode 100644 index 000000000..962843f6f --- /dev/null +++ b/ui/admin/app/components/form/host-catalog/gcp/index.hbs @@ -0,0 +1,261 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + {{t 'form.name.label'}} + {{t 'form.name.help'}} + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'form.description.label'}} + {{t 'form.description.help'}} + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if @model.isNew}} + + {{t 'form.type.label'}} + {{#each @hostCatalogTypes as |hostCatalogType|}} + + {{t + (concat 'resources.host-catalog.types.' hostCatalogType) + }} + {{t + (concat 'resources.host-catalog.help.' hostCatalogType) + }} + + {{/each}} + + + {{t 'titles.provider'}} + {{t 'titles.choose-a-provider'}} + {{concat + (t 'descriptions.choose-a-provider') + }} + + {{#each-in @mapResourceTypeWithIcon as |pluginType icon|}} + + {{t + (concat 'resources.host-catalog.types.' pluginType) + }} + + + {{/each-in}} + + {{else}} + + {{t 'titles.provider'}} + {{t 'descriptions.provider'}} + + + {{/if}} + + + {{t 'resources.host-catalog.form.project_id.label'}} + {{t + 'resources.host-catalog.form.project_id.help' + }} + {{#if @model.errors.project_id}} + + {{#each @model.errors.project_id as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.host-catalog.form.zone.label'}} + {{t 'resources.host-catalog.form.zone.help'}} + {{#if @model.errors.zone}} + + {{#each @model.errors.zone as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.host-catalog.form.client_email.label'}} + {{t + 'resources.host-catalog.form.client_email.help' + }} + {{#if @model.errors.client_email}} + + {{#each @model.errors.client_email as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t + 'resources.host-catalog.form.target_service_account_id.label' + }} + {{t + 'resources.host-catalog.form.target_service_account_id.help' + }} + {{#if @model.errors.target_service_account_id}} + + {{#each @model.errors.target_service_account_id as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.host-catalog.form.private_key_id.label'}} + {{t + 'resources.host-catalog.form.private_key_id.help' + }} + {{#if @model.errors.private_key_id}} + + {{#each @model.errors.private_key_id as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.host-catalog.form.private_key.label'}} + {{t + 'resources.host-catalog.form.private_key.help' + }} + {{#if @model.errors.private_key}} + + {{#each @model.errors.private_key as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'form.disable_credential_rotation.label'}} + + + {{#if (can 'save model' @model)}} + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/components/form/host-catalog/index.hbs b/ui/admin/app/components/form/host-catalog/index.hbs index 2bd87974e..f65e9fdae 100644 --- a/ui/admin/app/components/form/host-catalog/index.hbs +++ b/ui/admin/app/components/form/host-catalog/index.hbs @@ -12,4 +12,5 @@ changeCredentialType=@changeCredentialType mapResourceTypeWithIcon=this.mapResourceTypeWithIcon hostCatalogTypes=this.hostCatalogTypes + toggleDisableCredentialRotation=this.toggleDisableCredentialRotation }} \ No newline at end of file diff --git a/ui/admin/app/components/form/host-catalog/index.js b/ui/admin/app/components/form/host-catalog/index.js index 5d8c45a9b..b7a7232db 100644 --- a/ui/admin/app/components/form/host-catalog/index.js +++ b/ui/admin/app/components/form/host-catalog/index.js @@ -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; + } } diff --git a/ui/admin/app/components/host-catalog-type-badge/index.js b/ui/admin/app/components/host-catalog-type-badge/index.js index 250518327..988e81c93 100644 --- a/ui/admin/app/components/host-catalog-type-badge/index.js +++ b/ui/admin/app/components/host-catalog-type-badge/index.js @@ -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` ); } diff --git a/ui/admin/tests/acceptance/host-catalogs/create-test.js b/ui/admin/tests/acceptance/host-catalogs/create-test.js index 71fbd40db..baaa3afe9 100644 --- a/ui/admin/tests/acceptance/host-catalogs/create-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/create-test.js @@ -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);