Commit 3cd9520c authored by Alexander Turinske's avatar Alexander Turinske

Refactor unsupported attribute logic to be cleaner

- use every method
- abstract out reusable parts
parent 66a083e6
import { s__ } from '~/locale';
export const EditorModeRule = 'rule';
export const EditorModeYAML = 'yaml';
......@@ -35,3 +37,7 @@ export const DisabledByLabel = 'network-policy.gitlab.com/disabled_by';
export const CiliumNetworkPolicyKind = 'CiliumNetworkPolicy';
export const ProjectIdLabel = 'app.gitlab.com/proj';
export const PARSING_ERROR_MESSAGE = s__(
'NetworkPolicies|Rule mode is unavailable for this policy. In some cases, we cannot parse the YAML file back into the rules editor.',
);
......@@ -106,13 +106,80 @@ function parseRule(item, direction) {
};
}
/**
* Checks for parameters unsupported by the network policy "Rule Mode"
* @param {String} manifest YAML of network policy
* @returns {Boolean} whether the YAML is valid to be parsed into "Rule Mode"
*/
const checkForUnsupportedAttributes = (manifest) => {
const primaryKeys = ['apiVersion', 'description', 'kind', 'metadata', 'spec'];
const metadataKeys = ['annotations', 'labels', 'name', 'namespace', 'resourceVersion'];
const specKeys = ['egress', 'endpointSelector', 'ingress'];
const ruleKeys = [
'fromEntities',
'toEntities',
'fromCIDR',
'toCIDR',
'toFQDNs',
'fromEndpoints',
'toEndpoints',
'toPorts',
];
const toPortKeys = ['ports'];
const portKeys = ['port', 'protocol'];
let isUnsupported = false;
const hasInvalidKeys = (object, allowedValues) => {
return !Object.keys(object).every((item) => allowedValues.includes(item));
};
const hasInvalidPolicy = (type) => {
let hasUnsupportedAttributes = false;
manifest.spec[type].forEach((item) => {
hasUnsupportedAttributes = hasInvalidKeys(item, ruleKeys);
if (item.toPorts?.length && !hasUnsupportedAttributes) {
item.toPorts.forEach((entry) => {
hasUnsupportedAttributes = hasInvalidKeys(entry, toPortKeys);
if (entry.ports?.length && !hasUnsupportedAttributes) {
entry.ports.forEach((portEntry) => {
hasUnsupportedAttributes = hasInvalidKeys(portEntry, portKeys);
});
}
});
}
});
return hasUnsupportedAttributes;
};
isUnsupported = hasInvalidKeys(manifest, primaryKeys);
if (manifest?.metadata && !isUnsupported) {
isUnsupported = hasInvalidKeys(manifest.metadata, metadataKeys);
}
if (manifest?.spec && !isUnsupported) {
isUnsupported = hasInvalidKeys(manifest.spec, specKeys);
}
if (manifest?.spec?.ingress?.length && !isUnsupported) {
isUnsupported = hasInvalidPolicy('ingress');
}
if (manifest?.spec?.egress?.length && !isUnsupported) {
isUnsupported = hasInvalidPolicy('egress');
}
return isUnsupported;
};
/*
Construct a policy object expected by the policy editor from a yaml manifest.
Expected yaml structure is defined in the official documentation:
https://docs.cilium.io/en/v1.8/policy/language
*/
export default function fromYaml(manifest) {
const { description, metadata, spec } = safeLoad(manifest, { json: true });
const policy = safeLoad(manifest, { json: true });
const unsupportedAttribute = checkForUnsupportedAttributes(policy);
if (unsupportedAttribute) return { error: unsupportedAttribute };
const { description, metadata, spec } = policy;
const { name, resourceVersion, annotations, labels } = metadata;
const { endpointSelector = {}, ingress = [], egress = [] } = spec;
const matchLabels = endpointSelector.matchLabels || {};
......@@ -129,77 +196,6 @@ export default function fromYaml(manifest) {
egress.map((item) => parseRule(item, RuleDirectionOutbound)),
)
.filter((rule) => Boolean(rule));
// Check for unsupported parameters
const manifestObj = JSON.parse(manifest);
const primaryKeys = ['description', 'metadata', 'spec'];
const metadataKeys = ['name', 'resourceVersion', 'annotations', 'labels'];
const specKeys = ['endpointSelector', 'ingress', 'egress'];
const ruleKeys = [
'fromEntities',
'toEntities',
'fromCIDR',
'toCIDR',
'toFQDNs',
'fromEndpoints',
'toEndpoints',
'toPorts',
];
const toPortKeys = ['ports'];
const portKeys = ['port', 'protocol'];
if (manifestObj) {
Object.keys(manifestObj).forEach((item) => {
if (!primaryKeys.includes(item)) throw new Error('Unsupported attribute');
});
if (manifestObj.metadata) {
Object.keys(manifestObj.metadata).forEach((item) => {
if (!metadataKeys.includes(item)) throw new Error('Unsupported attribute');
});
}
if (manifestObj.spec) {
Object.keys(manifestObj.spec).forEach((item) => {
if (!specKeys.includes(item)) throw new Error('Unsupported attribute');
});
if (manifestObj.spec.ingress) {
Object.keys(manifestObj.spec.ingress).forEach((item) => {
if (!ruleKeys.includes(item)) throw new Error('Unsupported attribute');
});
if (manifestObj.spec.ingress.toPorts) {
manifestObj.spec.ingress.toPorts.forEach((entry) => {
Object.keys(entry).forEach((item) => {
if (!toPortKeys.includes(item)) throw new Error('Unsupported attribute');
});
if (entry.ports) {
entry.ports.forEach((portEntry) => {
Object.keys(portEntry).forEach((item) => {
if (!portKeys.includes(item)) throw new Error('Unsupported attribute');
});
});
}
});
}
}
if (manifestObj.spec.egress) {
Object.keys(manifestObj.spec.egress).forEach((item) => {
if (!ruleKeys.includes(item)) throw new Error('Unsupported attribute');
});
if (manifestObj.spec.egress.toPorts) {
manifestObj.spec.egress.toPorts.forEach((entry) => {
Object.keys(entry).forEach((item) => {
if (!toPortKeys.includes(item)) throw new Error('Unsupported attribute');
});
if (entry.ports) {
entry.ports.forEach((portEntry) => {
Object.keys(portEntry).forEach((item) => {
if (!portKeys.includes(item)) throw new Error('Unsupported attribute');
});
});
}
});
}
}
}
}
return {
name,
......
......@@ -17,19 +17,15 @@ export default {
},
},
computed: {
canHumanizePolicy() {
console.log('to/from', toYaml(fromYaml(this.value)));
console.log('original', this.value);
return this.value.includes(toYaml(fromYaml(this.value)));
},
initialTab() {
return this.canHumanizePolicy ? 1 : 0;
return this.policy ? 1 : 0;
},
policy() {
return this.canHumanizePolicy ? fromYaml(this.value) : null;
const policy = fromYaml(this.value);
return policy.error ? null : policy;
},
humanizedPolicy() {
return this.canHumanizePolicy ? humanizeNetworkPolicy(this.policy) : null;
return this.policy ? humanizeNetworkPolicy(this.policy) : this.policy;
},
policyYaml() {
return this.value;
......@@ -51,7 +47,7 @@ export default {
<h5 class="gl-mt-6">{{ s__('NetworkPolicies|Policy type') }}</h5>
<p>{{ s__('NetworkPolicies|Network Policy') }}</p>
<div v-if="canHumanizePolicy">
<div v-if="policy">
<h5 class="gl-mt-6">{{ s__('NetworkPolicies|Description') }}</h5>
<gl-form-textarea :value="policy.description" @input="updateManifest" />
</div>
......
......@@ -22,6 +22,7 @@ import {
EndpointMatchModeAny,
RuleTypeEndpoint,
ProjectIdLabel,
PARSING_ERROR_MESSAGE,
} from './constants';
import DimDisableContainer from './dim_disable_container.vue';
import fromYaml from './lib/from_yaml';
......@@ -36,6 +37,7 @@ import PolicyRuleBuilder from './policy_rule_builder.vue';
export default {
i18n: {
toggleLabel: s__('NetworkPolicies|Policy status'),
PARSING_ERROR_MESSAGE,
},
components: {
GlFormGroup,
......@@ -165,7 +167,11 @@ export default {
this.yamlEditorError = null;
try {
Object.assign(this.policy, fromYaml(manifest));
const newPolicy = fromYaml(manifest);
if (newPolicy.error) {
throw new Error(newPolicy.error);
}
Object.assign(this.policy, newPolicy);
} catch (error) {
this.yamlEditorError = error;
}
......@@ -203,9 +209,6 @@ export default {
{ value: EditorModeRule, text: s__('NetworkPolicies|Rule mode') },
{ value: EditorModeYAML, text: s__('NetworkPolicies|.yaml mode') },
],
parsingErrorMessage: s__(
'NetworkPolicies|Rule mode is unavailable for this policy. In some cases, we cannot parse the YAML file back into the rules editor.',
),
deleteModal: {
id: 'delete-modal',
secondary: {
......@@ -281,7 +284,7 @@ export default {
class="gl-z-index-1"
data-testid="parsing-alert"
:dismissible="false"
>{{ $options.parsingErrorMessage }}</gl-alert
>{{ $options.i18n.PARSING_ERROR_MESSAGE }}</gl-alert
>
<dim-disable-container data-testid="rule-builder-container" :disabled="hasParsingError">
......
<script>
import { GlAlert, GlTabs, GlTab, GlSafeHtmlDirective } from '@gitlab/ui';
import { PARSING_ERROR_MESSAGE } from './constants';
export default {
i18n: {
PARSING_ERROR_MESSAGE,
},
components: {
GlAlert,
GlTabs,
......@@ -48,7 +52,7 @@ export default {
></div>
<div v-else>
<gl-alert variant="info" :dismissible="false"
>{{ s__('NetworkPolicies|Unsupported attribute') }}
>{{ $options.i18n.PARSING_ERROR_MESSAGE }}
</gl-alert>
</div>
</gl-tab>
......
---
title: Throw an error when yaml mode contains unparsable attributes
merge_request: 54987
merge_request: 58457
author:
type: fixed
......@@ -16,16 +16,18 @@ exports[`PolicyDrawer component renders policy preview tabs 1`] = `
Network Policy
</p>
<h5
class="gl-mt-6"
>
Description
</h5>
<gl-form-textarea-stub
noresize="true"
value="test description"
/>
<div>
<h5
class="gl-mt-6"
>
Description
</h5>
<gl-form-textarea-stub
noresize="true"
value="test description"
/>
</div>
<policy-preview-stub
class="gl-mt-4"
......
......@@ -20790,9 +20790,6 @@ msgstr ""
msgid "NetworkPolicies|Unable to parse policy"
msgstr ""
msgid "NetworkPolicies|Unsupported attribute"
msgstr ""
msgid "NetworkPolicies|YAML editor"
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