Chore: refactor host catalog model and serializer to accommodate GCP plugin (#2627)

* chore: 🤖 GCP plugin support
This commit is contained in:
Dharini Jeeva authored and Dharini committed 2025-01-10 14:18:48 -08:00
1 parent 31f1ed8dd4
commit 520020f2c6
16 files changed
+399 -179

No files matched your search

+128 -15
View File
@@ -55,10 +55,13 @@ export default class GeneratedHostCatalogModel extends BaseModel {
})
plugin;
// AWS & Azure
// AWS, Azure & GCP
@attr('boolean', {
for: 'plugin',
for: {
type: 'plugin',
name: ['aws', 'azure', 'gcp'],
},
isNestedAttribute: true,
description: '',
})
@@ -67,14 +70,20 @@ export default class GeneratedHostCatalogModel extends BaseModel {
// AWS specific
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'aws',
},
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',
for: {
type: 'plugin',
name: 'aws',
},
isNestedAttribute: true,
description: '',
})
@@ -82,14 +91,22 @@ export default class GeneratedHostCatalogModel extends BaseModel {
// AWS static credentials
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'aws',
credentialType: 'static-credential',
},
isNestedSecret: true,
description: '',
})
access_key_id;
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'aws',
credentialType: 'static-credential',
},
isNestedSecret: true,
description: '',
})
@@ -97,28 +114,44 @@ export default class GeneratedHostCatalogModel extends BaseModel {
// AWS dynamic credentials
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'aws',
credentialType: 'dynamic-credential',
},
isNestedAttribute: true,
description: 'The role ARN to use.',
})
role_arn;
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'aws',
credentialType: 'dynamic-credential',
},
isNestedAttribute: true,
description: 'The role external ID to use.',
})
role_external_id;
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'aws',
credentialType: 'dynamic-credential',
},
isNestedAttribute: true,
description: 'The role session to use.',
})
role_session_name;
@attr('object-as-array', {
for: 'plugin',
for: {
type: 'plugin',
name: 'aws',
credentialType: 'dynamic-credential',
},
isNestedAttribute: true,
description: 'The role tags to use.',
})
@@ -127,37 +160,117 @@ export default class GeneratedHostCatalogModel extends BaseModel {
// Azure specific
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'azure',
},
isNestedAttribute: true,
description: '',
})
tenant_id;
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'azure',
},
isNestedAttribute: true,
description: '',
})
client_id;
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'azure',
},
isNestedAttribute: true,
description: '',
})
subscription_id;
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'azure',
},
isNestedSecret: true,
description: '',
})
secret_id;
@attr('string', {
for: 'plugin',
for: {
type: 'plugin',
name: 'azure',
},
isNestedSecret: true,
description: '',
})
secret_value;
// GCP specific
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
isNestedAttribute: true,
description: 'The project ID associated with the service account.',
})
project_id;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
isNestedAttribute: true,
description: 'The deployment area within a region.',
})
zone;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
isNestedAttribute: true,
description:
'The email address associated with the service account. The email address used to uniquely identify the service account. It is required for authentication and authorization.',
})
client_email;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
isNestedAttribute: true,
description:
'The unique identifier for the service account that will be impersonated.',
})
target_service_account_id;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
description:
'The ID of the private key used to sign the JWT and authentication.',
isNestedSecret: true,
})
private_key_id;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
description:
'The private key used to sign the JWT and obtain a OAuth 2.0 access token.',
isNestedSecret: true,
})
private_key;
}
@@ -83,7 +83,7 @@ export default class GeneratedHostSetModel extends BaseModel {
})
preferred_endpoints;
// AWS specific
// AWS & GCP specific
@attr('string-array', {
isNestedAttribute: true,
emptyArrayIfMissing: true,
+12 -1
View File
@@ -14,9 +14,11 @@ export const TYPES_HOST_CATALOG = Object.freeze([
export const TYPE_HOST_CATALOG_PLUGIN_AWS = 'aws';
export const TYPE_HOST_CATALOG_PLUGIN_AZURE = 'azure';
export const TYPE_HOST_CATALOG_PLUGIN_GCP = 'gcp';
export const TYPES_HOST_CATALOG_PLUGIN = [
TYPE_HOST_CATALOG_PLUGIN_AWS,
TYPE_HOST_CATALOG_PLUGIN_AZURE,
TYPE_HOST_CATALOG_PLUGIN_GCP,
];
export const TYPE_CREDENTIAL_STATIC = 'static-credential';
@@ -27,12 +29,13 @@ export const TYPES_CREDENTIALS = Object.freeze([
TYPE_CREDENTIAL_DYNAMIC,
]);
const DYNAMIC_CREDENTIAL_FIELDS = [
export const DYNAMIC_CREDENTIAL_FIELDS = [
'role_arn',
'role_external_id',
'role_session_name',
'role_tags',
];
export const STATIC_CREDENTIAL_FIELDS = ['access_key_id, secret_access_key'];
export default class HostCatalogModel extends GeneratedHostCatalogModel {
// =attributes
@@ -103,6 +106,14 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel {
return this.compositeType === 'azure';
}
/**
* True if host catalog plugin type is GCP.
* @type {boolean}
*/
get isGCP() {
return this.compositeType === 'gcp';
}
/**
* If host catalog is a plugin return `plugin.name`,
* otherwise return the host catalog type.
+33 -12
View File
@@ -6,6 +6,7 @@
import RESTSerializer from '@ember-data/serializer/rest';
import { underscore } from '@ember/string';
import { get } from '@ember/object';
import { typeOf } from '@ember/utils';
/**
* Manages serialization/normalization of data to/from the API.
@@ -42,7 +43,7 @@ export default class ApplicationSerializer extends RESTSerializer {
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
const { type: recordType } = snapshot.record;
const { type: recordType, compositeType } = snapshot.record;
let value = super.serializeAttribute(...arguments);
// Remove secret attributes that are null or empty
if (options.isSecret) {
@@ -74,18 +75,38 @@ export default class ApplicationSerializer extends RESTSerializer {
delete json[key];
}
// If an attribute has a `for` option, it must match the
// record's `type`, else the attribute is excluded
// record's `type`, otherwise the attribute is excluded
// from serialization.
//note: This doesn't handle secrets yet, we can add support for them if needed.
if (
options?.for &&
options.for !== recordType &&
!options.for.includes(recordType)
) {
if (options.isNestedAttribute) {
delete json?.attributes?.[key];
} else {
delete json[key];
if (options?.for) {
// Check the typeof the `for` option to determine if it is a plugin or not
const isPlugin = options.for?.type === 'plugin';
// Helper function to handle deleting attribute and secret keys
const deleteKey = (json) => {
if (options.isNestedAttribute) {
delete json?.attributes?.[key];
} else if (options.isNestedSecret && json?.secrets?.[key]) {
delete json?.secrets?.[key];
if (Object.keys(json.secrets).length === 0) {
delete json.secrets;
}
} else {
delete json[key];
}
};
// For plugins, the `for` option is an object with a `name` key
// For non-plugins, the `for` option can be string or an array
if (
(isPlugin && !options.for?.name.includes(compositeType)) ||
(!isPlugin &&
!options.for?.includes(recordType) &&
typeOf(options.for) === 'array') ||
(!isPlugin &&
options.for !== recordType &&
typeOf(options.for) === 'string')
) {
deleteKey(json);
}
}
return value;
+20 -90
View File
@@ -4,115 +4,45 @@
*/
import ApplicationSerializer from './application';
import {
TYPE_HOST_CATALOG_PLUGIN_AWS,
TYPE_HOST_CATALOG_PLUGIN_AZURE,
TYPE_CREDENTIAL_DYNAMIC,
} from '../models/host-catalog';
// These fields are Azure specific
const azureFields = [
'disable_credential_rotation',
'tenant_id',
'client_id',
'subscription_id',
'secret_id',
'secret_value',
];
// These fields are AWS specific, AWS provider now has two credentialTypes: static and dynamic [not to be confused with static and dynamic host-catalogs]
const AWSfieldsWithCredentialType = {
'static-credential': [
'access_key_id',
'secret_access_key',
'region',
'disable_credential_rotation',
'worker_filter',
],
'dynamic-credential': [
'role_arn',
'role_external_id',
'role_session_name',
'role_tags',
'region',
'disable_credential_rotation',
'worker_filter',
],
};
import { TYPE_CREDENTIAL_DYNAMIC } from '../models/host-catalog';
export default class HostCatalogSerializer extends ApplicationSerializer {
serialize(snapshot) {
const { compositeType, credentialType } = snapshot.record;
const { credentialType } = snapshot.record;
const serialized = super.serialize(...arguments);
// This is a hack to remove the worker_filter field from the serialized data if the compositeType is not AWS
// Ideally we should refactor the model to include the compositeType using `for` attribute
if (compositeType !== TYPE_HOST_CATALOG_PLUGIN_AWS) {
delete serialized.worker_filter;
}
// By default, disable credential rotation for dynamic credentials
if (credentialType === TYPE_CREDENTIAL_DYNAMIC) {
serialized.attributes.disable_credential_rotation = true;
}
switch (compositeType) {
case 'static':
return this.serializeStatic(...arguments);
default:
return serialized;
}
return serialized;
}
serializeAttribute(snapshot, json, key, attribute) {
const value = super.serializeAttribute(...arguments);
const { compositeType, credentialType, isPlugin } = snapshot.record;
const { compositeType, credentialType } = snapshot.record;
const { options } = attribute;
const fields =
compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE
? azureFields
: AWSfieldsWithCredentialType[credentialType];
const isSamePluginName = options?.for?.name?.includes(compositeType);
// Delete any nested attribute fields that don't belong to the record's compositeType
if (isPlugin && options.isNestedAttribute && json.attributes) {
// 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];
}
}
// Delete any secret fields that don't belong to the record type
if (isPlugin && options.isNestedSecret && json.secrets) {
if (!fields.includes(key)) {
// Remove attributes that are not applicable to the current credential type for the same plugin
// For example, 'aws' dynamic plugin has two credential types: 'static' and 'dynamic'
// API expects the fields to be 'null' for the fields that are not from the current credential type (that's why we are not using the application serializer for this, where we delete attrs)
// Empty secrets should be removed from the JSON
if (
isSamePluginName &&
options.for.credentialType &&
options.for.credentialType !== credentialType
) {
if (options.isNestedAttribute) {
json.attributes[key] = null;
} else if (options.isNestedSecret && json.secrets[key]) {
delete json.secrets[key];
if (json['secrets'] && Object.keys(json.secrets).length === 0) {
if (Object.keys(json.secrets).length === 0) {
delete json.secrets;
}
} else {
delete json[key];
}
}
return value;
}
serializeStatic() {
const serialized = super.serialize(...arguments);
// Delete unnecessary fields for static host-catalog
delete serialized.attributes;
delete serialized.secrets;
return serialized;
}
}
@@ -81,6 +81,19 @@ module('Unit | Model | host catalog', function (hooks) {
assert.false(modelRandom.isAzure);
});
test('it has isGCP property and return the expected values', async function (assert) {
const store = this.owner.lookup('service:store');
const modelGCP = store.createRecord('host-catalog', {
type: 'plugin',
plugin: { name: 'gcp' },
});
const modelRandom = store.createRecord('host-catalog', {
plugin: { name: 'random' },
});
assert.true(modelGCP.isGCP);
assert.false(modelRandom.isGCP);
});
test('get compositeType returns expected values', async function (assert) {
const store = this.owner.lookup('service:store');
const modelA = store.createRecord('host-catalog', {
@@ -94,8 +107,13 @@ module('Unit | Model | host catalog', function (hooks) {
type: 'plugin',
plugin: { name: 'no-such-type' },
});
const modelD = store.createRecord('host-catalog', {
type: 'plugin',
plugin: { name: 'gcp' },
});
assert.strictEqual(modelA.compositeType, 'static');
assert.strictEqual(modelB.compositeType, 'aws');
assert.strictEqual(modelD.compositeType, 'gcp');
assert.strictEqual(modelC.compositeType, 'unknown');
});
@@ -107,8 +125,12 @@ module('Unit | Model | host catalog', function (hooks) {
const modelStatic = store.createRecord('host-catalog', {
compositeType: 'static',
});
const modelGCP = store.createRecord('host-catalog', {
compositeType: 'gcp',
});
assert.strictEqual(modelPlugin.type, 'plugin');
assert.strictEqual(modelPlugin.plugin.name, 'aws');
assert.strictEqual(modelStatic.type, 'static');
assert.strictEqual(modelGCP.plugin.name, 'gcp');
});
});
@@ -6,6 +6,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method';
import { TYPE_HOST_CATALOG_PLUGIN_GCP } from 'api/models/host-catalog';
module('Unit | Serializer | application', function (hooks) {
setupTest(hooks);
@@ -133,7 +134,7 @@ module('Unit | Serializer | application', function (hooks) {
});
});
test('it serializes attributes with `for` option containing array or string correctly', function (assert) {
test('it serializes attributes with `for` option containing array or string correctly for non plugins', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('auth-method', {
type: TYPE_AUTH_METHOD_OIDC, //has both string and array `for` option
@@ -184,6 +185,53 @@ module('Unit | Serializer | application', function (hooks) {
});
});
test('it serializes attributes with `for` option containing array or string correctly for plugin types', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('host-catalog', {
compositeType: TYPE_HOST_CATALOG_PLUGIN_GCP,
name: 'GCP plugin',
description: 'this is a GCP plugin host-catalog',
disable_credential_rotation: true,
worker_filter: 'workerfilter',
access_key_id: 'foobars',
secret_access_key: 'testing',
secret_id: 'a1b2c3',
secret_value: 'a1b2c3',
role_arn: 'test',
role_external_id: 'test',
role_session_name: 'test',
role_tags: [
{ key: 'Project', value: 'Automation' },
{ key: 'foo', value: 'bar' },
],
project_id: 'project',
zone: 'zone',
client_email: 'email',
target_service_account_id: 'service-account',
private_key_id: 'key-id',
private_key: 'key',
});
let serializedRecord = record.serialize();
const expectedResult = {
name: 'GCP plugin',
description: 'this is a GCP plugin host-catalog',
type: 'plugin',
attributes: {
disable_credential_rotation: true,
project_id: 'project',
zone: 'zone',
target_service_account_id: 'service-account',
client_email: 'email',
},
secrets: {
private_key_id: 'key-id',
private_key: 'key',
},
};
assert.deepEqual(serializedRecord, expectedResult);
});
test('it normalizes array records from an `items` root key', function (assert) {
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('user');
@@ -9,6 +9,7 @@ import {
TYPE_HOST_CATALOG_PLUGIN_AWS,
TYPE_CREDENTIAL_STATIC,
TYPE_CREDENTIAL_DYNAMIC,
TYPE_HOST_CATALOG_PLUGIN_GCP,
} from 'api/models/host-catalog';
module('Unit | Serializer | host catalog', function (hooks) {
@@ -32,6 +33,52 @@ module('Unit | Serializer | host catalog', function (hooks) {
name: 'static',
description: 'this is a static host-catalog',
type: 'static',
attributes: {},
};
assert.deepEqual(record.serialize(), expectedResult);
});
test('it serializes a new gcp plugin as expected', async function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {
compositeType: TYPE_HOST_CATALOG_PLUGIN_GCP,
name: 'GCP',
description: 'this is a GCP plugin host-catalog',
disable_credential_rotation: true,
worker_filter: 'workerfilter',
access_key_id: 'foobars',
secret_access_key: 'testing',
secret_id: 'a1b2c3',
secret_value: 'a1b2c3',
role_arn: 'test',
role_external_id: 'test',
role_session_name: 'test',
role_tags: [
{ key: 'Project', value: 'Automation' },
{ key: 'foo', value: 'bar' },
],
project_id: 'project',
zone: 'zone',
client_email: 'email',
target_service_account_id: 'service-account',
private_key_id: 'key-id',
private_key: 'key',
});
const expectedResult = {
name: 'GCP',
description: 'this is a GCP plugin host-catalog',
type: 'plugin',
attributes: {
disable_credential_rotation: true,
project_id: 'project',
zone: 'zone',
target_service_account_id: 'service-account',
client_email: 'email',
},
secrets: {
private_key_id: 'key-id',
private_key: 'key',
},
};
assert.deepEqual(record.serialize(), expectedResult);
});
@@ -79,7 +126,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
assert.deepEqual(record.serialize(), expectedResult);
});
test('it serializes a new aws dynamic plugin as expected', async function (assert) {
test('it serializes a new aws dynamic credential type plugin as expected', async function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {
compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS,
@@ -88,7 +135,7 @@ 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
// these are static AWS fields and should be excluded
region: 'west',
access_key_id: 'foobars',
secret_access_key: 'testing',
@@ -123,7 +170,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
assert.deepEqual(record.serialize(), expectedResult);
});
test('it serializes a new aws static plugin as expected, ignoring azure fields', async function (assert) {
test('it serializes a new aws static credential type plugin as expected, ignoring azure and GCP fields', async function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {
compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS,
@@ -142,6 +189,8 @@ module('Unit | Serializer | host catalog', function (hooks) {
subscription_id: 'a1b2c3',
secret_id: 'a1b2c3',
secret_value: 'a1b2c3',
project_id: 'project',
zone: 'zone',
});
const expectedResult = {
name: 'AWS',
@@ -164,7 +213,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
assert.deepEqual(record.serialize(), expectedResult);
});
test('it serializes a new azure record as expected, ignoring aws fields', async function (assert) {
test('it serializes a new azure record as expected, ignoring aws and GCP fields', async function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {
compositeType: 'azure',
@@ -181,6 +230,8 @@ module('Unit | Serializer | host catalog', function (hooks) {
subscription_id: 'a1b2c3',
secret_id: 'a1b2c3',
secret_value: 'a1b2c3',
project_id: 'project',
zone: 'zone',
});
const expectedResult = {
name: 'Azure',
@@ -271,7 +322,7 @@ module('Unit | Serializer | host catalog', function (hooks) {
assert.deepEqual(record.serialize(), expectedResult);
});
test('it normalizes an static host catalog as expected', function (assert) {
test('it normalizes a static host catalog as expected', function (assert) {
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host-catalog');
const hostCatalog = store.createRecord('host-catalog').constructor;
@@ -300,6 +351,8 @@ module('Unit | Serializer | host catalog', function (hooks) {
secret_access_key: null,
secret_id: null,
secret_value: null,
private_key_id: null,
private_key: null,
},
relationships: {},
},
@@ -358,6 +411,8 @@ module('Unit | Serializer | host catalog', function (hooks) {
secret_access_key: 'zq{2:IVc8@W^',
secret_id: null,
secret_value: null,
private_key_id: null,
private_key: null,
},
relationships: {},
},
@@ -418,6 +473,70 @@ module('Unit | Serializer | host catalog', function (hooks) {
subscription_id: 'subscription',
access_key_id: null,
secret_access_key: null,
private_key_id: null,
private_key: null,
},
relationships: {},
},
included: [],
});
});
test('it normalizes a GCP plugin type host catalog as expected', function (assert) {
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host-catalog');
const hostCatalog = store.createRecord('host-catalog').constructor;
const payload = {
id: '1',
name: 'Host catalog test',
description: 'Test description',
authorized_actions: ['no-op', 'read', 'update', 'delete'],
type: 'plugin',
plugin: {
id: 'plugin-id-7',
name: 'gcp',
description: 'gcp host catalog',
},
attributes: {
disable_credential_rotation: true,
project_id: 'project_id',
zone: 'zone',
target_service_account_id: 'target_service_account_id',
},
secrets: {
private_key_id: '0xF3B0a6f8',
private_key: 'zq{2:IVc8@W^',
},
};
const normalized = serializer.normalizeSingleResponse(
store,
hostCatalog,
payload,
);
assert.deepEqual(normalized, {
data: {
id: '1',
type: 'host-catalog',
attributes: {
name: 'Host catalog test',
type: 'plugin',
description: 'Test description',
authorized_actions: ['no-op', 'read', 'update', 'delete'],
plugin: {
id: 'plugin-id-7',
name: 'gcp',
description: 'gcp host catalog',
},
disable_credential_rotation: true,
private_key_id: '0xF3B0a6f8',
private_key: 'zq{2:IVc8@W^',
project_id: 'project_id',
zone: 'zone',
target_service_account_id: 'target_service_account_id',
access_key_id: null,
secret_access_key: null,
secret_id: null,
secret_value: null,
},
relationships: {},
},
@@ -66,7 +66,7 @@
<G.Legend>
{{t 'form.type.label'}}
</G.Legend>
{{#each this.hostCatalogTypes as |hostCatalogType|}}
{{#each @hostCatalogTypes as |hostCatalogType|}}
<G.RadioCard
@value={{hostCatalogType}}
@maxWidth='20rem'
@@ -98,7 +98,7 @@
{{t 'descriptions.choose-a-provider'}}
</G.HelperText>
{{#each-in this.mapResourceTypeWithIcon as |pluginType icon|}}
{{#each-in @mapResourceTypeWithIcon as |pluginType icon|}}
<G.RadioCard
@value={{pluginType}}
@maxWidth='20rem'
@@ -6,20 +6,12 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import {
TYPES_HOST_CATALOG,
TYPES_HOST_CATALOG_PLUGIN,
TYPES_CREDENTIALS,
TYPE_CREDENTIAL_DYNAMIC,
} from 'api/models/host-catalog';
import { tracked } from '@glimmer/tracking';
//Note: this is a temporary solution till we have resource type helper in place
const icons = ['aws-color', 'azure-color'];
export default class FormHostCatalogAwsComponent extends Component {
// =properties
hostCatalogTypes = TYPES_HOST_CATALOG;
// =attributes
@tracked selectedCredentialType = this.args.model.credentialType;
@@ -34,16 +26,6 @@ export default class FormHostCatalogAwsComponent extends Component {
return TYPES_CREDENTIALS;
}
/**
* maps resource type with icon
* @type {object}
*/
get mapResourceTypeWithIcon() {
return TYPES_HOST_CATALOG_PLUGIN.reduce(
(obj, plugin, i) => ({ ...obj, [plugin]: icons[i] }),
{},
);
}
// =actions
@action
@@ -52,7 +52,7 @@
{{#if @model.isNew}}
<Hds::Form::RadioCard::Group @name={{t 'form.type.label'}} as |G|>
<G.Legend>{{t 'form.type.label'}}</G.Legend>
{{#each this.hostCatalogTypes as |hostCatalogType|}}
{{#each @hostCatalogTypes as |hostCatalogType|}}
<G.RadioCard
@value={{hostCatalogType}}
@maxWidth='20rem'
@@ -80,7 +80,7 @@
(t 'descriptions.choose-a-provider')
}}</G.HelperText>
{{#each-in this.mapResourceTypeWithIcon as |pluginType icon|}}
{{#each-in @mapResourceTypeWithIcon as |pluginType icon|}}
<G.RadioCard
@value={{pluginType}}
@maxWidth='20rem'
@@ -10,4 +10,6 @@
cancel=@cancel
changeType=@changeType
changeCredentialType=@changeCredentialType
mapResourceTypeWithIcon=this.mapResourceTypeWithIcon
hostCatalogTypes=this.hostCatalogTypes
}}
@@ -9,10 +9,9 @@ import {
TYPES_HOST_CATALOG_PLUGIN,
} from 'api/models/host-catalog';
//Note: this is a temporary solution till we have resource type helper in place
const icons = ['aws-color', 'azure-color'];
const icons = ['aws-color', 'azure-color', 'gcp-color'];
export default class FormAzureHostCatalogAwsComponent extends Component {
export default class FormHostCatalogIndexComponent extends Component {
// =properties
hostCatalogTypes = TYPES_HOST_CATALOG;
/**
@@ -53,7 +53,7 @@
<Hds::Form::RadioCard::Group @name={{t 'form.type.label'}} as |G|>
<G.Legend>{{t 'form.type.label'}}</G.Legend>
{{#each this.hostCatalogTypes as |hostCatalogType|}}
{{#each @hostCatalogTypes as |hostCatalogType|}}
<G.RadioCard
@value={{hostCatalogType}}
@maxWidth='20rem'
@@ -1,28 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import {
TYPES_HOST_CATALOG,
TYPES_HOST_CATALOG_PLUGIN,
} from 'api/models/host-catalog';
//Note: this is a temporary solution till we have resource type helper in place
const icons = ['aws-color', 'azure-color'];
export default class FormStaticHostCatalogAwsComponent extends Component {
// =properties
hostCatalogTypes = TYPES_HOST_CATALOG;
/**
* maps resource type with icon
* @type {object}
*/
get mapResourceTypeWithIcon() {
return TYPES_HOST_CATALOG_PLUGIN.reduce(
(obj, plugin, i) => ({ ...obj, [plugin]: icons[i] }),
{},
);
}
}
@@ -81,6 +81,7 @@ module('Acceptance | host-catalogs | update', function (hooks) {
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',