Commit 3d0bde1a authored by ap4y's avatar ap4y

Implement new policy drawer for the policy list

This commit implements a new policy drawer for the cilium network
policies. This drawer is visible only when 'network_policy_editor' is
enabled and selected policy is a cilium policy, otherwise we are using
existing drawer implementation. Fix for the 'description' related bug
in toYaml and fromYaml helpers was also included.
parent 4a0c6ab9
......@@ -15,6 +15,8 @@ import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment } from '~/lib/utils/url_utility';
import EnvironmentPicker from './environment_picker.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';
......@@ -30,6 +32,7 @@ export default {
GlToggle,
EnvironmentPicker,
NetworkPolicyEditor,
PolicyDrawer,
},
mixins: [glFeatureFlagsMixin()],
props: {
......@@ -71,6 +74,14 @@ export default {
hasAutoDevopsPolicy() {
return this.policiesWithDefaults.some(policy => policy.isAutodevops);
},
shouldShowCiliumDrawer() {
if (!this.hasSelectedPolicy) return false;
return (
this.glFeatures.networkPolicyEditor &&
this.selectedPolicy.manifest.includes(CiliumNetworkPolicyKind)
);
},
},
methods: {
...mapActions('networkPolicies', ['createPolicy', 'updatePolicy']),
......@@ -233,17 +244,23 @@ export default {
</template>
<template>
<div v-if="hasSelectedPolicy">
<h5>{{ s__('NetworkPolicies|Policy definition') }}</h5>
<p>{{ s__("NetworkPolicies|Define this policy's location, conditions and actions.") }}</p>
<div class="gl-p-3 gl-bg-gray-50">
<network-policy-editor
ref="policyEditor"
v-model="selectedPolicy.manifest"
class="network-policy-editor"
/>
<policy-drawer v-if="shouldShowCiliumDrawer" v-model="selectedPolicy.manifest" />
<div v-else>
<h5>{{ s__('NetworkPolicies|Policy definition') }}</h5>
<p>
{{ s__("NetworkPolicies|Define this policy's location, conditions and actions.") }}
</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>
<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>
<gl-toggle v-model="selectedPolicy.isEnabled" data-testid="policyToggle" />
</div>
......
......@@ -29,3 +29,5 @@ export const PortMatchModeAny = 'any';
export const PortMatchModePortProtocol = 'port/protocol';
export const DisabledByLabel = 'network-policy.gitlab.com/disabled_by';
export const CiliumNetworkPolicyKind = 'CiliumNetworkPolicy';
......@@ -112,7 +112,7 @@ function parseRule(item, direction) {
https://docs.cilium.io/en/v1.8/policy/language
*/
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 matchLabels = endpointSelector.matchLabels || {};
......@@ -131,7 +131,7 @@ export default function fromYaml(manifest) {
return {
name: metadata.name,
description: spec.description,
description,
isEnabled: !Object.keys(matchLabels).includes(DisabledByLabel),
endpointMatchMode: endpointLabels.length > 0 ? EndpointMatchModeLabel : EndpointMatchModeAny,
endpointLabels: endpointLabels.join(' '),
......
import { safeDump } from 'js-yaml';
import { ruleSpec } from './rules';
import { labelSelector } from './utils';
import { EndpointMatchModeAny, DisabledByLabel } from '../constants';
import { EndpointMatchModeAny, DisabledByLabel, CiliumNetworkPolicyKind } from '../constants';
/*
Return kubernetes resource specification object for a policy.
*/
function spec({ description, rules, isEnabled, endpointMatchMode, endpointLabels }) {
function spec({ rules, isEnabled, endpointMatchMode, endpointLabels }) {
const matchLabels =
endpointMatchMode === EndpointMatchModeAny ? {} : labelSelector(endpointLabels);
const policySpec = {};
if (description?.length > 0) {
policySpec.description = description;
}
policySpec.endpointSelector = Object.keys(matchLabels).length > 0 ? { matchLabels } : {};
rules.forEach(rule => {
const { direction } = rule;
......@@ -37,14 +33,22 @@ function spec({ description, rules, isEnabled, endpointMatchMode, endpointLabels
Return yaml representation of a policy.
*/
export default function toYaml(policy) {
const { name } = policy;
const { name, description } = policy;
const policySpec = {
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 },
spec: spec(policy),
};
});
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 {
type: String,
required: true,
},
initialTab: {
type: Number,
required: false,
default: 0,
},
},
data() {
return { selectedTab: this.initialTab };
},
safeHtmlConfig: { ALLOWED_TAGS: ['strong', 'br'] },
};
</script>
<template>
<gl-tabs content-class="gl-pt-0">
<gl-tabs v-model="selectedTab" 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
......
......@@ -6,10 +6,10 @@ exports[`NetworkPolicyList component renders policies table 1`] = `
<table
aria-busy="false"
aria-colcount="3"
aria-describedby="__BVID__143__caption_"
aria-describedby="__BVID__334__caption_"
aria-multiselectable="false"
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"
>
<!---->
......
......@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils';
import { GlTable } from '@gitlab/ui';
import createStore from 'ee/threat_monitoring/store';
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 { useFakeDate } from 'helpers/fake_date';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
......@@ -80,6 +81,43 @@ describe('NetworkPolicyList component', () => {
const button = wrapper.find('[data-testid="new-policy"]');
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', () => {
......
// 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`] = `
</h5>
<policy-preview-stub
initialtab="0"
policydescription="Deny all traffic"
policyyaml="apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
......
......@@ -4,6 +4,7 @@ exports[`PolicyPreview component renders policy preview tabs 1`] = `
<gl-tabs-stub
contentclass="gl-pt-0"
theme="indigo"
value="0"
>
<gl-tab-stub
title=".yaml"
......
......@@ -29,10 +29,10 @@ spec:
it('returns yaml representation', () => {
expect(toYaml(policy)).toEqual(`apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
description: test description
metadata:
name: test-policy
spec:
description: test description
endpointSelector:
matchLabels:
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', () => {
it('updates policy on yaml editor value change', async () => {
const manifest = `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
description: test description
metadata:
name: test-policy
spec:
description: test description
endpointSelector:
matchLabels:
network-policy.gitlab.com/disabled_by: gitlab
......
......@@ -29,4 +29,20 @@ describe('PolicyPreview component', () => {
it('renders policy preview tabs', () => {
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