Commit 1ba779f8 authored by ap4y's avatar ap4y

Implement preview component for the policy editor

This commit implements yaml and humanized previews for policy editor
parent 2d04c2cd
......@@ -7,13 +7,7 @@ import {
RuleTypeCIDR,
RuleTypeFQDN,
} from '../constants';
import {
endpointSelector,
portSelectors,
ruleEndpointSelector,
ruleCIDRList,
ruleFQDNList,
} from './utils';
import { portSelectors, labelSelector, splitItems } from './utils';
const strongArgs = { strongOpen: '<strong>', strongClose: '</strong>' };
......@@ -41,7 +35,7 @@ function humanizeNetworkPolicyRulePorts(rule) {
Return humanizied description of an endpoint rule.
*/
function humanizeNetworkPolicyRuleEndpoint({ matchLabels }) {
const matchSelector = ruleEndpointSelector(matchLabels);
const matchSelector = labelSelector(matchLabels);
const labels = Object.keys(matchSelector)
.map(key => `${key}: ${matchSelector[key]}`)
.join(', ');
......@@ -68,7 +62,7 @@ function humanizeNetworkPolicyRuleEntity({ entities }) {
Return humanizied description of a cidr rule.
*/
function humanizeNetworkPolicyRuleCIDR({ cidr }) {
const cidrList = ruleCIDRList(cidr);
const cidrList = splitItems(cidr);
const cidrs =
cidrList.length === 0 ? s__('NetworkPolicies|all IP addresses') : cidrList.join(', ');
return `<strong>${cidrs}</strong>`;
......@@ -78,7 +72,7 @@ function humanizeNetworkPolicyRuleCIDR({ cidr }) {
Return humanizied description of a fqdn rule.
*/
function humanizeNetworkPolicyRuleFQDN({ fqdn }) {
const fqdnList = ruleFQDNList(fqdn);
const fqdnList = splitItems(fqdn);
const fqdns = fqdnList.length === 0 ? s__('NetworkPolicies|all DNS names') : fqdnList.join(', ');
return `<strong>${fqdns}</strong>`;
}
......@@ -104,12 +98,11 @@ function humanizeNetworkPolicyRule(rule) {
/*
Return humanizied description of an endpoint matcher of a policy.
*/
function humanizeEndpointSelector(policy) {
const { endpointMatchMode } = policy;
function humanizeEndpointSelector({ endpointMatchMode, endpointLabels }) {
if (endpointMatchMode === EndpointMatchModeAny)
return sprintf(s__('NetworkPolicies|%{strongOpen}all%{strongClose} pods'), strongArgs, false);
const selector = endpointSelector(policy);
const selector = labelSelector(endpointLabels);
const pods = Object.keys(selector)
.map(key => `${key}: ${selector[key]}`)
.join(', ');
......@@ -146,5 +139,5 @@ export default function humanizeNetworkPolicy(policy) {
return sprintf(template, { selector, ruleSelector, ports }, false);
});
return humanizedRules.join(`<br><br>${__('and').toUpperCase()}<br><br>`);
return humanizedRules.join(`<br><br>${__('and').toLocaleUpperCase()}<br><br>`);
}
......@@ -6,7 +6,7 @@ import {
RuleDirectionInbound,
PortMatchModeAny,
} from '../constants';
import { portSelectors, ruleEndpointSelector, ruleCIDRList, ruleFQDNList } from './utils';
import { portSelectors, labelSelector, splitItems } from './utils';
/*
Return kubernetes specification object that is shared by all rule types.
......@@ -22,7 +22,7 @@ function commonSpec(rule) {
Return kubernetes specification object for an endpoint rule.
*/
function ruleEndpointSpec({ direction, matchLabels }) {
const matchSelector = ruleEndpointSelector(matchLabels);
const matchSelector = labelSelector(matchLabels);
if (Object.keys(matchSelector).length === 0) return {};
return {
......@@ -49,7 +49,7 @@ function ruleEntitySpec({ direction, entities }) {
Return kubernetes specification object for a cidr rule.
*/
function ruleCIDRSpec({ direction, cidr }) {
const cidrList = ruleCIDRList(cidr);
const cidrList = splitItems(cidr);
if (cidrList.length === 0) return {};
return {
......@@ -63,7 +63,7 @@ function ruleCIDRSpec({ direction, cidr }) {
function ruleFQDNSpec({ direction, fqdn }) {
if (direction === RuleDirectionInbound) return {};
const fqdnList = ruleFQDNList(fqdn);
const fqdnList = splitItems(fqdn);
if (fqdnList.length === 0) return {};
return {
......
import { safeDump } from 'js-yaml';
import { ruleSpec } from './rules';
import { endpointSelector } from './utils';
import { labelSelector } from './utils';
import { EndpointMatchModeAny } from '../constants';
/*
Return kubernetes resource specification object for a policy.
*/
function spec(policy) {
const { description, rules, isEnabled } = policy;
const matchLabels = endpointSelector(policy);
function spec({ description, rules, isEnabled, endpointMatchMode, endpointLabels }) {
const matchLabels =
endpointMatchMode === EndpointMatchModeAny ? {} : labelSelector(endpointLabels);
const policySpec = {};
if (description?.length > 0) {
......
import { EndpointMatchModeAny, PortMatchModeAny } from '../constants';
import { PortMatchModeAny } from '../constants';
/*
Convert enpdoint labels provided as a string into a kubernetes selector.
Expects endpointLabels in format "one two:three"
Convert space separated list of labels into a kubernetes selector.
Expects matchLabels in format "one two:three"
*/
export function endpointSelector({ endpointMatchMode, endpointLabels }) {
if (endpointMatchMode === EndpointMatchModeAny) return {};
return endpointLabels.split(/\s/).reduce((acc, item) => {
export function labelSelector(labels) {
return labels.split(/\s/).reduce((acc, item) => {
const [key, value = ''] = item.split(':');
if (key.length === 0) return acc;
......@@ -21,7 +19,7 @@ export function endpointSelector({ endpointMatchMode, endpointLabels }) {
Expects ports in format "80/tcp 81"
*/
export function portSelectors({ portMatchMode, ports }) {
if (portMatchMode === PortMatchModeAny) return {};
if (portMatchMode === PortMatchModeAny) return [];
return ports.split(/\s/).reduce((acc, item) => {
const [port, protocol = 'tcp'] = item.split('/');
......@@ -34,31 +32,9 @@ export function portSelectors({ portMatchMode, ports }) {
}
/*
Convert list of labels provided as a string into a kubernetes endpoint selector.
Expects matchLabels in format "one two:three"
*/
export function ruleEndpointSelector(matchLabels) {
return matchLabels.split(/\s/).reduce((acc, item) => {
const [key, value = ''] = item.split(':');
if (key.length === 0) return acc;
acc[key] = value.trim();
return acc;
}, {});
}
/*
Convert list of CIDRs provided as a string into a CIDR list expected by the kubernetes policy.
Expects cidr in format "0.0.0.0/24 1.1.1.1/32"
*/
export function ruleCIDRList(cidr) {
return cidr.length === 0 ? [] : cidr.split(/\s/);
}
/*
Convert list of FQDNs provided as a string into a FQDN list expected be the kubernetes policy.
Expects fqdn in format "one-service.com another-service.com"
Convert whitespace separated list of items provided as a string into a list.
Expects items in format "0.0.0.0/24 1.1.1.1/32"
*/
export function ruleFQDNList(fqdn) {
return fqdn.length === 0 ? [] : fqdn.split(/\s/);
export function splitItems(items) {
return items.split(/\s/).filter(item => item.length > 0);
}
......@@ -21,7 +21,9 @@ import {
EndpointMatchModeAny,
RuleTypeEndpoint,
} from './constants';
import toYaml from './lib/to_yaml';
import { buildRule } from './lib/rules';
import humanizeNetworkPolicy from './lib/humanize';
export default {
components: {
......@@ -52,6 +54,12 @@ export default {
};
},
computed: {
humanizedPolicy() {
return humanizeNetworkPolicy(this.policy);
},
policyYaml() {
return toYaml(this.policy);
},
shouldShowRuleEditor() {
return this.editorMode === EditorModeRule;
},
......@@ -163,7 +171,7 @@ export default {
</div>
<div class="col-sm-12 col-md-6 col-lg-5 col-xl-4">
<h5>{{ s__('NetworkPolicies|Policy preview') }}</h5>
<policy-preview />
<policy-preview :policy-yaml="policyYaml" :policy-description="humanizedPolicy" />
</div>
</div>
<div v-if="shouldShowYamlEditor" class="row" data-testid="yaml-editor">
......
<script>
export default {};
import { GlTabs, GlTab, GlSafeHtmlDirective } from '@gitlab/ui';
export default {
components: {
GlTabs,
GlTab,
},
directives: {
safeHtml: GlSafeHtmlDirective,
},
props: {
policyYaml: {
type: String,
required: true,
},
policyDescription: {
type: String,
required: true,
},
},
safeHtmlConfig: { ALLOWED_TAGS: ['strong', 'br'] },
};
</script>
<template>
<div class="gl-bg-gray-100 p-9"></div>
<gl-tabs content-class="gl-pt-0">
<gl-tab :title="s__('NetworkPolicies|.yaml')">
<pre class="gl-bg-white gl-rounded-top-left-none gl-rounded-top-right-none">{{
policyYaml
}}</pre>
</gl-tab>
<gl-tab :title="s__('NetworkPolicies|Rule')">
<div
v-safe-html:[$options.safeHtmlConfig]="policyDescription"
class="gl-bg-white gl-rounded-top-left-none gl-rounded-top-right-none gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-py-3 gl-px-4 gl-border-1 gl-border-solid gl-border-gray-100"
></div>
</gl-tab>
</gl-tabs>
</template>
......@@ -178,7 +178,18 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = `
Policy preview
</h5>
<policy-preview-stub />
<policy-preview-stub
policydescription="Deny all traffic"
policyyaml="apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: ''
spec:
endpointSelector:
matchLabels:
network-policy.gitlab.com/disabled_by: gitlab
"
/>
</div>
</div>
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PolicyPreview component renders policy preview tabs 1`] = `
<gl-tabs-stub
contentclass="gl-pt-0"
theme="indigo"
>
<gl-tab-stub
title=".yaml"
>
<pre
class="gl-bg-white gl-rounded-top-left-none gl-rounded-top-right-none"
>
foo
</pre>
</gl-tab-stub>
<gl-tab-stub
title="Rule"
>
<div
class="gl-bg-white gl-rounded-top-left-none gl-rounded-top-right-none gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-py-3 gl-px-4 gl-border-1 gl-border-solid gl-border-gray-100"
>
<strong>
bar
</strong>
<br />
test
</div>
</gl-tab-stub>
</gl-tabs-stub>
`;
......@@ -10,7 +10,7 @@ import {
RuleTypeFQDN,
} from 'ee/threat_monitoring/components/policy_editor/constants';
describe('humatnizeNetworkPolicy', () => {
describe('humanizeNetworkPolicy', () => {
let policy;
let rule;
......
import {
labelSelector,
portSelectors,
splitItems,
} from 'ee/threat_monitoring/components/policy_editor/lib/utils';
import {
PortMatchModeAny,
PortMatchModePortProtocol,
} from 'ee/threat_monitoring/components/policy_editor/constants';
describe('labelSelector', () => {
it('returns selector map', () => {
expect(labelSelector('one two: three:value three:override ')).toMatchObject({
one: '',
two: '',
three: 'override',
});
});
});
describe('portSelectors', () => {
it('returns list of selectors', () => {
expect(
portSelectors({
portMatchMode: PortMatchModePortProtocol,
ports: '80 81/tcp 82/UDP ',
}),
).toEqual([
{ port: '80', protocol: 'TCP' },
{ port: '81', protocol: 'TCP' },
{ port: '82', protocol: 'UDP' },
]);
});
describe('when port match mode is any', () => {
it('returns empty selector', () => {
expect(
portSelectors({
portMatchMode: PortMatchModeAny,
ports: '80 81/tcp 81/UDP ',
}),
).toEqual([]);
});
});
});
describe('splitItems', () => {
it('returns list of entries', () => {
expect(splitItems('10.0.0.1/32 10.0.0.1/24 ')).toEqual(['10.0.0.1/32', '10.0.0.1/24']);
});
});
import { shallowMount } from '@vue/test-utils';
import PolicyEditorApp from 'ee/threat_monitoring/components/policy_editor/policy_editor.vue';
import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue';
import PolicyRuleBuilder from 'ee/threat_monitoring/components/policy_editor/policy_rule_builder.vue';
import createStore from 'ee/threat_monitoring/store';
import {
......@@ -29,6 +30,7 @@ describe('PolicyEditorApp component', () => {
const findRuleEditor = () => wrapper.find('[data-testid="rule-editor"]');
const findYamlEditor = () => wrapper.find('[data-testid="yaml-editor"]');
const findPreview = () => wrapper.find(PolicyPreview);
beforeEach(() => {
factory();
......@@ -67,6 +69,32 @@ describe('PolicyEditorApp component', () => {
});
});
describe('given there is a name change', () => {
let initialValue;
beforeEach(() => {
initialValue = findPreview().props('policyYaml');
wrapper.find("[id='policyName']").vm.$emit('input', 'new');
});
it('updates policy yaml preview', () => {
expect(findPreview().props('policyYaml')).not.toEqual(initialValue);
});
});
describe('given there is a rule change', () => {
let initialValue;
beforeEach(() => {
initialValue = findPreview().props('policyDescription');
wrapper.find("[data-testid='add-rule']").vm.$emit('click');
});
it('updates policy description preview', () => {
expect(findPreview().props('policyDescription')).not.toEqual(initialValue);
});
});
it('adds a new rule', async () => {
expect(wrapper.findAll(PolicyRuleBuilder).length).toEqual(0);
const button = wrapper.find("[data-testid='add-rule']");
......
import { shallowMount } from '@vue/test-utils';
import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue';
import { GlTabs } from '@gitlab/ui';
describe('PolicyPreview component', () => {
let wrapper;
const factory = ({ propsData } = {}) => {
wrapper = shallowMount(PolicyPreview, {
propsData: {
...propsData,
},
});
};
beforeEach(() => {
factory({
propsData: {
policyYaml: 'foo',
policyDescription: '<strong>bar</strong><br><div>test</div><script></script>',
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('renders policy preview tabs', () => {
expect(wrapper.find(GlTabs).element).toMatchSnapshot();
});
});
......@@ -16131,6 +16131,15 @@ msgstr ""
msgid "NetworkPolicies|%{number} selected"
msgstr ""
msgid "NetworkPolicies|%{strongOpen}all%{strongClose} pods"
msgstr ""
msgid "NetworkPolicies|%{strongOpen}any%{strongClose} port"
msgstr ""
msgid "NetworkPolicies|.yaml"
msgstr ""
msgid "NetworkPolicies|.yaml mode"
msgstr ""
......@@ -16140,6 +16149,12 @@ msgstr ""
msgid "NetworkPolicies|All selected"
msgstr ""
msgid "NetworkPolicies|Allow all inbound traffic to %{selector} from %{ruleSelector} on %{ports}"
msgstr ""
msgid "NetworkPolicies|Allow all outbound traffic from %{selector} to %{ruleSelector} on %{ports}"
msgstr ""
msgid "NetworkPolicies|Choose whether to enforce this policy."
msgstr ""
......@@ -16149,6 +16164,9 @@ msgstr ""
msgid "NetworkPolicies|Define this policy's location, conditions and actions."
msgstr ""
msgid "NetworkPolicies|Deny all traffic"
msgstr ""
msgid "NetworkPolicies|Description"
msgstr ""
......@@ -16218,6 +16236,9 @@ msgstr ""
msgid "NetworkPolicies|Policy type"
msgstr ""
msgid "NetworkPolicies|Rule"
msgstr ""
msgid "NetworkPolicies|Rule mode"
msgstr ""
......@@ -16236,6 +16257,12 @@ msgstr ""
msgid "NetworkPolicies|YAML editor"
msgstr ""
msgid "NetworkPolicies|all DNS names"
msgstr ""
msgid "NetworkPolicies|all IP addresses"
msgstr ""
msgid "NetworkPolicies|any pod"
msgstr ""
......@@ -16251,15 +16278,24 @@ msgstr ""
msgid "NetworkPolicies|inbound to"
msgstr ""
msgid "NetworkPolicies|nowhere"
msgstr ""
msgid "NetworkPolicies|outbound from"
msgstr ""
msgid "NetworkPolicies|pod with labels"
msgstr ""
msgid "NetworkPolicies|pods %{pods}"
msgstr ""
msgid "NetworkPolicies|pods with labels"
msgstr ""
msgid "NetworkPolicies|ports %{ports}"
msgstr ""
msgid "NetworkPolicies|ports/protocols"
msgstr ""
......@@ -28751,6 +28787,9 @@ msgstr ""
msgid "among other things"
msgstr ""
msgid "and"
msgstr ""
msgid "any-approver for the merge request already exists"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment