From eefb9eba4f75e918e9725c0d036d9ff5b4f16139 Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Fri, 12 Jan 2024 11:29:19 -0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20scope=20policy=20associa?= =?UTF-8?q?tion=20api=20changes=20(#2086)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scope policy association API changes --- addons/api/addon/generated/models/scope.js | 5 ++ addons/api/addon/models/policy.js | 1 + addons/api/addon/models/scope.js | 44 +++++++++++ addons/api/addon/serializers/scope.js | 35 +++++++++ addons/api/app/serializers/scope.js | 6 ++ addons/api/tests/unit/models/scope-test.js | 78 +++++++++++++++++-- .../form/policy/policy-selection/index.js | 1 - 7 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 addons/api/addon/serializers/scope.js create mode 100644 addons/api/app/serializers/scope.js diff --git a/addons/api/addon/generated/models/scope.js b/addons/api/addon/generated/models/scope.js index c1d3e053e..24201f839 100644 --- a/addons/api/addon/generated/models/scope.js +++ b/addons/api/addon/generated/models/scope.js @@ -49,4 +49,9 @@ export default class GeneratedScopeModel extends BaseModel { description: 'Current version number of this resource.', }) version; + + @attr('string', { + description: 'The ID of the associated storage policy', + }) + storage_policy_id; } diff --git a/addons/api/addon/models/policy.js b/addons/api/addon/models/policy.js index 337357212..7a8af937a 100644 --- a/addons/api/addon/models/policy.js +++ b/addons/api/addon/models/policy.js @@ -5,4 +5,5 @@ import GeneratedPolicyModel from '../generated/models/policy'; +export const TYPE_POLICY = 'storage'; export default class PolicyModel extends GeneratedPolicyModel {} diff --git a/addons/api/addon/models/scope.js b/addons/api/addon/models/scope.js index f00420d05..6bb0ea25c 100644 --- a/addons/api/addon/models/scope.js +++ b/addons/api/addon/models/scope.js @@ -46,4 +46,48 @@ export default class ScopeModel extends GeneratedScopeModel { set isProject(value) { if (value) this.type = scopeTypes.project; } + + /** + * Attach policy via the `attach-staorage-policy` method. + * @param {[string]} policyId + * @param {object} options + * @param {object} options.adapterOptions + * @return {Promise} + */ + attachStoragePolicy(policyId, options = { adapterOptions: {} }) { + const defaultAdapterOptions = { + method: 'attach-storage-policy', + policyId, + }; + // There is no "deep merge" in ES. + return this.save({ + ...options, + adapterOptions: { + ...defaultAdapterOptions, + ...options.adapterOptions, + }, + }); + } + + /** + * Detaches policy via the `detach-storage-policy` method. + * @param {[string]} policyId + * @param {object} options + * @param {object} options.adapterOptions + * @return {Promise} + */ + detachStoragePolicy(policyId, options = { adapterOptions: {} }) { + const defaultAdapterOptions = { + method: 'detach-storage-policy', + policyId, + }; + // There is no "deep merge" in ES. + return this.save({ + ...options, + adapterOptions: { + ...defaultAdapterOptions, + ...options.adapterOptions, + }, + }); + } } diff --git a/addons/api/addon/serializers/scope.js b/addons/api/addon/serializers/scope.js new file mode 100644 index 000000000..ff607cb46 --- /dev/null +++ b/addons/api/addon/serializers/scope.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import ApplicationSerializer from './application'; + +export default class ScopeSerializer extends ApplicationSerializer { + // =methods + + serialize(snapshot) { + let serialized = super.serialize(...arguments); + const policyId = snapshot?.adapterOptions?.policyId; + + if (policyId) { + serialized = this.serializeWithStoragePolicy(snapshot, policyId); + } + + return serialized; + } + + /** + * Returns a payload containing only version and storage policy id, + * rather than existing instances on the model. + * @param {Snapshot} snapshot + * @param {[string]} hostSetIDs + * @return {object} + */ + serializeWithStoragePolicy(snapshot, policyId) { + return { + version: snapshot.attr('version'), + storage_policy_id: policyId, + }; + } +} diff --git a/addons/api/app/serializers/scope.js b/addons/api/app/serializers/scope.js new file mode 100644 index 000000000..bd4cc3054 --- /dev/null +++ b/addons/api/app/serializers/scope.js @@ -0,0 +1,6 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +export { default } from 'api/serializers/scope'; diff --git a/addons/api/tests/unit/models/scope-test.js b/addons/api/tests/unit/models/scope-test.js index 89af688a0..b264156b6 100644 --- a/addons/api/tests/unit/models/scope-test.js +++ b/addons/api/tests/unit/models/scope-test.js @@ -27,27 +27,27 @@ module('Unit | Model | scope', function (hooks) { // check integrity of scope relationships assert.notOk(await scopes[0].get('scope'), 'Global scope has no parent'); assert.strictEqual( - await scopes.objectAt(1).get('scope.scope_id'), + await scopes.at(1).get('scope.scope_id'), 'global', 'Org 1 parent scope is global', ); assert.strictEqual( - await scopes.objectAt(2).get('scope.scope_id'), + await scopes.at(2).get('scope.scope_id'), 'global', 'Org 2 parent scope is global', ); assert.strictEqual( - await scopes.objectAt(3).get('scope.scope_id'), + await scopes.at(3).get('scope.scope_id'), 'o_1', 'Project 1 parent scope is org 1', ); assert.strictEqual( - await scopes.objectAt(4).get('scope.scope_id'), + await scopes.at(4).get('scope.scope_id'), 'o_1', 'Project 2 parent scope is org 1', ); assert.strictEqual( - await scopes.objectAt(5).get('scope.scope_id'), + await scopes.at(5).get('scope.scope_id'), 'o_2', 'Project 3 parent scope is org 2', ); @@ -68,4 +68,72 @@ module('Unit | Model | scope', function (hooks) { assert.notOk(model.isOrg); assert.ok(model.isProject); }); + + test('it has an `attachStoragePolicy` method that targets a specific POST API endpoint and serialization', async function (assert) { + assert.expect(1); + this.server.post( + '/v1/scopes/123abc:attach-storage-policy', + (schema, request) => { + const body = JSON.parse(request.requestBody); + assert.deepEqual(body, { + storage_policy_id: 'abc', + version: 1, + }); + return { id: '123abc' }; + }, + ); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'scope', + attributes: { + name: 'scope', + description: 'Description', + storage_policy_id: 'abc', + version: 1, + scope: { + scope_id: 'o_1', + type: 'scope', + }, + }, + }, + }); + const model = store.peekRecord('scope', '123abc'); + await model.attachStoragePolicy('abc'); + }); + + test('it has a `detachStoragePolicy` method that targets a specific POST API endpoint and serialization', async function (assert) { + assert.expect(1); + this.server.post( + '/v1/scopes/123abc:detach-storage-policy', + (schema, request) => { + const body = JSON.parse(request.requestBody); + assert.deepEqual(body, { + storage_policy_id: 'abc', + version: 1, + }); + return { id: '123abc' }; + }, + ); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'scope', + attributes: { + name: 'Target', + description: 'Description', + storage_policy_id: 'abc', + version: 1, + scope: { + scope_id: 'o_1', + type: 'scope', + }, + }, + }, + }); + const model = store.peekRecord('scope', '123abc'); + await model.detachStoragePolicy('abc'); + }); }); diff --git a/ui/admin/app/components/form/policy/policy-selection/index.js b/ui/admin/app/components/form/policy/policy-selection/index.js index 1afe83f9e..e0f898a72 100644 --- a/ui/admin/app/components/form/policy/policy-selection/index.js +++ b/ui/admin/app/components/form/policy/policy-selection/index.js @@ -53,7 +53,6 @@ export default class FormPolicySelectionComponent extends Component { return false; } } - //actions /** * Handles custom input changes