diff --git a/addons/api/addon/generated/models/host-catalog.js b/addons/api/addon/generated/models/host-catalog.js index 30ada833f..585b8031b 100644 --- a/addons/api/addon/generated/models/host-catalog.js +++ b/addons/api/addon/generated/models/host-catalog.js @@ -58,6 +58,7 @@ export default class GeneratedHostCatalogModel extends BaseModel { // AWS & Azure @attr('boolean', { + for: 'plugin', isNestedAttribute: true, description: '', }) @@ -66,12 +67,14 @@ export default class GeneratedHostCatalogModel extends BaseModel { // AWS specific @attr('string', { + for: 'plugin', description: 'An expression used to filter the workers that have network access to a service that is hosting the external object store.', }) worker_filter; @attr('string', { + for: 'plugin', isNestedAttribute: true, description: '', }) @@ -79,12 +82,14 @@ export default class GeneratedHostCatalogModel extends BaseModel { // AWS static credentials @attr('string', { + for: 'plugin', isNestedSecret: true, description: '', }) access_key_id; @attr('string', { + for: 'plugin', isNestedSecret: true, description: '', }) @@ -92,24 +97,28 @@ export default class GeneratedHostCatalogModel extends BaseModel { // AWS dynamic credentials @attr('string', { + for: 'plugin', isNestedAttribute: true, description: 'The role ARN to use.', }) role_arn; @attr('string', { + for: 'plugin', isNestedAttribute: true, description: 'The role external ID to use.', }) role_external_id; @attr('string', { + for: 'plugin', isNestedAttribute: true, description: 'The role session to use.', }) role_session_name; @attr('object-as-array', { + for: 'plugin', isNestedAttribute: true, description: 'The role tags to use.', }) @@ -118,30 +127,35 @@ export default class GeneratedHostCatalogModel extends BaseModel { // Azure specific @attr('string', { + for: 'plugin', isNestedAttribute: true, description: '', }) tenant_id; @attr('string', { + for: 'plugin', isNestedAttribute: true, description: '', }) client_id; @attr('string', { + for: 'plugin', isNestedAttribute: true, description: '', }) subscription_id; @attr('string', { + for: 'plugin', isNestedSecret: true, description: '', }) secret_id; @attr('string', { + for: 'plugin', isNestedSecret: true, description: '', }) diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js index 4a556035a..e8b47a2ac 100644 --- a/addons/api/addon/models/host-catalog.js +++ b/addons/api/addon/models/host-catalog.js @@ -27,6 +27,13 @@ export const TYPES_CREDENTIALS = Object.freeze([ TYPE_CREDENTIAL_DYNAMIC, ]); +const DYNAMIC_CREDENTIAL_FIELDS = [ + 'role_arn', + 'role_external_id', + 'role_session_name', + 'role_tags', +]; + export default class HostCatalogModel extends GeneratedHostCatalogModel { // =attributes @@ -53,7 +60,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel { */ get credentialType() { if (!this.#credentialType) { - if (this.role_arn) { + if (DYNAMIC_CREDENTIAL_FIELDS.some((field) => this[field])) { this.#credentialType = TYPE_CREDENTIAL_DYNAMIC; } else { this.#credentialType = TYPE_CREDENTIAL_STATIC; diff --git a/addons/api/addon/serializers/host-catalog.js b/addons/api/addon/serializers/host-catalog.js index b83a053f3..ff885c8af 100644 --- a/addons/api/addon/serializers/host-catalog.js +++ b/addons/api/addon/serializers/host-catalog.js @@ -68,15 +68,29 @@ export default class HostCatalogSerializer extends ApplicationSerializer { const value = super.serializeAttribute(...arguments); const { compositeType, credentialType, isPlugin } = snapshot.record; const { options } = attribute; - const fields = compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE ? azureFields : AWSfieldsWithCredentialType[credentialType]; - // Delete any nested attribute fields that don't belong to the record type + // Delete any nested attribute fields that don't belong to the record's compositeType if (isPlugin && options.isNestedAttribute && json.attributes) { - if (!fields.includes(key)) { + // If compositeType is AWS, delete azure fields from the json, and set the rest that don't belong to respective compositeType to null + if (compositeType === TYPE_HOST_CATALOG_PLUGIN_AWS) { + if (!AWSfieldsWithCredentialType[credentialType].includes(key)) { + if (azureFields.includes(key)) { + delete json.attributes[key]; + } else { + json.attributes[key] = null; + } + } + } + + // If compositeType is Azure, delete AWS fields from the json + if ( + compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE && + !azureFields.includes(key) + ) { delete json.attributes[key]; } } @@ -98,7 +112,7 @@ export default class HostCatalogSerializer extends ApplicationSerializer { // Delete unnecessary fields for static host-catalog delete serialized.attributes; delete serialized.secrets; - delete serialized.worker_filter; + return serialized; } } diff --git a/addons/api/tests/unit/serializers/host-catalog-test.js b/addons/api/tests/unit/serializers/host-catalog-test.js index b8b1b7530..45b6ef55f 100644 --- a/addons/api/tests/unit/serializers/host-catalog-test.js +++ b/addons/api/tests/unit/serializers/host-catalog-test.js @@ -45,14 +45,9 @@ module('Unit | Serializer | host catalog', function (hooks) { description: 'this is a Aws plugin host-catalog', disable_credential_rotation: true, worker_filter: 'workerfilter', - // these are AWS fields and should be included region: 'west', access_key_id: 'foobars', secret_access_key: 'testing', - // these are Azure fields and should be excluded - tenant_id: 'a1b2c3', - client_id: 'a1b2c3', - subscription_id: 'a1b2c3', secret_id: 'a1b2c3', secret_value: 'a1b2c3', role_arn: 'test', @@ -71,6 +66,10 @@ module('Unit | Serializer | host catalog', function (hooks) { attributes: { disable_credential_rotation: true, region: 'west', + role_arn: null, + role_external_id: null, + role_session_name: null, + role_tags: null, }, secrets: { access_key_id: 'foobars', @@ -152,6 +151,10 @@ module('Unit | Serializer | host catalog', function (hooks) { attributes: { disable_credential_rotation: true, region: 'spain', + role_arn: null, + role_external_id: null, + role_session_name: null, + role_tags: null, }, secrets: { access_key_id: 'foobars', @@ -225,6 +228,10 @@ module('Unit | Serializer | host catalog', function (hooks) { attributes: { disable_credential_rotation: false, region: 'andorra', + role_arn: null, + role_external_id: null, + role_session_name: null, + role_tags: null, }, }; assert.deepEqual(record.serialize(), expectedResult); 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 1776cf8d0..f5c304e60 100644 --- a/ui/admin/app/components/form/host-catalog/aws/index.hbs +++ b/ui/admin/app/components/form/host-catalog/aws/index.hbs @@ -144,74 +144,51 @@ - {{#if @model.isNew}} - - - {{t 'resources.host-catalog.types.credential.label'}} - - - {{t 'resources.host-catalog.types.credential.help'}} - + + + {{t 'resources.host-catalog.types.credential.label'}} + + + {{t 'resources.host-catalog.types.credential.help'}} + - {{#each this.credentials as |credentialType|}} - - - {{t - (concat - 'resources.host-catalog.types.credential.' - credentialType - '.label' - ) - }} - - - {{t - (concat - 'resources.host-catalog.types.credential.' - credentialType - '.help' - ) - }} - - - {{/each}} - - {{else}} - - - {{t 'resources.host-catalog.types.credential.label'}} - - - + + {{t (concat - 'resources.host-catalog.types.credential.' - this.selectedCredentialType - '.edit' + 'resources.host-catalog.types.credential.' credentialType '.label' ) }} - /> - - - {{/if}} + + + {{t + (concat + 'resources.host-catalog.types.credential.' credentialType '.help' + ) + }} + + + {{/each}} + {{#if this.showDynamicCredentials}} {{! role_arn }} \ No newline at end of file diff --git a/ui/admin/tests/acceptance/host-catalogs/update-test.js b/ui/admin/tests/acceptance/host-catalogs/update-test.js index 7e87dbca2..b5840313a 100644 --- a/ui/admin/tests/acceptance/host-catalogs/update-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/update-test.js @@ -4,7 +4,7 @@ */ import { module, test } from 'qunit'; -import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; +import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; @@ -15,6 +15,10 @@ import { //invalidateSession, } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { + TYPE_HOST_CATALOG_DYNAMIC, + TYPE_HOST_CATALOG_PLUGIN_AWS, +} from 'api/models/host-catalog'; module('Acceptance | host-catalogs | update', function (hooks) { setupApplicationTest(hooks); @@ -35,6 +39,7 @@ module('Acceptance | host-catalogs | update', function (hooks) { projectScope: null, hostCatalogs: null, hostCatalog: null, + AWSHostCatalogWithStaticCredential: null, }; const NAME_INPUT_SELECTOR = '[name="name"]'; @@ -44,6 +49,8 @@ module('Acceptance | host-catalogs | update', function (hooks) { const MODAL_DISCARD_BUTTON_SELECTOR = '.rose-dialog-footer button:first-child'; const MODAL_CANCEL_BUTTON_SELECTOR = '.rose-dialog-footer button:last-child'; + const CREDENTIAL_TYPE_SELECTOR = + '.dynamic-credential-selection input:checked'; hooks.beforeEach(function () { // Generate resources @@ -59,15 +66,43 @@ module('Acceptance | host-catalogs | update', function (hooks) { instances.hostCatalog = this.server.create('host-catalog', { scope: instances.scopes.project, }); + instances.AWSHostCatalogWithStaticCredential = this.server.create( + 'host-catalog', + { + scope: instances.scopes.project, + type: TYPE_HOST_CATALOG_DYNAMIC, + plugin: { name: TYPE_HOST_CATALOG_PLUGIN_AWS }, + credentialType: 'static-credential', + }, + ); // Generate route URLs for resources urls.globalScope = `/scopes/global/scopes`; urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.hostCatalogs = `${urls.projectScope}/host-catalogs`; urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; + urls.AWSHostCatalogWithStaticCredential = `${urls.hostCatalogs}/${instances.AWSHostCatalogWithStaticCredential.id}`; authenticateSession({}); }); + test('can update static AWS credentials to Dynamic AWS credentials', async function (assert) { + await visit(urls.AWSHostCatalogWithStaticCredential); + await click(EDIT_BUTTON_SELECTOR, 'Activate edit mode'); + assert.strictEqual( + find(CREDENTIAL_TYPE_SELECTOR).value, + 'static-credential', + ); + await click('[value=dynamic-credential]', 'Dynamic Credential'); + await fillIn('[name=role_arn]', 'arn:aws:iam'); + + await click(SAVE_BUTTON_SELECTOR); + // Check if the host catalog is updated + assert.strictEqual( + find(CREDENTIAL_TYPE_SELECTOR).value, + 'dynamic-credential', + ); + }); + test('can save changes to existing host catalog', async function (assert) { assert.notEqual(instances.hostCatalog.name, 'random string'); await visit(urls.hostCatalog);