Skip to content

Commit

Permalink
Merge pull request rancher#10318 from ly5156/secret-resource-type
Browse files Browse the repository at this point in the history
fix(steve/actions): fix missing type attribute of secret resource
  • Loading branch information
richard-cox authored Feb 2, 2024
2 parents 21327c5 + 66093f8 commit aa2653b
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 43 deletions.
5 changes: 1 addition & 4 deletions shell/components/ResourceDetail/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,14 @@ function modeFor(route) {
}
async function getYaml(store, model) {
const inStore = store.getters['currentStore'](model.type);
let yaml;
const opt = { headers: { accept: 'application/yaml' } };
if ( model.hasLink('view') ) {
yaml = (await model.followLink('view', opt)).data;
}
const cleanedYaml = await store.dispatch(`${ inStore }/cleanForDownload`, yaml);
return cleanedYaml;
return model.cleanForDownload(yaml);
}
export default {
Expand Down
37 changes: 37 additions & 0 deletions shell/models/__tests__/secret.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Secret from '@shell/models/secret';

describe('class Secret', () => {
it('should contains the type attribute if cleanForDownload', async() => {
const secret = new Secret({});
const yaml = `apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
`;
const cleanYaml = await secret.cleanForDownload(yaml);

expect(cleanYaml).toBe(yaml);
});

it('should remove id, links and actions keys if cleanForDownload', async() => {
const secret = new Secret({});
const expectedYamlStr = `apiVersion: v1
kind: Secret
metadata:
name: my-secret
namespace: default
type: Opaque
`;
const part = `id: test_id
links:
view: https://example.com
actions:
remove: https://example.com`;
const yaml = `${ expectedYamlStr }
${ part }`;
const cleanYaml = await secret.cleanForDownload(yaml);

expect(cleanYaml).toBe(expectedYamlStr);
});
});
9 changes: 9 additions & 0 deletions shell/models/secret.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SteveModel from '@shell/plugins/steve/steve-class';
import { colorForState, stateDisplay, STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
import { diffFrom } from '@shell/utils/time';
import day from 'dayjs';
import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';

export const TYPES = {
OPAQUE: 'Opaque',
Expand Down Expand Up @@ -456,4 +457,12 @@ export default class Secret extends SteveModel {

return val;
}

async cleanForDownload(yaml) {
// secret resource contains the type attribute
// ref: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/secret-v1/
// ref: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types

return steveCleanForDownload(yaml, { rootKeys: ['id', 'links', 'actions'] });
}
}
8 changes: 6 additions & 2 deletions shell/plugins/dashboard-store/resource-class.js
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,7 @@ export default class Resource {

async download() {
const value = await this.followLink('view', { headers: { accept: 'application/yaml' } });
const data = await this.$dispatch('cleanForDownload', value.data);
const data = await this.cleanForDownload(value.data);

downloadFile(`${ this.nameDisplay }.yaml`, data, 'application/yaml');
}
Expand All @@ -1404,7 +1404,7 @@ export default class Resource {
await eachLimit(items, 10, (item, idx) => {
return item.followLink('view', { headers: { accept: 'application/yaml' } } ).then(async(data) => {
const yaml = data.data || data;
const cleanedYaml = await this.$dispatch('cleanForDownload', yaml);
const cleanedYaml = await this.cleanForDownload(yaml);

files[`resources/${ names[idx] }`] = cleanedYaml;
});
Expand Down Expand Up @@ -1481,6 +1481,10 @@ export default class Resource {
this.$dispatch(`cleanForDiff`, this.toJSON());
}

async cleanForDownload(yaml) {
return this.$dispatch(`cleanForDownload`, yaml);
}

yamlForSave(yaml) {
try {
const obj = jsyaml.load(yaml);
Expand Down
159 changes: 159 additions & 0 deletions shell/plugins/steve/__tests__/resource-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';

describe('steve: ressource-utils', () => {
it('should do nothing if the yaml is not passed', () => {
const r = steveCleanForDownload();

expect(r).toBeUndefined();
});
it('should remove all default rootKeys', () => {
const expectedYamlStr = `apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
`;
const yamlStr = `
id: test_id
links:
view: https://example.com2
type: test_type
actions:
remove: https://example.com
${ expectedYamlStr }
`;
const cleanedYamlStr = steveCleanForDownload(yamlStr);

expect(cleanedYamlStr).toBe(expectedYamlStr);
});
it('should remove all the specified root keys', () => {
const part = `apiVersion: v1
kind: Secret
metadata:
name: my-secret`;

const rootKeyToYamlStringMap = {
id: 'id: test_id',
links: `links:
view: https://example.com`,
actions: `actions:
remove: https://example.com`,
type: 'type: Opaque'
};

const entries = Object.entries(rootKeyToYamlStringMap);
const yamlStr = `${ part }
${ entries.map(([_, str]) => str).join('\n') }`;

entries.forEach(([key, str]) => {
const expectedYamlStr = `${ part }
${ entries.filter(([k]) => k !== key).map(([_, str]) => str).join('\n') }\n`;
const cleanedYamlStr = steveCleanForDownload(yamlStr, { rootKeys: [key] });

expect(cleanedYamlStr).toBe(expectedYamlStr);
});
});
it('should remove all default metadata keys', () => {
const expectedYamlStr = `apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
`;
const yamlStr = `apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
fields:
- kube-root-ca.crt
- 1
- 7d23h
relationships:
- rel: 'owner'
state: 'active'
`;
const cleanedYamlStr = steveCleanForDownload(yamlStr);

expect(cleanedYamlStr).toBe(expectedYamlStr);
});

it('should remove all the specified metadata keys', () => {
const part = `apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap`;

const metadataKeyToYamlStringMap = {
fields:
` fields:
- kube-root-ca.crt
- 1
- 7d23h`,
relationships:
` relationships:
- rel: owner`,
state: ` state: active`
};

const entries = Object.entries(metadataKeyToYamlStringMap);
const yamlStr = `${ part }
${ entries.map(([_, str]) => str).join('\n') }`;

entries.forEach(([key, str]) => {
const expectedYamlStr = `${ part }
${ entries.filter(([k]) => k !== key).map(([_, str]) => str).join('\n') }\n`;
const cleanedYamlStr = steveCleanForDownload(yamlStr, { metadataKeys: [key] });

expect(cleanedYamlStr).toBe(expectedYamlStr);
});
});
it('should remove all defalut condition keys', () => {
const expectedYamlStr = `apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
status:
conditions:
- {}
- {}
- message: message
`;
const yamlStr = `apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
status:
conditions:
- error: 'error'
- transitioning: false
- message: message
`;
const cleanedYamlStr = steveCleanForDownload(yamlStr);

expect(cleanedYamlStr).toBe(expectedYamlStr);
});
it('should remove all the specified condition keys', () => {
const part = `apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
status:
conditions:
- message: message`;

const conditionKeyToYamlStringMap = {
error: ' - error: error',
transitioning: ' - transitioning: false'
};

const entries = Object.entries(conditionKeyToYamlStringMap);
const yamlStr = `${ part }
${ entries.map(([_, str]) => str).join('\n') }`;

entries.forEach(([key, str]) => {
const expectedYamlStr = `${ part }
${ entries.map(([k, str]) => k === key ? ' - {}' : str).join('\n') }\n`;
const cleanedYamlStr = steveCleanForDownload(yamlStr, { conditionKeys: [key] });

expect(cleanedYamlStr).toBe(expectedYamlStr);
});
});
});
40 changes: 3 additions & 37 deletions shell/plugins/steve/actions.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import https from 'https';
import { addParam, parse as parseUrl, stringify as unParseUrl } from '@shell/utils/url';
import { handleSpoofedRequest, loadSchemas } from '@shell/plugins/dashboard-store/actions';
import { set } from '@shell/utils/object';
import { dropKeys, set } from '@shell/utils/object';
import { deferred } from '@shell/utils/promise';
import { streamJson, streamingSupported } from '@shell/utils/stream';
import isObject from 'lodash/isObject';
import { classify } from '@shell/plugins/dashboard-store/classify';
import { NAMESPACE } from '@shell/config/types';
import jsyaml from 'js-yaml';
import { handleKubeApiHeaderWarnings } from '@shell/plugins/steve/header-warnings';
import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';

export default {

Expand Down Expand Up @@ -330,31 +330,7 @@ export default {

// remove fields added by steve before showing/downloading yamls
cleanForDownload(ctx, yaml) {
if (!yaml) {
return;
}
const rootKeys = [
'id',
'links',
'type',
'actions'
];
const metadataKeys = [
'fields',
'relationships',
'state',
];
const conditionKeys = [
'error',
'transitioning',
];
const obj = jsyaml.load(yaml);

dropKeys(obj, rootKeys);
dropKeys(obj?.metadata, metadataKeys);
(obj?.status?.conditions || []).forEach((condition) => dropKeys(condition, conditionKeys));

return jsyaml.dump(obj);
return steveCleanForDownload(yaml);
}
};

Expand Down Expand Up @@ -398,16 +374,6 @@ function dropUnderscores(obj) {
}
}

function dropKeys(obj, keys) {
if ( !obj ) {
return;
}

for ( const k of keys ) {
delete obj[k];
}
}

function dropCattleKeys(obj) {
if ( !obj ) {
return;
Expand Down
38 changes: 38 additions & 0 deletions shell/plugins/steve/resource-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { dropKeys } from '@shell/utils/object';
import jsyaml from 'js-yaml';

export function steveCleanForDownload(yaml: string, keys?: {
rootKeys?: string[],
metadataKeys?: string[],
conditionKeys?: string[]
}): string | undefined {
if (!yaml) {
return;
}

const {
rootKeys = [
'id',
'links',
'type',
'actions'
],
metadataKeys = [
'fields',
'relationships',
'state',
],
conditionKeys = [
'error',
'transitioning',
]
} = keys || {};

const obj: any = jsyaml.load(yaml);

dropKeys(obj, rootKeys);
dropKeys(obj?.metadata, metadataKeys);
(obj?.status?.conditions || []).forEach((condition: any) => dropKeys(condition, conditionKeys));

return jsyaml.dump(obj);
}
10 changes: 10 additions & 0 deletions shell/utils/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,13 @@ export function pickBy(obj = {}, predicate = (value, key) => false) {
export const toDictionary = (array, callback) => Object.assign(
{}, ...array.map((item) => ({ [item]: callback(item) }))
);

export function dropKeys(obj, keys) {
if ( !obj ) {
return;
}

for ( const k of keys ) {
delete obj[k];
}
}

0 comments on commit aa2653b

Please sign in to comment.