ui: [BUGFIX] Request intention listing with ns parameter (#9432)

This PR adds the ns=* query parameter when namespaces are enabled to keep backwards compatibility with how the UI used to work (Intentions page always lists all intention across all namespace you have access to)

I found a tiny dev bug for printing out the current URL during acceptance testing and fixed that up while I was there.
This commit is contained in:
John Cowen 2021-01-04 17:22:10 +00:00 committed by GitHub
parent 1dcf7fe0f8
commit 7a21bd7720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 30 deletions

4
.changelog/9432.txt Normal file
View File

@ -0,0 +1,4 @@
```release-note:bug
ui: request intention listing with ns=* parameter to retrieve all intentions
across namespaces
```

View File

@ -2,12 +2,16 @@ import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './applica
import { get } from '@ember/object'; import { get } from '@ember/object';
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
// Intentions use SourceNS and DestinationNS properties for namespacing so we // Intentions have different namespacing to the rest of the UI in that the don't
// don't need to add the `?ns=` anywhere here // have a Namespace property, the DestinationNS is essentially its namespace.
// Listing of intentions still requires the `ns` query string parameter which
// will give us all the intentions that have the `ns` as either the SourceNS or
// the DestinationNS.
// We currently list intentions by the * wildcard namespace for back compat reasons
// TODO: Update to use this.formatDatacenter() // TODO: Update to use this.formatDatacenter()
export default class IntentionAdapter extends Adapter { export default class IntentionAdapter extends Adapter {
requestForQuery(request, { dc, filter, index, uri }) { requestForQuery(request, { dc, ns, filter, index, uri }) {
return request` return request`
GET /v1/connect/intentions?${{ dc }} GET /v1/connect/intentions?${{ dc }}
X-Request-ID: ${uri}${ X-Request-ID: ${uri}${
@ -18,6 +22,7 @@ export default class IntentionAdapter extends Adapter {
} }
${{ ${{
...this.formatNspace('*'),
index, index,
filter, filter,
}} }}

View File

@ -72,7 +72,7 @@ as |api|>
{{/if}} {{/if}}
<DataSource <DataSource
@src={{concat '/' @nspace '/' @dc '/services'}} @src={{concat '/*/' @dc '/services'}}
@onchange={{action this.createServices item}} @onchange={{action this.createServices item}}
/> />
{{#if (env 'CONSUL_NSPACES_ENABLED')}} {{#if (env 'CONSUL_NSPACES_ENABLED')}}

View File

@ -1,24 +1,30 @@
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Route from 'consul-ui/routing/route'; import Route from 'consul-ui/routing/route';
import { hash } from 'rsvp';
export default class EditRoute extends Route { export default class EditRoute extends Route {
@service('repository/intention') @service('repository/intention') repo;
repo; @service('env') env;
model({ intention_id }, transition) { async model({ intention_id }, transition) {
const dc = this.modelFor('dc').dc.Name; const dc = this.modelFor('dc').dc.Name;
const nspace = '*'; const nspace = this.modelFor('nspace').nspace.substr(1);
return hash({
dc: dc, let item;
nspace: nspace, if (typeof intention_id !== 'undefined') {
item: item = await this.repo.findBySlug(intention_id, dc, nspace);
typeof intention_id !== 'undefined' } else {
? this.repo.findBySlug(intention_id, dc, nspace) const defaultNspace = this.env.var('CONSUL_NSPACES_ENABLED') ? '*' : 'default';
: this.repo.create({ item = await this.repo.create({
Datacenter: dc, SourceNS: nspace || defaultNspace,
}), DestinationNS: nspace || defaultNspace,
}); Datacenter: dc,
});
}
return {
dc,
nspace,
item,
};
} }
setupController(controller, model) { setupController(controller, model) {

View File

@ -17,7 +17,7 @@ export default class IndexRoute extends Route {
model(params) { model(params) {
return { return {
dc: this.modelFor('dc').dc.Name, dc: this.modelFor('dc').dc.Name,
nspace: this.modelFor('nspace').nspace.substr(1) || 'default', nspace: this.modelFor('nspace').nspace.substr(1),
}; };
} }

View File

@ -30,7 +30,7 @@ export default class RepositoryService extends Service {
if (dc === meta.dc) { if (dc === meta.dc) {
if (checkNspace) { if (checkNspace) {
const nspace = get(item, 'Namespace'); const nspace = get(item, 'Namespace');
if (nspace !== meta.namespace) { if (typeof nspace !== 'undefined' && nspace !== meta.nspace) {
return; return;
} }
} }

View File

@ -3,7 +3,51 @@ Feature: dc / intentions / create: Intention Create
In order to define intentions In order to define intentions
As a user As a user
I want to visit the intention create page, fill in the form and hit the create button and see a success notification I want to visit the intention create page, fill in the form and hit the create button and see a success notification
Scenario: @onlyNamespaceable
Scenario: with namespaces enabled
Given 1 datacenter model with the value "datacenter"
And 3 service models from yaml
---
- Name: web
Kind: ~
- Name: db
Kind: ~
- Name: cache
Kind: ~
---
When I visit the intention page for yaml
---
dc: datacenter
---
Then the url should be /datacenter/intentions/create
And the title should be "New Intention - Consul"
# Set source
And I click "[data-test-source-element] .ember-power-select-trigger"
And I type "web" into ".ember-power-select-search-input"
And I click ".ember-power-select-option:first-child"
Then I see the text "web" in "[data-test-source-element] .ember-power-select-selected-item"
# Set destination
And I click "[data-test-destination-element] .ember-power-select-trigger"
And I type "db" into ".ember-power-select-search-input"
And I click ".ember-power-select-option:first-child"
Then I see the text "db" in "[data-test-destination-element] .ember-power-select-selected-item"
# Specifically set deny
And I click "[value=deny]"
And I submit
# TODO: When namespace is empty we expect *
# Then a PUT request was made to "/v1/connect/intentions/exact?source=@namespace%2Fweb&destination=@namespace%2Fdb&dc=datacenter" from yaml
# ---
# body:
# SourceName: web
# DestinationName: db
# Action: deny
# ---
Then the url should be /datacenter/intentions
And the title should be "Intentions - Consul"
And "[data-notification]" has the "notification-update" class
And "[data-notification]" has the "success" class
@notNamespaceable
Scenario: with namespaces disabled
Given 1 datacenter model with the value "datacenter" Given 1 datacenter model with the value "datacenter"
And 3 service models from yaml And 3 service models from yaml
--- ---

View File

@ -1,17 +1,35 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit'; import { setupTest } from 'ember-qunit';
import getNspaceRunner from 'consul-ui/tests/helpers/get-nspace-runner';
const nspaceRunner = getNspaceRunner('intention');
module('Integration | Adapter | intention', function(hooks) { module('Integration | Adapter | intention', function(hooks) {
setupTest(hooks); setupTest(hooks);
const dc = 'dc-1'; const dc = 'dc-1';
const id = 'SourceNS:SourceName:DestinationNS:DestinationName'; const id = 'SourceNS:SourceName:DestinationNS:DestinationName';
test('requestForQuery returns the correct url', function(assert) { test('requestForQuery returns the correct url', function(assert) {
const adapter = this.owner.lookup('adapter:intention'); return nspaceRunner(
const client = this.owner.lookup('service:client/http'); (adapter, serializer, client) => {
const expected = `GET /v1/connect/intentions?dc=${dc}`; return adapter.requestForQuery(client.body, {
const actual = adapter.requestForQuery(client.requestParams.bind(client), { dc: dc,
dc: dc, ns: 'team-1',
}); filter: '*',
assert.equal(`${actual.method} ${actual.url}`, expected); index: 1,
});
},
{
filter: '*',
index: 1,
ns: '*',
},
{
filter: '*',
index: 1,
},
this,
assert
);
}); });
test('requestForQueryRecord returns the correct url', function(assert) { test('requestForQueryRecord returns the correct url', function(assert) {
const adapter = this.owner.lookup('adapter:intention'); const adapter = this.owner.lookup('adapter:intention');

View File

@ -102,7 +102,7 @@ export default function({
visit(library, pages, utils.setCurrentPage, reset); visit(library, pages, utils.setCurrentPage, reset);
click(library, utils.find, helpers.click); click(library, utils.find, helpers.click);
form(library, utils.find, helpers.fillIn, helpers.triggerKeyEvent, utils.getCurrentPage); form(library, utils.find, helpers.fillIn, helpers.triggerKeyEvent, utils.getCurrentPage);
debug(library, assert, utils.currentURL); debug(library, assert, helpers.currentURL);
assertHttp(library, assert, lastNthRequest); assertHttp(library, assert, lastNthRequest);
assertModel(library, assert, utils.find, utils.getCurrentPage, pauseUntil, pluralize); assertModel(library, assert, utils.find, utils.getCurrentPage, pauseUntil, pluralize);
assertPage(library, assert, utils.find, utils.getCurrentPage, $); assertPage(library, assert, utils.find, utils.getCurrentPage, $);