Merge branch 'main' into chore-update-ember-cli-htmlbars

This commit is contained in:
Carlos Alcaide Corvo authored and GitHub committed 2022-01-24 17:07:14 -06:00
commit b5e5b9164f
7 files changed
+351 -5

No files matched your search

@@ -54,4 +54,64 @@ export default class GeneratedHostCatalogModel extends BaseModel {
readOnly: true,
})
plugin;
// AWS & Azure
@attr('boolean', {
isNestedAttribute: true,
description: '',
})
disable_credential_rotation;
// AWS specific
@attr('string', {
isNestedAttribute: true,
description: '',
})
region;
@attr('string', {
isNestedSecret: true,
description: '',
})
access_key_id;
@attr('string', {
isNestedSecret: true,
description: '',
})
secret_access_key;
// Azure specific
@attr('string', {
isNestedAttribute: true,
description: '',
})
tenant_id;
@attr('string', {
isNestedAttribute: true,
description: '',
})
client_id;
@attr('string', {
isNestedAttribute: true,
description: '',
})
subscription_id;
@attr('string', {
isNestedSecret: true,
description: '',
})
secret_id;
@attr('string', {
isNestedSecret: true,
description: '',
})
secret_value;
}
+35 -4
View File
@@ -56,6 +56,14 @@ export default class ApplicationSerializer extends RESTSerializer {
json.attributes[key] = json[key];
delete json[key];
}
// Push nested secrets down into the secrets key
if (options.isNestedSecret) {
if (json[key]) {
if (!json.secrets) json.secrets = {};
json.secrets[key] = json[key];
}
delete json[key];
}
return value;
}
@@ -205,6 +213,7 @@ export default class ApplicationSerializer extends RESTSerializer {
const scopeID = get(normalizedHash, 'scope.id');
if (scopeID) normalizedHash.scope.scope_id = scopeID;
normalizedHash = this.normalizeNestedAttributes(typeClass, normalizedHash);
normalizedHash = this.normalizeNestedSecrets(typeClass, normalizedHash);
return super.normalize(typeClass, normalizedHash);
}
@@ -224,14 +233,36 @@ export default class ApplicationSerializer extends RESTSerializer {
* @return {Object}
*/
normalizeNestedAttributes(typeClass, hash) {
const normalizedHash = copy(hash, true);
typeClass.attributes.forEach((attribute) => {
const { isNestedAttribute } = attribute.options;
const attributeValue = normalizedHash.attributes?.[attribute.name];
const attributeValue = hash.attributes?.[attribute.name];
if (isNestedAttribute && attributeValue) {
normalizedHash[attribute.name] = attributeValue;
hash[attribute.name] = attributeValue;
}
});
return normalizedHash;
return hash;
}
/**
* If an attribute is declared with the `isNestedSecret` option, it will
* be explicitly unset in the incoming payload to `null`. This helps ensure
* secret values do not linger in the UI.
*
* For example:
* `@attr('string', { isNestedSecret: true }) superSecretShhh;`
*
* @param {Model} typeClass
* @param {Object} hash
* @return {Object}
*/
normalizeNestedSecrets(typeClass, hash) {
typeClass.attributes.forEach((attribute) => {
const {
name,
options: { isNestedSecret },
} = attribute;
if (isNestedSecret) hash[name] = null;
});
return hash;
}
}
@@ -0,0 +1,59 @@
import ApplicationSerializer from './application';
const fieldsByType = {
aws: [
'disable_credential_rotation',
'region',
'access_key_id',
'secret_access_key',
],
azure: [
'disable_credential_rotation',
'tenant_id',
'client_id',
'subscription_id',
'secret_id',
'secret_value',
],
};
export default class HostCatalogSerializer extends ApplicationSerializer {
// =methods
serialize(snapshot) {
const serialized = super.serialize(...arguments);
switch (snapshot.record.compositeType) {
case 'static':
return this.serializeStatic(...arguments);
default:
return serialized;
}
}
serializeAttribute(snapshot, json, key, attribute) {
const value = super.serializeAttribute(...arguments);
const { isPlugin, compositeType } = snapshot.record;
const { options } = attribute;
// Nested conditionals for readability
// This deletes any fields that don't belong to the record type
if (isPlugin && options.isNestedAttribute && json.attributes) {
// The key must be included in the fieldsByType list above
if (!fieldsByType[compositeType].includes(key))
delete json.attributes[key];
}
// This deletes any fields that don't belong to the record type
if (isPlugin && options.isNestedSecret && json.secrets) {
if (!fieldsByType[compositeType].includes(key)) delete json.secrets[key];
}
// new one for isNestedSecret
return value;
}
serializeStatic() {
const serialized = super.serialize(...arguments);
// Delete unnecessary fields for static host-catalog
delete serialized.attributes;
delete serialized.secrets;
return serialized;
}
}
@@ -0,0 +1 @@
export { default } from 'api/serializers/host-catalog';
@@ -1,6 +1,7 @@
import factory from '../generated/factories/host-catalog';
import { trait } from 'ember-cli-mirage';
import permissions from '../helpers/permissions';
import { datatype, address } from 'faker';
export default factory.extend({
authorized_actions: () =>
@@ -16,6 +17,26 @@ export default factory.extend({
'host-sets': ['create', 'list'],
};
},
attributes() {
switch (this.plugin?.name) {
case 'aws':
return {
disable_credential_rotation: datatype.boolean(),
tenant_id: datatype.hexaDecimal(6),
client_id: datatype.hexaDecimal(6),
subscription_id: datatype.hexaDecimal(8),
secret_id: datatype.hexaDecimal(6),
secret_value: datatype.string(12),
};
case 'azure':
return {
disable_credential_rotation: datatype.boolean(),
region: address.state(),
access_key_id: datatype.hexaDecimal(8),
secret_access_key: datatype.string(12),
};
}
},
withChildren: trait({
afterCreate(hostCatalog, server) {
const { scope, type } = hostCatalog;
+1 -1
View File
@@ -46,7 +46,7 @@ export default function (server) {
// Other resources
server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => {
server.createList('host-catalog', 2, { scope }, 'withChildren');
server.createList('host-catalog', 4, { scope }, 'withChildren');
server.createList('credential-store', 3, { scope }, 'withAssociations');
server.createList('target', 2, { scope }, 'withAssociations');
// Sessions have target data. Create it after targets.
@@ -0,0 +1,174 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | host catalog', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('host-catalog');
assert.ok(serializer);
});
test('it serializes records', function (assert) {
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {});
assert.ok(record.serialize());
});
test('it serializes a static host-catalog as expected', async function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {
name: 'static',
description: 'this is a static host-catalog',
type: 'static',
});
const expectedResult = {
name: 'static',
description: 'this is a static host-catalog',
type: 'static',
};
assert.deepEqual(record.serialize(), expectedResult);
});
test('it serializes a new aws plugin as expected, ignoring azure fields', async function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {
compositeType: 'aws',
name: 'AWS',
description: 'this is a Aws plugin host-catalog',
disable_credential_rotation: true,
// these are AWS fields and should be included
region: 'spain',
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',
});
const expectedResult = {
name: 'AWS',
description: 'this is a Aws plugin host-catalog',
type: 'plugin',
attributes: {
disable_credential_rotation: true,
region: 'spain',
},
secrets: {
access_key_id: 'foobars',
secret_access_key: 'testing',
},
};
assert.deepEqual(record.serialize(), expectedResult);
});
test('it serializes a new azure record as expected, ignoring aws fields', async function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const record = store.createRecord('host-catalog', {
compositeType: 'azure',
name: 'Azure',
description: 'this is a Azure plugin host-catalog',
disable_credential_rotation: true,
region: 'spain',
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',
});
const expectedResult = {
name: 'Azure',
description: 'this is a Azure plugin host-catalog',
type: 'plugin',
attributes: {
disable_credential_rotation: true,
tenant_id: 'a1b2c3',
client_id: 'a1b2c3',
subscription_id: 'a1b2c3',
},
secrets: {
secret_id: 'a1b2c3',
secret_value: 'a1b2c3',
},
};
assert.deepEqual(record.serialize(), expectedResult);
});
test('it serializes an existing aws record correctly', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
store.push({
data: {
id: '1',
type: 'host-catalog',
attributes: {
type: 'plugin',
name: 'aws',
description: 'test description',
plugin: {
name: 'aws',
},
region: 'andorra',
},
},
});
const record = store.peekRecord('host-catalog', '1');
const expectedResult = {
type: 'plugin',
name: 'aws',
description: 'test description',
attributes: {
disable_credential_rotation: false,
region: 'andorra',
},
};
assert.deepEqual(record.serialize(), expectedResult);
});
test('it serializes an existing azure record correctly', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
store.push({
data: {
id: '2',
type: 'host-catalog',
attributes: {
type: 'plugin',
name: 'azure',
description: 'test description',
plugin: {
name: 'azure',
},
tenant_id: 'a1b2c3',
client_id: 'foobars12',
subscription_id: 'barsfoo21',
},
},
});
const record = store.peekRecord('host-catalog', '2');
const expectedResult = {
type: 'plugin',
name: 'azure',
description: 'test description',
attributes: {
disable_credential_rotation: false,
tenant_id: 'a1b2c3',
client_id: 'foobars12',
subscription_id: 'barsfoo21',
},
};
assert.deepEqual(record.serialize(), expectedResult);
});
});