Commit 161fc228 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'network-policy-editor-drawer' into 'master'

Network policy editor drawer

See merge request gitlab-org/gitlab!40810
parents da1a6d72 3d0bde1a
...@@ -15,6 +15,8 @@ import { getTimeago } from '~/lib/utils/datetime_utility'; ...@@ -15,6 +15,8 @@ import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment } from '~/lib/utils/url_utility'; import { setUrlFragment } from '~/lib/utils/url_utility';
import EnvironmentPicker from './environment_picker.vue'; import EnvironmentPicker from './environment_picker.vue';
import NetworkPolicyEditor from './network_policy_editor.vue'; import NetworkPolicyEditor from './network_policy_editor.vue';
import PolicyDrawer from './policy_editor/policy_drawer.vue';
import { CiliumNetworkPolicyKind } from './policy_editor/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
...@@ -30,6 +32,7 @@ export default { ...@@ -30,6 +32,7 @@ export default {
GlToggle, GlToggle,
EnvironmentPicker, EnvironmentPicker,
NetworkPolicyEditor, NetworkPolicyEditor,
PolicyDrawer,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
props: { props: {
...@@ -71,6 +74,14 @@ export default { ...@@ -71,6 +74,14 @@ export default {
hasAutoDevopsPolicy() { hasAutoDevopsPolicy() {
return this.policiesWithDefaults.some(policy => policy.isAutodevops); return this.policiesWithDefaults.some(policy => policy.isAutodevops);
}, },
shouldShowCiliumDrawer() {
if (!this.hasSelectedPolicy) return false;
return (
this.glFeatures.networkPolicyEditor &&
this.selectedPolicy.manifest.includes(CiliumNetworkPolicyKind)
);
},
}, },
methods: { methods: {
...mapActions('networkPolicies', ['createPolicy', 'updatePolicy']), ...mapActions('networkPolicies', ['createPolicy', 'updatePolicy']),
...@@ -233,17 +244,23 @@ export default { ...@@ -233,17 +244,23 @@ export default {
</template> </template>
<template> <template>
<div v-if="hasSelectedPolicy"> <div v-if="hasSelectedPolicy">
<h5>{{ s__('NetworkPolicies|Policy definition') }}</h5> <policy-drawer v-if="shouldShowCiliumDrawer" v-model="selectedPolicy.manifest" />
<p>{{ s__("NetworkPolicies|Define this policy's location, conditions and actions.") }}</p>
<div class="gl-p-3 gl-bg-gray-50"> <div v-else>
<network-policy-editor <h5>{{ s__('NetworkPolicies|Policy definition') }}</h5>
ref="policyEditor" <p>
v-model="selectedPolicy.manifest" {{ s__("NetworkPolicies|Define this policy's location, conditions and actions.") }}
class="network-policy-editor" </p>
/> <div class="gl-p-3 gl-bg-gray-50">
<network-policy-editor
ref="policyEditor"
v-model="selectedPolicy.manifest"
class="network-policy-editor"
/>
</div>
</div> </div>
<h5 class="mt-4">{{ s__('NetworkPolicies|Enforcement status') }}</h5> <h5 class="gl-mt-6">{{ s__('NetworkPolicies|Enforcement status') }}</h5>
<p>{{ s__('NetworkPolicies|Choose whether to enforce this policy.') }}</p> <p>{{ s__('NetworkPolicies|Choose whether to enforce this policy.') }}</p>
<gl-toggle v-model="selectedPolicy.isEnabled" data-testid="policyToggle" /> <gl-toggle v-model="selectedPolicy.isEnabled" data-testid="policyToggle" />
</div> </div>
......
...@@ -29,3 +29,5 @@ export const PortMatchModeAny = 'any'; ...@@ -29,3 +29,5 @@ export const PortMatchModeAny = 'any';
export const PortMatchModePortProtocol = 'port/protocol'; export const PortMatchModePortProtocol = 'port/protocol';
export const DisabledByLabel = 'network-policy.gitlab.com/disabled_by'; export const DisabledByLabel = 'network-policy.gitlab.com/disabled_by';
export const CiliumNetworkPolicyKind = 'CiliumNetworkPolicy';
...@@ -112,7 +112,7 @@ function parseRule(item, direction) { ...@@ -112,7 +112,7 @@ function parseRule(item, direction) {
https://docs.cilium.io/en/v1.8/policy/language https://docs.cilium.io/en/v1.8/policy/language
*/ */
export default function fromYaml(manifest) { export default function fromYaml(manifest) {
const { metadata, spec } = safeLoad(manifest, { json: true }); const { description, metadata, spec } = safeLoad(manifest, { json: true });
const { endpointSelector = {}, ingress = [], egress = [] } = spec; const { endpointSelector = {}, ingress = [], egress = [] } = spec;
const matchLabels = endpointSelector.matchLabels || {}; const matchLabels = endpointSelector.matchLabels || {};
...@@ -131,7 +131,7 @@ export default function fromYaml(manifest) { ...@@ -131,7 +131,7 @@ export default function fromYaml(manifest) {
return { return {
name: metadata.name, name: metadata.name,
description: spec.description, description,
isEnabled: !Object.keys(matchLabels).includes(DisabledByLabel), isEnabled: !Object.keys(matchLabels).includes(DisabledByLabel),
endpointMatchMode: endpointLabels.length > 0 ? EndpointMatchModeLabel : EndpointMatchModeAny, endpointMatchMode: endpointLabels.length > 0 ? EndpointMatchModeLabel : EndpointMatchModeAny,
endpointLabels: endpointLabels.join(' '), endpointLabels: endpointLabels.join(' '),
......
import { safeDump } from 'js-yaml'; import { safeDump } from 'js-yaml';
import { ruleSpec } from './rules'; import { ruleSpec } from './rules';
import { labelSelector } from './utils'; import { labelSelector } from './utils';
import { EndpointMatchModeAny, DisabledByLabel } from '../constants'; import { EndpointMatchModeAny, DisabledByLabel, CiliumNetworkPolicyKind } from '../constants';
/* /*
Return kubernetes resource specification object for a policy. Return kubernetes resource specification object for a policy.
*/ */
function spec({ description, rules, isEnabled, endpointMatchMode, endpointLabels }) { function spec({ rules, isEnabled, endpointMatchMode, endpointLabels }) {
const matchLabels = const matchLabels =
endpointMatchMode === EndpointMatchModeAny ? {} : labelSelector(endpointLabels); endpointMatchMode === EndpointMatchModeAny ? {} : labelSelector(endpointLabels);
const policySpec = {}; const policySpec = {};
if (description?.length > 0) {
policySpec.description = description;
}
policySpec.endpointSelector = Object.keys(matchLabels).length > 0 ? { matchLabels } : {}; policySpec.endpointSelector = Object.keys(matchLabels).length > 0 ? { matchLabels } : {};
rules.forEach(rule => { rules.forEach(rule => {
const { direction } = rule; const { direction } = rule;
...@@ -37,14 +33,22 @@ function spec({ description, rules, isEnabled, endpointMatchMode, endpointLabels ...@@ -37,14 +33,22 @@ function spec({ description, rules, isEnabled, endpointMatchMode, endpointLabels
Return yaml representation of a policy. Return yaml representation of a policy.
*/ */
export default function toYaml(policy) { export default function toYaml(policy) {
const { name } = policy; const { name, description } = policy;
const policySpec = { const policySpec = {
apiVersion: 'cilium.io/v2', apiVersion: 'cilium.io/v2',
kind: 'CiliumNetworkPolicy', kind: CiliumNetworkPolicyKind,
};
if (description?.length > 0) {
policySpec.description = description;
}
// We want description at a specific position to have yaml in a common form.
Object.assign(policySpec, {
metadata: { name }, metadata: { name },
spec: spec(policy), spec: spec(policy),
}; });
return safeDump(policySpec, { noArrayIndent: true }); return safeDump(policySpec, { noArrayIndent: true });
} }
<script>
import { GlFormTextarea } from '@gitlab/ui';
import PolicyPreview from './policy_preview.vue';
import fromYaml from './lib/from_yaml';
import toYaml from './lib/to_yaml';
import humanizeNetworkPolicy from './lib/humanize';
export default {
components: {
GlFormTextarea,
PolicyPreview,
},
props: {
value: {
type: String,
required: true,
},
},
computed: {
policy() {
return fromYaml(this.value);
},
humanizedPolicy() {
return humanizeNetworkPolicy(this.policy);
},
policyYaml() {
return toYaml(this.policy);
},
},
methods: {
updateManifest(description) {
const manifest = toYaml({ ...this.policy, description });
this.$emit('input', manifest);
},
},
};
</script>
<template>
<div>
<h4>{{ s__('NetworkPolicies|Policy description') }}</h4>
<h5 class="gl-mt-6">{{ s__('NetworkPolicies|Policy type') }}</h5>
<p>{{ s__('NetworkPolicies|Network Policy') }}</p>
<h5 class="gl-mt-6">{{ s__('NetworkPolicies|Description') }}</h5>
<gl-form-textarea :value="policy.description" @input="updateManifest" />
<policy-preview
class="gl-mt-4"
:initial-tab="1"
:policy-yaml="policyYaml"
:policy-description="humanizedPolicy"
/>
</div>
</template>
...@@ -18,13 +18,21 @@ export default { ...@@ -18,13 +18,21 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
initialTab: {
type: Number,
required: false,
default: 0,
},
},
data() {
return { selectedTab: this.initialTab };
}, },
safeHtmlConfig: { ALLOWED_TAGS: ['strong', 'br'] }, safeHtmlConfig: { ALLOWED_TAGS: ['strong', 'br'] },
}; };
</script> </script>
<template> <template>
<gl-tabs content-class="gl-pt-0"> <gl-tabs v-model="selectedTab" content-class="gl-pt-0">
<gl-tab :title="s__('NetworkPolicies|.yaml')"> <gl-tab :title="s__('NetworkPolicies|.yaml')">
<pre class="gl-bg-white gl-rounded-top-left-none gl-rounded-top-right-none">{{ <pre class="gl-bg-white gl-rounded-top-left-none gl-rounded-top-right-none">{{
policyYaml policyYaml
......
...@@ -6,10 +6,10 @@ exports[`NetworkPolicyList component renders policies table 1`] = ` ...@@ -6,10 +6,10 @@ exports[`NetworkPolicyList component renders policies table 1`] = `
<table <table
aria-busy="false" aria-busy="false"
aria-colcount="3" aria-colcount="3"
aria-describedby="__BVID__143__caption_" aria-describedby="__BVID__334__caption_"
aria-multiselectable="false" aria-multiselectable="false"
class="table b-table gl-table table-hover b-table-stacked-md b-table-selectable b-table-select-single" class="table b-table gl-table table-hover b-table-stacked-md b-table-selectable b-table-select-single"
id="__BVID__143" id="__BVID__334"
role="table" role="table"
> >
<!----> <!---->
......
...@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils'; ...@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils';
import { GlTable } from '@gitlab/ui'; import { GlTable } from '@gitlab/ui';
import createStore from 'ee/threat_monitoring/store'; import createStore from 'ee/threat_monitoring/store';
import NetworkPolicyList from 'ee/threat_monitoring/components/network_policy_list.vue'; import NetworkPolicyList from 'ee/threat_monitoring/components/network_policy_list.vue';
import PolicyDrawer from 'ee/threat_monitoring/components/policy_editor/policy_drawer.vue';
import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants'; import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants';
import { useFakeDate } from 'helpers/fake_date'; import { useFakeDate } from 'helpers/fake_date';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
...@@ -80,6 +81,43 @@ describe('NetworkPolicyList component', () => { ...@@ -80,6 +81,43 @@ describe('NetworkPolicyList component', () => {
const button = wrapper.find('[data-testid="new-policy"]'); const button = wrapper.find('[data-testid="new-policy"]');
expect(button.exists()).toBe(true); expect(button.exists()).toBe(true);
}); });
it('does not render the new policy drawer', () => {
expect(wrapper.find(PolicyDrawer).exists()).toBe(false);
});
describe('given selected policy is a cilium policy', () => {
beforeEach(() => {
factory({
provide: {
glFeatures: {
networkPolicyEditor: true,
},
},
data: () => ({
selectedPolicyName: 'policy',
}),
state: {
policies: [
{
name: 'policy',
isEnabled: false,
manifest: `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: test-policy
spec:
endpointSelector: {}`,
},
],
},
});
});
it('renders the new policy drawer', () => {
expect(wrapper.find(PolicyDrawer).exists()).toBe(true);
});
});
}); });
it('renders policies table', () => { it('renders policies table', () => {
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PolicyDrawer component renders policy preview tabs 1`] = `
<div>
<h4>
Policy description
</h4>
<h5
class="gl-mt-6"
>
Policy type
</h5>
<p>
Network Policy
</p>
<h5
class="gl-mt-6"
>
Description
</h5>
<gl-form-textarea-stub
noresize="true"
value="test description"
/>
<policy-preview-stub
class="gl-mt-4"
initialtab="1"
policydescription="Deny all traffic"
policyyaml="apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
description: test description
metadata:
name: test-policy
spec:
endpointSelector:
matchLabels:
network-policy.gitlab.com/disabled_by: gitlab
"
/>
</div>
`;
...@@ -186,6 +186,7 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = ` ...@@ -186,6 +186,7 @@ exports[`PolicyEditorApp component renders the policy editor layout 1`] = `
</h5> </h5>
<policy-preview-stub <policy-preview-stub
initialtab="0"
policydescription="Deny all traffic" policydescription="Deny all traffic"
policyyaml="apiVersion: cilium.io/v2 policyyaml="apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy kind: CiliumNetworkPolicy
......
...@@ -4,6 +4,7 @@ exports[`PolicyPreview component renders policy preview tabs 1`] = ` ...@@ -4,6 +4,7 @@ exports[`PolicyPreview component renders policy preview tabs 1`] = `
<gl-tabs-stub <gl-tabs-stub
contentclass="gl-pt-0" contentclass="gl-pt-0"
theme="indigo" theme="indigo"
value="0"
> >
<gl-tab-stub <gl-tab-stub
title=".yaml" title=".yaml"
......
...@@ -29,10 +29,10 @@ spec: ...@@ -29,10 +29,10 @@ spec:
it('returns yaml representation', () => { it('returns yaml representation', () => {
expect(toYaml(policy)).toEqual(`apiVersion: cilium.io/v2 expect(toYaml(policy)).toEqual(`apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy kind: CiliumNetworkPolicy
description: test description
metadata: metadata:
name: test-policy name: test-policy
spec: spec:
description: test description
endpointSelector: endpointSelector:
matchLabels: matchLabels:
network-policy.gitlab.com/disabled_by: gitlab network-policy.gitlab.com/disabled_by: gitlab
......
import { GlFormTextarea } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PolicyDrawer from 'ee/threat_monitoring/components/policy_editor/policy_drawer.vue';
import toYaml from 'ee/threat_monitoring/components/policy_editor/lib/to_yaml';
import fromYaml from 'ee/threat_monitoring/components/policy_editor/lib/from_yaml';
describe('PolicyDrawer component', () => {
let wrapper;
const policy = {
name: 'test-policy',
description: 'test description',
endpointLabels: '',
rules: [],
};
const factory = ({ propsData } = {}) => {
wrapper = shallowMount(PolicyDrawer, {
propsData: {
...propsData,
},
});
};
beforeEach(() => {
factory({
propsData: {
value: toYaml(policy),
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('renders policy preview tabs', () => {
expect(wrapper.find('div').element).toMatchSnapshot();
});
it('emits input event on description change', () => {
wrapper.find(GlFormTextarea).vm.$emit('input', 'new description');
expect(wrapper.emitted().input.length).toEqual(1);
const updatedPolicy = fromYaml(wrapper.emitted().input[0][0]);
expect(updatedPolicy.description).toEqual('new description');
});
});
...@@ -82,10 +82,10 @@ describe('PolicyEditorApp component', () => { ...@@ -82,10 +82,10 @@ describe('PolicyEditorApp component', () => {
it('updates policy on yaml editor value change', async () => { it('updates policy on yaml editor value change', async () => {
const manifest = `apiVersion: cilium.io/v2 const manifest = `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy kind: CiliumNetworkPolicy
description: test description
metadata: metadata:
name: test-policy name: test-policy
spec: spec:
description: test description
endpointSelector: endpointSelector:
matchLabels: matchLabels:
network-policy.gitlab.com/disabled_by: gitlab network-policy.gitlab.com/disabled_by: gitlab
......
...@@ -29,4 +29,20 @@ describe('PolicyPreview component', () => { ...@@ -29,4 +29,20 @@ describe('PolicyPreview component', () => {
it('renders policy preview tabs', () => { it('renders policy preview tabs', () => {
expect(wrapper.find(GlTabs).element).toMatchSnapshot(); expect(wrapper.find(GlTabs).element).toMatchSnapshot();
}); });
describe('with initialTab', () => {
beforeEach(() => {
factory({
propsData: {
policyYaml: 'foo',
policyDescription: 'bar',
initialTab: 1,
},
});
});
it('selects initial tab', () => {
expect(wrapper.find(GlTabs).attributes().value).toEqual('1');
});
});
}); });
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