From 404fbc40e70e7e9b53c3ec82cd6089ea6429ed20 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:01:02 +0000 Subject: [PATCH 01/61] Add `ns=` query parameter to all adapters that require it also.. 1. Add ApplicationAdapter.formatNspace for making URLs using "" nicer i.e. don't show an empty `ns=` 2. Make a ApplicationAdapter.formatDatacenter mirroring that ^ but only use it in places we are changing, add TODOs elsewhere --- ui-v2/app/adapters/acl.js | 2 + ui-v2/app/adapters/application.js | 13 ++++++ ui-v2/app/adapters/coordinate.js | 1 + ui-v2/app/adapters/kv.js | 40 ++++++++++++----- ui-v2/app/adapters/node.js | 1 + ui-v2/app/adapters/policy.js | 36 +++++++++++---- ui-v2/app/adapters/proxy.js | 8 +++- ui-v2/app/adapters/role.js | 36 +++++++++++---- ui-v2/app/adapters/service.js | 15 +++++-- ui-v2/app/adapters/session.js | 27 +++++++++--- ui-v2/app/adapters/token.js | 73 +++++++++++++++++++++---------- 11 files changed, 189 insertions(+), 63 deletions(-) diff --git a/ui-v2/app/adapters/acl.js b/ui-v2/app/adapters/acl.js index 324449e25739..d6d94a3e6a3e 100644 --- a/ui-v2/app/adapters/acl.js +++ b/ui-v2/app/adapters/acl.js @@ -2,6 +2,8 @@ import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './applica import { SLUG_KEY } from 'consul-ui/models/acl'; import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +// The old ACL system doesn't support the `ns=` query param +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ requestForQuery: function(request, { dc, index }) { // https://www.consul.io/api/acl.html#list-acls diff --git a/ui-v2/app/adapters/application.js b/ui-v2/app/adapters/application.js index 97e0d1aeee40..39ffd6eb4bcc 100644 --- a/ui-v2/app/adapters/application.js +++ b/ui-v2/app/adapters/application.js @@ -1,10 +1,23 @@ import Adapter from './http'; import { inject as service } from '@ember/service'; +import config from 'consul-ui/config/environment'; export const DATACENTER_QUERY_PARAM = 'dc'; +export const NSPACE_QUERY_PARAM = 'ns'; export default Adapter.extend({ repo: service('settings'), + nspaceRepo: service('repository/nspace/enabled'), client: service('client/http'), + formatNspace: function(nspace) { + if (config.CONSUL_NSPACES_ENABLED) { + return nspace !== '' ? { [NSPACE_QUERY_PARAM]: nspace } : undefined; + } + }, + formatDatacenter: function(dc) { + return { + [DATACENTER_QUERY_PARAM]: dc, + }; + }, // TODO: kinda protected for the moment // decide where this should go either read/write from http // should somehow use this or vice versa diff --git a/ui-v2/app/adapters/coordinate.js b/ui-v2/app/adapters/coordinate.js index 09b50adc9b2c..a9cdb13983ad 100644 --- a/ui-v2/app/adapters/coordinate.js +++ b/ui-v2/app/adapters/coordinate.js @@ -1,4 +1,5 @@ import Adapter from './application'; +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ requestForQuery: function(request, { dc, index }) { return request` diff --git a/ui-v2/app/adapters/kv.js b/ui-v2/app/adapters/kv.js index 2655953e9d67..4f9efae39511 100644 --- a/ui-v2/app/adapters/kv.js +++ b/ui-v2/app/adapters/kv.js @@ -1,46 +1,62 @@ -import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application'; +import Adapter from './application'; import isFolder from 'consul-ui/utils/isFolder'; import keyToArray from 'consul-ui/utils/keyToArray'; import { SLUG_KEY } from 'consul-ui/models/kv'; import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +import { NSPACE_KEY } from 'consul-ui/models/nspace'; +// TODO: Update to use this.formatDatacenter() const API_KEYS_KEY = 'keys'; export default Adapter.extend({ - requestForQuery: function(request, { dc, index, id, separator }) { + requestForQuery: function(request, { dc, ns, index, id, separator }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/kv/${keyToArray(id)}?${{ [API_KEYS_KEY]: null, dc, separator }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, - requestForQueryRecord: function(request, { dc, index, id }) { + requestForQueryRecord: function(request, { dc, ns, index, id }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/kv/${keyToArray(id)}?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, // TODO: Should we replace text/plain here with x-www-form-encoded? // See https://github.com/hashicorp/consul/issues/3804 requestForCreateRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/kv/${keyToArray(data[SLUG_KEY])}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/kv/${keyToArray(data[SLUG_KEY])}?${params} Content-Type: text/plain; charset=utf-8 ${serialized} `; }, requestForUpdateRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/kv/${keyToArray(data[SLUG_KEY])}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/kv/${keyToArray(data[SLUG_KEY])}?${params} Content-Type: text/plain; charset=utf-8 ${serialized} @@ -51,11 +67,13 @@ export default Adapter.extend({ if (isFolder(data[SLUG_KEY])) { recurse = null; } - return request` - DELETE /v1/kv/${keyToArray(data[SLUG_KEY])}?${{ - [API_DATACENTER_KEY]: data[DATACENTER_KEY], + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), recurse, - }} + }; + return request` + DELETE /v1/kv/${keyToArray(data[SLUG_KEY])}?${params} `; }, }); diff --git a/ui-v2/app/adapters/node.js b/ui-v2/app/adapters/node.js index 17a80d567dae..978aebc94853 100644 --- a/ui-v2/app/adapters/node.js +++ b/ui-v2/app/adapters/node.js @@ -1,4 +1,5 @@ import Adapter from './application'; +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ requestForQuery: function(request, { dc, index, id }) { return request` diff --git a/ui-v2/app/adapters/policy.js b/ui-v2/app/adapters/policy.js index f24ae80265ed..6a370d7246cc 100644 --- a/ui-v2/app/adapters/policy.js +++ b/ui-v2/app/adapters/policy.js @@ -1,29 +1,41 @@ -import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application'; +import Adapter from './application'; import { SLUG_KEY } from 'consul-ui/models/policy'; import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +import { NSPACE_KEY } from 'consul-ui/models/nspace'; +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, index, id }) { + requestForQuery: function(request, { dc, ns, index, id }) { return request` GET /v1/acl/policies?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, - requestForQueryRecord: function(request, { dc, index, id }) { + requestForQueryRecord: function(request, { dc, ns, index, id }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/acl/policy/${id}?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, requestForCreateRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/acl/policy?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/acl/policy?${params} ${{ Name: serialized.Name, @@ -34,8 +46,12 @@ export default Adapter.extend({ `; }, requestForUpdateRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/acl/policy/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/acl/policy/${data[SLUG_KEY]}?${params} ${{ Name: serialized.Name, @@ -46,8 +62,12 @@ export default Adapter.extend({ `; }, requestForDeleteRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - DELETE /v1/acl/policy/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + DELETE /v1/acl/policy/${data[SLUG_KEY]}?${params} `; }, }); diff --git a/ui-v2/app/adapters/proxy.js b/ui-v2/app/adapters/proxy.js index bf7cfd56b1af..7cd999cbf542 100644 --- a/ui-v2/app/adapters/proxy.js +++ b/ui-v2/app/adapters/proxy.js @@ -1,13 +1,17 @@ import Adapter from './application'; +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, index, id }) { + requestForQuery: function(request, { dc, ns, index, id }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/catalog/connect/${id}?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, }); diff --git a/ui-v2/app/adapters/role.js b/ui-v2/app/adapters/role.js index 8f271912a327..42418fe5837f 100644 --- a/ui-v2/app/adapters/role.js +++ b/ui-v2/app/adapters/role.js @@ -1,29 +1,41 @@ -import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application'; +import Adapter from './application'; import { SLUG_KEY } from 'consul-ui/models/role'; import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +import { NSPACE_KEY } from 'consul-ui/models/nspace'; +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, index, id }) { + requestForQuery: function(request, { dc, ns, index, id }) { return request` GET /v1/acl/roles?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, - requestForQueryRecord: function(request, { dc, index, id }) { + requestForQueryRecord: function(request, { dc, ns, index, id }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/acl/role/${id}?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, requestForCreateRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/acl/role?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/acl/role?${params} ${{ Name: serialized.Name, @@ -35,8 +47,12 @@ export default Adapter.extend({ `; }, requestForUpdateRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/acl/role/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/acl/role/${data[SLUG_KEY]}?${params} ${{ Name: serialized.Name, @@ -48,8 +64,12 @@ export default Adapter.extend({ `; }, requestForDeleteRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - DELETE /v1/acl/role/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + DELETE /v1/acl/role/${data[SLUG_KEY]}?${params} `; }, }); diff --git a/ui-v2/app/adapters/service.js b/ui-v2/app/adapters/service.js index ec858934c350..27276fecb105 100644 --- a/ui-v2/app/adapters/service.js +++ b/ui-v2/app/adapters/service.js @@ -1,20 +1,27 @@ import Adapter from './application'; +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, index }) { + requestForQuery: function(request, { dc, ns, index }) { return request` GET /v1/internal/ui/services?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, - requestForQueryRecord: function(request, { dc, index, id }) { + requestForQueryRecord: function(request, { dc, ns, index, id }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/health/service/${id}?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, }); diff --git a/ui-v2/app/adapters/session.js b/ui-v2/app/adapters/session.js index c324796a1b79..869205124b87 100644 --- a/ui-v2/app/adapters/session.js +++ b/ui-v2/app/adapters/session.js @@ -1,31 +1,44 @@ -import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application'; -import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +import Adapter from './application'; + import { SLUG_KEY } from 'consul-ui/models/session'; +import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +import { NSPACE_KEY } from 'consul-ui/models/nspace'; +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ - requestForQuery: function(request, { dc, index, id }) { + requestForQuery: function(request, { dc, ns, index, id }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/session/node/${id}?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, - requestForQueryRecord: function(request, { dc, index, id }) { + requestForQueryRecord: function(request, { dc, ns, index, id }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/session/info/${id}?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, requestForDeleteRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/session/destroy/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/session/destroy/${data[SLUG_KEY]}?${params} `; }, }); diff --git a/ui-v2/app/adapters/token.js b/ui-v2/app/adapters/token.js index ee63f4565ff4..9ed0e9fe6bde 100644 --- a/ui-v2/app/adapters/token.js +++ b/ui-v2/app/adapters/token.js @@ -1,29 +1,42 @@ -import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application'; +import Adapter from './application'; import { inject as service } from '@ember/service'; + import { SLUG_KEY } from 'consul-ui/models/token'; import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +import { NSPACE_KEY } from 'consul-ui/models/nspace'; +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ store: service('store'), - requestForQuery: function(request, { dc, index, role, policy }) { + requestForQuery: function(request, { dc, ns, index, role, policy }) { return request` GET /v1/acl/tokens?${{ role, policy, dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, - requestForQueryRecord: function(request, { dc, index, id }) { + requestForQueryRecord: function(request, { dc, ns, index, id }) { if (typeof id === 'undefined') { throw new Error('You must specify an id'); } return request` GET /v1/acl/token/${id}?${{ dc }} - ${{ index }} + ${{ + ...this.formatNspace(ns), + index, + }} `; }, requestForCreateRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` PUT /v1/acl/token?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} @@ -44,14 +57,19 @@ export default Adapter.extend({ // If a token has Rules, use the old API if (typeof data['Rules'] !== 'undefined') { // https://www.consul.io/api/acl/legacy.html#update-acl-token + // as we are using the old API we don't need to specify a nspace return request` - PUT /v1/acl/update?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/acl/update?${this.formatDatacenter(data[DATACENTER_KEY])} ${serialized} `; } + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/acl/token/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + PUT /v1/acl/token/${data[SLUG_KEY]}?${params} ${{ Description: serialized.Description, @@ -63,8 +81,12 @@ export default Adapter.extend({ `; }, requestForDeleteRecord: function(request, serialized, data) { + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - DELETE /v1/acl/token/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} + DELETE /v1/acl/token/${data[SLUG_KEY]}?${params} `; }, requestForSelf: function(request, serialized, { dc, index, secret }) { @@ -77,15 +99,18 @@ export default Adapter.extend({ ${{ index }} `; }, - requestForCloneRecord: function(request, serialized, unserialized) { + requestForCloneRecord: function(request, serialized, data) { // this uses snapshots - const id = unserialized[SLUG_KEY]; - const dc = unserialized[DATACENTER_KEY]; + const id = data[SLUG_KEY]; if (typeof id === 'undefined') { throw new Error('You must specify an id'); } + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; return request` - PUT /v1/acl/token/${id}/clone?${{ [API_DATACENTER_KEY]: dc }} + PUT /v1/acl/token/${id}/clone?${params} `; }, // TODO: self doesn't get passed a snapshot right now @@ -94,11 +119,11 @@ export default Adapter.extend({ // plus we can't create Snapshots as they are private, see services/store.js self: function(store, type, id, unserialized) { return this.request( - function(adapter, request, serialized, unserialized) { - return adapter.requestForSelf(request, serialized, unserialized); + function(adapter, request, serialized, data) { + return adapter.requestForSelf(request, serialized, data); }, - function(serializer, respond, serialized, unserialized) { - return serializer.respondForQueryRecord(respond, serialized, unserialized); + function(serializer, respond, serialized, data) { + return serializer.respondForQueryRecord(respond, serialized, data); }, unserialized, type.modelName @@ -106,16 +131,18 @@ export default Adapter.extend({ }, clone: function(store, type, id, snapshot) { return this.request( - function(adapter, request, serialized, unserialized) { - return adapter.requestForCloneRecord(request, serialized, unserialized); + function(adapter, request, serialized, data) { + return adapter.requestForCloneRecord(request, serialized, data); }, - function(serializer, respond, serialized, unserialized) { + (serializer, respond, serialized, data) => { // here we just have to pass through the dc (like when querying) - // eventually the id is created with this dc value and the id talen from the + // eventually the id is created with this dc value and the id taken from the // json response of `acls/token/*/clone` - return serializer.respondForQueryRecord(respond, { - [API_DATACENTER_KEY]: unserialized[SLUG_KEY], - }); + const params = { + ...this.formatDatacenter(data[DATACENTER_KEY]), + ...this.formatNspace(data[NSPACE_KEY]), + }; + return serializer.respondForQueryRecord(respond, params); }, snapshot, type.modelName From 73f4d293748cc02c2fa8a295c725f12950eb4b3b Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:05:46 +0000 Subject: [PATCH 02/61] Add adapter tests for new `ns=` query parameter --- ui-v2/tests/integration/adapters/kv-test.js | 199 +++++++------ .../tests/integration/adapters/policy-test.js | 161 ++++++----- ui-v2/tests/integration/adapters/role-test.js | 149 +++++----- .../integration/adapters/service-test.js | 43 +-- .../integration/adapters/session-test.js | 83 +++--- .../tests/integration/adapters/token-test.js | 265 ++++++++++-------- 6 files changed, 520 insertions(+), 380 deletions(-) diff --git a/ui-v2/tests/integration/adapters/kv-test.js b/ui-v2/tests/integration/adapters/kv-test.js index fdcfe1c8660a..0c6f67c7bd55 100644 --- a/ui-v2/tests/integration/adapters/kv-test.js +++ b/ui-v2/tests/integration/adapters/kv-test.js @@ -4,33 +4,118 @@ module('Integration | Adapter | kv', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'key-name/here'; - test('requestForQuery returns the correct url/method', function(assert) { - const adapter = this.owner.lookup('adapter:kv'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/kv/${id}?keys&dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { - dc: dc, - id: id, - }); - assert.equal(actual, expected); - }); - test('requestForQueryRecord returns the correct url/method', function(assert) { - const adapter = this.owner.lookup('adapter:kv'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/kv/${id}?dc=${dc}`; - const actual = adapter.requestForQueryRecord(client.url, { - dc: dc, - id: id, + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:kv'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/kv/${id}?keys&dc=${dc}`; + let actual = adapter.requestForQuery(client.url, { + dc: dc, + id: id, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); }); - assert.equal(actual, expected); - }); - test("requestForQueryRecord throws if you don't specify an id", function(assert) { - const adapter = this.owner.lookup('adapter:kv'); - const client = this.owner.lookup('service:client/http'); - assert.throws(function() { - adapter.requestForQueryRecord(client.url, { + test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:kv'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/kv/${id}?dc=${dc}`; + let actual = adapter.requestForQueryRecord(client.url, { dc: dc, + id: id, + ns: nspace, }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); + }); + test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:kv'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/kv/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + let actual = adapter + .requestForCreateRecord( + client.url, + {}, + { + Datacenter: dc, + Key: id, + Value: '', + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForUpdateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:kv'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/kv/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + let actual = adapter + .requestForUpdateRecord( + client.url, + {}, + { + Datacenter: dc, + Key: id, + Value: '', + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForDeleteRecord returns the correct url/method when the nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:kv'); + const client = this.owner.lookup('service:client/http'); + const expected = `DELETE /v1/kv/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + let actual = adapter + .requestForDeleteRecord( + client.url, + {}, + { + Datacenter: dc, + Key: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForDeleteRecord returns the correct url/method for folders when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:kv'); + const client = this.owner.lookup('service:client/http'); + const folder = `${id}/`; + const expected = `DELETE /v1/kv/${folder}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }&recurse`; + let actual = adapter + .requestForDeleteRecord( + client.url, + {}, + { + Datacenter: dc, + Key: folder, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); }); }); test("requestForQuery throws if you don't specify an id", function(assert) { @@ -42,67 +127,13 @@ module('Integration | Adapter | kv', function(hooks) { }); }); }); - test('requestForCreateRecord returns the correct url/method', function(assert) { - const adapter = this.owner.lookup('adapter:kv'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/kv/${id}?dc=${dc}`; - const actual = adapter - .requestForCreateRecord( - client.url, - {}, - { - Datacenter: dc, - Key: id, - Value: '', - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForUpdateRecord returns the correct url/method', function(assert) { - const adapter = this.owner.lookup('adapter:kv'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/kv/${id}?dc=${dc}`; - const actual = adapter - .requestForUpdateRecord( - client.url, - {}, - { - Datacenter: dc, - Key: id, - Value: '', - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForDeleteRecord returns the correct url/method', function(assert) { - const adapter = this.owner.lookup('adapter:kv'); - const client = this.owner.lookup('service:client/http'); - const expected = `DELETE /v1/kv/${id}?dc=${dc}`; - const actual = adapter.requestForDeleteRecord( - client.url, - {}, - { - Datacenter: dc, - Key: id, - } - ); - assert.equal(actual, expected); - }); - test('requestForDeleteRecord returns the correct url/method for folders', function(assert) { + test("requestForQueryRecord throws if you don't specify an id", function(assert) { const adapter = this.owner.lookup('adapter:kv'); const client = this.owner.lookup('service:client/http'); - const folder = `${id}/`; - const expected = `DELETE /v1/kv/${folder}?dc=${dc}&recurse`; - const actual = adapter.requestForDeleteRecord( - client.url, - {}, - { - Datacenter: dc, - Key: folder, - } - ); - assert.equal(actual, expected); + assert.throws(function() { + adapter.requestForQueryRecord(client.url, { + dc: dc, + }); + }); }); }); diff --git a/ui-v2/tests/integration/adapters/policy-test.js b/ui-v2/tests/integration/adapters/policy-test.js index 9077fc0c5eac..58d20521aed0 100644 --- a/ui-v2/tests/integration/adapters/policy-test.js +++ b/ui-v2/tests/integration/adapters/policy-test.js @@ -2,26 +2,103 @@ import { module, test, skip } from 'qunit'; import { setupTest } from 'ember-qunit'; module('Integration | Adapter | policy', function(hooks) { setupTest(hooks); - const dc = 'dc-1'; - const id = 'policy-name'; - test('requestForQuery returns the correct url', function(assert) { + skip('urlForTranslateRecord returns the correct url', function(assert) { const adapter = this.owner.lookup('adapter:policy'); const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/policies?dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { - dc: dc, - }); + const expected = `GET /v1/acl/policy/translate`; + const actual = adapter.requestForTranslateRecord(client.id, {}); assert.equal(actual, expected); }); - test('requestForQueryRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:policy'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/policy/${id}?dc=${dc}`; - const actual = adapter.requestForQueryRecord(client.url, { - dc: dc, - id: id, + const dc = 'dc-1'; + const id = 'policy-name'; + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:policy'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/acl/policies?dc=${dc}`; + let actual = adapter.requestForQuery(client.url, { + dc: dc, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); + }); + test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:policy'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/acl/policy/${id}?dc=${dc}`; + let actual = adapter.requestForQueryRecord(client.url, { + dc: dc, + id: id, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); + }); + test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:policy'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/acl/policy?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForCreateRecord( + client.url, + {}, + { + Datacenter: dc, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForUpdateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:policy'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/acl/policy/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForUpdateRecord( + client.url, + {}, + { + Datacenter: dc, + ID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForDeleteRecord returns the correct url/method when the nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:policy'); + const client = this.owner.lookup('service:client/http'); + const expected = `DELETE /v1/acl/policy/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForDeleteRecord( + client.url, + {}, + { + Datacenter: dc, + ID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); }); - assert.equal(actual, expected); }); test("requestForQueryRecord throws if you don't specify an id", function(assert) { const adapter = this.owner.lookup('adapter:policy'); @@ -32,58 +109,4 @@ module('Integration | Adapter | policy', function(hooks) { }); }); }); - test('requestForCreateRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:policy'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/acl/policy?dc=${dc}`; - const actual = adapter - .requestForCreateRecord( - client.url, - {}, - { - Datacenter: dc, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForUpdateRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:policy'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/acl/policy/${id}?dc=${dc}`; - const actual = adapter - .requestForUpdateRecord( - client.url, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForDeleteRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:policy'); - const client = this.owner.lookup('service:client/http'); - const expected = `DELETE /v1/acl/policy/${id}?dc=${dc}`; - const actual = adapter - .requestForDeleteRecord( - client.url, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - skip('urlForTranslateRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:policy'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/policy/translate`; - const actual = adapter.requestForTranslateRecord(client.id, {}); - assert.equal(actual, expected); - }); }); diff --git a/ui-v2/tests/integration/adapters/role-test.js b/ui-v2/tests/integration/adapters/role-test.js index d5a8b8bfdbe1..fda44719e496 100644 --- a/ui-v2/tests/integration/adapters/role-test.js +++ b/ui-v2/tests/integration/adapters/role-test.js @@ -4,24 +4,94 @@ module('Integration | Adapter | role', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'role-name'; - test('requestForQuery returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:role'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/roles?dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { - dc: dc, + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:role'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/acl/roles?dc=${dc}`; + let actual = adapter.requestForQuery(client.url, { + dc: dc, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); }); - assert.equal(actual, expected); - }); - test('requestForQueryRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:role'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/role/${id}?dc=${dc}`; - const actual = adapter.requestForQueryRecord(client.url, { - dc: dc, - id: id, + test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:role'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/acl/role/${id}?dc=${dc}`; + let actual = adapter.requestForQueryRecord(client.url, { + dc: dc, + id: id, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); + }); + test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:role'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/acl/role?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForCreateRecord( + client.url, + {}, + { + Datacenter: dc, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForUpdateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:role'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/acl/role/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForUpdateRecord( + client.url, + {}, + { + Datacenter: dc, + ID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForDeleteRecord returns the correct url/method when the nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:role'); + const client = this.owner.lookup('service:client/http'); + const expected = `DELETE /v1/acl/role/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForDeleteRecord( + client.url, + {}, + { + Datacenter: dc, + ID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); }); - assert.equal(actual, expected); }); test("requestForQueryRecord throws if you don't specify an id", function(assert) { const adapter = this.owner.lookup('adapter:role'); @@ -32,51 +102,4 @@ module('Integration | Adapter | role', function(hooks) { }); }); }); - test('requestForCreateRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:role'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/acl/role?dc=${dc}`; - const actual = adapter - .requestForCreateRecord( - client.url, - {}, - { - Datacenter: dc, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForUpdateRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:role'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/acl/role/${id}?dc=${dc}`; - const actual = adapter - .requestForUpdateRecord( - client.url, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForDeleteRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:role'); - const client = this.owner.lookup('service:client/http'); - const expected = `DELETE /v1/acl/role/${id}?dc=${dc}`; - const actual = adapter - .requestForDeleteRecord( - client.url, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); }); diff --git a/ui-v2/tests/integration/adapters/service-test.js b/ui-v2/tests/integration/adapters/service-test.js index 3235b50f2883..5212cfe17c46 100644 --- a/ui-v2/tests/integration/adapters/service-test.js +++ b/ui-v2/tests/integration/adapters/service-test.js @@ -4,24 +4,35 @@ module('Integration | Adapter | service', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'service-name'; - test('requestForQuery returns the correct url/method', function(assert) { - const adapter = this.owner.lookup('adapter:service'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/internal/ui/services?dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { - dc: dc, + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:service'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/internal/ui/services?dc=${dc}`; + let actual = adapter.requestForQuery(client.url, { + dc: dc, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); }); - assert.equal(actual, expected); - }); - test('requestForQueryRecord returns the correct url/method', function(assert) { - const adapter = this.owner.lookup('adapter:service'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/health/service/${id}?dc=${dc}`; - const actual = adapter.requestForQueryRecord(client.url, { - dc: dc, - id: id, + test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:service'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/health/service/${id}?dc=${dc}`; + let actual = adapter.requestForQueryRecord(client.url, { + dc: dc, + id: id, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); }); - assert.equal(actual, expected); }); test("requestForQueryRecord throws if you don't specify an id", function(assert) { const adapter = this.owner.lookup('adapter:service'); diff --git a/ui-v2/tests/integration/adapters/session-test.js b/ui-v2/tests/integration/adapters/session-test.js index c8bc9614b359..f72d2c755675 100644 --- a/ui-v2/tests/integration/adapters/session-test.js +++ b/ui-v2/tests/integration/adapters/session-test.js @@ -4,26 +4,57 @@ module('Integration | Adapter | session', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'session-id'; - test('requestForQuery returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:session'); - const client = this.owner.lookup('service:client/http'); - const node = 'node-id'; - const expected = `GET /v1/session/node/${node}?dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { - dc: dc, - id: node, + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:session'); + const client = this.owner.lookup('service:client/http'); + const node = 'node-id'; + const expected = `GET /v1/session/node/${node}?dc=${dc}`; + let actual = adapter.requestForQuery(client.url, { + dc: dc, + id: node, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); }); - assert.equal(actual, expected); - }); - test('requestForQueryRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:session'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/session/info/${id}?dc=${dc}`; - const actual = adapter.requestForQueryRecord(client.url, { - dc: dc, - id: id, + test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:session'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/session/info/${id}?dc=${dc}`; + let actual = adapter.requestForQueryRecord(client.url, { + dc: dc, + id: id, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); + }); + test(`requestForDeleteRecord returns the correct url/method when the nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:session'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/session/destroy/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForDeleteRecord( + client.url, + {}, + { + Datacenter: dc, + ID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); }); - assert.equal(actual, expected); }); test("requestForQuery throws if you don't specify an id", function(assert) { const adapter = this.owner.lookup('adapter:session'); @@ -43,20 +74,4 @@ module('Integration | Adapter | session', function(hooks) { }); }); }); - test('urlForDeleteRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:session'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/session/destroy/${id}?dc=${dc}`; - const actual = adapter - .requestForDeleteRecord( - client.url, - {}, - { - Datacenter: dc, - ID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); }); diff --git a/ui-v2/tests/integration/adapters/token-test.js b/ui-v2/tests/integration/adapters/token-test.js index a310e25e2750..36fb74913b57 100644 --- a/ui-v2/tests/integration/adapters/token-test.js +++ b/ui-v2/tests/integration/adapters/token-test.js @@ -4,44 +4,161 @@ module('Integration | Adapter | token', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'policy-id'; - test('requestForQuery returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/tokens?dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { - dc: dc, + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/acl/tokens?dc=${dc}`; + let actual = adapter.requestForQuery(client.url, { + dc: dc, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); }); - assert.equal(actual, expected); - }); - test('requestForQuery returns the correct url when a policy is specified', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/tokens?policy=${id}&dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { - dc: dc, - policy: id, + test(`requestForQuery returns the correct url/method when a policy is specified when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/acl/tokens?policy=${id}&dc=${dc}`; + let actual = adapter.requestForQuery(client.url, { + dc: dc, + policy: id, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); }); - assert.equal(actual, expected); - }); - test('requestForQuery returns the correct url when a role is specified', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/tokens?role=${id}&dc=${dc}`; - const actual = adapter.requestForQuery(client.url, { - dc: dc, - role: id, + test(`requestForQuery returns the correct url/method when a role is specified when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/acl/tokens?role=${id}&dc=${dc}`; + let actual = adapter.requestForQuery(client.url, { + dc: dc, + role: id, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); }); - assert.equal(actual, expected); - }); - test('urlForQueryRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `GET /v1/acl/token/${id}?dc=${dc}`; - const actual = adapter.requestForQueryRecord(client.url, { - dc: dc, - id: id, + test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/acl/token/${id}?dc=${dc}`; + let actual = adapter.requestForQueryRecord(client.url, { + dc: dc, + id: id, + ns: nspace, + }); + actual = actual.split('\n'); + assert.equal(actual.shift().trim(), expected); + actual = actual.join('\n').trim(); + assert.equal(actual, `${typeof nspace !== 'undefined' ? `ns=${nspace}` : ``}`); + }); + test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/acl/token?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForCreateRecord( + client.url, + {}, + { + Datacenter: dc, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForUpdateRecord returns the correct url (without Rules it uses the v2 API) when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/acl/token/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForUpdateRecord( + client.url, + {}, + { + Datacenter: dc, + AccessorID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForUpdateRecord returns the correct url (with Rules it uses the v1 API) when nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/acl/update?dc=${dc}`; + const actual = adapter + .requestForUpdateRecord( + client.url, + {}, + { + Rules: 'key {}', + Datacenter: dc, + AccessorID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForDeleteRecord returns the correct url/method when the nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `DELETE /v1/acl/token/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForDeleteRecord( + client.url, + {}, + { + Datacenter: dc, + AccessorID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); + }); + test(`requestForCloneRecord returns the correct url when the nspace is ${nspace}`, function(assert) { + const adapter = this.owner.lookup('adapter:token'); + const client = this.owner.lookup('service:client/http'); + const expected = `PUT /v1/acl/token/${id}/clone?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`; + const actual = adapter + .requestForCloneRecord( + client.url, + {}, + { + Datacenter: dc, + AccessorID: id, + Namespace: nspace, + } + ) + .split('\n') + .shift(); + assert.equal(actual, expected); }); - assert.equal(actual, expected); }); test("requestForQueryRecord throws if you don't specify an id", function(assert) { const adapter = this.owner.lookup('adapter:token'); @@ -52,86 +169,6 @@ module('Integration | Adapter | token', function(hooks) { }); }); }); - test('requestForCreateRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/acl/token?dc=${dc}`; - const actual = adapter - .requestForCreateRecord( - client.url, - {}, - { - Datacenter: dc, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForUpdateRecord returns the correct url (without Rules it uses the v2 API)', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/acl/token/${id}?dc=${dc}`; - const actual = adapter - .requestForUpdateRecord( - client.url, - {}, - { - Datacenter: dc, - AccessorID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForUpdateRecord returns the correct url (with Rules it uses the v1 API)', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/acl/update?dc=${dc}`; - const actual = adapter - .requestForUpdateRecord( - client.url, - {}, - { - Rules: 'key {}', - Datacenter: dc, - AccessorID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForDeleteRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `DELETE /v1/acl/token/${id}?dc=${dc}`; - const actual = adapter - .requestForDeleteRecord( - client.url, - {}, - { - Datacenter: dc, - AccessorID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); - test('requestForCloneRecord returns the correct url', function(assert) { - const adapter = this.owner.lookup('adapter:token'); - const client = this.owner.lookup('service:client/http'); - const expected = `PUT /v1/acl/token/${id}/clone?dc=${dc}`; - const actual = adapter - .requestForCloneRecord( - client.url, - {}, - { - Datacenter: dc, - AccessorID: id, - } - ) - .split('\n')[0]; - assert.equal(actual, expected); - }); test('requestForSelf returns the correct url', function(assert) { const adapter = this.owner.lookup('adapter:token'); const client = this.owner.lookup('service:client/http'); From 61a4d3e8c12a9f409097586fe06932e662406d5a Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:09:01 +0000 Subject: [PATCH 03/61] Add Namespace to models and their serializers where required (inc uid) --- ui-v2/app/models/kv.js | 1 + ui-v2/app/models/policy.js | 1 + ui-v2/app/models/proxy.js | 2 ++ ui-v2/app/models/role.js | 1 + ui-v2/app/models/service.js | 1 + ui-v2/app/models/session.js | 1 + ui-v2/app/models/token.js | 1 + ui-v2/app/serializers/application.js | 13 ++++++++++++- ui-v2/app/serializers/kv.js | 3 +++ ui-v2/app/serializers/service.js | 10 +++++++++- ui-v2/app/utils/create-fingerprinter.js | 6 ++++-- ui-v2/app/utils/http/consul.js | 5 ++++- 12 files changed, 40 insertions(+), 5 deletions(-) diff --git a/ui-v2/app/models/kv.js b/ui-v2/app/models/kv.js index f1f9d28c0448..fdd5fd8df640 100644 --- a/ui-v2/app/models/kv.js +++ b/ui-v2/app/models/kv.js @@ -22,6 +22,7 @@ export default Model.extend({ ModifyIndex: attr('number'), Session: attr('string'), Datacenter: attr('string'), + Namespace: attr('string'), isFolder: computed('Key', function() { return isFolder(get(this, 'Key') || ''); diff --git a/ui-v2/app/models/policy.js b/ui-v2/app/models/policy.js index 4c5bd2e25e08..6360e28040c6 100644 --- a/ui-v2/app/models/policy.js +++ b/ui-v2/app/models/policy.js @@ -20,6 +20,7 @@ export default Model.extend({ CreateTime: attr('date'), // Datacenter: attr('string'), + Namespace: attr('string'), Datacenters: attr(), CreateIndex: attr('number'), ModifyIndex: attr('number'), diff --git a/ui-v2/app/models/proxy.js b/ui-v2/app/models/proxy.js index cbd26bbc4fcd..652fbdecede3 100644 --- a/ui-v2/app/models/proxy.js +++ b/ui-v2/app/models/proxy.js @@ -11,4 +11,6 @@ export default Model.extend({ Node: attr('string'), ServiceProxy: attr(), SyncTime: attr('number'), + Datacenter: attr('string'), + Namespace: attr('string'), }); diff --git a/ui-v2/app/models/role.js b/ui-v2/app/models/role.js index a1b1e7d021dd..8a4bbcdffa4a 100644 --- a/ui-v2/app/models/role.js +++ b/ui-v2/app/models/role.js @@ -26,6 +26,7 @@ export default Model.extend({ CreateTime: attr('date'), // Datacenter: attr('string'), + Namespace: attr('string'), // TODO: Figure out whether we need this or not Datacenters: attr(), Hash: attr('string'), diff --git a/ui-v2/app/models/service.js b/ui-v2/app/models/service.js index 6c7300fb7fd6..e9548c574b54 100644 --- a/ui-v2/app/models/service.js +++ b/ui-v2/app/models/service.js @@ -28,6 +28,7 @@ export default Model.extend({ ChecksWarning: attr(), Nodes: attr(), Datacenter: attr('string'), + Namespace: attr('string'), Node: attr(), Service: attr(), Checks: attr(), diff --git a/ui-v2/app/models/session.js b/ui-v2/app/models/session.js index c16f900b2432..02066bd431e2 100644 --- a/ui-v2/app/models/session.js +++ b/ui-v2/app/models/session.js @@ -20,5 +20,6 @@ export default Model.extend({ }, }), Datacenter: attr('string'), + Namespace: attr('string'), SyncTime: attr('number'), }); diff --git a/ui-v2/app/models/token.js b/ui-v2/app/models/token.js index e1de817358f0..dfbd7101eb18 100644 --- a/ui-v2/app/models/token.js +++ b/ui-v2/app/models/token.js @@ -21,6 +21,7 @@ export default Model.extend({ defaultValue: '', }), Datacenter: attr('string'), + Namespace: attr('string'), Local: attr('boolean'), Policies: attr({ defaultValue: function() { diff --git a/ui-v2/app/serializers/application.js b/ui-v2/app/serializers/application.js index 486228251bb3..650054ea2155 100644 --- a/ui-v2/app/serializers/application.js +++ b/ui-v2/app/serializers/application.js @@ -1,11 +1,13 @@ import Serializer from './http'; import { set } from '@ember/object'; +import config from 'consul-ui/config/environment'; import { HEADERS_SYMBOL as HTTP_HEADERS_SYMBOL, HEADERS_INDEX as HTTP_HEADERS_INDEX, } from 'consul-ui/utils/http/consul'; import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +import { NSPACE_KEY } from 'consul-ui/models/nspace'; import createFingerprinter from 'consul-ui/utils/create-fingerprinter'; const map = function(obj, cb) { @@ -26,7 +28,12 @@ const attachHeaders = function(headers, body) { }; export default Serializer.extend({ - fingerprint: createFingerprinter(DATACENTER_KEY), + attachHeaders: attachHeaders, + fingerprint: createFingerprinter( + DATACENTER_KEY, + NSPACE_KEY, + config.CONSUL_NSPACES_UNDEFINED_NAME + ), respondForQuery: function(respond, query) { return respond((headers, body) => attachHeaders(headers, map(body, this.fingerprint(this.primaryKey, this.slugKey, query.dc))) @@ -54,6 +61,10 @@ export default Serializer.extend({ const primaryKey = this.primaryKey; return respond((headers, body) => { // If updates are true use the info we already have + // TODO: We may aswell avoid re-fingerprinting here if we are just + // going to reuse data then its already fingerprinted and as the response + // is true we don't have anything changed so the old fingerprint stays the same + // as long as nothing in the fingerprint has been edited (the namespace?) if (body === true) { body = data; } diff --git a/ui-v2/app/serializers/kv.js b/ui-v2/app/serializers/kv.js index 6277bc5fc89d..09a01b841fa9 100644 --- a/ui-v2/app/serializers/kv.js +++ b/ui-v2/app/serializers/kv.js @@ -2,6 +2,8 @@ import Serializer from './application'; import { inject as service } from '@ember/service'; import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/kv'; +import { NSPACE_KEY } from 'consul-ui/models/nspace'; +import { NSPACE_QUERY_PARAM as API_NSPACE_KEY } from 'consul-ui/adapters/application'; import removeNull from 'consul-ui/utils/remove-null'; export default Serializer.extend({ @@ -25,6 +27,7 @@ export default Serializer.extend({ body.map(item => { return { [this.slugKey]: item, + [NSPACE_KEY]: query[API_NSPACE_KEY], }; }) ); diff --git a/ui-v2/app/serializers/service.js b/ui-v2/app/serializers/service.js index 0cdbb16c0435..16a25a1e795b 100644 --- a/ui-v2/app/serializers/service.js +++ b/ui-v2/app/serializers/service.js @@ -1,5 +1,6 @@ import Serializer from './application'; import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/service'; +import { get } from '@ember/object'; export default Serializer.extend({ primaryKey: PRIMARY_KEY, @@ -8,7 +9,14 @@ export default Serializer.extend({ // Name is added here from the query, which is used to make the uid // Datacenter gets added in the ApplicationSerializer return this._super( - cb => respond((headers, body) => cb(headers, { Name: query.id, Nodes: body })), + cb => + respond((headers, body) => { + return cb(headers, { + Name: query.id, + Namespace: get(body, 'firstObject.Service.Namespace'), + Nodes: body, + }); + }), query ); }, diff --git a/ui-v2/app/utils/create-fingerprinter.js b/ui-v2/app/utils/create-fingerprinter.js index f43ed91f4a35..37d0bda70755 100644 --- a/ui-v2/app/utils/create-fingerprinter.js +++ b/ui-v2/app/utils/create-fingerprinter.js @@ -1,4 +1,4 @@ -export default function(foreignKey, hash = JSON.stringify) { +export default function(foreignKey, nspaceKey, nspaceUndefinedName, hash = JSON.stringify) { return function(primaryKey, slugKey, foreignKeyValue) { if (foreignKeyValue == null || foreignKeyValue.length < 1) { throw new Error('Unable to create fingerprint, missing foreignKey value'); @@ -7,11 +7,13 @@ export default function(foreignKey, hash = JSON.stringify) { if (item[slugKey] == null || item[slugKey].length < 1) { throw new Error('Unable to create fingerprint, missing slug'); } + const nspaceValue = item[nspaceKey] || nspaceUndefinedName; return { ...item, ...{ + [nspaceKey]: nspaceValue, [foreignKey]: foreignKeyValue, - [primaryKey]: hash([foreignKeyValue, item[slugKey]]), + [primaryKey]: hash([nspaceValue, foreignKeyValue, item[slugKey]]), }, }; }; diff --git a/ui-v2/app/utils/http/consul.js b/ui-v2/app/utils/http/consul.js index f81e47d3665a..10031c08c51d 100644 --- a/ui-v2/app/utils/http/consul.js +++ b/ui-v2/app/utils/http/consul.js @@ -1,3 +1,6 @@ -export const HEADERS_SYMBOL = '__consul_ui_http_headers__'; +// TODO: Need to make all these headers capital case +export const HEADERS_NAMESPACE = 'X-Consul-Namespace'; export const HEADERS_INDEX = 'x-consul-index'; export const HEADERS_DIGEST = 'x-consul-contenthash'; +// +export const HEADERS_SYMBOL = '__consul_ui_http_headers__'; From c56b9ac7cfabc4c7a3133912e648ded867d3c325 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:10:03 +0000 Subject: [PATCH 04/61] Add serializer tests for new Namespace property and uid Please note, even things that don't use namespacing include the `default` namespace in the uid. This will be changed at a later date --- .../tests/integration/serializers/acl-test.js | 11 +- .../serializers/coordinate-test.js | 6 +- .../integration/serializers/intention-test.js | 13 +- .../tests/integration/serializers/kv-test.js | 106 +++++++++-------- .../integration/serializers/node-test.js | 11 +- .../integration/serializers/policy-test.js | 98 ++++++++------- .../integration/serializers/role-test.js | 102 ++++++++-------- .../integration/serializers/service-test.js | 112 ++++++++++-------- .../integration/serializers/session-test.js | 104 +++++++++------- .../integration/serializers/token-test.js | 102 ++++++++-------- 10 files changed, 372 insertions(+), 293 deletions(-) diff --git a/ui-v2/tests/integration/serializers/acl-test.js b/ui-v2/tests/integration/serializers/acl-test.js index 75ea55db3b90..284577c5c1e2 100644 --- a/ui-v2/tests/integration/serializers/acl-test.js +++ b/ui-v2/tests/integration/serializers/acl-test.js @@ -5,6 +5,7 @@ import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul'; module('Integration | Serializer | acl', function(hooks) { setupTest(hooks); const dc = 'dc-1'; + const nspace = 'default'; const id = 'token-name'; test('respondForQuery returns the correct data for list endpoint', function(assert) { const serializer = this.owner.lookup('serializer:acl'); @@ -15,7 +16,10 @@ module('Integration | Serializer | acl', function(hooks) { const expected = payload.map(item => Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + Namespace: nspace, + uid: `["${nspace}","${dc}","${item.ID}"]`, }) ); const actual = serializer.respondForQuery( @@ -40,7 +44,10 @@ module('Integration | Serializer | acl', function(hooks) { const expected = Object.assign({}, payload[0], { Datacenter: dc, [META]: {}, - uid: `["${dc}","${id}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + Namespace: nspace, + uid: `["${nspace}","${dc}","${id}"]`, }); const actual = serializer.respondForQueryRecord( function(cb) { diff --git a/ui-v2/tests/integration/serializers/coordinate-test.js b/ui-v2/tests/integration/serializers/coordinate-test.js index 8cfce9c4188f..3498678cd38c 100644 --- a/ui-v2/tests/integration/serializers/coordinate-test.js +++ b/ui-v2/tests/integration/serializers/coordinate-test.js @@ -4,6 +4,7 @@ import { get } from 'consul-ui/tests/helpers/api'; module('Integration | Serializer | coordinate', function(hooks) { setupTest(hooks); const dc = 'dc-1'; + const nspace = 'default'; test('respondForQuery returns the correct data for list endpoint', function(assert) { const serializer = this.owner.lookup('serializer:coordinate'); const request = { @@ -13,7 +14,10 @@ module('Integration | Serializer | coordinate', function(hooks) { const expected = payload.map(item => Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.Node}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + Namespace: nspace, + uid: `["${nspace}","${dc}","${item.Node}"]`, }) ); const actual = serializer.respondForQuery( diff --git a/ui-v2/tests/integration/serializers/intention-test.js b/ui-v2/tests/integration/serializers/intention-test.js index 5155084733db..048e42416b79 100644 --- a/ui-v2/tests/integration/serializers/intention-test.js +++ b/ui-v2/tests/integration/serializers/intention-test.js @@ -6,17 +6,20 @@ module('Integration | Serializer | intention', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'intention-name'; + const nspace = 'default'; test('respondForQuery returns the correct data for list endpoint', function(assert) { const serializer = this.owner.lookup('serializer:intention'); const request = { url: `/v1/connect/intentions?dc=${dc}`, - method: 'GET', }; return get(request.url).then(function(payload) { const expected = payload.map(item => Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + Namespace: nspace, + uid: `["${nspace}","${dc}","${item.ID}"]`, }) ); const actual = serializer.respondForQuery( @@ -36,13 +39,15 @@ module('Integration | Serializer | intention', function(hooks) { const serializer = this.owner.lookup('serializer:intention'); const request = { url: `/v1/connect/intentions/${id}?dc=${dc}`, - method: 'GET', }; return get(request.url).then(function(payload) { const expected = Object.assign({}, payload, { Datacenter: dc, [META]: {}, - uid: `["${dc}","${id}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + Namespace: nspace, + uid: `["${nspace}","${dc}","${id}"]`, }); const actual = serializer.respondForQueryRecord( function(cb) { diff --git a/ui-v2/tests/integration/serializers/kv-test.js b/ui-v2/tests/integration/serializers/kv-test.js index 031764da1a1f..cc878c1df3a0 100644 --- a/ui-v2/tests/integration/serializers/kv-test.js +++ b/ui-v2/tests/integration/serializers/kv-test.js @@ -6,59 +6,69 @@ module('Integration | Serializer | kv', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'key-name/here'; - test('respondForQuery returns the correct data for list endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:kv'); - const request = { - url: `/v1/kv/${id}?keys&dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = payload.map(item => - Object.assign( - {}, - { - Key: item, + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`respondForQuery returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:kv'); + const request = { + url: `/v1/kv/${id}?keys&dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + }; + return get(request.url).then(function(payload) { + const expected = payload.map(item => + Object.assign( + {}, + { + Key: item, + }, + { + Datacenter: dc, + // the payload here is just an array of strings + // so we reuse the query param + Namespace: nspace || undefinedNspace, + uid: `["${nspace || undefinedNspace}","${dc}","${item}"]`, + } + ) + ); + const actual = serializer.respondForQuery( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); }, { - Datacenter: dc, - uid: `["${dc}","${item}"]`, + dc: dc, + ns: nspace, } - ) - ); - const actual = serializer.respondForQuery( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); + ); + assert.deepEqual(actual, expected); + }); }); - }); - test('respondForQueryRecord returns the correct data for item endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:kv'); - const request = { - url: `/v1/kv/${id}?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = Object.assign({}, payload[0], { - Datacenter: dc, - [META]: {}, - uid: `["${dc}","${id}"]`, + test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:kv'); + const request = { + url: `/v1/kv/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + }; + return get(request.url).then(function(payload) { + const expected = Object.assign({}, payload[0], { + Datacenter: dc, + [META]: {}, + Namespace: payload[0].Namespace || undefinedNspace, + uid: `["${payload[0].Namespace || undefinedNspace}","${dc}","${id}"]`, + }); + const actual = serializer.respondForQueryRecord( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + id: id, + } + ); + assert.deepEqual(actual, expected); }); - const actual = serializer.respondForQueryRecord( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); }); }); }); diff --git a/ui-v2/tests/integration/serializers/node-test.js b/ui-v2/tests/integration/serializers/node-test.js index 0f59ca46a9f2..6962665ee020 100644 --- a/ui-v2/tests/integration/serializers/node-test.js +++ b/ui-v2/tests/integration/serializers/node-test.js @@ -4,6 +4,7 @@ import { get } from 'consul-ui/tests/helpers/api'; import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul'; module('Integration | Serializer | node', function(hooks) { setupTest(hooks); + const nspace = 'default'; test('respondForQuery returns the correct data for list endpoint', function(assert) { const serializer = this.owner.lookup('serializer:node'); const dc = 'dc-1'; @@ -14,7 +15,10 @@ module('Integration | Serializer | node', function(hooks) { const expected = payload.map(item => Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + Namespace: nspace, + uid: `["${nspace}","${dc}","${item.ID}"]`, }) ); const actual = serializer.respondForQuery( @@ -41,7 +45,10 @@ module('Integration | Serializer | node', function(hooks) { const expected = Object.assign({}, payload, { Datacenter: dc, [META]: {}, - uid: `["${dc}","${id}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + Namespace: nspace, + uid: `["${nspace}","${dc}","${id}"]`, }); const actual = serializer.respondForQueryRecord( function(cb) { diff --git a/ui-v2/tests/integration/serializers/policy-test.js b/ui-v2/tests/integration/serializers/policy-test.js index def19bc6b560..4cb7063ec9a6 100644 --- a/ui-v2/tests/integration/serializers/policy-test.js +++ b/ui-v2/tests/integration/serializers/policy-test.js @@ -6,53 +6,61 @@ module('Integration | Serializer | policy', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'policy-name'; - test('respondForQuery returns the correct data for list endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:policy'); - const request = { - url: `/v1/acl/policies?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = payload.map(item => - Object.assign({}, item, { - Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, - }) - ); - const actual = serializer.respondForQuery( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`respondForQuery returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:policy'); + const request = { + url: `/v1/acl/policies?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + }; + return get(request.url).then(function(payload) { + const expected = payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, + }) + ); + const actual = serializer.respondForQuery( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + } + ); + assert.deepEqual(actual, expected); + }); }); - }); - test('respondForQueryRecord returns the correct data for item endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:policy'); - const request = { - url: `/v1/acl/policy/${id}?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = Object.assign({}, payload, { - Datacenter: dc, - [META]: {}, - uid: `["${dc}","${id}"]`, + test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:policy'); + const request = { + url: `/v1/acl/policy/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + }; + return get(request.url).then(function(payload) { + const expected = Object.assign({}, payload, { + Datacenter: dc, + [META]: {}, + Namespace: payload.Namespace || undefinedNspace, + uid: `["${payload.Namespace || undefinedNspace}","${dc}","${id}"]`, + }); + const actual = serializer.respondForQueryRecord( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + id: id, + } + ); + assert.deepEqual(actual, expected); }); - const actual = serializer.respondForQueryRecord( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); }); }); }); diff --git a/ui-v2/tests/integration/serializers/role-test.js b/ui-v2/tests/integration/serializers/role-test.js index f9949a518c66..03f4ebe579dc 100644 --- a/ui-v2/tests/integration/serializers/role-test.js +++ b/ui-v2/tests/integration/serializers/role-test.js @@ -8,55 +8,63 @@ module('Integration | Serializer | role', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'role-name'; - test('respondForQuery returns the correct data for list endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:role'); - const request = { - url: `/v1/acl/roles?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = payload.map(item => - Object.assign({}, item, { - Datacenter: dc, - Policies: createPolicies(item), - uid: `["${dc}","${item.ID}"]`, - }) - ); - const actual = serializer.respondForQuery( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`respondForQuery returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:role'); + const request = { + url: `/v1/acl/roles?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + }; + return get(request.url).then(function(payload) { + const expected = payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + Policies: createPolicies(item), + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, + }) + ); + const actual = serializer.respondForQuery( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + } + ); + assert.deepEqual(actual, expected); + }); }); - }); - test('respondForQueryRecord returns the correct data for item endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:role'); - const request = { - url: `/v1/acl/role/${id}?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = Object.assign({}, payload, { - Datacenter: dc, - Policies: createPolicies(payload), - [META]: {}, - uid: `["${dc}","${id}"]`, + test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:role'); + const request = { + url: `/v1/acl/role/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + }; + return get(request.url).then(function(payload) { + const expected = Object.assign({}, payload, { + Datacenter: dc, + Policies: createPolicies(payload), + [META]: {}, + Namespace: payload.Namespace || undefinedNspace, + uid: `["${payload.Namespace || undefinedNspace}","${dc}","${id}"]`, + }); + const actual = serializer.respondForQueryRecord( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + id: id, + } + ); + assert.deepEqual(actual, expected); }); - const actual = serializer.respondForQueryRecord( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); }); }); }); diff --git a/ui-v2/tests/integration/serializers/service-test.js b/ui-v2/tests/integration/serializers/service-test.js index 4aca6bf29b68..c624eb889ea1 100644 --- a/ui-v2/tests/integration/serializers/service-test.js +++ b/ui-v2/tests/integration/serializers/service-test.js @@ -4,59 +4,69 @@ import { get } from 'consul-ui/tests/helpers/api'; import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul'; module('Integration | Serializer | service', function(hooks) { setupTest(hooks); - test('respondForQuery returns the correct data for list endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:service'); - const dc = 'dc-1'; - const request = { - url: `/v1/internal/ui/services?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = payload.map(item => - Object.assign({}, item, { - Datacenter: dc, - uid: `["${dc}","${item.Name}"]`, - }) - ); - const actual = serializer.respondForQuery( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); + const dc = 'dc-1'; + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`respondForQuery returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:service'); + const request = { + url: `/v1/internal/ui/services?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`, + }; + return get(request.url).then(function(payload) { + const expected = payload.map(item => + Object.assign({}, item, { + Namespace: item.Namespace || undefinedNspace, + Datacenter: dc, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.Name}"]`, + }) + ); + const actual = serializer.respondForQuery( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + } + ); + assert.deepEqual(actual, expected); + }); }); - }); - test('respondForQueryRecord returns the correct data for item endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:service'); - const dc = 'dc-1'; - const id = 'service-name'; - const request = { - url: `/v1/health/service/${id}?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = { - Datacenter: dc, - [META]: {}, - uid: `["${dc}","${id}"]`, - Name: id, - Nodes: payload, + test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:service'); + const id = 'service-name'; + const request = { + url: `/v1/health/service/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`, }; - const actual = serializer.respondForQueryRecord( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - id: id, - } - ); - assert.deepEqual(actual, expected); + return get(request.url).then(function(payload) { + const expected = { + Datacenter: dc, + [META]: {}, + Namespace: payload[0].Service.Namespace || undefinedNspace, + uid: `["${payload[0].Service.Namespace || undefinedNspace}","${dc}","${id}"]`, + Name: id, + Nodes: payload, + }; + const actual = serializer.respondForQueryRecord( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + id: id, + } + ); + assert.deepEqual(actual, expected); + }); }); }); }); diff --git a/ui-v2/tests/integration/serializers/session-test.js b/ui-v2/tests/integration/serializers/session-test.js index 1be212396971..c7bdfdcc7596 100644 --- a/ui-v2/tests/integration/serializers/session-test.js +++ b/ui-v2/tests/integration/serializers/session-test.js @@ -6,54 +6,66 @@ module('Integration | Adapter | session | response', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'session-id'; - test('respondForQuery returns the correct data for list endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:session'); - const node = 'node-id'; - const request = { - url: `/v1/session/node/${node}?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = payload.map(item => - Object.assign({}, item, { - Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, - }) - ); - const actual = serializer.respondForQuery( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`respondForQuery returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:session'); + const node = 'node-id'; + const request = { + url: `/v1/session/node/${node}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`, + }; + return get(request.url).then(function(payload) { + const expected = payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, + }) + ); + const actual = serializer.respondForQuery( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + } + ); + assert.deepEqual(actual, expected); + }); }); - }); - test('respondForQueryRecord returns the correct data for item endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:session'); - const request = { - url: `/v1/session/info/${id}?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = Object.assign({}, payload[0], { - Datacenter: dc, - [META]: {}, - uid: `["${dc}","${id}"]`, + test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:session'); + const request = { + url: `/v1/session/info/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`, + }; + return get(request.url).then(function(payload) { + const expected = Object.assign({}, payload[0], { + Datacenter: dc, + [META]: {}, + Namespace: payload[0].Namespace || undefinedNspace, + uid: `["${payload[0].Namespace || undefinedNspace}","${dc}","${id}"]`, + }); + const actual = serializer.respondForQueryRecord( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + id: id, + } + ); + assert.deepEqual(actual, expected); }); - const actual = serializer.respondForQueryRecord( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); }); }); }); diff --git a/ui-v2/tests/integration/serializers/token-test.js b/ui-v2/tests/integration/serializers/token-test.js index b0d096c64ddb..23bd4dbd6a7e 100644 --- a/ui-v2/tests/integration/serializers/token-test.js +++ b/ui-v2/tests/integration/serializers/token-test.js @@ -9,55 +9,63 @@ module('Integration | Serializer | token', function(hooks) { setupTest(hooks); const dc = 'dc-1'; const id = 'token-name'; - test('respondForQuery returns the correct data for list endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:token'); - const request = { - url: `/v1/acl/tokens?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = payload.map(item => - Object.assign({}, item, { - Datacenter: dc, - uid: `["${dc}","${item.AccessorID}"]`, - Policies: createPolicies(item), - }) - ); - const actual = serializer.respondForQuery( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); + const undefinedNspace = 'default'; + [undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`respondForQuery returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:token'); + const request = { + url: `/v1/acl/tokens?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + }; + return get(request.url).then(function(payload) { + const expected = payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.AccessorID}"]`, + Policies: createPolicies(item), + }) + ); + const actual = serializer.respondForQuery( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + } + ); + assert.deepEqual(actual, expected); + }); }); - }); - test('respondForQueryRecord returns the correct data for item endpoint', function(assert) { - const serializer = this.owner.lookup('serializer:token'); - const request = { - url: `/v1/acl/token/${id}?dc=${dc}`, - }; - return get(request.url).then(function(payload) { - const expected = Object.assign({}, payload, { - Datacenter: dc, - [META]: {}, - uid: `["${dc}","${id}"]`, - Policies: createPolicies(payload), + test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) { + const serializer = this.owner.lookup('serializer:token'); + const request = { + url: `/v1/acl/token/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + }; + return get(request.url).then(function(payload) { + const expected = Object.assign({}, payload, { + Datacenter: dc, + [META]: {}, + Namespace: payload.Namespace || undefinedNspace, + uid: `["${payload.Namespace || undefinedNspace}","${dc}","${id}"]`, + Policies: createPolicies(payload), + }); + const actual = serializer.respondForQueryRecord( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + dc: dc, + ns: nspace, + id: id, + } + ); + assert.deepEqual(actual, expected); }); - const actual = serializer.respondForQueryRecord( - function(cb) { - const headers = {}; - const body = payload; - return cb(headers, body); - }, - { - dc: dc, - } - ); - assert.deepEqual(actual, expected); }); }); }); From db852e83e0651c1987abbf249e58446911970e62 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:21:26 +0000 Subject: [PATCH 05/61] Add nspace model layer (adapter, serializer, model, repo) --- ui-v2/app/adapters/nspace.js | 55 +++++++++++++++++ ui-v2/app/models/nspace.js | 18 ++++++ ui-v2/app/serializers/nspace.js | 59 +++++++++++++++++++ ui-v2/app/services/repository/nspace.js | 15 +++++ .../services/repository/nspace/disabled.js | 21 +++++++ .../app/services/repository/nspace/enabled.js | 51 ++++++++++++++++ 6 files changed, 219 insertions(+) create mode 100644 ui-v2/app/adapters/nspace.js create mode 100644 ui-v2/app/models/nspace.js create mode 100644 ui-v2/app/serializers/nspace.js create mode 100644 ui-v2/app/services/repository/nspace.js create mode 100644 ui-v2/app/services/repository/nspace/disabled.js create mode 100644 ui-v2/app/services/repository/nspace/enabled.js diff --git a/ui-v2/app/adapters/nspace.js b/ui-v2/app/adapters/nspace.js new file mode 100644 index 000000000000..261cb876536c --- /dev/null +++ b/ui-v2/app/adapters/nspace.js @@ -0,0 +1,55 @@ +import Adapter from './application'; +import { SLUG_KEY } from 'consul-ui/models/nspace'; + +// namespaces aren't categorized by datacenter, therefore no dc +export default Adapter.extend({ + requestForQuery: function(request, { index }) { + return request` + GET /v1/namespaces + + ${{ index }} + `; + }, + requestForQueryRecord: function(request, { index, id }) { + if (typeof id === 'undefined') { + throw new Error('You must specify an name'); + } + return request` + GET /v1/namespace/${id} + + ${{ index }} + `; + }, + requestForCreateRecord: function(request, serialized, data) { + return request` + PUT /v1/namespace/${data[SLUG_KEY]} + + ${{ + Name: serialized.Name, + Description: serialized.Description, + ACLs: { + PolicyDefaults: serialized.ACLs.PolicyDefaults.map(item => ({ ID: item.ID })), + RoleDefaults: serialized.ACLs.RoleDefaults.map(item => ({ ID: item.ID })), + }, + }} + `; + }, + requestForUpdateRecord: function(request, serialized, data) { + return request` + PUT /v1/namespace/${data[SLUG_KEY]} + + ${{ + Description: serialized.Description, + ACLs: { + PolicyDefaults: serialized.ACLs.PolicyDefaults.map(item => ({ ID: item.ID })), + RoleDefaults: serialized.ACLs.RoleDefaults.map(item => ({ ID: item.ID })), + }, + }} + `; + }, + requestForDeleteRecord: function(request, serialized, data) { + return request` + DELETE /v1/namespace/${data[SLUG_KEY]} + `; + }, +}); diff --git a/ui-v2/app/models/nspace.js b/ui-v2/app/models/nspace.js new file mode 100644 index 000000000000..bde85148f3b2 --- /dev/null +++ b/ui-v2/app/models/nspace.js @@ -0,0 +1,18 @@ +import Model from 'ember-data/model'; +import attr from 'ember-data/attr'; + +export const PRIMARY_KEY = 'Name'; +// keep this for consistency +export const SLUG_KEY = 'Name'; +export const NSPACE_KEY = 'Namespace'; +export default Model.extend({ + [PRIMARY_KEY]: attr('string'), + [SLUG_KEY]: attr('string'), + + Description: attr('string', { defaultValue: '' }), + // TODO: Is there some sort of date we can use here + DeletedAt: attr('string'), + ACLs: attr(undefined, function() { + return { defaultValue: { PolicyDefaults: [], RoleDefaults: [] } }; + }), +}); diff --git a/ui-v2/app/serializers/nspace.js b/ui-v2/app/serializers/nspace.js new file mode 100644 index 000000000000..1380525903d4 --- /dev/null +++ b/ui-v2/app/serializers/nspace.js @@ -0,0 +1,59 @@ +import Serializer from './application'; +import { get } from '@ember/object'; +import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/nspace'; + +export default Serializer.extend({ + primaryKey: PRIMARY_KEY, + slugKey: SLUG_KEY, + respondForQuery: function(respond, serialized, data) { + return respond((headers, body) => { + return this.attachHeaders( + headers, + body.map(function(item) { + if (get(item, 'ACLs.PolicyDefaults')) { + item.ACLs.PolicyDefaults = item.ACLs.PolicyDefaults.map(function(item) { + item.template = ''; + return item; + }); + } + // Both of these might come though unset so we make sure + // we at least have an empty array here so we can add + // children to them if we need to whilst saving nspaces + ['PolicyDefaults', 'RoleDefaults'].forEach(function(prop) { + if (typeof item.ACLs === 'undefined') { + item.ACLs = []; + } + if (typeof item.ACLs[prop] === 'undefined') { + item.ACLs[prop] = []; + } + }); + return item; + }) + ); + }); + }, + respondForQueryRecord: function(respond, serialized, data) { + // We don't attachHeaders here yet, mainly because we don't use + // blocking queries on form views yet, and by the time we do + // Serializers should have been refactored to not use attachHeaders + return respond((headers, body) => { + return body; + }); + }, + respondForCreateRecord: function(respond, serialized, data) { + return respond((headers, body) => { + return body; + }); + }, + respondForUpdateRecord: function(respond, serialized, data) { + return respond((headers, body) => { + return body; + }); + }, + respondForDeleteRecord: function(respond, serialized, data) { + return respond((headers, body) => { + // Deletes only need the primaryKey/uid returning + return body; + }); + }, +}); diff --git a/ui-v2/app/services/repository/nspace.js b/ui-v2/app/services/repository/nspace.js new file mode 100644 index 000000000000..24176e52fd00 --- /dev/null +++ b/ui-v2/app/services/repository/nspace.js @@ -0,0 +1,15 @@ +import RepositoryService from 'consul-ui/services/repository'; + +const modelName = 'nspace'; +export default RepositoryService.extend({ + getModelName: function() { + return modelName; + }, + findAll: function(configuration = {}) { + const query = {}; + if (typeof configuration.cursor !== 'undefined') { + query.index = configuration.cursor; + } + return this.store.query(this.getModelName(), query); + }, +}); diff --git a/ui-v2/app/services/repository/nspace/disabled.js b/ui-v2/app/services/repository/nspace/disabled.js new file mode 100644 index 000000000000..650457558d5d --- /dev/null +++ b/ui-v2/app/services/repository/nspace/disabled.js @@ -0,0 +1,21 @@ +import RepositoryService from 'consul-ui/services/repository'; +import config from 'consul-ui/config/environment'; +import { Promise } from 'rsvp'; + +const modelName = 'nspace'; +export default RepositoryService.extend({ + getModelName: function() { + return modelName; + }, + findAll: function(configuration = {}) { + return Promise.resolve([]); + }, + getUndefinedName: function() { + return config.CONSUL_NSPACES_UNDEFINED_NAME; + }, + getActive: function() { + return { + Name: this.getUndefinedName(), + }; + }, +}); diff --git a/ui-v2/app/services/repository/nspace/enabled.js b/ui-v2/app/services/repository/nspace/enabled.js new file mode 100644 index 000000000000..99fc1a2cf3b0 --- /dev/null +++ b/ui-v2/app/services/repository/nspace/enabled.js @@ -0,0 +1,51 @@ +import { inject as service } from '@ember/service'; +import { get } from '@ember/object'; +import config from 'consul-ui/config/environment'; +import RepositoryService from 'consul-ui/services/repository'; + +const modelName = 'nspace'; +export default RepositoryService.extend({ + router: service('router'), + settings: service('settings'), + getModelName: function() { + return modelName; + }, + findAll: function(configuration = {}) { + const query = {}; + if (typeof configuration.cursor !== 'undefined') { + query.index = configuration.cursor; + } + return this.store.query(this.getModelName(), query); + }, + getUndefinedName: function() { + return config.CONSUL_NSPACES_UNDEFINED_NAME; + }, + getActive: function() { + let routeParams = {}; + // this is only populated before the model hook as fired, + // it is then deleted after the model hook has finished + const infos = get(this, 'router._router.currentState.router.activeTransition.routeInfos'); + if (typeof infos !== 'undefined') { + infos.forEach(function(item) { + Object.keys(item.params).forEach(function(prop) { + routeParams[prop] = item.params[prop]; + }); + }); + } else { + // this is only populated after the model hook has finished + // + const current = get(this, 'router.currentRoute'); + if (current) { + const nspacedRoute = current.find(function(item, i, arr) { + return item.paramNames.includes('nspace'); + }); + if (typeof nspacedRoute !== 'undefined') { + routeParams.nspace = nspacedRoute.params.nspace; + } + } + } + return { + Name: (routeParams.nspace || `~`).substr(1), + }; + }, +}); From 4f00dc3ed0a21cf68f3c33b193ba39a0be68415f Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:22:57 +0000 Subject: [PATCH 06/61] Add nspace model layer tests --- .../tests/integration/adapters/nspace-test.js | 31 +++++++++++++ .../integration/serializers/nspace-test.js | 45 +++++++++++++++++++ ui-v2/tests/unit/adapters/nspace-test.js | 12 +++++ ui-v2/tests/unit/serializers/nspace-test.js | 23 ++++++++++ .../repository/nspace/disabled-test.js | 12 +++++ .../repository/nspace/enabled-test.js | 12 +++++ 6 files changed, 135 insertions(+) create mode 100644 ui-v2/tests/integration/adapters/nspace-test.js create mode 100644 ui-v2/tests/integration/serializers/nspace-test.js create mode 100644 ui-v2/tests/unit/adapters/nspace-test.js create mode 100644 ui-v2/tests/unit/serializers/nspace-test.js create mode 100644 ui-v2/tests/unit/services/repository/nspace/disabled-test.js create mode 100644 ui-v2/tests/unit/services/repository/nspace/enabled-test.js diff --git a/ui-v2/tests/integration/adapters/nspace-test.js b/ui-v2/tests/integration/adapters/nspace-test.js new file mode 100644 index 000000000000..b0333ec8af41 --- /dev/null +++ b/ui-v2/tests/integration/adapters/nspace-test.js @@ -0,0 +1,31 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +// nspaces aren't categorized by datacenter therefore no dc +module('Integration | Adapter | nspace', function(hooks) { + setupTest(hooks); + const id = 'slug'; + test('requestForQuery returns the correct url/method', function(assert) { + const adapter = this.owner.lookup('adapter:nspace'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/namespaces`; + const actual = adapter.requestForQuery(client.url, {}); + assert.equal(actual, expected); + }); + test('requestForQueryRecord returns the correct url/method', function(assert) { + const adapter = this.owner.lookup('adapter:nspace'); + const client = this.owner.lookup('service:client/http'); + const expected = `GET /v1/namespace/${id}`; + const actual = adapter.requestForQueryRecord(client.url, { + id: id, + }); + assert.equal(actual, expected); + }); + test("requestForQueryRecord throws if you don't specify an id", function(assert) { + const adapter = this.owner.lookup('adapter:nspace'); + const client = this.owner.lookup('service:client/http'); + assert.throws(function() { + adapter.requestForQueryRecord(client.url, {}); + }); + }); +}); diff --git a/ui-v2/tests/integration/serializers/nspace-test.js b/ui-v2/tests/integration/serializers/nspace-test.js new file mode 100644 index 000000000000..dc22ca5cce17 --- /dev/null +++ b/ui-v2/tests/integration/serializers/nspace-test.js @@ -0,0 +1,45 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +import { get } from 'consul-ui/tests/helpers/api'; +// Nspace don't need any datacenter or nspace, and don't +module('Integration | Serializer | nspace', function(hooks) { + setupTest(hooks); + test('respondForQuery returns the correct data for list endpoint', function(assert) { + const serializer = this.owner.lookup('serializer:nspace'); + const request = { + url: `/v1/namespaces`, + }; + return get(request.url).then(function(payload) { + const expected = payload.map(item => Object.assign({}, item, {})); + const actual = serializer.respondForQuery(function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, {}); + assert.deepEqual(actual, expected); + }); + }); + test('respondForQueryRecord returns the correct data for item endpoint', function(assert) { + const serializer = this.owner.lookup('serializer:nspace'); + const id = 'slug'; + const request = { + url: `/v1/namespace/${id}`, + }; + return get(request.url).then(function(payload) { + // Namespace items don't currently get META attached + const expected = payload; + const actual = serializer.respondForQueryRecord( + function(cb) { + const headers = {}; + const body = payload; + return cb(headers, body); + }, + { + id: id, + } + ); + assert.deepEqual(actual, expected); + }); + }); +}); diff --git a/ui-v2/tests/unit/adapters/nspace-test.js b/ui-v2/tests/unit/adapters/nspace-test.js new file mode 100644 index 000000000000..609337aa07c8 --- /dev/null +++ b/ui-v2/tests/unit/adapters/nspace-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Adapter | nspace', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let adapter = this.owner.lookup('adapter:nspace'); + assert.ok(adapter); + }); +}); diff --git a/ui-v2/tests/unit/serializers/nspace-test.js b/ui-v2/tests/unit/serializers/nspace-test.js new file mode 100644 index 000000000000..0aab890bdc62 --- /dev/null +++ b/ui-v2/tests/unit/serializers/nspace-test.js @@ -0,0 +1,23 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | nspace', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let serializer = store.serializerFor('nspace'); + + assert.ok(serializer); + }); + + test('it serializes records', function(assert) { + let store = this.owner.lookup('service:store'); + let record = store.createRecord('nspace', {}); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + }); +}); diff --git a/ui-v2/tests/unit/services/repository/nspace/disabled-test.js b/ui-v2/tests/unit/services/repository/nspace/disabled-test.js new file mode 100644 index 000000000000..c28bf919e02b --- /dev/null +++ b/ui-v2/tests/unit/services/repository/nspace/disabled-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Service | repository/nspace/disabled', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let service = this.owner.lookup('service:repository/nspace/disabled'); + assert.ok(service); + }); +}); diff --git a/ui-v2/tests/unit/services/repository/nspace/enabled-test.js b/ui-v2/tests/unit/services/repository/nspace/enabled-test.js new file mode 100644 index 000000000000..9f6aeb82ff3b --- /dev/null +++ b/ui-v2/tests/unit/services/repository/nspace/enabled-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Service | repository/nspace/enabled', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let service = this.owner.lookup('service:repository/nspace/enabled'); + assert.ok(service); + }); +}); From 24e9ffe03a5bfc15f96b032c77c6f353fe88c3f4 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:23:33 +0000 Subject: [PATCH 07/61] Add seom missed model layer tests --- ui-v2/tests/unit/serializers/kv-test.js | 8 ++++++-- ui-v2/tests/unit/utils/create-fingerprinter-test.js | 9 +++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ui-v2/tests/unit/serializers/kv-test.js b/ui-v2/tests/unit/serializers/kv-test.js index 8ba791cd2577..75a55593ddab 100644 --- a/ui-v2/tests/unit/serializers/kv-test.js +++ b/ui-v2/tests/unit/serializers/kv-test.js @@ -29,9 +29,11 @@ module('Unit | Serializer | kv', function(hooks) { test('respondForCreate/UpdateRecord returns a KV uid object when receiving a `true` payload', function(assert) { const uid = 'key/name'; const dc = 'dc1'; + const nspace = 'default'; const expected = { - uid: JSON.stringify([dc, uid]), + uid: JSON.stringify([nspace, dc, uid]), Key: uid, + Namespace: nspace, Datacenter: dc, }; const serializer = this.owner.lookup('serializer:kv'); @@ -56,9 +58,11 @@ module('Unit | Serializer | kv', function(hooks) { test("respondForCreate/UpdateRecord returns the original object if it's not a Boolean", function(assert) { const uid = 'key/name'; const dc = 'dc1'; + const nspace = 'default'; const expected = { - uid: JSON.stringify([dc, uid]), + uid: JSON.stringify([nspace, dc, uid]), Key: uid, + Namespace: nspace, Datacenter: dc, }; const serializer = this.owner.lookup('serializer:kv'); diff --git a/ui-v2/tests/unit/utils/create-fingerprinter-test.js b/ui-v2/tests/unit/utils/create-fingerprinter-test.js index 0edc1b1ec8f7..11fbde2a8fca 100644 --- a/ui-v2/tests/unit/utils/create-fingerprinter-test.js +++ b/ui-v2/tests/unit/utils/create-fingerprinter-test.js @@ -8,15 +8,16 @@ module('Unit | Utility | create fingerprinter', function() { }; const expected = { Datacenter: 'dc', + Namespace: 'default', ID: 'slug', - uid: '["dc","slug"]', + uid: '["default","dc","slug"]', }; - const fingerprint = createFingerprinter('Datacenter'); + const fingerprint = createFingerprinter('Datacenter', 'Namespace', 'default'); const actual = fingerprint('uid', 'ID', 'dc')(obj); assert.deepEqual(actual, expected); }); test("fingerprint throws an error if it can't find a foreignKey", function(assert) { - const fingerprint = createFingerprinter('Datacenter'); + const fingerprint = createFingerprinter('Datacenter', 'Namespace', 'default'); [undefined, null].forEach(function(item) { assert.throws(function() { fingerprint('uid', 'ID', item); @@ -24,7 +25,7 @@ module('Unit | Utility | create fingerprinter', function() { }); }); test("fingerprint throws an error if it can't find a slug", function(assert) { - const fingerprint = createFingerprinter('Datacenter'); + const fingerprint = createFingerprinter('Datacenter', 'Namespace', 'default'); [ {}, { From 0dbcc4ed9e31cb6aebbd771525c0532222fdad47 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:25:09 +0000 Subject: [PATCH 08/61] Add notion of nspaces to repos and routes that need them --- ui-v2/app/routes/application.js | 6 +++-- ui-v2/app/routes/dc.js | 13 ++++++++- ui-v2/app/routes/dc/acls/create.js | 7 ++--- ui-v2/app/routes/dc/acls/policies/edit.js | 3 ++- ui-v2/app/routes/dc/acls/policies/index.js | 8 +++--- ui-v2/app/routes/dc/acls/roles/edit.js | 3 ++- ui-v2/app/routes/dc/acls/roles/index.js | 8 +++--- ui-v2/app/routes/dc/acls/tokens/index.js | 8 +++--- ui-v2/app/routes/dc/intentions/create.js | 20 +++++++++----- ui-v2/app/routes/dc/intentions/edit.js | 22 +++++++++++----- ui-v2/app/routes/dc/intentions/index.js | 5 +++- ui-v2/app/routes/dc/kv/create.js | 12 +++++---- ui-v2/app/routes/dc/kv/edit.js | 8 +++--- ui-v2/app/routes/dc/kv/index.js | 6 ++--- ui-v2/app/routes/dc/nodes/index.js | 2 +- ui-v2/app/routes/dc/nodes/show.js | 13 +++++---- ui-v2/app/routes/dc/services/index.js | 6 +++-- ui-v2/app/routes/dc/services/instance.js | 9 +++---- ui-v2/app/routes/dc/services/show.js | 6 ++--- ui-v2/app/routing/single.js | 10 +++++-- ui-v2/app/services/repository.js | 10 +++++-- ui-v2/app/services/repository/coordinate.js | 4 +-- ui-v2/app/services/repository/dc.js | 1 + ui-v2/app/services/repository/kv.js | 29 +++++++++++++++------ ui-v2/app/services/repository/proxy.js | 7 ++--- ui-v2/app/services/repository/service.js | 5 ++++ ui-v2/app/services/repository/session.js | 7 ++--- ui-v2/app/services/repository/token.js | 6 +++-- 28 files changed, 159 insertions(+), 85 deletions(-) diff --git a/ui-v2/app/routes/application.js b/ui-v2/app/routes/application.js index 8fb66e878d47..c5e15c7ff06e 100644 --- a/ui-v2/app/routes/application.js +++ b/ui-v2/app/routes/application.js @@ -14,6 +14,7 @@ export default Route.extend(WithBlockingActions, { init: function() { this._super(...arguments); }, + nspaceRepo: service('repository/nspace/disabled'), repo: service('repository/dc'), settings: service('settings'), actions: { @@ -27,6 +28,7 @@ export default Route.extend(WithBlockingActions, { hash({ loading: !$root.classList.contains('ember-loading'), dc: dc, + nspace: this.nspaceRepo.getActive(), }).then(model => { next(() => { const controller = this.controllerFor('application'); @@ -81,8 +83,8 @@ export default Route.extend(WithBlockingActions, { error.status.toString().indexOf('5') !== 0 ? this.repo.getActive() : model && model.dc - ? model.dc - : { Name: 'Error' }, + ? model.dc + : { Name: 'Error' }, dcs: model && model.dcs ? model.dcs : [], }) .then(model => { diff --git a/ui-v2/app/routes/dc.js b/ui-v2/app/routes/dc.js index c366ebb09c8f..8701c6c5d30b 100644 --- a/ui-v2/app/routes/dc.js +++ b/ui-v2/app/routes/dc.js @@ -1,18 +1,29 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; + +// TODO: We should potentially move all this up a level to application.js export default Route.extend({ repo: service('repository/dc'), - settings: service('settings'), + nspaceRepo: service('repository/nspace/disabled'), model: function(params) { const repo = this.repo; + const nspaceRepo = this.nspaceRepo; + const nspace = this.nspaceRepo.getActive(); return hash({ dcs: repo.findAll(), + nspaces: nspaceRepo.findAll(), }).then(function(model) { return hash({ ...model, ...{ dc: repo.findBySlug(params.dc, model.dcs), + nspace: + model.nspaces.length > 0 + ? model.nspaces.find(function(item) { + return item.Name === nspace.Name; + }) + : null, }, }); }); diff --git a/ui-v2/app/routes/dc/acls/create.js b/ui-v2/app/routes/dc/acls/create.js index 9890b7c3a826..2d5240c73365 100644 --- a/ui-v2/app/routes/dc/acls/create.js +++ b/ui-v2/app/routes/dc/acls/create.js @@ -1,7 +1,7 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; -import { get, set } from '@ember/object'; +import { get } from '@ember/object'; import WithAclActions from 'consul-ui/mixins/acl/with-actions'; @@ -12,8 +12,9 @@ export default Route.extend(WithAclActions, { this.repo.invalidate(); }, model: function(params) { - this.item = this.repo.create(); - set(this.item, 'Datacenter', this.modelFor('dc').dc.Name); + this.item = this.repo.create({ + Datacenter: this.modelFor('dc').dc.Name, + }); return hash({ create: true, isLoading: false, diff --git a/ui-v2/app/routes/dc/acls/policies/edit.js b/ui-v2/app/routes/dc/acls/policies/edit.js index 9716ceee10b1..6ea8c636517d 100644 --- a/ui-v2/app/routes/dc/acls/policies/edit.js +++ b/ui-v2/app/routes/dc/acls/policies/edit.js @@ -10,12 +10,13 @@ export default SingleRoute.extend(WithPolicyActions, { tokenRepo: service('repository/token'), model: function(params) { const dc = this.modelFor('dc').dc.Name; + const nspace = this.modelFor('nspace').nspace.substr(1); const tokenRepo = this.tokenRepo; return this._super(...arguments).then(model => { return hash({ ...model, ...{ - items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc).catch(function(e) { + items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc, nspace).catch(function(e) { switch (get(e, 'errors.firstObject.status')) { case '403': case '401': diff --git a/ui-v2/app/routes/dc/acls/policies/index.js b/ui-v2/app/routes/dc/acls/policies/index.js index 10e0eae0b138..1c0e05d7bc06 100644 --- a/ui-v2/app/routes/dc/acls/policies/index.js +++ b/ui-v2/app/routes/dc/acls/policies/index.js @@ -13,10 +13,12 @@ export default Route.extend(WithPolicyActions, { }, }, model: function(params) { - const repo = this.repo; return hash({ - ...repo.status({ - items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name), + ...this.repo.status({ + items: this.repo.findAllByDatacenter( + this.modelFor('dc').dc.Name, + this.modelFor('nspace').nspace.substr(1) + ), }), isLoading: false, }); diff --git a/ui-v2/app/routes/dc/acls/roles/edit.js b/ui-v2/app/routes/dc/acls/roles/edit.js index 2eae3e4d0286..a6b13c829047 100644 --- a/ui-v2/app/routes/dc/acls/roles/edit.js +++ b/ui-v2/app/routes/dc/acls/roles/edit.js @@ -10,12 +10,13 @@ export default SingleRoute.extend(WithRoleActions, { tokenRepo: service('repository/token'), model: function(params) { const dc = this.modelFor('dc').dc.Name; + const nspace = this.modelFor('nspace').nspace.substr(1); const tokenRepo = this.tokenRepo; return this._super(...arguments).then(model => { return hash({ ...model, ...{ - items: tokenRepo.findByRole(get(model.item, 'ID'), dc).catch(function(e) { + items: tokenRepo.findByRole(get(model.item, 'ID'), dc, nspace).catch(function(e) { switch (get(e, 'errors.firstObject.status')) { case '403': case '401': diff --git a/ui-v2/app/routes/dc/acls/roles/index.js b/ui-v2/app/routes/dc/acls/roles/index.js index 2503029834e9..caa7dde2e58c 100644 --- a/ui-v2/app/routes/dc/acls/roles/index.js +++ b/ui-v2/app/routes/dc/acls/roles/index.js @@ -13,10 +13,12 @@ export default Route.extend(WithRoleActions, { }, }, model: function(params) { - const repo = this.repo; return hash({ - ...repo.status({ - items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name), + ...this.repo.status({ + items: this.repo.findAllByDatacenter( + this.modelFor('dc').dc.Name, + this.modelFor('nspace').nspace.substr(1) + ), }), isLoading: false, }); diff --git a/ui-v2/app/routes/dc/acls/tokens/index.js b/ui-v2/app/routes/dc/acls/tokens/index.js index 948db80dc57d..1c09e6bfcae0 100644 --- a/ui-v2/app/routes/dc/acls/tokens/index.js +++ b/ui-v2/app/routes/dc/acls/tokens/index.js @@ -24,10 +24,12 @@ export default Route.extend(WithTokenActions, { }); }, model: function(params) { - const repo = this.repo; return hash({ - ...repo.status({ - items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name), + ...this.repo.status({ + items: this.repo.findAllByDatacenter( + this.modelFor('dc').dc.Name, + this.modelFor('nspace').nspace.substr(1) + ), }), isLoading: false, token: this.settings.findBySlug('token'), diff --git a/ui-v2/app/routes/dc/intentions/create.js b/ui-v2/app/routes/dc/intentions/create.js index e848b56e559d..6aac6835b17d 100644 --- a/ui-v2/app/routes/dc/intentions/create.js +++ b/ui-v2/app/routes/dc/intentions/create.js @@ -1,32 +1,38 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; -import { get, set } from '@ember/object'; +import { get } from '@ember/object'; import WithIntentionActions from 'consul-ui/mixins/intention/with-actions'; +// TODO: This route and the edit Route need merging somehow export default Route.extend(WithIntentionActions, { templateName: 'dc/intentions/edit', repo: service('repository/intention'), servicesRepo: service('repository/service'), + nspacesRepo: service('repository/nspace/disabled'), beforeModel: function() { this.repo.invalidate(); }, model: function(params) { - this.item = this.repo.create(); - set(this.item, 'Datacenter', this.modelFor('dc').dc.Name); + const dc = this.modelFor('dc').dc.Name; + const nspace = this.modelFor('nspace').nspace.substr(1); + this.item = this.repo.create({ + Datacenter: dc, + }); return hash({ create: true, isLoading: false, item: this.item, - items: this.servicesRepo.findAllByDatacenter(this.modelFor('dc').dc.Name), - intents: ['allow', 'deny'], + services: this.servicesRepo.findAllByDatacenter(dc, nspace), + nspaces: this.nspacesRepo.findAll(), }).then(function(model) { return { ...model, ...{ - items: [{ Name: '*' }].concat( - model.items.toArray().filter(item => get(item, 'Kind') !== 'connect-proxy') + services: [{ Name: '*' }].concat( + model.services.toArray().filter(item => get(item, 'Kind') !== 'connect-proxy') ), + nspaces: [{ Name: '*' }].concat(model.nspaces.toArray()), }, }; }); diff --git a/ui-v2/app/routes/dc/intentions/edit.js b/ui-v2/app/routes/dc/intentions/edit.js index 3c6f4f569aa4..27f9967e595e 100644 --- a/ui-v2/app/routes/dc/intentions/edit.js +++ b/ui-v2/app/routes/dc/intentions/edit.js @@ -3,24 +3,32 @@ import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; import { get } from '@ember/object'; -import WithAclActions from 'consul-ui/mixins/intention/with-actions'; +import WithIntentionActions from 'consul-ui/mixins/intention/with-actions'; -export default Route.extend(WithAclActions, { +// TODO: This route and the create Route need merging somehow +export default Route.extend(WithIntentionActions, { repo: service('repository/intention'), servicesRepo: service('repository/service'), + nspacesRepo: service('repository/nspace/disabled'), model: function(params) { + const dc = this.modelFor('dc').dc.Name; + // We load all of your services that you are able to see here + // as even if it doesn't exist in the namespace you are targetting + // you may want to add it after you've added the intention + const nspace = '*'; return hash({ isLoading: false, - item: this.repo.findBySlug(params.id, this.modelFor('dc').dc.Name), - items: this.servicesRepo.findAllByDatacenter(this.modelFor('dc').dc.Name), - intents: ['allow', 'deny'], + item: this.repo.findBySlug(params.id, dc, nspace), + services: this.servicesRepo.findAllByDatacenter(dc, nspace), + nspaces: this.nspacesRepo.findAll(), }).then(function(model) { return { ...model, ...{ - items: [{ Name: '*' }].concat( - model.items.toArray().filter(item => get(item, 'Kind') !== 'connect-proxy') + services: [{ Name: '*' }].concat( + model.services.toArray().filter(item => get(item, 'Kind') !== 'connect-proxy') ), + nspaces: [{ Name: '*' }].concat(model.nspaces.toArray()), }, }; }); diff --git a/ui-v2/app/routes/dc/intentions/index.js b/ui-v2/app/routes/dc/intentions/index.js index d90b29cfaa78..47536064cf4f 100644 --- a/ui-v2/app/routes/dc/intentions/index.js +++ b/ui-v2/app/routes/dc/intentions/index.js @@ -14,7 +14,10 @@ export default Route.extend(WithIntentionActions, { }, model: function(params) { return hash({ - items: this.repo.findAllByDatacenter(this.modelFor('dc').dc.Name), + items: this.repo.findAllByDatacenter( + this.modelFor('dc').dc.Name, + this.modelFor('nspace').nspace.substr(1) + ), }); }, setupController: function(controller, model) { diff --git a/ui-v2/app/routes/dc/kv/create.js b/ui-v2/app/routes/dc/kv/create.js index c25e18eef94a..f1b41cf34c3a 100644 --- a/ui-v2/app/routes/dc/kv/create.js +++ b/ui-v2/app/routes/dc/kv/create.js @@ -1,7 +1,7 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; -import { get, set } from '@ember/object'; +import { get } from '@ember/object'; import WithKvActions from 'consul-ui/mixins/kv/with-actions'; export default Route.extend(WithKvActions, { @@ -12,15 +12,17 @@ export default Route.extend(WithKvActions, { }, model: function(params) { const key = params.key || '/'; - const repo = this.repo; const dc = this.modelFor('dc').dc.Name; - this.item = repo.create(); - set(this.item, 'Datacenter', dc); + const nspace = this.modelFor('nspace').nspace.substr(1); + this.item = this.repo.create({ + Datacenter: dc, + Namespace: nspace, + }); return hash({ create: true, isLoading: false, item: this.item, - parent: repo.findBySlug(key, dc), + parent: this.repo.findBySlug(key, dc, nspace), }); }, setupController: function(controller, model) { diff --git a/ui-v2/app/routes/dc/kv/edit.js b/ui-v2/app/routes/dc/kv/edit.js index bf763f4663dd..5e9652810eab 100644 --- a/ui-v2/app/routes/dc/kv/edit.js +++ b/ui-v2/app/routes/dc/kv/edit.js @@ -12,11 +12,11 @@ export default Route.extend(WithKvActions, { model: function(params) { const key = params.key; const dc = this.modelFor('dc').dc.Name; - const repo = this.repo; + const nspace = this.modelFor('nspace').nspace.substr(1); return hash({ isLoading: false, - parent: repo.findBySlug(ascend(key, 1) || '/', dc), - item: repo.findBySlug(key, dc), + parent: this.repo.findBySlug(ascend(key, 1) || '/', dc, nspace), + item: this.repo.findBySlug(key, dc, nspace), session: null, }).then(model => { // TODO: Consider loading this after initial page load @@ -25,7 +25,7 @@ export default Route.extend(WithKvActions, { return hash({ ...model, ...{ - session: this.sessionRepo.findByKey(session, dc), + session: this.sessionRepo.findByKey(session, dc, nspace), }, }); } diff --git a/ui-v2/app/routes/dc/kv/index.js b/ui-v2/app/routes/dc/kv/index.js index 1a7324401a77..f34b4a051e85 100644 --- a/ui-v2/app/routes/dc/kv/index.js +++ b/ui-v2/app/routes/dc/kv/index.js @@ -25,15 +25,15 @@ export default Route.extend(WithKvActions, { model: function(params) { let key = params.key || '/'; const dc = this.modelFor('dc').dc.Name; - const repo = this.repo; + const nspace = this.modelFor('nspace').nspace.substr(1); return hash({ isLoading: false, - parent: repo.findBySlug(key, dc), + parent: this.repo.findBySlug(key, dc, nspace), }).then(model => { return hash({ ...model, ...{ - items: repo.findAllBySlug(get(model.parent, 'Key'), dc).catch(e => { + items: this.repo.findAllBySlug(get(model.parent, 'Key'), dc, nspace).catch(e => { const status = get(e, 'errors.firstObject.status'); switch (status) { case '403': diff --git a/ui-v2/app/routes/dc/nodes/index.js b/ui-v2/app/routes/dc/nodes/index.js index eed05db68889..875eeb84c651 100644 --- a/ui-v2/app/routes/dc/nodes/index.js +++ b/ui-v2/app/routes/dc/nodes/index.js @@ -13,7 +13,7 @@ export default Route.extend({ model: function(params) { const dc = this.modelFor('dc').dc.Name; return hash({ - items: this.repo.findAllByDatacenter(dc), + items: this.repo.findAllByDatacenter(dc, this.modelFor('nspace').nspace.substr(1)), leader: this.repo.findByLeader(dc), }); }, diff --git a/ui-v2/app/routes/dc/nodes/show.js b/ui-v2/app/routes/dc/nodes/show.js index 1776fdab3196..9e4295b7a435 100644 --- a/ui-v2/app/routes/dc/nodes/show.js +++ b/ui-v2/app/routes/dc/nodes/show.js @@ -1,7 +1,6 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { hash } from 'rsvp'; -import { get } from '@ember/object'; import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions'; @@ -17,11 +16,12 @@ export default Route.extend(WithBlockingActions, { }, model: function(params) { const dc = this.modelFor('dc').dc.Name; + const nspace = this.modelFor('nspace').nspace.substr(1); const name = params.name; return hash({ - item: this.repo.findBySlug(name, dc), + item: this.repo.findBySlug(name, dc, nspace), + sessions: this.sessionRepo.findByNode(name, dc, nspace), tomography: this.coordinateRepo.findAllByNode(name, dc), - sessions: this.sessionRepo.findByNode(name, dc), }); }, setupController: function(controller, model) { @@ -30,12 +30,11 @@ export default Route.extend(WithBlockingActions, { actions: { invalidateSession: function(item) { const dc = this.modelFor('dc').dc.Name; + const nspace = this.modelFor('nspace').nspace.substr(1); const controller = this.controller; - const repo = this.sessionRepo; return this.feedback.execute(() => { - const node = get(item, 'Node'); - return repo.remove(item).then(() => { - return repo.findByNode(node, dc).then(function(sessions) { + return this.sessionRepo.remove(item).then(() => { + return this.sessionRepo.findByNode(item.Node, dc, nspace).then(function(sessions) { controller.setProperties({ sessions: sessions, }); diff --git a/ui-v2/app/routes/dc/services/index.js b/ui-v2/app/routes/dc/services/index.js index 43a971c8fc91..9a1768a11aef 100644 --- a/ui-v2/app/routes/dc/services/index.js +++ b/ui-v2/app/routes/dc/services/index.js @@ -15,7 +15,6 @@ export default Route.extend({ }, }, model: function(params) { - const repo = this.repo; let terms = params.s || ''; // we check for the old style `status` variable here // and convert it to the new style filter=status:critical @@ -32,7 +31,10 @@ export default Route.extend({ } return hash({ terms: terms !== '' ? terms.split('\n') : [], - items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name), + items: this.repo.findAllByDatacenter( + this.modelFor('dc').dc.Name, + this.modelFor('nspace').nspace.substr(1) + ), }); }, setupController: function(controller, model) { diff --git a/ui-v2/app/routes/dc/services/instance.js b/ui-v2/app/routes/dc/services/instance.js index 57b9cb610cf9..40db8a5e4351 100644 --- a/ui-v2/app/routes/dc/services/instance.js +++ b/ui-v2/app/routes/dc/services/instance.js @@ -7,12 +7,11 @@ export default Route.extend({ repo: service('repository/service'), proxyRepo: service('repository/proxy'), model: function(params) { - const repo = this.repo; - const proxyRepo = this.proxyRepo; const dc = this.modelFor('dc').dc.Name; + const nspace = this.modelFor('nspace').nspace.substr(1); return hash({ - item: repo.findInstanceBySlug(params.id, params.node, params.name, dc), - }).then(function(model) { + item: this.repo.findInstanceBySlug(params.id, params.node, params.name, dc, nspace), + }).then(model => { // this will not be run in a blocking loop, but this is ok as // its highly unlikely that a service will suddenly change to being a // connect-proxy or vice versa so leave as is for now @@ -21,7 +20,7 @@ export default Route.extend({ // proxies and mesh-gateways can't have proxies themselves so don't even look ['connect-proxy', 'mesh-gateway'].includes(get(model.item, 'Kind')) ? null - : proxyRepo.findInstanceBySlug(params.id, params.node, params.name, dc), + : this.proxyRepo.findInstanceBySlug(params.id, params.node, params.name, dc, nspace), ...model, }); }); diff --git a/ui-v2/app/routes/dc/services/show.js b/ui-v2/app/routes/dc/services/show.js index 1c5adc9c8eb3..33907a4acf68 100644 --- a/ui-v2/app/routes/dc/services/show.js +++ b/ui-v2/app/routes/dc/services/show.js @@ -12,12 +12,10 @@ export default Route.extend({ }, }, model: function(params) { - const repo = this.repo; - const settings = this.settings; const dc = this.modelFor('dc').dc.Name; return hash({ - item: repo.findBySlug(params.name, dc), - urls: settings.findBySlug('urls'), + item: this.repo.findBySlug(params.name, dc, this.modelFor('nspace').nspace.substr(1)), + urls: this.settings.findBySlug('urls'), dc: dc, }); }, diff --git a/ui-v2/app/routing/single.js b/ui-v2/app/routing/single.js index 7e2e3e8533f1..29e252d95eda 100644 --- a/ui-v2/app/routing/single.js +++ b/ui-v2/app/routing/single.js @@ -13,6 +13,7 @@ export default Route.extend({ typeof repo !== 'undefined' ); const dc = this.modelFor('dc').dc.Name; + const nspace = this.modelFor('nspace').nspace.substr(1); const create = this.isCreate(...arguments); return hash({ isLoading: false, @@ -20,8 +21,13 @@ export default Route.extend({ create: create, ...repo.status({ item: create - ? Promise.resolve(repo.create({ Datacenter: dc })) - : repo.findBySlug(params.id, dc), + ? Promise.resolve( + repo.create({ + Datacenter: dc, + Namespace: nspace, + }) + ) + : repo.findBySlug(params.id, dc, nspace), }), }); }, diff --git a/ui-v2/app/services/repository.js b/ui-v2/app/services/repository.js index dbf51ddc6a09..7cbc88e916c0 100644 --- a/ui-v2/app/services/repository.js +++ b/ui-v2/app/services/repository.js @@ -13,18 +13,20 @@ export default Service.extend({ }, // store: service('store'), - findAllByDatacenter: function(dc, configuration = {}) { + findAllByDatacenter: function(dc, nspace, configuration = {}) { const query = { dc: dc, + ns: nspace, }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; } return this.store.query(this.getModelName(), query); }, - findBySlug: function(slug, dc, configuration = {}) { + findBySlug: function(slug, dc, nspace, configuration = {}) { const query = { dc: dc, + ns: nspace, id: slug, }; if (typeof configuration.cursor !== 'undefined') { @@ -44,6 +46,9 @@ export default Service.extend({ if (typeof obj.destroyRecord === 'undefined') { item = obj.get('data'); } + // TODO: Change this to use vanilla JS + // I think this was originally looking for a plain object + // as opposed to an ember one if (typeOf(item) === 'object') { item = this.store.peekRecord(this.getModelName(), item[this.getPrimaryKey()]); } @@ -52,6 +57,7 @@ export default Service.extend({ }); }, invalidate: function() { + // TODO: This should probably return a Promise this.store.unloadAll(this.getModelName()); }, }); diff --git a/ui-v2/app/services/repository/coordinate.js b/ui-v2/app/services/repository/coordinate.js index 75b3f2f1f7dc..f20d701850a9 100644 --- a/ui-v2/app/services/repository/coordinate.js +++ b/ui-v2/app/services/repository/coordinate.js @@ -10,8 +10,8 @@ export default RepositoryService.extend({ getModelName: function() { return modelName; }, - findAllByNode: function(node, dc, configuration) { - return this.findAllByDatacenter(dc, configuration).then(function(coordinates) { + findAllByNode: function(node, dc, nspace, configuration) { + return this.findAllByDatacenter(dc, nspace, configuration).then(function(coordinates) { let results = {}; if (get(coordinates, 'length') > 1) { results = tomography(node, coordinates.map(item => get(item, 'data'))); diff --git a/ui-v2/app/services/repository/dc.js b/ui-v2/app/services/repository/dc.js index 84b97d8378aa..74c9f6b756cf 100644 --- a/ui-v2/app/services/repository/dc.js +++ b/ui-v2/app/services/repository/dc.js @@ -11,6 +11,7 @@ export default RepositoryService.extend({ }, findAll: function() { return this.store.findAll(this.getModelName()).then(function(items) { + // TODO: Move to view/template return items.sortBy('Name'); }); }, diff --git a/ui-v2/app/services/repository/kv.js b/ui-v2/app/services/repository/kv.js index 831281964fdd..2e3664580565 100644 --- a/ui-v2/app/services/repository/kv.js +++ b/ui-v2/app/services/repository/kv.js @@ -1,7 +1,7 @@ import RepositoryService from 'consul-ui/services/repository'; import { Promise } from 'rsvp'; import isFolder from 'consul-ui/utils/isFolder'; -import { get, set } from '@ember/object'; +import { get } from '@ember/object'; import { PRIMARY_KEY } from 'consul-ui/models/kv'; const modelName = 'kv'; @@ -13,20 +13,26 @@ export default RepositoryService.extend({ return PRIMARY_KEY; }, // this one gives you the full object so key,values and meta - findBySlug: function(key, dc, configuration = {}) { + findBySlug: function(key, dc, nspace, configuration = {}) { if (isFolder(key)) { - const id = JSON.stringify([dc, key]); + // TODO: This very much shouldn't be here, + // needs to eventually use ember-datas generateId thing + // in the meantime at least our fingerprinter + const id = JSON.stringify([nspace, dc, key]); let item = this.store.peekRecord(this.getModelName(), id); if (!item) { - item = this.create(); - set(item, 'Key', key); - set(item, 'Datacenter', dc); + item = this.create({ + Key: key, + Datacenter: dc, + Namespace: nspace, + }); } return Promise.resolve(item); } const query = { id: key, dc: dc, + ns: nspace, }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; @@ -35,13 +41,14 @@ export default RepositoryService.extend({ }, // this one only gives you keys // https://www.consul.io/api/kv.html - findAllBySlug: function(key, dc, configuration = {}) { + findAllBySlug: function(key, dc, nspace, configuration = {}) { if (key === '/') { key = ''; } const query = { id: key, dc: dc, + ns: nspace, separator: '/', }; if (typeof configuration.cursor !== 'undefined') { @@ -55,7 +62,13 @@ export default RepositoryService.extend({ }); }) .catch(e => { - if (e.errors && e.errors[0] && e.errors[0].status == '404') { + // TODO: Double check this was loose on purpose, its probably as we were unsure of + // type of ember-data error.Status at first, we could probably change this + // to `===` now + if (get(e, 'errors.firstObject.status') == '404') { + // TODO: This very much shouldn't be here, + // needs to eventually use ember-datas generateId thing + // in the meantime at least our fingerprinter const id = JSON.stringify([dc, key]); const record = this.store.peekRecord(this.getModelName(), id); if (record) { diff --git a/ui-v2/app/services/repository/proxy.js b/ui-v2/app/services/repository/proxy.js index b760bc379db9..5c779a2723d4 100644 --- a/ui-v2/app/services/repository/proxy.js +++ b/ui-v2/app/services/repository/proxy.js @@ -9,9 +9,10 @@ export default RepositoryService.extend({ getPrimaryKey: function() { return PRIMARY_KEY; }, - findAllBySlug: function(slug, dc, configuration = {}) { + findAllBySlug: function(slug, dc, nspace, configuration = {}) { const query = { id: slug, + ns: nspace, dc: dc, }; if (typeof configuration.cursor !== 'undefined') { @@ -19,8 +20,8 @@ export default RepositoryService.extend({ } return this.store.query(this.getModelName(), query); }, - findInstanceBySlug: function(id, node, slug, dc, configuration) { - return this.findAllBySlug(slug, dc, configuration).then(function(items) { + findInstanceBySlug: function(id, node, slug, dc, nspace, configuration) { + return this.findAllBySlug(slug, dc, nspace, configuration).then(function(items) { let res = {}; if (get(items, 'length') > 0) { let instance = items.filterBy('ServiceProxy.DestinationServiceID', id).findBy('Node', node); diff --git a/ui-v2/app/services/repository/service.js b/ui-v2/app/services/repository/service.js index 7b85acf54e7b..48bb92c1beda 100644 --- a/ui-v2/app/services/repository/service.js +++ b/ui-v2/app/services/repository/service.js @@ -7,6 +7,7 @@ export default RepositoryService.extend({ }, findBySlug: function(slug, dc) { return this._super(...arguments).then(function(item) { + // TODO: Move this to the Serializer const nodes = get(item, 'Nodes'); if (nodes.length === 0) { // TODO: Add an store.error("404", "message") or similar @@ -21,6 +22,7 @@ export default RepositoryService.extend({ throw e; } const service = get(nodes, 'firstObject'); + // TODO: Use [...new Set()] instead of uniq const tags = nodes .reduce(function(prev, item) { return prev.concat(get(item, 'Service.Tags') || []); @@ -29,11 +31,13 @@ export default RepositoryService.extend({ set(service, 'Tags', tags); set(service, 'Nodes', nodes); set(service, 'meta', get(item, 'meta')); + set(service, 'Namespace', get(item, 'Namespace')); return service; }); }, findInstanceBySlug: function(id, node, slug, dc, configuration) { return this.findBySlug(slug, dc, configuration).then(function(item) { + // TODO: Move this to the Serializer // Loop through all the service instances and pick out the one // that has the same service id AND node name // node names are unique per datacenter @@ -50,6 +54,7 @@ export default RepositoryService.extend({ return item.ServiceID == ''; }); set(service, 'meta', get(item, 'meta')); + set(service, 'Namespace', get(item, 'Namespace')); return service; } // TODO: Add an store.error("404", "message") or similar diff --git a/ui-v2/app/services/repository/session.js b/ui-v2/app/services/repository/session.js index 75fe3fc8fa4b..acf11f14d5fc 100644 --- a/ui-v2/app/services/repository/session.js +++ b/ui-v2/app/services/repository/session.js @@ -7,10 +7,11 @@ export default RepositoryService.extend({ getModelName: function() { return modelName; }, - findByNode: function(node, dc, configuration = {}) { + findByNode: function(node, dc, nspace, configuration = {}) { const query = { id: node, dc: dc, + ns: nspace, }; if (typeof configuration.cursor !== 'undefined') { query.index = configuration.cursor; @@ -18,7 +19,7 @@ export default RepositoryService.extend({ return this.store.query(this.getModelName(), query); }, // TODO: Why Key? Probably should be findBySlug like the others - findByKey: function(slug, dc) { - return this.findBySlug(slug, dc); + findByKey: function(slug, dc, nspace) { + return this.findBySlug(...arguments); }, }); diff --git a/ui-v2/app/services/repository/token.js b/ui-v2/app/services/repository/token.js index 2ebf92a94511..6ec3b6803519 100644 --- a/ui-v2/app/services/repository/token.js +++ b/ui-v2/app/services/repository/token.js @@ -43,16 +43,18 @@ export default RepositoryService.extend({ clone: function(item) { return this.store.clone(this.getModelName(), get(item, PRIMARY_KEY)); }, - findByPolicy: function(id, dc) { + findByPolicy: function(id, dc, nspace) { return this.store.query(this.getModelName(), { policy: id, dc: dc, + ns: nspace, }); }, - findByRole: function(id, dc) { + findByRole: function(id, dc, nspace) { return this.store.query(this.getModelName(), { role: id, dc: dc, + ns: nspace, }); }, }); From 8800e09082bd3e1ae380aaca51d0ac4004700204 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:25:58 +0000 Subject: [PATCH 09/61] Amend repo integration tests to add nspaces --- .../services/repository/acl-test.js | 11 +- .../services/repository/coordinate-test.js | 5 +- .../services/repository/intention-test.js | 9 +- .../services/repository/kv-test.js | 112 ++++++----- .../services/repository/node-test.js | 5 +- .../services/repository/policy-test.js | 114 ++++++----- .../services/repository/role-test.js | 114 ++++++----- .../services/repository/service-test.js | 160 ++++++++------- .../services/repository/session-test.js | 120 ++++++----- .../services/repository/token-test.js | 188 +++++++++++++----- 10 files changed, 495 insertions(+), 343 deletions(-) diff --git a/ui-v2/tests/integration/services/repository/acl-test.js b/ui-v2/tests/integration/services/repository/acl-test.js index f5b707e0bb35..5883ab4f1842 100644 --- a/ui-v2/tests/integration/services/repository/acl-test.js +++ b/ui-v2/tests/integration/services/repository/acl-test.js @@ -3,9 +3,10 @@ import repo from 'consul-ui/tests/helpers/repo'; const NAME = 'acl'; moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { // Specify the other units that are required for this test. - integration: true + integration: true, }); const dc = 'dc-1'; +const nspace = 'default'; const id = 'token-name'; test('findByDatacenter returns the correct data for list endpoint', function(assert) { return repo( @@ -27,7 +28,9 @@ test('findByDatacenter returns the correct data for list endpoint', function(ass return payload.map(item => Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + uid: `["${nspace}","${dc}","${item.ID}"]`, }) ); }) @@ -53,7 +56,9 @@ test('findBySlug returns the correct data for item endpoint', function(assert) { const item = payload[0]; return Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + // TODO: default isn't required here, once we've + // refactored out our Serializer this can go + uid: `["${nspace}","${dc}","${item.ID}"]`, }); }) ); diff --git a/ui-v2/tests/integration/services/repository/coordinate-test.js b/ui-v2/tests/integration/services/repository/coordinate-test.js index ff7d0994ceb6..dcbba7398817 100644 --- a/ui-v2/tests/integration/services/repository/coordinate-test.js +++ b/ui-v2/tests/integration/services/repository/coordinate-test.js @@ -8,6 +8,7 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { }); const dc = 'dc-1'; +const nspace = 'default'; const now = new Date().getTime(); test('findAllByDatacenter returns the correct data for list endpoint', function(assert) { get(this.subject(), 'store').serializerFor(NAME).timestamp = function() { @@ -33,7 +34,9 @@ test('findAllByDatacenter returns the correct data for list endpoint', function( Object.assign({}, item, { SyncTime: now, Datacenter: dc, - uid: `["${dc}","${item.Node}"]`, + // TODO: nspace isn't required here, once we've + // refactored out our Serializer this can go + uid: `["${nspace}","${dc}","${item.Node}"]`, }) ); }) diff --git a/ui-v2/tests/integration/services/repository/intention-test.js b/ui-v2/tests/integration/services/repository/intention-test.js index 59797baa41d7..f6ccdd153e96 100644 --- a/ui-v2/tests/integration/services/repository/intention-test.js +++ b/ui-v2/tests/integration/services/repository/intention-test.js @@ -7,6 +7,7 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { const dc = 'dc-1'; const id = 'token-name'; +const nspace = 'default'; test('findAllByDatacenter returns the correct data for list endpoint', function(assert) { return repo( 'Intention', @@ -29,7 +30,9 @@ test('findAllByDatacenter returns the correct data for list endpoint', function( CreatedAt: new Date(item.CreatedAt), UpdatedAt: new Date(item.UpdatedAt), Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + // TODO: nspace isn't required here, once we've + // refactored out our Serializer this can go + uid: `["${nspace}","${dc}","${item.ID}"]`, }) ); }) @@ -57,7 +60,9 @@ test('findBySlug returns the correct data for item endpoint', function(assert) { CreatedAt: new Date(item.CreatedAt), UpdatedAt: new Date(item.UpdatedAt), Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + // TODO: nspace isn't required here, once we've + // refactored out our Serializer this can go + uid: `["${nspace}","${dc}","${item.ID}"]`, }); }) ); diff --git a/ui-v2/tests/integration/services/repository/kv-test.js b/ui-v2/tests/integration/services/repository/kv-test.js index a1b48badc41d..98bd9b55ae29 100644 --- a/ui-v2/tests/integration/services/repository/kv-test.js +++ b/ui-v2/tests/integration/services/repository/kv-test.js @@ -7,57 +7,65 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { }); const dc = 'dc-1'; const id = 'key-name'; -test('findAllBySlug returns the correct data for list endpoint', function(assert) { - return repo( - 'Kv', - 'findAllBySlug', - this.subject(), - function retrieveTest(stub) { - return stub(`/v1/kv/${id}?keys&dc=${dc}`, { - CONSUL_KV_COUNT: '1', - }); - }, - function performTest(service) { - return service.findAllBySlug(id, dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - return payload.map(item => { - return { +const undefinedNspace = 'default'; +[undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`findAllBySlug returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + return repo( + 'Kv', + 'findAllBySlug', + this.subject(), + function retrieveTest(stub) { + return stub( + `/v1/kv/${id}?keys&dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + { + CONSUL_KV_COUNT: '1', + } + ); + }, + function performTest(service) { + return service.findAllBySlug(id, dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => { + return { + Datacenter: dc, + Namespace: nspace || undefinedNspace, + uid: `["${nspace || undefinedNspace}","${dc}","${item}"]`, + Key: item, + }; + }); + }) + ); + } + ); + }); + test(`findBySlug returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) { + return repo( + 'Kv', + 'findBySlug', + this.subject(), + function(stub) { + return stub(`/v1/kv/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`); + }, + function(service) { + return service.findBySlug(id, dc, nspace || undefinedNspace); + }, + function(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload[0]; + return Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item}"]`, - Key: item, - }; - }); - }) - ); - } - ); -}); -test('findAllBySlug returns the correct data for item endpoint', function(assert) { - return repo( - 'Kv', - 'findAllBySlug', - this.subject(), - function(stub) { - return stub(`/v1/kv/${id}?dc=${dc}`); - }, - function(service) { - return service.findBySlug(id, dc); - }, - function(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - const item = payload[0]; - return Object.assign({}, item, { - Datacenter: dc, - uid: `["${dc}","${item.Key}"]`, - }); - }) - ); - } - ); + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.Key}"]`, + }); + }) + ); + } + ); + }); }); diff --git a/ui-v2/tests/integration/services/repository/node-test.js b/ui-v2/tests/integration/services/repository/node-test.js index af5e9ee397e4..9f32a8bedc20 100644 --- a/ui-v2/tests/integration/services/repository/node-test.js +++ b/ui-v2/tests/integration/services/repository/node-test.js @@ -10,6 +10,7 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { const dc = 'dc-1'; const id = 'token-name'; const now = new Date().getTime(); +const nspace = 'default'; test('findByDatacenter returns the correct data for list endpoint', function(assert) { get(this.subject(), 'store').serializerFor(NAME).timestamp = function() { return now; @@ -34,7 +35,7 @@ test('findByDatacenter returns the correct data for list endpoint', function(ass Object.assign({}, item, { SyncTime: now, Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + uid: `["${nspace}","${dc}","${item.ID}"]`, }) ); }) @@ -60,7 +61,7 @@ test('findBySlug returns the correct data for item endpoint', function(assert) { const item = payload; return Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + uid: `["${nspace}","${dc}","${item.ID}"]`, meta: { cursor: undefined, }, diff --git a/ui-v2/tests/integration/services/repository/policy-test.js b/ui-v2/tests/integration/services/repository/policy-test.js index 96fd154c5b8c..fa58bd04ad33 100644 --- a/ui-v2/tests/integration/services/repository/policy-test.js +++ b/ui-v2/tests/integration/services/repository/policy-test.js @@ -5,59 +5,69 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { // Specify the other units that are required for this test. integration: true, }); +skip('translate returns the correct data for the translate endpoint'); const dc = 'dc-1'; const id = 'policy-name'; -test('findByDatacenter returns the correct data for list endpoint', function(assert) { - return repo( - 'Policy', - 'findAllByDatacenter', - this.subject(), - function retrieveStub(stub) { - return stub(`/v1/acl/policies?dc=${dc}`, { - CONSUL_POLICY_COUNT: '100', - }); - }, - function performTest(service) { - return service.findAllByDatacenter(dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - return payload.map(item => - Object.assign({}, item, { +const undefinedNspace = 'default'; +[undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`findByDatacenter returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + return repo( + 'Policy', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/acl/policies?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + { + CONSUL_POLICY_COUNT: '100', + } + ); + }, + function performTest(service) { + return service.findAllByDatacenter(dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, + }) + ); + }) + ); + } + ); + }); + test(`findBySlug returns the correct data for item endpoint when the nspace is ${nspace}`, function(assert) { + return repo( + 'Policy', + 'findBySlug', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/acl/policy/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}` + ); + }, + function performTest(service) { + return service.findBySlug(id, dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload; + return Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, - }) - ); - }) - ); - } - ); + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, + }); + }) + ); + } + ); + }); }); -test('findBySlug returns the correct data for item endpoint', function(assert) { - return repo( - 'Policy', - 'findBySlug', - this.subject(), - function retrieveStub(stub) { - return stub(`/v1/acl/policy/${id}?dc=${dc}`); - }, - function performTest(service) { - return service.findBySlug(id, dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - const item = payload; - return Object.assign({}, item, { - Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, - }); - }) - ); - } - ); -}); -skip('translate returns the correct data for the translate enpoint'); diff --git a/ui-v2/tests/integration/services/repository/role-test.js b/ui-v2/tests/integration/services/repository/role-test.js index bcaaeb7d6ecb..faed1751627f 100644 --- a/ui-v2/tests/integration/services/repository/role-test.js +++ b/ui-v2/tests/integration/services/repository/role-test.js @@ -9,58 +9,68 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { }); const dc = 'dc-1'; const id = 'role-name'; -test('findByDatacenter returns the correct data for list endpoint', function(assert) { - return repo( - 'Role', - 'findAllByDatacenter', - this.subject(), - function retrieveStub(stub) { - return stub(`/v1/acl/roles?dc=${dc}`, { - CONSUL_ROLE_COUNT: '100', - }); - }, - function performTest(service) { - return service.findAllByDatacenter(dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - return payload.map(item => - Object.assign({}, item, { +const undefinedNspace = 'default'; +[undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`findByDatacenter returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + return repo( + 'Role', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/acl/roles?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + { + CONSUL_ROLE_COUNT: '100', + } + ); + }, + function performTest(service) { + return service.findAllByDatacenter(dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, + Policies: createPolicies(item), + }) + ); + }) + ); + } + ); + }); + test(`findBySlug returns the correct data for item endpoint when the nspace is ${nspace}`, function(assert) { + return repo( + 'Role', + 'findBySlug', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/acl/role/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}` + ); + }, + function performTest(service) { + return service.findBySlug(id, dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload; + return Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, Policies: createPolicies(item), - }) - ); - }) - ); - } - ); -}); -test('findBySlug returns the correct data for item endpoint', function(assert) { - return repo( - 'Role', - 'findBySlug', - this.subject(), - function retrieveStub(stub) { - return stub(`/v1/acl/role/${id}?dc=${dc}`); - }, - function performTest(service) { - return service.findBySlug(id, dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - const item = payload; - return Object.assign({}, item, { - Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, - Policies: createPolicies(item), - }); - }) - ); - } - ); + }); + }) + ); + } + ); + }); }); diff --git a/ui-v2/tests/integration/services/repository/service-test.js b/ui-v2/tests/integration/services/repository/service-test.js index ce39339c85e6..4487b42e49a6 100644 --- a/ui-v2/tests/integration/services/repository/service-test.js +++ b/ui-v2/tests/integration/services/repository/service-test.js @@ -7,80 +7,96 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { // Specify the other units that are required for this test. integration: true, }); +skip('findBySlug returns a sane tree'); const dc = 'dc-1'; const id = 'token-name'; const now = new Date().getTime(); -test('findByDatacenter returns the correct data for list endpoint', function(assert) { - get(this.subject(), 'store').serializerFor(NAME).timestamp = function() { - return now; - }; - return repo( - 'Service', - 'findAllByDatacenter', - this.subject(), - function retrieveStub(stub) { - return stub(`/v1/internal/ui/services?dc=${dc}`, { - CONSUL_SERVICE_COUNT: '100', - }); - }, - function performTest(service) { - return service.findAllByDatacenter(dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - return payload.map(item => - Object.assign({}, item, { - SyncTime: now, - Datacenter: dc, - uid: `["${dc}","${item.Name}"]`, - }) - ); - }) - ); - } - ); -}); -skip('findBySlug returns a sane tree'); -test('findBySlug returns the correct data for item endpoint', function(assert) { - return repo( - 'Service', - 'findBySlug', - this.subject(), - function(stub) { - return stub(`/v1/health/service/${id}?dc=${dc}`, { - CONSUL_NODE_COUNT: 1, - }); - }, - function(service) { - return service.findBySlug(id, dc); - }, - function(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - // TODO: So this tree is all 'wrong', it's not having any major impact - // this this tree needs revisting to something that makes more sense - payload = Object.assign( - {}, - { Nodes: payload }, - { - Datacenter: dc, - uid: `["${dc}","${id}"]`, - } - ); - const nodes = payload.Nodes; - const service = payload.Nodes[0]; - service.Nodes = nodes; - service.Tags = [...new Set(payload.Nodes[0].Service.Tags)]; - service.meta = { - cursor: undefined, - }; +const undefinedNspace = 'default'; +[undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`findByDatacenter returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + get(this.subject(), 'store').serializerFor(NAME).timestamp = function() { + return now; + }; + return repo( + 'Service', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/internal/ui/services?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`, + { + CONSUL_SERVICE_COUNT: '100', + } + ); + }, + function performTest(service) { + return service.findAllByDatacenter(dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + SyncTime: now, + Datacenter: dc, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.Name}"]`, + }) + ); + }) + ); + } + ); + }); + test(`findBySlug returns the correct data for item endpoint when the nspace is ${nspace}`, function(assert) { + return repo( + 'Service', + 'findBySlug', + this.subject(), + function(stub) { + return stub( + `/v1/health/service/${id}?dc=${dc}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`, + { + CONSUL_NODE_COUNT: 1, + } + ); + }, + function(service) { + return service.findBySlug(id, dc, nspace || undefinedNspace); + }, + function(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + // TODO: So this tree is all 'wrong', it's not having any major impact + // this this tree needs revisting to something that makes more sense + payload = Object.assign( + {}, + { Nodes: payload }, + { + Datacenter: dc, + Namespace: payload[0].Service.Namespace || undefinedNspace, + uid: `["${payload[0].Service.Namespace || undefinedNspace}","${dc}","${id}"]`, + } + ); + const nodes = payload.Nodes; + const service = payload.Nodes[0]; + service.Nodes = nodes; + service.Tags = [...new Set(payload.Nodes[0].Service.Tags)]; + service.Namespace = payload.Namespace; + service.meta = { + cursor: undefined, + }; - return service; - }) - ); - } - ); + return service; + }) + ); + } + ); + }); }); diff --git a/ui-v2/tests/integration/services/repository/session-test.js b/ui-v2/tests/integration/services/repository/session-test.js index cbdb20849743..300252a31358 100644 --- a/ui-v2/tests/integration/services/repository/session-test.js +++ b/ui-v2/tests/integration/services/repository/session-test.js @@ -10,60 +10,70 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { const dc = 'dc-1'; const id = 'node-name'; const now = new Date().getTime(); -test('findByNode returns the correct data for list endpoint', function(assert) { - get(this.subject(), 'store').serializerFor(NAME).timestamp = function() { - return now; - }; - return repo( - 'Session', - 'findByNode', - this.subject(), - function retrieveStub(stub) { - return stub(`/v1/session/node/${id}?dc=${dc}`, { - CONSUL_SESSION_COUNT: '100', - }); - }, - function performTest(service) { - return service.findByNode(id, dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - return payload.map(item => - Object.assign({}, item, { - SyncTime: now, +const undefinedNspace = 'default'; +[undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`findByNode returns the correct data for list endpoint when the nspace is ${nspace}`, function(assert) { + get(this.subject(), 'store').serializerFor(NAME).timestamp = function() { + return now; + }; + return repo( + 'Session', + 'findByNode', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/session/node/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + { + CONSUL_SESSION_COUNT: '100', + } + ); + }, + function performTest(service) { + return service.findByNode(id, dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + SyncTime: now, + Datacenter: dc, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, + }) + ); + }) + ); + } + ); + }); + test(`findByKey returns the correct data for item endpoint when the nspace is ${nspace}`, function(assert) { + return repo( + 'Session', + 'findByKey', + this.subject(), + function(stub) { + return stub( + `/v1/session/info/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}` + ); + }, + function(service) { + return service.findByKey(id, dc, nspace || undefinedNspace); + }, + function(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload[0]; + return Object.assign({}, item, { Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, - }) - ); - }) - ); - } - ); -}); -test('findByKey returns the correct data for item endpoint', function(assert) { - return repo( - 'Session', - 'findByKey', - this.subject(), - function(stub) { - return stub(`/v1/session/info/${id}?dc=${dc}`); - }, - function(service) { - return service.findByKey(id, dc); - }, - function(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - const item = payload[0]; - return Object.assign({}, item, { - Datacenter: dc, - uid: `["${dc}","${item.ID}"]`, - }); - }) - ); - } - ); + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.ID}"]`, + }); + }) + ); + } + ); + }); }); diff --git a/ui-v2/tests/integration/services/repository/token-test.js b/ui-v2/tests/integration/services/repository/token-test.js index 1f73805f2594..a4acc5f8adf9 100644 --- a/ui-v2/tests/integration/services/repository/token-test.js +++ b/ui-v2/tests/integration/services/repository/token-test.js @@ -7,63 +7,147 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, { // Specify the other units that are required for this test. integration: true, }); +skip('clone returns the correct data for the clone endpoint'); const dc = 'dc-1'; const id = 'token-id'; -test('findByDatacenter returns the correct data for list endpoint', function(assert) { - return repo( - 'Token', - 'findAllByDatacenter', - this.subject(), - function retrieveStub(stub) { - return stub(`/v1/acl/tokens?dc=${dc}`, { - CONSUL_TOKEN_COUNT: '100', - }); - }, - function performTest(service) { - return service.findAllByDatacenter(dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - return payload.map(function(item) { +const undefinedNspace = 'default'; +[undefinedNspace, 'team-1', undefined].forEach(nspace => { + test(`findByDatacenter returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) { + return repo( + 'Token', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/acl/tokens?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}`, + { + CONSUL_TOKEN_COUNT: '100', + } + ); + }, + function performTest(service) { + return service.findAllByDatacenter(dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(function(item) { + return Object.assign({}, item, { + Datacenter: dc, + CreateTime: new Date(item.CreateTime), + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.AccessorID}"]`, + Policies: createPolicies(item), + }); + }); + }) + ); + } + ); + }); + test(`findBySlug returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) { + return repo( + 'Token', + 'findBySlug', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/acl/token/${id}?dc=${dc}${typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``}` + ); + }, + function performTest(service) { + return service.findBySlug(id, dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload; return Object.assign({}, item, { Datacenter: dc, CreateTime: new Date(item.CreateTime), - uid: `["${dc}","${item.AccessorID}"]`, + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.AccessorID}"]`, Policies: createPolicies(item), }); - }); - }) - ); - } - ); -}); -test('findBySlug returns the correct data for item endpoint', function(assert) { - return repo( - 'Token', - 'findBySlug', - this.subject(), - function retrieveStub(stub) { - return stub(`/v1/acl/token/${id}?dc=${dc}`); - }, - function performTest(service) { - return service.findBySlug(id, dc); - }, - function performAssertion(actual, expected) { - assert.deepEqual( - actual, - expected(function(payload) { - const item = payload; - return Object.assign({}, item, { - Datacenter: dc, - CreateTime: new Date(item.CreateTime), - uid: `["${dc}","${item.AccessorID}"]`, - Policies: createPolicies(item), - }); - }) - ); - } - ); + }) + ); + } + ); + }); + test(`findByPolicy returns the correct data when nspace is ${nspace}`, function(assert) { + const policy = 'policy-1'; + return repo( + 'Token', + 'findByPolicy', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/acl/tokens?dc=${dc}&policy=${policy}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`, + { + CONSUL_TOKEN_COUNT: '100', + } + ); + }, + function performTest(service) { + return service.findByPolicy(policy, dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(function(item) { + return Object.assign({}, item, { + Datacenter: dc, + CreateTime: new Date(item.CreateTime), + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.AccessorID}"]`, + Policies: createPolicies(item), + }); + }); + }) + ); + } + ); + }); + test(`findByRole returns the correct data when nspace is ${nspace}`, function(assert) { + const role = 'role-1'; + return repo( + 'Token', + 'findByPolicy', + this.subject(), + function retrieveStub(stub) { + return stub( + `/v1/acl/tokens?dc=${dc}&role=${role}${ + typeof nspace !== 'undefined' ? `&ns=${nspace}` : `` + }`, + { + CONSUL_TOKEN_COUNT: '100', + } + ); + }, + function performTest(service) { + return service.findByRole(role, dc, nspace || undefinedNspace); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(function(item) { + return Object.assign({}, item, { + Datacenter: dc, + CreateTime: new Date(item.CreateTime), + Namespace: item.Namespace || undefinedNspace, + uid: `["${item.Namespace || undefinedNspace}","${dc}","${item.AccessorID}"]`, + Policies: createPolicies(item), + }); + }); + }) + ); + } + ); + }); }); -skip('clone returns the correct data for the clone endpoint'); From 8167ada6d0aec865aa564ee683a6151c50882b9b Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:29:00 +0000 Subject: [PATCH 10/61] Add all the things to deal with inserting a new route in the middle This allows URLs like `/~nspace/dc-1/services` and `/dc-1/services`. Also, allows you to refer to `dc.services` within templates etc, and if you are in the namespaced page it will still take you to the right place. --- ui-v2/app/helpers/href-to.js | 10 ++++ ui-v2/app/helpers/is-href.js | 7 ++- ui-v2/app/helpers/nspace/or.js | 6 +++ ui-v2/app/initializers/nspace.js | 61 +++++++++++++++++++++++ ui-v2/app/instance-initializers/nspace.js | 18 +++++++ ui-v2/app/router.js | 17 ++++++- ui-v2/app/routes/nspace.js | 28 +++++++++++ ui-v2/app/templates/nspace.hbs | 1 + ui-v2/tests/unit/routes/nspace-test.js | 11 ++++ 9 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 ui-v2/app/helpers/nspace/or.js create mode 100644 ui-v2/app/initializers/nspace.js create mode 100644 ui-v2/app/instance-initializers/nspace.js create mode 100644 ui-v2/app/routes/nspace.js create mode 100644 ui-v2/app/templates/nspace.hbs create mode 100644 ui-v2/tests/unit/routes/nspace-test.js diff --git a/ui-v2/app/helpers/href-to.js b/ui-v2/app/helpers/href-to.js index f374e2b9a0bc..fb6b3917bc08 100644 --- a/ui-v2/app/helpers/href-to.js +++ b/ui-v2/app/helpers/href-to.js @@ -1,6 +1,7 @@ // This helper requires `ember-href-to` for the moment at least // It's similar code but allows us to check on the type of route // (dynamic or wildcard) and encode or not depending on the type +import { inject as service } from '@ember/service'; import Helper from '@ember/component/helper'; import { hrefTo } from 'ember-href-to/helpers/href-to'; @@ -11,6 +12,7 @@ import { routes } from 'consul-ui/router'; const isWildcard = wildcard(routes); export default Helper.extend({ + router: service('router'), compute([targetRouteName, ...rest], namedArgs) { if (isWildcard(targetRouteName)) { rest = rest.map(function(item, i) { @@ -23,6 +25,14 @@ export default Helper.extend({ if (namedArgs.params) { return hrefTo(this, namedArgs.params); } else { + // we don't check to see if nspaces are enabled here as routes + // with beginning with 'nspace' only exist if nspaces are enabled + + // this globally converts non-nspaced href-to's to nspace aware + // href-to's only if you are within a namespace + if (this.router.currentRouteName.startsWith('nspace.') && targetRouteName.startsWith('dc.')) { + targetRouteName = `nspace.${targetRouteName}`; + } return hrefTo(this, [targetRouteName, ...rest]); } }, diff --git a/ui-v2/app/helpers/is-href.js b/ui-v2/app/helpers/is-href.js index 7c7fd403256e..5928018a0b67 100644 --- a/ui-v2/app/helpers/is-href.js +++ b/ui-v2/app/helpers/is-href.js @@ -6,8 +6,11 @@ import { observer } from '@ember/object'; export default Helper.extend({ router: service('router'), - compute(params) { - return this.router.isActive(...params); + compute([targetRouteName, ...rest]) { + if (this.router.currentRouteName.startsWith('nspace.') && targetRouteName.startsWith('dc.')) { + targetRouteName = `nspace.${targetRouteName}`; + } + return this.router.isActive(...[targetRouteName, ...rest]); }, onURLChange: observer('router.currentURL', function() { this.recompute(); diff --git a/ui-v2/app/helpers/nspace/or.js b/ui-v2/app/helpers/nspace/or.js new file mode 100644 index 000000000000..d194adb2199e --- /dev/null +++ b/ui-v2/app/helpers/nspace/or.js @@ -0,0 +1,6 @@ +import { helper } from '@ember/component/helper'; +import config from 'consul-ui/config/environment'; + +export default helper(function nspaceOr([nspace], hash) { + return nspace || config.CONSUL_NSPACES_UNDEFINED_NAME; +}); diff --git a/ui-v2/app/initializers/nspace.js b/ui-v2/app/initializers/nspace.js new file mode 100644 index 000000000000..5672a4a03059 --- /dev/null +++ b/ui-v2/app/initializers/nspace.js @@ -0,0 +1,61 @@ +import Route from '@ember/routing/route'; +import config from 'consul-ui/config/environment'; + +import { routes } from 'consul-ui/router'; +import flat from 'flat'; + +let initialize = function() {}; +Route.reopen( + ['modelFor', 'transitionTo', 'paramsFor'].reduce(function(prev, item) { + prev[item] = function(routeName, ...rest) { + const isNspaced = this.routeName.startsWith('nspace.'); + if (routeName === 'nspace') { + if (isNspaced || this.routeName === 'nspace') { + return this._super(...[routeName, ...rest]); + } else { + return { + nspace: '~', + }; + } + } + if (isNspaced && routeName.startsWith('dc')) { + routeName = `nspace.${routeName}`; + } + return this._super(...[routeName, ...rest]); + }; + return prev; + }, {}) +); +if (config.CONSUL_NSPACES_ENABLED) { + const dotRe = /\./g; + initialize = function(container) { + const all = Object.keys(flat(routes)) + .filter(function(item) { + return item.startsWith('dc'); + }) + .map(function(item) { + return item.replace('._options.path', '').replace(dotRe, '/'); + }); + all.forEach(function(item) { + let route = container.resolveRegistration(`route:${item}`); + if (!route) { + item = `${item}/index`; + route = container.resolveRegistration(`route:${item}`); + } + route.reopen({ + templateName: item + .replace('/root-create', '/create') + .replace('/create', '/edit') + .replace('/folder', '/index'), + }); + container.register(`route:nspace/${item}`, route); + const controller = container.resolveRegistration(`controller:${item}`); + if (controller) { + container.register(`controller:nspace/${item}`, controller); + } + }); + }; +} +export default { + initialize, +}; diff --git a/ui-v2/app/instance-initializers/nspace.js b/ui-v2/app/instance-initializers/nspace.js new file mode 100644 index 000000000000..e41b0dfc8d56 --- /dev/null +++ b/ui-v2/app/instance-initializers/nspace.js @@ -0,0 +1,18 @@ +import config from 'consul-ui/config/environment'; +export function initialize(container) { + if (config.CONSUL_NSPACES_ENABLED) { + ['dc', 'dc.intentions.edit', 'dc.intentions.create'].forEach(function(item) { + container.inject(`route:${item}`, 'nspaceRepo', 'service:repository/nspace/enabled'); + container.inject(`route:nspace.${item}`, 'nspaceRepo', 'service:repository/nspace/enabled'); + }); + container.inject('route:application', 'nspaceRepo', 'service:repository/nspace/enabled'); + container + .lookup('service:dom') + .root() + .classList.add('has-nspaces'); + } +} + +export default { + initialize, +}; diff --git a/ui-v2/app/router.js b/ui-v2/app/router.js index d281ac887c87..1752825ff356 100644 --- a/ui-v2/app/router.js +++ b/ui-v2/app/router.js @@ -10,7 +10,7 @@ export const routes = { // Our parent datacenter resource sets the namespace // for the entire application dc: { - _options: { path: ':dc' }, + _options: { path: '/:dc' }, // Services represent a consul service services: { _options: { path: '/services' }, @@ -107,4 +107,19 @@ export const routes = { _options: { path: '/*path' }, }, }; +if (config.CONSUL_NSPACES_ENABLED) { + routes.dc.nspaces = { + _options: { path: '/namespaces' }, + edit: { + _options: { path: '/:name' }, + }, + create: { + _options: { path: '/create' }, + }, + }; + routes.nspace = { + _options: { path: '/:nspace' }, + dc: routes.dc, + }; +} export default Router.map(walk(routes)); diff --git a/ui-v2/app/routes/nspace.js b/ui-v2/app/routes/nspace.js new file mode 100644 index 000000000000..8a228e93b876 --- /dev/null +++ b/ui-v2/app/routes/nspace.js @@ -0,0 +1,28 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { hash } from 'rsvp'; + +export default Route.extend({ + repo: service('repository/dc'), + router: service('router'), + model: function(params) { + return hash({ + item: this.repo.getActive(), + nspace: params.nspace, + }); + }, + afterModel: function(params) { + // We need to redirect if someone doesn't specify + // the section they want, but not redirect if the 'section' is + // specified (i.e. /dc-1/ vs /dc-1/services) + // check how many parts are in the URL to figure this out + // if there is a better way to do this then would be good to change + if (this.router.currentURL.split('/').length < 4) { + if (!params.nspace.startsWith('~')) { + this.transitionTo('dc.services', params.nspace); + } else { + this.transitionTo('nspace.dc.services', params.nspace, params.item.Name); + } + } + }, +}); diff --git a/ui-v2/app/templates/nspace.hbs b/ui-v2/app/templates/nspace.hbs new file mode 100644 index 000000000000..e2147cab02d6 --- /dev/null +++ b/ui-v2/app/templates/nspace.hbs @@ -0,0 +1 @@ +{{outlet}} \ No newline at end of file diff --git a/ui-v2/tests/unit/routes/nspace-test.js b/ui-v2/tests/unit/routes/nspace-test.js new file mode 100644 index 000000000000..4c8da4853495 --- /dev/null +++ b/ui-v2/tests/unit/routes/nspace-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | nspace', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:nspace'); + assert.ok(route); + }); +}); From 3bd18d61bce89373bfe1dcd97a8e51154f99fa1d Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:32:45 +0000 Subject: [PATCH 11/61] Make intentions aware of namespacing --- ui-v2/app/adapters/intention.js | 24 ++- ui-v2/app/controllers/dc/intentions/edit.js | 62 ++++--- ui-v2/app/models/intention.js | 15 +- ui-v2/app/serializers/intention.js | 3 +- ui-v2/app/templates/dc/intentions/-form.hbs | 177 +++++++++++++------- ui-v2/app/templates/dc/intentions/edit.hbs | 2 +- ui-v2/app/templates/dc/intentions/index.hbs | 6 + ui-v2/app/validations/intention.js | 19 ++- 8 files changed, 203 insertions(+), 105 deletions(-) diff --git a/ui-v2/app/adapters/intention.js b/ui-v2/app/adapters/intention.js index f0dee774e805..fb27a5a0aa38 100644 --- a/ui-v2/app/adapters/intention.js +++ b/ui-v2/app/adapters/intention.js @@ -1,6 +1,10 @@ import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application'; import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; import { SLUG_KEY } from 'consul-ui/models/intention'; +// Intentions use SourceNS and DestinationNS properties for namespacing +// so we don't need to add the `?ns=` anywhere here + +// TODO: Update to use this.formatDatacenter() export default Adapter.extend({ requestForQuery: function(request, { dc, index, id }) { return request` @@ -24,14 +28,30 @@ export default Adapter.extend({ return request` POST /v1/connect/intentions?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} - ${serialized} + ${{ + SourceNS: serialized.SourceNS, + DestinationNS: serialized.DestinationNS, + SourceName: serialized.SourceName, + DestinationName: serialized.DestinationName, + SourceType: serialized.SourceType, + Action: serialized.Action, + Description: serialized.Description, + }} `; }, requestForUpdateRecord: function(request, serialized, data) { return request` PUT /v1/connect/intentions/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }} - ${serialized} + ${{ + SourceNS: serialized.SourceNS, + DestinationNS: serialized.DestinationNS, + SourceName: serialized.SourceName, + DestinationName: serialized.DestinationName, + SourceType: serialized.SourceType, + Action: serialized.Action, + Description: serialized.Description, + }} `; }, requestForDeleteRecord: function(request, serialized, data) { diff --git a/ui-v2/app/controllers/dc/intentions/edit.js b/ui-v2/app/controllers/dc/intentions/edit.js index f413a93e46c9..8e1e72ea8a9f 100644 --- a/ui-v2/app/controllers/dc/intentions/edit.js +++ b/ui-v2/app/controllers/dc/intentions/edit.js @@ -9,17 +9,27 @@ export default Controller.extend({ this.form = this.builder.form('intention'); }, setProperties: function(model) { - const sourceName = get(model.item, 'SourceName'); - const destinationName = get(model.item, 'DestinationName'); - let source = model.items.findBy('Name', sourceName); - let destination = model.items.findBy('Name', destinationName); + let source = model.services.findBy('Name', model.item.SourceName); + if (!source) { - source = { Name: sourceName }; - model.items = [source].concat(model.items); + source = { Name: model.item.SourceName }; + model.services = [source].concat(model.services); } + let destination = model.services.findBy('Name', model.item.DestinationName); if (!destination) { - destination = { Name: destinationName }; - model.items = [destination].concat(model.items); + destination = { Name: model.item.DestinationName }; + model.services = [destination].concat(model.services); + } + + let sourceNS = model.nspaces.findBy('Name', model.item.SourceNS); + if (!sourceNS) { + sourceNS = { Name: model.item.SourceNS }; + model.nspaces = [sourceNS].concat(model.nspaces); + } + let destinationNS = model.nspaces.findBy('Name', model.item.DestinationNS); + if (!destinationNS) { + destinationNS = { Name: model.item.DestinationNS }; + model.nspaces = [destinationNS].concat(model.nspaces); } this._super({ ...model, @@ -27,6 +37,8 @@ export default Controller.extend({ item: this.form.setData(model.item).getData(), SourceName: source, DestinationName: destination, + SourceNS: sourceNS, + DestinationNS: destinationNS, }, }); }, @@ -35,34 +47,25 @@ export default Controller.extend({ return template.replace(/{{term}}/g, term); }, isUnique: function(term) { - return !this.items.findBy('Name', term); + return !this.services.findBy('Name', term); }, change: function(e, value, item) { const event = this.dom.normalizeEvent(e, value); const form = this.form; const target = event.target; - let name; - let selected; - let match; + let name, selected, match; switch (target.name) { case 'SourceName': case 'DestinationName': + case 'SourceNS': + case 'DestinationNS': name = selected = target.value; // Names can be selected Service EmberObjects or typed in strings // if its not a string, use the `Name` from the Service EmberObject if (typeof name !== 'string') { name = get(target.value, 'Name'); } - // see if the name is already in the list - match = this.items.filterBy('Name', name); - if (match.length === 0) { - // if its not make a new 'fake' Service that doesn't exist yet - // and add it to the possible services to make an intention between - selected = { Name: name }; - const items = [selected].concat(this.items.toArray()); - set(this, 'items', items); - } // mutate the value with the string name // which will be handled by the form target.value = name; @@ -71,6 +74,23 @@ export default Controller.extend({ // the current selection // basically the difference between // `item.DestinationName` and just `DestinationName` + // see if the name is already in the list + match = this.services.filterBy('Name', name); + if (match.length === 0) { + // if its not make a new 'fake' Service that doesn't exist yet + // and add it to the possible services to make an intention between + selected = { Name: name }; + switch (target.name) { + case 'SourceName': + case 'DestinationName': + set(this, 'services', [selected].concat(this.services.toArray())); + break; + case 'SourceNS': + case 'DestinationNS': + set(this, 'nspaces', [selected].concat(this.nspaces.toArray())); + break; + } + } set(this, target.name, selected); break; } diff --git a/ui-v2/app/models/intention.js b/ui-v2/app/models/intention.js index b0c4f033ea6a..0c16c7931026 100644 --- a/ui-v2/app/models/intention.js +++ b/ui-v2/app/models/intention.js @@ -1,10 +1,9 @@ import Model from 'ember-data/model'; import attr from 'ember-data/attr'; -import writable from 'consul-ui/utils/model/writable'; export const PRIMARY_KEY = 'uid'; export const SLUG_KEY = 'ID'; -const model = Model.extend({ +export default Model.extend({ [PRIMARY_KEY]: attr('string'), [SLUG_KEY]: attr('string'), Description: attr('string'), @@ -15,8 +14,11 @@ const model = Model.extend({ Precedence: attr('number'), SourceType: attr('string', { defaultValue: 'consul' }), Action: attr('string', { defaultValue: 'deny' }), + // These are in the API response but up until now + // aren't used for anything DefaultAddr: attr('string'), DefaultPort: attr('number'), + // Meta: attr(), Datacenter: attr('string'), CreatedAt: attr('date'), @@ -24,12 +26,3 @@ const model = Model.extend({ CreateIndex: attr('number'), ModifyIndex: attr('number'), }); -// TODO: Remove this in favour of just specifying it in the Adapter -export const ATTRS = writable(model, [ - 'Action', - 'SourceName', - 'DestinationName', - 'SourceType', - 'Description', -]); -export default model; diff --git a/ui-v2/app/serializers/intention.js b/ui-v2/app/serializers/intention.js index 94b9ea144fdb..8c6d169b9850 100644 --- a/ui-v2/app/serializers/intention.js +++ b/ui-v2/app/serializers/intention.js @@ -1,8 +1,7 @@ import Serializer from './application'; -import { PRIMARY_KEY, SLUG_KEY, ATTRS } from 'consul-ui/models/intention'; +import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/intention'; export default Serializer.extend({ primaryKey: PRIMARY_KEY, slugKey: SLUG_KEY, - attrs: ATTRS, }); diff --git a/ui-v2/app/templates/dc/intentions/-form.hbs b/ui-v2/app/templates/dc/intentions/-form.hbs index f85fb8e3ab58..2cfbf790dde7 100644 --- a/ui-v2/app/templates/dc/intentions/-form.hbs +++ b/ui-v2/app/templates/dc/intentions/-form.hbs @@ -1,76 +1,127 @@
-
+
+
+
+

Source

+{{#if (gt nspaces.length 0)}} + +{{/if}} +
+
+

Destination

-
- {{#each intents as |intent|}} - - {{/each}} -
-
-
+{{/if}} +
+ +
+ {{#each (array 'allow' 'deny') as |intent|}} + + {{/each}} +
+ +
+
{{#if create }} - + {{ else }} - + {{/if}} - + {{# if (and item.ID (not-eq item.ID 'anonymous')) }} - {{#confirmation-dialog message='Are you sure you want to delete this Intention?'}} - {{#block-slot 'action' as |confirm|}} - - {{/block-slot}} - {{#block-slot 'dialog' as |execute cancel message|}} - {{delete-confirmation message=message execute=execute cancel=cancel}} - {{/block-slot}} - {{/confirmation-dialog}} + {{#confirmation-dialog message='Are you sure you want to delete this Intention?'}} + {{#block-slot 'action' as |confirm|}} + + {{/block-slot}} + {{#block-slot 'dialog' as |execute cancel message|}} + {{delete-confirmation message=message execute=execute cancel=cancel}} + {{/block-slot}} + {{/confirmation-dialog}} {{/if}} -
+
diff --git a/ui-v2/app/templates/dc/intentions/edit.hbs b/ui-v2/app/templates/dc/intentions/edit.hbs index 5002bbfe1a0c..01100f3520d5 100644 --- a/ui-v2/app/templates/dc/intentions/edit.hbs +++ b/ui-v2/app/templates/dc/intentions/edit.hbs @@ -1,4 +1,4 @@ -{{#app-view class="acl edit" loading=isLoading}} +{{#app-view class="intention edit" loading=isLoading}} {{#block-slot 'notification' as |status type|}} {{partial 'dc/intentions/notifications'}} {{/block-slot}} diff --git a/ui-v2/app/templates/dc/intentions/index.hbs b/ui-v2/app/templates/dc/intentions/index.hbs index b521d8b6e768..3cd49e3e596e 100644 --- a/ui-v2/app/templates/dc/intentions/index.hbs +++ b/ui-v2/app/templates/dc/intentions/index.hbs @@ -38,17 +38,23 @@ {{else}} {{item.SourceName}} {{/if}} + {{! TODO: slugify }} + {{nspace/or item.SourceNS}} {{item.Action}} + {{#if (eq item.DestinationName '*') }} All Services (*) {{else}} {{item.DestinationName}} {{/if}} + {{! TODO: slugify }} + {{nspace/or item.DestinationNS}} + {{item.Precedence}} diff --git a/ui-v2/app/validations/intention.js b/ui-v2/app/validations/intention.js index 922ba9e413bb..41dbf8b75cec 100644 --- a/ui-v2/app/validations/intention.js +++ b/ui-v2/app/validations/intention.js @@ -1,6 +1,15 @@ import { validatePresence, validateLength } from 'ember-changeset-validations/validators'; -export default { - SourceName: [validatePresence(true), validateLength({ min: 1 })], - DestinationName: [validatePresence(true), validateLength({ min: 1 })], - Action: validatePresence(true), -}; +import config from 'consul-ui/config/environment'; +export default Object.assign( + { + SourceName: [validatePresence(true), validateLength({ min: 1 })], + DestinationName: [validatePresence(true), validateLength({ min: 1 })], + Action: validatePresence(true), + }, + config.CONSUL_NAMESPACES_ENABLED + ? { + SourceNS: [validatePresence(true), validateLength({ min: 1 })], + DestinationNS: [validatePresence(true), validateLength({ min: 1 })], + } + : {} +); From 3e216679171812274cb4dd4c5acbbca9729e8fb0 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:33:21 +0000 Subject: [PATCH 12/61] Make upstreams aware of namespacing --- ui-v2/app/templates/dc/services/-upstreams.hbs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui-v2/app/templates/dc/services/-upstreams.hbs b/ui-v2/app/templates/dc/services/-upstreams.hbs index c80b433e0dfc..ffa50d1f46a1 100644 --- a/ui-v2/app/templates/dc/services/-upstreams.hbs +++ b/ui-v2/app/templates/dc/services/-upstreams.hbs @@ -11,7 +11,13 @@ {{/block-slot}} {{#block-slot 'row'}} - {{item.DestinationName}} + + {{item.DestinationName}} + {{#if (not-eq item.DestinationType 'prepared_query')}} + {{! TODO: slugify }} + {{nspace/or item.DestinationNamespace}} + {{/if}} + {{item.Datacenter}} From 6fde68962043bc8dd18260c40eb83fee4d429c5f Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:34:34 +0000 Subject: [PATCH 13/61] CSS amends related to making upstreams and intentions namespace aware --- ui-v2/app/styles/core/layout.scss | 3 +++ ui-v2/app/styles/layouts/containers.scss | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/ui-v2/app/styles/core/layout.scss b/ui-v2/app/styles/core/layout.scss index 94e8bc8b1284..d6dc5acf260d 100644 --- a/ui-v2/app/styles/core/layout.scss +++ b/ui-v2/app/styles/core/layout.scss @@ -12,6 +12,9 @@ html.template-with-vertical-menu, html.template-with-vertical-menu body { overflow: hidden; } +html:not(.has-nspaces) [class*='nspace-'] { + display: none; +} #wrapper { @extend %viewport-container; } diff --git a/ui-v2/app/styles/layouts/containers.scss b/ui-v2/app/styles/layouts/containers.scss index 4402761fefd0..697c8fb04169 100644 --- a/ui-v2/app/styles/layouts/containers.scss +++ b/ui-v2/app/styles/layouts/containers.scss @@ -52,3 +52,19 @@ $ideal-content-padding: 33px; padding-right: $ideal-viewport-padding; } } +form > fieldset [role='group'] { + display: flex; + flex-wrap: wrap; + flex-direction: row; +} +[role='group'] fieldset { + width: 50%; +} +[role='group'] fieldset:not(:first-of-type) { + padding-left: 20px; + border-left: 1px solid; + border-left: $gray-500; +} +[role='group'] fieldset:not(:last-of-type) { + padding-right: 20px; +} From 2c9445ea0d20922b8439722ab3dbe8e8d244a69e Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 17 Oct 2019 12:37:20 +0000 Subject: [PATCH 14/61] Add nspace menu into the main nav header, refactor the old dc menu also Both of these now work the same --- ui-v2/app/components/datacenter-picker.js | 7 -- ui-v2/app/components/hashicorp-consul.js | 14 +-- ui-v2/app/components/toggle-button.js | 42 ++++++++ ui-v2/app/mixins/click-outside.js | 42 -------- ui-v2/app/services/dom.js | 6 ++ ui-v2/app/templates/application.hbs | 2 +- .../components/datacenter-picker.hbs | 7 -- .../templates/components/hashicorp-consul.hbs | 57 +++++++++-- .../templates/components/toggle-button.hbs | 2 + ui-v2/app/templates/dc.hbs | 2 +- ui-v2/app/templates/error.hbs | 2 +- ui-v2/app/templates/settings.hbs | 2 +- ui-v2/app/utils/dom/is-outside.js | 11 +++ ...r-picker-test.js => toggle-button-test.js} | 14 +-- ui-v2/tests/unit/mixins/click-outside-test.js | 12 --- ui-v2/tests/unit/utils/dom/is-outside-test.js | 95 +++++++++++++++++++ 16 files changed, 220 insertions(+), 97 deletions(-) delete mode 100644 ui-v2/app/components/datacenter-picker.js create mode 100644 ui-v2/app/components/toggle-button.js delete mode 100644 ui-v2/app/mixins/click-outside.js delete mode 100644 ui-v2/app/templates/components/datacenter-picker.hbs create mode 100644 ui-v2/app/templates/components/toggle-button.hbs create mode 100644 ui-v2/app/utils/dom/is-outside.js rename ui-v2/tests/integration/components/{datacenter-picker-test.js => toggle-button-test.js} (50%) delete mode 100644 ui-v2/tests/unit/mixins/click-outside-test.js create mode 100644 ui-v2/tests/unit/utils/dom/is-outside-test.js diff --git a/ui-v2/app/components/datacenter-picker.js b/ui-v2/app/components/datacenter-picker.js deleted file mode 100644 index 54d60a89c77c..000000000000 --- a/ui-v2/app/components/datacenter-picker.js +++ /dev/null @@ -1,7 +0,0 @@ -import Component from '@ember/component'; -import WithClickOutside from 'consul-ui/mixins/click-outside'; - -export default Component.extend(WithClickOutside, { - tagName: 'ul', - onchange: function() {}, -}); diff --git a/ui-v2/app/components/hashicorp-consul.js b/ui-v2/app/components/hashicorp-consul.js index cc718ee8ad5b..b982a71964fa 100644 --- a/ui-v2/app/components/hashicorp-consul.js +++ b/ui-v2/app/components/hashicorp-consul.js @@ -1,23 +1,15 @@ import Component from '@ember/component'; -import { get, set } from '@ember/object'; import { inject as service } from '@ember/service'; export default Component.extend({ dom: service('dom'), - isDropdownVisible: false, didInsertElement: function() { this.dom.root().classList.remove('template-with-vertical-menu'); }, actions: { - dropdown: function(e) { - if (get(this, 'dcs.length') > 0) { - set(this, 'isDropdownVisible', !this.isDropdownVisible); - } - }, change: function(e) { - const dom = this.dom; - const win = dom.viewport(); - const $root = dom.root(); - const $body = dom.element('body'); + const win = this.dom.viewport(); + const $root = this.dom.root(); + const $body = this.dom.element('body'); if (e.target.checked) { $root.classList.add('template-with-vertical-menu'); $body.style.height = $root.style.height = win.innerHeight + 'px'; diff --git a/ui-v2/app/components/toggle-button.js b/ui-v2/app/components/toggle-button.js new file mode 100644 index 000000000000..7dd4ed7f9fac --- /dev/null +++ b/ui-v2/app/components/toggle-button.js @@ -0,0 +1,42 @@ +import Component from '@ember/component'; +import { inject as service } from '@ember/service'; +import { set } from '@ember/object'; + +export default Component.extend({ + dom: service('dom'), + // TODO(octane): Remove when we can move to glimmer components + // so we aren't using ember-test-selectors + supportsDataTestProperties: true, + tagName: '', + // TODO: reserved for the moment but we don't need it yet + onblur: null, + init: function() { + this._super(...arguments); + this.guid = this.dom.guid(this); + this._listeners = this.dom.listeners(); + }, + didInsertElement: function() { + this._super(...arguments); + // TODO(octane): move to ref + set(this, 'input', this.dom.element(`#toggle-button-${this.guid}`)); + set(this, 'label', this.input.nextElementSibling); + }, + willDestroyElement: function() { + this._super(...arguments); + this._listeners.remove(); + }, + actions: { + change: function(e) { + if (this.input.checked) { + // default onblur event + this._listeners.remove(); + this._listeners.add(this.dom.document(), 'click', e => { + if (this.dom.isOutside(this.label, e.target)) { + this.input.checked = !this.input.checked; + } + this._listeners.remove(); + }); + } + }, + }, +}); diff --git a/ui-v2/app/mixins/click-outside.js b/ui-v2/app/mixins/click-outside.js deleted file mode 100644 index b6fe7a15ec7b..000000000000 --- a/ui-v2/app/mixins/click-outside.js +++ /dev/null @@ -1,42 +0,0 @@ -import Mixin from '@ember/object/mixin'; -import { inject as service } from '@ember/service'; -import { next } from '@ember/runloop'; - -// TODO: Potentially move this to dom service -const isOutside = function(element, e, doc = document) { - if (element) { - const isRemoved = !e.target || !doc.contains(e.target); - const isInside = element === e.target || element.contains(e.target); - return !isRemoved && !isInside; - } else { - return false; - } -}; - -const handler = function(e) { - const el = this.element; - if (isOutside(el, e)) { - this.onblur(e); - } -}; -export default Mixin.create({ - dom: service('dom'), - init: function() { - this._super(...arguments); - this.handler = handler.bind(this); - }, - onchange: function() {}, - onblur: function() {}, - didInsertElement: function() { - this._super(...arguments); - const doc = this.dom.document(); - next(this, () => { - doc.addEventListener('click', this.handler); - }); - }, - willDestroyElement: function() { - this._super(...arguments); - const doc = this.dom.document(); - doc.removeEventListener('click', this.handler); - }, -}); diff --git a/ui-v2/app/services/dom.js b/ui-v2/app/services/dom.js index 20a52b05b43d..1af967f1c830 100644 --- a/ui-v2/app/services/dom.js +++ b/ui-v2/app/services/dom.js @@ -1,5 +1,6 @@ import Service from '@ember/service'; import { getOwner } from '@ember/application'; +import { guidFor } from '@ember/object/internals'; // selecting import qsaFactory from 'consul-ui/utils/dom/qsa-factory'; @@ -8,6 +9,7 @@ import qsaFactory from 'consul-ui/utils/dom/qsa-factory'; // see if its possible to standardize import sibling from 'consul-ui/utils/dom/sibling'; import closest from 'consul-ui/utils/dom/closest'; +import isOutside from 'consul-ui/utils/dom/is-outside'; import getComponentFactory from 'consul-ui/utils/dom/get-component-factory'; // events @@ -33,10 +35,14 @@ export default Service.extend({ viewport: function() { return this.win; }, + guid: function(el) { + return guidFor(el); + }, // TODO: should this be here? Needs a better name at least clickFirstAnchor: clickFirstAnchor, closest: closest, sibling: sibling, + isOutside: isOutside, normalizeEvent: normalizeEvent, listeners: createListeners, root: function() { diff --git a/ui-v2/app/templates/application.hbs b/ui-v2/app/templates/application.hbs index 1eba47bd58c6..fc1cdb29eca0 100644 --- a/ui-v2/app/templates/application.hbs +++ b/ui-v2/app/templates/application.hbs @@ -1,7 +1,7 @@ {{#if (not loading)}} {{outlet}} {{else}} -{{#hashicorp-consul id="wrapper" dc=dc}} +{{#hashicorp-consul id="wrapper" dc=dc nspaces=nspaces nspace=nspace}} {{#app-view class="loading show"}} {{#block-slot 'content'}} {{partial 'consul-loading'}} diff --git a/ui-v2/app/templates/components/datacenter-picker.hbs b/ui-v2/app/templates/components/datacenter-picker.hbs deleted file mode 100644 index 992243801cba..000000000000 --- a/ui-v2/app/templates/components/datacenter-picker.hbs +++ /dev/null @@ -1,7 +0,0 @@ -{{!
    }} - {{#each items as |item|}} -
  • - {{item.Name}} -
  • - {{/each}} -{{!
}} \ No newline at end of file diff --git a/ui-v2/app/templates/components/hashicorp-consul.hbs b/ui-v2/app/templates/components/hashicorp-consul.hbs index 2872e6d58458..ba12b3e42900 100644 --- a/ui-v2/app/templates/components/hashicorp-consul.hbs +++ b/ui-v2/app/templates/components/hashicorp-consul.hbs @@ -9,18 +9,59 @@ Close