ui: Add initial partition support to intentions (#11129)

* ui: Add initial partition support to intentions
This commit is contained in:
John Cowen 2021-09-24 17:31:58 +01:00 committed by GitHub
parent 78a3b9f3e1
commit b19e14e8a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 258 additions and 73 deletions

4
.changelog/11129.txt Normal file
View File

@ -0,0 +1,4 @@
```release-note:improvement
ui: Add initial support for partitions to intentions
```

View File

@ -7,10 +7,11 @@ import { get } from '@ember/object';
// will give us all the intentions that have the `ns` as either the SourceNS or // will give us all the intentions that have the `ns` as either the SourceNS or
// the DestinationNS. // the DestinationNS.
// We currently list intentions by the * wildcard namespace for back compat reasons // We currently list intentions by the * wildcard namespace for back compat reasons
// FIXME: Is now a good time to change this behaviour ^ ?
// 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, ns, filter, index, uri }) { requestForQuery(request, { dc, ns, partition, 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}${
@ -21,7 +22,7 @@ export default class IntentionAdapter extends Adapter {
} }
${{ ${{
partition: '', partition: '*',
ns: '*', ns: '*',
index, index,
filter, filter,
@ -36,14 +37,21 @@ export default class IntentionAdapter extends Adapter {
// get the information we need from the id, which has been previously // get the information we need from the id, which has been previously
// encoded // encoded
const [SourceNS, SourceName, DestinationNS, DestinationName] = id const [
.split(':') SourcePartition,
.map(decodeURIComponent); SourceNS,
SourceName,
DestinationPartition,
DestinationNS,
DestinationName,
] = id.split(':').map(decodeURIComponent);
// FIXME: Service and Namespace are encoded into the URL here
// guessing we need to do the same thing for Partitions
return request` return request`
GET /v1/connect/intentions/exact?${{ GET /v1/connect/intentions/exact?${{
source: `${SourceNS}/${SourceName}`, source: `${SourcePartition}/${SourceNS}/${SourceName}`,
destination: `${DestinationNS}/${DestinationName}`, destination: `${DestinationPartition}/${DestinationNS}/${DestinationName}`,
dc: dc, dc: dc,
}} }}
Cache-Control: no-store Cache-Control: no-store
@ -54,10 +62,12 @@ export default class IntentionAdapter extends Adapter {
requestForCreateRecord(request, serialized, data) { requestForCreateRecord(request, serialized, data) {
const body = { const body = {
SourceNS: serialized.SourceNS,
DestinationNS: serialized.DestinationNS,
SourceName: serialized.SourceName, SourceName: serialized.SourceName,
DestinationName: serialized.DestinationName, DestinationName: serialized.DestinationName,
SourceNS: serialized.SourceNS,
DestinationNS: serialized.DestinationNS,
SourcePartition: serialized.SourcePartition,
DestinationPartition: serialized.DestinationPartition,
SourceType: serialized.SourceType, SourceType: serialized.SourceType,
Meta: serialized.Meta, Meta: serialized.Meta,
Description: serialized.Description, Description: serialized.Description,
@ -72,10 +82,12 @@ export default class IntentionAdapter extends Adapter {
body.Permissions = serialized.Permissions; body.Permissions = serialized.Permissions;
} }
} }
// FIXME: Service and Namespace are encoded into the URL here
// guessing we need to do the same thing for Partitions
return request` return request`
PUT /v1/connect/intentions/exact?${{ PUT /v1/connect/intentions/exact?${{
source: `${data.SourceNS}/${data.SourceName}`, source: `${data.SourcePartition}/${data.SourceNS}/${data.SourceName}`,
destination: `${data.DestinationNS}/${data.DestinationName}`, destination: `${data.DestinationPartition}/${data.DestinationNS}/${data.DestinationName}`,
dc: data.Datacenter, dc: data.Datacenter,
}} }}
@ -85,16 +97,20 @@ export default class IntentionAdapter extends Adapter {
requestForUpdateRecord(request, serialized, data) { requestForUpdateRecord(request, serialized, data) {
// you can no longer save Destinations // you can no longer save Destinations
delete serialized.DestinationNS;
delete serialized.DestinationName; delete serialized.DestinationName;
delete serialized.DestinationNS;
// FIXME: Does the above comment stand for partitions also?
delete serialized.DestinationPartition;
return this.requestForCreateRecord(...arguments); return this.requestForCreateRecord(...arguments);
} }
requestForDeleteRecord(request, serialized, data) { requestForDeleteRecord(request, serialized, data) {
// FIXME: Service and Namespace are encoded into the URL here
// guessing we need to do the same thing for Partitions
return request` return request`
DELETE /v1/connect/intentions/exact?${{ DELETE /v1/connect/intentions/exact?${{
source: `${data.SourceNS}/${data.SourceName}`, source: `${data.SourcePartition}/${data.SourceNS}/${data.SourceName}`,
destination: `${data.DestinationNS}/${data.DestinationName}`, destination: `${data.DestinationPartition}/${data.DestinationNS}/${data.DestinationName}`,
dc: data.Datacenter, dc: data.Datacenter,
}} }}
`; `;

View File

@ -28,7 +28,7 @@
<em>Search for an existing service, or enter any Service name.</em> <em>Search for an existing service, or enter any Service name.</em>
{{/if}} {{/if}}
</label> </label>
{{#if (env 'CONSUL_NSPACES_ENABLED')}} {{#if (can 'choose nspaces')}}
<label data-test-source-nspace class="type-select{{if item.error.SourceNS ' has-error'}}"> <label data-test-source-nspace class="type-select{{if item.error.SourceNS ' has-error'}}">
<span>Source Namespace</span> <span>Source Namespace</span>
<PowerSelectWithCreate <PowerSelectWithCreate
@ -50,7 +50,30 @@
<em>Search for an existing namespace, or enter any Namespace name.</em> <em>Search for an existing namespace, or enter any Namespace name.</em>
{{/if}} {{/if}}
</label> </label>
{{/if}} {{/if}}
{{#if (can 'choose partitions')}}
<label data-test-source-partition class="type-select{{if item.error.SourcePartition ' has-error'}}">
<span>Source Partition</span>
<PowerSelectWithCreate
@disabled={{not create}}
@options={{partitions}}
@selected={{SourcePartition}}
@searchPlaceholder="Type partition name"
@buildSuggestion={{action "createNewLabel" "Use a Consul Partition called '{{term}}'"}}
@showCreateWhen={{action "isUnique" partitions}}
@onCreate={{action onchange "SourcePartition"}}
@onChange={{action onchange "SourcePartition"}} as |partition|>
{{#if (eq partition.Name '*') }}
* (All Partitions)
{{else}}
{{partition.Name}}
{{/if}}
</PowerSelectWithCreate>
{{#if create}}
<em>Search for an existing partition, or enter any Partition name.</em>
{{/if}}
</label>
{{/if}}
</fieldset> </fieldset>
<fieldset> <fieldset>
<h2>Destination</h2> <h2>Destination</h2>
@ -76,7 +99,7 @@
<em>Search for an existing service, or enter any Service name.</em> <em>Search for an existing service, or enter any Service name.</em>
{{/if}} {{/if}}
</label> </label>
{{#if (env 'CONSUL_NSPACES_ENABLED')}} {{#if (can 'choose nspaces')}}
<label data-test-destination-nspace class="type-select{{if item.error.DestinationNS ' has-error'}}"> <label data-test-destination-nspace class="type-select{{if item.error.DestinationNS ' has-error'}}">
<span>Destination Namespace</span> <span>Destination Namespace</span>
<PowerSelectWithCreate <PowerSelectWithCreate
@ -99,7 +122,31 @@
<em>For the destination, you may choose any namespace for which you have access.</em> <em>For the destination, you may choose any namespace for which you have access.</em>
{{/if}} {{/if}}
</label> </label>
{{/if}} {{/if}}
{{#if (can 'choose partitions')}}
<label data-test-destination-partition class="type-select{{if item.error.DestinationPartition ' has-error'}}">
<span>Destination Partition</span>
<PowerSelectWithCreate
@disabled={{not create}}
@options={{partitions}}
@searchField="Name"
@selected={{DestinationPartition}}
@searchPlaceholder="Type partition name"
@buildSuggestion={{action "createNewLabel" "Use a future Consul Partition called '{{term}}'"}}
@showCreateWhen={{action "isUnique" partitions}}
@onCreate={{action onchange "DestinationPartition"}}
@onChange={{action onchange "DestinationPartition"}} as |partition|>
{{#if (eq partition.Name '*') }}
* (All Partitions)
{{else}}
{{partition.Name}}
{{/if}}
</PowerSelectWithCreate>
{{#if create}}
<em>For the destination, you may choose any partition for which you have access.</em>
{{/if}}
</label>
{{/if}}
</fieldset> </fieldset>
</div> </div>
<fieldset> <fieldset>

View File

@ -95,6 +95,17 @@ as |api|>
/> />
{{/if}} {{/if}}
{{#if (can 'use partitions')}}
<DataSource
@src={{uri '/*/*/${dc}/partitions'
(hash
dc=@dc
)
}}
@onchange={{action this.createPartitions item}}
/>
{{/if}}
{{#if (and api.isCreate this.isManagedByCRDs)}} {{#if (and api.isCreate this.isManagedByCRDs)}}
<Consul::Intention::Notice::CustomResource @type="warning" /> <Consul::Intention::Notice::CustomResource @type="warning" />
{{/if}} {{/if}}
@ -103,11 +114,14 @@ as |api|>
> >
<Consul::Intention::Form::Fieldsets <Consul::Intention::Form::Fieldsets
@nspaces={{this.nspaces}} @nspaces={{this.nspaces}}
@partitions={{this.partitions}}
@services={{this.services}} @services={{this.services}}
@SourceName={{this.SourceName}} @SourceName={{this.SourceName}}
@SourceNS={{this.SourceNS}} @SourceNS={{this.SourceNS}}
@SourcePartition={{this.SourcePartition}}
@DestinationName={{this.DestinationName}} @DestinationName={{this.DestinationName}}
@DestinationNS={{this.DestinationNS}} @DestinationNS={{this.DestinationNS}}
@DestinationPartition={{this.DestinationPartition}}
@item={{item}} @item={{item}}
@disabled={{api.disabled}} @disabled={{api.disabled}}
@create={{api.isCreate}} @create={{api.isCreate}}

View File

@ -12,6 +12,10 @@ export default class ConsulIntentionForm extends Component {
@tracked SourceNS; @tracked SourceNS;
@tracked DestinationNS; @tracked DestinationNS;
@tracked partitions;
@tracked SourcePartition;
@tracked DestinationPartition;
@tracked isManagedByCRDs; @tracked isManagedByCRDs;
modal = null; // reference to the warning modal modal = null; // reference to the warning modal
@ -115,6 +119,28 @@ export default class ConsulIntentionForm extends Component {
this.DestinationNS = destination; this.DestinationNS = destination;
} }
@action
createPartitions(item, e) {
// Partitions in the menus should:
// 1. Include an 'All Partitions' option
// 2. Include the current SourcePartition and DestinationPartition incase they don't exist yet
let items = e.data.toArray().sort((a, b) => a.Name.localeCompare(b.Name));
items = [{ Name: '*' }].concat(items);
let source = items.findBy('Name', item.SourcePartition);
if (!source) {
source = { Name: item.SourcePartition };
items = [source].concat(items);
}
let destination = items.findBy('Name', item.DestinationPartition);
if (!destination) {
destination = { Name: item.DestinationPartition };
items = [destination].concat(items);
}
this.partitions = items;
this.SourcePartition = source;
this.DestinationPartition = destination;
}
@action @action
change(e, form, item) { change(e, form, item) {
const target = e.target; const target = e.target;
@ -125,6 +151,8 @@ export default class ConsulIntentionForm extends Component {
case 'DestinationName': case 'DestinationName':
case 'SourceNS': case 'SourceNS':
case 'DestinationNS': case 'DestinationNS':
case 'SourcePartition':
case 'DestinationPartition':
name = selected = target.value; name = selected = target.value;
// Names can be selected Service EmberObjects or typed in strings // Names can be selected Service EmberObjects or typed in strings
// if its not a string, use the `Name` from the Service EmberObject // if its not a string, use the `Name` from the Service EmberObject
@ -158,6 +186,13 @@ export default class ConsulIntentionForm extends Component {
this.nspaces = [selected].concat(this.nspaces.toArray()); this.nspaces = [selected].concat(this.nspaces.toArray());
} }
break; break;
case 'SourcePartition':
case 'DestinationPartition':
if (this.partitions.filterBy('Name', name).length === 0) {
selected = { Name: name };
this.partitions = [selected].concat(this.partitions.toArray());
}
break;
} }
this[target.name] = selected; this[target.name] = selected;
break; break;

View File

@ -1,5 +1,26 @@
.consul-intention-list { %consul-intention-list td.permissions {
td.permissions { color: $blue-500;
color: $blue-500; }
} %consul-intention-list em {
--word-spacing: 0.25rem;
}
%consul-intention-list em span::before,
%consul-intention-list em span:first-child {
margin-right: var(--word-spacing);
}
%consul-intention-list em span:last-child {
margin-left: var(--word-spacing);
}
%consul-intention-list em span::before {
@extend %as-pseudo;
}
%consul-intention-list span[class|='nspace']::before {
@extend %with-folder-outline-mask;
}
%consul-intention-list span[class|='partition']::before {
@extend %with-user-team-mask;
}
.consul-intention-list {
@extend %consul-intention-list;
} }

View File

@ -25,7 +25,13 @@ as |item index|>
{{item.SourceName}} {{item.SourceName}}
{{/if}} {{/if}}
{{! TODO: slugify }} {{! TODO: slugify }}
<em class={{concat 'nspace-' (or item.SourceNS 'default')}}>{{or item.SourceNS 'default'}}</em> <em>
<span
class={{concat 'partition-' (or item.SourcePartition 'default')}}
>{{or item.SourcePartition 'default'}}</span> / <span
class={{concat 'nspace-' (or item.SourceNS 'default')}}
>{{or item.SourceNS 'default'}}</span>
</em>
</a> </a>
</td> </td>
<td class="intent intent-{{slugify item.Action}}" data-test-intention-action={{item.Action}}> <td class="intent intent-{{slugify item.Action}}" data-test-intention-action={{item.Action}}>
@ -39,7 +45,13 @@ as |item index|>
{{item.DestinationName}} {{item.DestinationName}}
{{/if}} {{/if}}
{{! TODO: slugify }} {{! TODO: slugify }}
<em class={{concat 'nspace-' (or item.DestinationNS 'default')}}>{{or item.DestinationNS 'default'}}</em> <em>
<span
class={{concat 'partition-' (or item.DestinationPartition 'default')}}
>{{or item.DestinationPartition 'default'}}</span> / <span
class={{concat 'nspace-' (or item.DestinationNS 'default')}}
>{{or item.DestinationNS 'default'}}</span>
</em>
</span> </span>
</td> </td>
<td class="permissions"> <td class="permissions">

View File

@ -4,11 +4,11 @@
<dl> <dl>
<dt>Destination</dt> <dt>Destination</dt>
<dd> <dd>
{{item.DestinationName}}{{#if (env "CONSUL_NSPACES_ENABLED")}} / {{item.DestinationNS}}{{/if}} {{item.DestinationName}}{{#if (can "use partitions")}} / {{item.DestinationPartition}}{{/if}}{{#if (can "use nspaces")}} / {{item.DestinationNS}}{{/if}}
</dd> </dd>
<dt>Source</dt> <dt>Source</dt>
<dd> <dd>
{{item.SourceName}}{{#if (env "CONSUL_NSPACES_ENABLED")}} / {{item.SourceNS}}{{/if}} {{item.SourceName}}{{#if (can "use partitions")}} / {{item.SourcePartition}}{{/if}}{{#if (can "use nspaces")}} / {{item.SourceNS}}{{/if}}
</dd> </dd>
{{#if item.Action}} {{#if item.Action}}
<dt>Action</dt> <dt>Action</dt>

View File

@ -12,11 +12,14 @@ export default class Intention extends Model {
@attr('string') Datacenter; @attr('string') Datacenter;
@attr('string') Description; @attr('string') Description;
// FIXME: Will we have Source/DestinationPartition?
@attr('string', { defaultValue: () => 'default' }) SourceNS;
@attr('string', { defaultValue: () => '*' }) SourceName; @attr('string', { defaultValue: () => '*' }) SourceName;
@attr('string', { defaultValue: () => 'default' }) DestinationNS;
@attr('string', { defaultValue: () => '*' }) DestinationName; @attr('string', { defaultValue: () => '*' }) DestinationName;
@attr('string', { defaultValue: () => 'default' }) SourceNS;
@attr('string', { defaultValue: () => 'default' }) DestinationNS;
@attr('string', { defaultValue: () => 'default' }) SourcePartition;
@attr('string', { defaultValue: () => 'default' }) DestinationPartition;
@attr('number') Precedence; @attr('number') Precedence;
@attr('string', { defaultValue: () => 'consul' }) SourceType; @attr('string', { defaultValue: () => 'consul' }) SourceType;
@nullValue(undefined) @attr('string') Action; @nullValue(undefined) @attr('string') Action;

View File

@ -22,7 +22,7 @@ export default class IntentionSerializer extends Serializer {
item.LegacyID = item.ID; item.LegacyID = item.ID;
} }
item.ID = this item.ID = this
.uri`${item.SourceNS}:${item.SourceName}:${item.DestinationNS}:${item.DestinationName}`; .uri`${item.SourcePartition}:${item.SourceNS}:${item.SourceName}:${item.DestinationPartition}:${item.DestinationNS}:${item.DestinationName}`;
return item; return item;
} }
@ -56,7 +56,7 @@ export default class IntentionSerializer extends Serializer {
return respond((headers, body) => { return respond((headers, body) => {
body = data; body = data;
body.ID = this body.ID = this
.uri`${serialized.SourceNS}:${serialized.SourceName}:${serialized.DestinationNS}:${serialized.DestinationName}`; .uri`${serialized.SourcePartition}:${serialized.SourceNS}:${serialized.SourceName}:${serialized.DestinationPartition}:${serialized.DestinationNS}:${serialized.DestinationName}`;
return this.fingerprint(primaryKey, slugKey, body.Datacenter)(body); return this.fingerprint(primaryKey, slugKey, body.Datacenter)(body);
}); });
} }

View File

@ -69,9 +69,12 @@ export default class IntentionRepository extends RepositoryService {
let item; let item;
if (params.id === '') { if (params.id === '') {
const defaultNspace = this.env.var('CONSUL_NSPACES_ENABLED') ? '*' : 'default'; const defaultNspace = this.env.var('CONSUL_NSPACES_ENABLED') ? '*' : 'default';
const defaultPartition = this.env.var('CONSUL_PARTITIONS_ENABLED') ? '*' : 'default';
item = await this.create({ item = await this.create({
SourceNS: params.nspace || defaultNspace, SourceNS: params.nspace || defaultNspace,
DestinationNS: params.nspace || defaultNspace, DestinationNS: params.nspace || defaultNspace,
SourcePartition: params.partition || defaultPartition,
DestinationPartition: params.partition || defaultPartition,
Datacenter: params.dc, Datacenter: params.dc,
Partition: params.partition, Partition: params.partition,
}); });

View File

@ -84,6 +84,9 @@ main,
html:not(.has-nspaces) [class*='nspace-'] { html:not(.has-nspaces) [class*='nspace-'] {
display: none; display: none;
} }
html:not(.has-partitions) [class*='partition-'] {
display: none;
}
#wrapper { #wrapper {
@extend %viewport-container; @extend %viewport-container;
display: flex; display: flex;

View File

@ -22,10 +22,12 @@ ${legacy ? `
"Action": "${fake.helpers.randomize(['allow', 'deny'])}", "Action": "${fake.helpers.randomize(['allow', 'deny'])}",
`:``} `:``}
"Description": "${fake.lorem.sentence()}", "Description": "${fake.lorem.sentence()}",
"SourceNS": "default",
"SourceName": "${fake.hacker.noun()}-${i}", "SourceName": "${fake.hacker.noun()}-${i}",
"DestinationNS": "default",
"DestinationName": "${fake.hacker.noun()}", "DestinationName": "${fake.hacker.noun()}",
"SourceNS": "default",
"DestinationNS": "default",
"SourcePartition": "default",
"DestinationPartition": "default",
"SourceType": "${fake.helpers.randomize(['consul', 'externaluri'])}", "SourceType": "${fake.helpers.randomize(['consul', 'externaluri'])}",
${!legacy ? ` ${!legacy ? `
"Permissions": [ "Permissions": [

View File

@ -6,10 +6,12 @@ return `
"ID": "${legacy ? ID : ''}" "ID": "${legacy ? ID : ''}"
${ http.method !== "PUT" ? ` ${ http.method !== "PUT" ? `
,"Description": "${fake.lorem.sentence()}", ,"Description": "${fake.lorem.sentence()}",
"SourceNS": "default",
"SourceName": "${fake.hacker.noun()}", "SourceName": "${fake.hacker.noun()}",
"DestinationNS": "default",
"DestinationName": "${fake.hacker.noun()}", "DestinationName": "${fake.hacker.noun()}",
"SourceNS": "default",
"DestinationNS": "default",
"SourcePartition": "default",
"DestinationPartition": "default",
"SourceType": "${fake.helpers.randomize(['consul', 'externaluri'])}", "SourceType": "${fake.helpers.randomize(['consul', 'externaluri'])}",
${legacy ? ` ${legacy ? `
"Action": "${fake.helpers.randomize(['allow', 'deny'])}", "Action": "${fake.helpers.randomize(['allow', 'deny'])}",

View File

@ -8,10 +8,12 @@ return `
"ID": "${legacy ? ID : ''}" "ID": "${legacy ? ID : ''}"
${ http.method !== "PUT" ? ` ${ http.method !== "PUT" ? `
,"Description": "${fake.lorem.sentence()}", ,"Description": "${fake.lorem.sentence()}",
"SourceNS": "${source[0]}", "SourceName": "${source[2]}",
"SourceName": "${source[1]}", "DestinationName": "${destination[2]}",
"DestinationNS": "${destination[0]}", "SourceNS": "${source[1]}",
"DestinationName": "${destination[1]}", "DestinationNS": "${destination[1]}",
"SourcePartition": "${source[0]}",
"DestinationPartition": "${destination[0]}",
"SourceType": "${fake.helpers.randomize(['consul', 'externaluri'])}", "SourceType": "${fake.helpers.randomize(['consul', 'externaluri'])}",
${legacy ? ` ${legacy ? `
"Action": "${fake.helpers.randomize(['allow', 'deny'])}", "Action": "${fake.helpers.randomize(['allow', 'deny'])}",

View File

@ -46,13 +46,15 @@ Feature: dc / intentions / create: Intention Create
# Specifically set deny # Specifically set deny
And I click ".value-deny" And I click ".value-deny"
And I submit And I submit
Then a PUT request was made to "/v1/connect/intentions/exact?source=nspace-0%2Fweb&destination=nspace-0%2Fdb&dc=datacenter" from yaml Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fnspace-0%2Fweb&destination=default%2Fnspace-0%2Fdb&dc=datacenter" from yaml
--- ---
body: body:
SourceName: web SourceName: web
DestinationName: db DestinationName: db
SourceNS: nspace-0 SourceNS: nspace-0
DestinationNS: nspace-0 DestinationNS: nspace-0
SourcePartition: default
DestinationPartition: default
Action: deny Action: deny
--- ---
Then the url should be /datacenter/intentions Then the url should be /datacenter/intentions
@ -90,7 +92,7 @@ Feature: dc / intentions / create: Intention Create
# Specifically set deny # Specifically set deny
And I click ".value-deny" And I click ".value-deny"
And I submit And I submit
Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fweb&destination=default%2Fdb&dc=datacenter" from yaml Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fdefault%2Fweb&destination=default%2Fdefault%2Fdb&dc=datacenter" from yaml
--- ---
body: body:
SourceName: web SourceName: web

View File

@ -4,10 +4,12 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
Given 1 datacenter model with the value "datacenter" Given 1 datacenter model with the value "datacenter"
And 1 intention model from yaml And 1 intention model from yaml
--- ---
SourceNS: default
SourceName: name SourceName: name
DestinationNS: default
DestinationName: destination DestinationName: destination
SourceNS: default
DestinationNS: default
SourcePartition: default
DestinationPartition: default
ID: ee52203d-989f-4f7a-ab5a-2bef004164ca ID: ee52203d-989f-4f7a-ab5a-2bef004164ca
Meta: ~ Meta: ~
--- ---
@ -19,7 +21,7 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
And I click actions on the intentionList.intentions And I click actions on the intentionList.intentions
And I click delete on the intentionList.intentions And I click delete on the intentionList.intentions
And I click confirmDelete on the intentionList.intentions And I click confirmDelete on the intentionList.intentions
Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter" Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fdefault%2Fname&destination=default%2Fdefault%2Fdestination&dc=datacenter"
And "[data-notification]" has the "notification-delete" class And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class And "[data-notification]" has the "success" class
Scenario: Deleting an intention from the intention detail page Scenario: Deleting an intention from the intention detail page
@ -30,7 +32,7 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
--- ---
And I click delete And I click delete
And I click confirmDelete And I click confirmDelete
Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter" Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fdefault%2Fname&destination=default%2Fdefault%2Fdestination&dc=datacenter"
And "[data-notification]" has the "notification-delete" class And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class And "[data-notification]" has the "success" class
Scenario: Deleting an intention from the intention detail page and getting an error Scenario: Deleting an intention from the intention detail page and getting an error
@ -39,7 +41,7 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
dc: datacenter dc: datacenter
intention: ee52203d-989f-4f7a-ab5a-2bef004164ca intention: ee52203d-989f-4f7a-ab5a-2bef004164ca
--- ---
Given the url "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter" responds with a 500 status Given the url "/v1/connect/intentions/exact?source=default%2Fdefault%2Fname&destination=default%2Fdefault%2Fdestination&dc=datacenter" responds with a 500 status
And I click delete And I click delete
And I click confirmDelete And I click confirmDelete
And "[data-notification]" has the "notification-update" class And "[data-notification]" has the "notification-update" class
@ -50,7 +52,7 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
dc: datacenter dc: datacenter
intention: ee52203d-989f-4f7a-ab5a-2bef004164ca intention: ee52203d-989f-4f7a-ab5a-2bef004164ca
--- ---
Given the url "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter" responds with from yaml Given the url "/v1/connect/intentions/exact?source=default%2Fdefault%2Fname&destination=default%2Fdefault%2Fdestination&dc=datacenter" responds with from yaml
--- ---
status: 500 status: 500
body: "duplicate intention found:" body: "duplicate intention found:"

View File

@ -4,10 +4,12 @@ Feature: dc / intentions / permissions / warn: Intention Permission Warn
Given 1 datacenter model with the value "datacenter" Given 1 datacenter model with the value "datacenter"
And 1 intention model from yaml And 1 intention model from yaml
--- ---
SourceNS: default
SourceName: web SourceName: web
DestinationNS: default
DestinationName: db DestinationName: db
SourceNS: default
DestinationNS: default
SourcePartition: default
DestinationPartition: default
Action: ~ Action: ~
Permissions: Permissions:
- Action: allow - Action: allow
@ -28,4 +30,4 @@ Feature: dc / intentions / permissions / warn: Intention Permission Warn
And I submit And I submit
And I see the warning object And I see the warning object
And I click the warning.confirm object And I click the warning.confirm object
Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fweb&destination=default%2Fdb&dc=datacenter" from yaml Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fdefault%2Fweb&destination=default%2Fdefault%2Fdb&dc=datacenter" from yaml

View File

@ -4,10 +4,12 @@ Feature: dc / intentions / update: Intention Update
Given 1 datacenter model with the value "datacenter" Given 1 datacenter model with the value "datacenter"
And 1 intention model from yaml And 1 intention model from yaml
--- ---
SourceNS: default
SourceName: web SourceName: web
DestinationNS: default
DestinationName: db DestinationName: db
SourceNS: default
DestinationNS: default
SourcePartition: default
DestinationPartition: default
ID: intention-id ID: intention-id
--- ---
When I visit the intention page for yaml When I visit the intention page for yaml
@ -24,7 +26,7 @@ Feature: dc / intentions / update: Intention Update
--- ---
And I click "[value=[Action]]" And I click "[value=[Action]]"
And I submit And I submit
Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fweb&destination=default%2Fdb&dc=datacenter" from yaml Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fdefault%2Fweb&destination=default%2Fdefault%2Fdb&dc=datacenter" from yaml
--- ---
Description: [Description] Description: [Description]
Action: [Action] Action: [Action]
@ -39,7 +41,7 @@ Feature: dc / intentions / update: Intention Update
| Desc | allow | | Desc | allow |
------------------------------ ------------------------------
Scenario: There was an error saving the intention Scenario: There was an error saving the intention
Given the url "/v1/connect/intentions/exact?source=default%2Fweb&destination=default%2Fdb&dc=datacenter" responds with a 500 status Given the url "/v1/connect/intentions/exact?source=default%2Fdefault%2Fweb&destination=default%2Fdefault%2Fdb&dc=datacenter" responds with a 500 status
And I submit And I submit
Then the url should be /datacenter/intentions/intention-id Then the url should be /datacenter/intentions/intention-id
Then "[data-notification]" has the "notification-update" class Then "[data-notification]" has the "notification-update" class

View File

@ -15,10 +15,12 @@ Feature: dc / services / show / intentions: Intentions per service
- ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f0 - ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f0
Action: allow Action: allow
Meta: ~ Meta: ~
SourceNS: default
SourceName: name SourceName: name
DestinationNS: default
DestinationName: destination DestinationName: destination
SourceNS: default
DestinationNS: default
SourcePartition: default
DestinationPartition: default
- ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f1 - ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f1
Action: deny Action: deny
@ -39,11 +41,11 @@ Feature: dc / services / show / intentions: Intentions per service
Scenario: I can see intentions Scenario: I can see intentions
And I see 3 intention models on the intentionList component And I see 3 intention models on the intentionList component
And I click intention on the intentionList.intentions component And I click intention on the intentionList.intentions component
Then the url should be /dc1/services/service-0/intentions/default:name:default:destination Then the url should be /dc1/services/service-0/intentions/default:default:name:default:default:destination
Scenario: I can delete intentions Scenario: I can delete intentions
And I click actions on the intentionList.intentions component And I click actions on the intentionList.intentions component
And I click delete on the intentionList.intentions component And I click delete on the intentionList.intentions component
And I click confirmDelete on the intentionList.intentions And I click confirmDelete on the intentionList.intentions
Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=dc1" Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fdefault%2Fname&destination=default%2Fdefault%2Fdestination&dc=dc1"
And "[data-notification]" has the "notification-delete" class And "[data-notification]" has the "notification-delete" class
And "[data-notification]" has the "success" class And "[data-notification]" has the "success" class

View File

@ -7,7 +7,8 @@ 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 =
'SourcePartition:SourceNS:SourceName:DestinationPartition:DestinationNS:DestinationName';
test('requestForQuery returns the correct url', function(assert) { test('requestForQuery returns the correct url', function(assert) {
return nspaceRunner( return nspaceRunner(
(adapter, serializer, client) => { (adapter, serializer, client) => {
@ -15,6 +16,7 @@ module('Integration | Adapter | intention', function(hooks) {
return adapter.requestForQuery(request, { return adapter.requestForQuery(request, {
dc: dc, dc: dc,
ns: 'team-1', ns: 'team-1',
partition: 'partition-1',
filter: '*', filter: '*',
index: 1, index: 1,
}); });
@ -23,6 +25,7 @@ module('Integration | Adapter | intention', function(hooks) {
filter: '*', filter: '*',
index: 1, index: 1,
ns: '*', ns: '*',
partition: '*',
}, },
{ {
filter: '*', filter: '*',
@ -36,7 +39,7 @@ module('Integration | Adapter | intention', function(hooks) {
const adapter = this.owner.lookup('adapter:intention'); const adapter = this.owner.lookup('adapter:intention');
const client = this.owner.lookup('service:client/http'); const client = this.owner.lookup('service:client/http');
const request = client.url.bind(client); const request = client.url.bind(client);
const expected = `GET /v1/connect/intentions/exact?source=SourceNS%2FSourceName&destination=DestinationNS%2FDestinationName&dc=${dc}`; const expected = `GET /v1/connect/intentions/exact?source=SourcePartition%2FSourceNS%2FSourceName&destination=DestinationPartition%2FDestinationNS%2FDestinationName&dc=${dc}`;
const actual = adapter const actual = adapter
.requestForQueryRecord(request, { .requestForQueryRecord(request, {
dc: dc, dc: dc,
@ -59,17 +62,19 @@ module('Integration | Adapter | intention', function(hooks) {
const adapter = this.owner.lookup('adapter:intention'); const adapter = this.owner.lookup('adapter:intention');
const client = this.owner.lookup('service:client/http'); const client = this.owner.lookup('service:client/http');
const request = client.url.bind(client); const request = client.url.bind(client);
const expected = `PUT /v1/connect/intentions/exact?source=SourceNS%2FSourceName&destination=DestinationNS%2FDestinationName&dc=${dc}`; const expected = `PUT /v1/connect/intentions/exact?source=SourcePartition%2FSourceNS%2FSourceName&destination=DestinationPartition%2FDestinationNS%2FDestinationName&dc=${dc}`;
const actual = adapter const actual = adapter
.requestForCreateRecord( .requestForCreateRecord(
request, request,
{}, {},
{ {
Datacenter: dc, Datacenter: dc,
SourceNS: 'SourceNS',
SourceName: 'SourceName', SourceName: 'SourceName',
DestinationNS: 'DestinationNS',
DestinationName: 'DestinationName', DestinationName: 'DestinationName',
SourceNS: 'SourceNS',
DestinationNS: 'DestinationNS',
SourcePartition: 'SourcePartition',
DestinationPartition: 'DestinationPartition',
} }
) )
.split('\n')[0]; .split('\n')[0];
@ -79,17 +84,19 @@ module('Integration | Adapter | intention', function(hooks) {
const adapter = this.owner.lookup('adapter:intention'); const adapter = this.owner.lookup('adapter:intention');
const client = this.owner.lookup('service:client/http'); const client = this.owner.lookup('service:client/http');
const request = client.url.bind(client); const request = client.url.bind(client);
const expected = `PUT /v1/connect/intentions/exact?source=SourceNS%2FSourceName&destination=DestinationNS%2FDestinationName&dc=${dc}`; const expected = `PUT /v1/connect/intentions/exact?source=SourcePartition%2FSourceNS%2FSourceName&destination=DestinationPartition%2FDestinationNS%2FDestinationName&dc=${dc}`;
const actual = adapter const actual = adapter
.requestForUpdateRecord( .requestForUpdateRecord(
request, request,
{}, {},
{ {
Datacenter: dc, Datacenter: dc,
SourceNS: 'SourceNS',
SourceName: 'SourceName', SourceName: 'SourceName',
DestinationNS: 'DestinationNS',
DestinationName: 'DestinationName', DestinationName: 'DestinationName',
SourceNS: 'SourceNS',
DestinationNS: 'DestinationNS',
SourcePartition: 'SourcePartition',
DestinationPartition: 'DestinationPartition',
} }
) )
.split('\n')[0]; .split('\n')[0];
@ -99,17 +106,19 @@ module('Integration | Adapter | intention', function(hooks) {
const adapter = this.owner.lookup('adapter:intention'); const adapter = this.owner.lookup('adapter:intention');
const client = this.owner.lookup('service:client/http'); const client = this.owner.lookup('service:client/http');
const request = client.url.bind(client); const request = client.url.bind(client);
const expected = `DELETE /v1/connect/intentions/exact?source=SourceNS%2FSourceName&destination=DestinationNS%2FDestinationName&dc=${dc}`; const expected = `DELETE /v1/connect/intentions/exact?source=SourcePartition%2FSourceNS%2FSourceName&destination=DestinationPartition%2FDestinationNS%2FDestinationName&dc=${dc}`;
const actual = adapter const actual = adapter
.requestForDeleteRecord( .requestForDeleteRecord(
request, request,
{}, {},
{ {
Datacenter: dc, Datacenter: dc,
SourceNS: 'SourceNS',
SourceName: 'SourceName', SourceName: 'SourceName',
DestinationNS: 'DestinationNS',
DestinationName: 'DestinationName', DestinationName: 'DestinationName',
SourceNS: 'SourceNS',
DestinationNS: 'DestinationNS',
SourcePartition: 'SourcePartition',
DestinationPartition: 'DestinationPartition',
} }
) )
.split('\n')[0]; .split('\n')[0];

View File

@ -26,7 +26,7 @@ module('Integration | Serializer | intention', function(hooks) {
// refactored out our Serializer this can go // refactored out our Serializer this can go
Namespace: nspace, Namespace: nspace,
Partition: partition, Partition: partition,
uid: `["${partition}","${nspace}","${dc}","${item.SourceNS}:${item.SourceName}:${item.DestinationNS}:${item.DestinationName}"]`, uid: `["${partition}","${nspace}","${dc}","${item.SourcePartition}:${item.SourceNS}:${item.SourceName}:${item.DestinationPartition}:${item.DestinationNS}:${item.DestinationName}"]`,
}) })
); );
const actual = serializer.respondForQuery( const actual = serializer.respondForQuery(
@ -55,10 +55,12 @@ module('Integration | Serializer | intention', function(hooks) {
url: `/v1/connect/intentions/${id}?dc=${dc}`, url: `/v1/connect/intentions/${id}?dc=${dc}`,
}; };
const item = { const item = {
SourceNS: 'SourceNS',
SourceName: 'SourceName', SourceName: 'SourceName',
DestinationNS: 'DestinationNS',
DestinationName: 'DestinationName', DestinationName: 'DestinationName',
SourceNS: 'SourceNS',
DestinationNS: 'DestinationNS',
SourcePartition: 'SourcePartition',
DestinationPartition: 'DestinationPartition',
}; };
return get(request.url).then(function(payload) { return get(request.url).then(function(payload) {
payload = { payload = {
@ -76,7 +78,7 @@ module('Integration | Serializer | intention', function(hooks) {
// refactored out our Serializer this can go // refactored out our Serializer this can go
Namespace: nspace, Namespace: nspace,
Partition: partition, Partition: partition,
uid: `["${partition}","${nspace}","${dc}","${item.SourceNS}:${item.SourceName}:${item.DestinationNS}:${item.DestinationName}"]`, uid: `["${partition}","${nspace}","${dc}","${item.SourcePartition}:${item.SourceNS}:${item.SourceName}:${item.DestinationPartition}:${item.DestinationNS}:${item.DestinationName}"]`,
}); });
const actual = serializer.respondForQueryRecord( const actual = serializer.respondForQueryRecord(
function(cb) { function(cb) {