Chore: refactor host catalog model and serializer to accommodate GCP plugin (#2627)
* chore: 🤖 GCP plugin support
This commit is contained in:
1 parent
31f1ed8dd4
commit
520020f2c6
16 files changed
+399
-179
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user