Fix KV bug on deletion_time (#22842)

* the fix

* working, but work in progress maybe change to another helper, put draft up?

* some fixes and restructing

* change names

* test assert wrong locally, lets see about gh
This commit is contained in:
Angel Garbarino 2023-09-07 15:09:33 -06:00 committed by GitHub
parent f96ecf3800
commit 7556dfd158
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 48 additions and 17 deletions

View file

@ -7,6 +7,7 @@ import Model, { attr } from '@ember-data/model';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import { withModelValidations } from 'vault/decorators/model-validations';
import { withFormFields } from 'vault/decorators/model-form-fields';
import { isDeleted } from 'kv/utils/kv-deleted';
/* sample response
{
@ -71,11 +72,16 @@ export default class KvSecretDataModel extends Model {
get state() {
if (this.destroyed) return 'destroyed';
if (this.deletionTime) return 'deleted';
if (this.isSecretDeleted) return 'deleted';
if (this.createdTime) return 'created';
return '';
}
// cannot use isDeleted as model property name because of an ember property conflict
get isSecretDeleted() {
return isDeleted(this.deletionTime);
}
// Permissions
@lazyCapabilities(apiPath`${'backend'}/data/${'path'}`, 'backend', 'path') dataPath;
@lazyCapabilities(apiPath`${'backend'}/metadata/${'path'}`, 'backend', 'path') metadataPath;

View file

@ -8,6 +8,7 @@ import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import { withModelValidations } from 'vault/decorators/model-validations';
import { withFormFields } from 'vault/decorators/model-form-fields';
import { keyIsFolder } from 'core/utils/key-utils';
import { isDeleted } from 'kv/utils/kv-deleted';
const validations = {
maxVersions: [
@ -44,7 +45,7 @@ export default class KvSecretMetadataModel extends Model {
editType: 'ttl',
label: 'Automate secret deletion',
helperTextDisabled: `A secret's version must be manually deleted.`,
helperTextEnabled: 'Delete all new versions of this secret after.',
helperTextEnabled: 'Delete all new versions of this secret after:',
})
deleteVersionAfter;
@ -67,10 +68,16 @@ export default class KvSecretMetadataModel extends Model {
return keyIsFolder(this.path);
}
// cannot use isDeleted due to ember property conflict
get isSecretDeleted() {
return isDeleted(this.deletionTime);
}
// turns version object into an array for version dropdown menu
get sortedVersions() {
const array = [];
for (const key in this.versions) {
this.versions[key].isSecretDeleted = isDeleted(this.versions[key].deletion_time);
array.push({ version: key, ...this.versions[key] });
}
// version keys are in order created with 1 being the oldest, we want newest first
@ -81,7 +88,7 @@ export default class KvSecretMetadataModel extends Model {
get currentSecret() {
if (!this.versions || !this.currentVersion) return false;
const data = this.versions[this.currentVersion];
const state = data.destroyed ? 'destroyed' : data.deletion_time ? 'deleted' : 'created';
const state = data.destroyed ? 'destroyed' : isDeleted(data.deletion_time) ? 'deleted' : 'created';
return {
state,
isDeactivated: state !== 'created',

View file

@ -14,7 +14,7 @@
{{versionData.version}}
{{#if versionData.destroyed}}
<Icon @name="x-square-fill" class="has-text-danger is-pulled-right" />
{{else if versionData.deletion_time}}
{{else if versionData.isSecretDeleted}}
<Icon @name="x-square-fill" class="has-text-grey is-pulled-right" />
{{else if (loose-equal versionData.version @metadata.currentVersion)}}
<Icon @name="check-circle" class="has-text-success is-pulled-right" />

View file

@ -64,7 +64,7 @@
</:toolbarActions>
</KvPageHeader>
{{#if (or @secret.deletionTime (not this.emptyState))}}
{{#if (or @secret.isSecretDeleted (not this.emptyState))}}
<div class="info-table-row-header">
<div class="info-table-row thead {{if this.showJsonView 'is-shadowless'}} ">
{{#unless this.hideHeaders}}
@ -76,10 +76,10 @@
</div>
{{/unless}}
<div class="th column justify-right">
{{#if (or @secret.deletionTime @secret.createdTime)}}
{{#if (or @secret.isSecretDeleted @secret.createdTime)}}
<KvTooltipTimestamp
@text="Version {{if @secret.version @secret.version}} {{@secret.state}}"
@timestamp={{or @secret.deletionTime @secret.createdTime}}
@timestamp={{(if @secret.isSecretDeleted @secret.deletionTime @secret.createdTime)}}
/>
{{/if}}
</div>

View file

@ -10,6 +10,7 @@ import { next } from '@ember/runloop';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';
import { waitFor } from '@ember/test-waiters';
import { isDeleted } from 'kv/utils/kv-deleted';
/**
* @module KvSecretDetails renders the key/value data of a KV secret.
@ -73,7 +74,7 @@ export default class KvSecretDetails extends Component {
this.refreshRoute();
} catch (err) {
this.flashMessages.danger(
`There was a problem undeleting ${secret.path}. Error: ${err.errors.join(' ')}.`
`There was a problem undeleting ${secret.path}. Error: ${err.errors?.join(' ')}.`
);
}
}
@ -129,7 +130,7 @@ export default class KvSecretDetails extends Component {
if (meta?.destroyed) {
return 'destroyed';
}
if (meta?.deletion_time) {
if (isDeleted(meta?.deletion_time)) {
return 'deleted';
}
if (meta?.created_time) {
@ -172,7 +173,7 @@ export default class KvSecretDetails extends Component {
};
}
// only destructure if we can read secret data
const { version, destroyed, deletionTime } = this.args.secret;
const { version, destroyed, isSecretDeleted } = this.args.secret;
if (destroyed) {
return {
title: `Version ${version} of this secret has been permanently destroyed`,
@ -184,7 +185,7 @@ export default class KvSecretDetails extends Component {
link: '/vault/docs/secrets/kv/kv-v2',
};
}
if (deletionTime) {
if (isSecretDeleted) {
return {
title: `Version ${version} of this secret has been deleted`,
message: `This version has been deleted but can be undeleted. ${

View file

@ -37,7 +37,7 @@
<Icon @name="x-square-fill" />Destroyed
</span>
</div>
{{else if versionData.deletion_time}}
{{else if versionData.isSecretDeleted}}
<div>
<span class="has-text-grey is-size-8 is-block">
<Icon @name="x-square-fill" />
@ -75,7 +75,7 @@
@route="secret.details.edit"
@query={{hash version=versionData.version}}
data-test-create-new-version-from={{versionData.version}}
@disabled={{or versionData.destroyed versionData.deletion_time}}
@disabled={{or versionData.destroyed versionData.isSecretDeleted}}
>
Create new version from
{{versionData.version}}

View file

@ -0,0 +1,14 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import timestamp from 'core/utils/timestamp';
export function isDeleted(date) {
// on the kv/data model, deletion_time does not always mean the secret has been deleted.
// if the delete_version_after is set then the deletion_time will be UTC of that time, even if it's a future time from now.
// to determine if the secret is deleted we check if deletion_time <= time right now.
const deletionTime = new Date(date);
const now = timestamp.now();
return deletionTime <= now;
}

View file

@ -0,0 +1 @@
export { default } from 'kv/utils/kv-deleted';

View file

@ -366,7 +366,7 @@ module('Acceptance | Enterprise | kv-v2 workflow | edge cases', function (hooks)
});
test('namespace: it manages state throughout delete, destroy and undelete operations', async function (assert) {
assert.expect(32);
assert.expect(34);
const backend = this.backend;
const ns = this.namespace;
const secret = 'my-delete-secret';

View file

@ -66,7 +66,7 @@ module('Integration | Component | kv | Page::Secret::Metadata::Version-History',
.dom(`${PAGE.versions.icon(version)} [data-test-icon="x-square-fill"]`)
.hasStyle({ color: 'rgb(199, 52, 69)' });
}
if (data.deletion_time) {
if (data.isSecretDeleted) {
assert
.dom(`${PAGE.versions.icon(version)} [data-test-icon="x-square-fill"]`)
.hasStyle({ color: 'rgb(111, 118, 130)' });

View file

@ -9,6 +9,8 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import { kvMetadataPath } from 'vault/utils/kv-path';
import { Response } from 'miragejs';
const UTC_DATE = '1994-11-05T00:00:00.000Z';
const EXAMPLE_KV_METADATA_GET_RESPONSE = {
request_id: 'foobar',
data: {
@ -23,7 +25,7 @@ const EXAMPLE_KV_METADATA_GET_RESPONSE = {
versions: {
1: {
created_time: 'created-time',
deletion_time: 'deletion-time',
deletion_time: UTC_DATE,
destroyed: false,
},
2: { created_time: 'created-time', deletion_time: '', destroyed: false },
@ -75,7 +77,7 @@ module('Unit | Adapter | kv/metadata', function (hooks) {
versions: {
1: {
created_time: 'created-time',
deletion_time: 'deletion-time',
deletion_time: UTC_DATE,
destroyed: false,
},
2: {