Merge branch 'main' into chore-update-ember-cli-htmlbars
This commit is contained in:
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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user