mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 04:16:31 +02:00
[UI] Ember Data Migration - Sync Cleanup (#30634)
* removes namespace param from activation flags endpoint in api client * updates sync activation modal to use api service * updates sync destination sync page to use api service * removes ember data type deps from sync engine and updates tests * updates sync activation modal to always override namespace header in activate request
This commit is contained in:
parent
6212f0986e
commit
ed67e9e59e
@ -7,7 +7,6 @@ src/apis/SecretsApi.ts
|
||||
src/apis/SystemApi.ts
|
||||
src/apis/index.ts
|
||||
src/index.ts
|
||||
src/models/ActivationFlagsActivateRequest.ts
|
||||
src/models/AliCloudConfigureRequest.ts
|
||||
src/models/AliCloudLoginRequest.ts
|
||||
src/models/AliCloudWriteAuthRoleRequest.ts
|
||||
|
||||
9
ui/api-client/dist/apis/SystemApi.d.ts
vendored
9
ui/api-client/dist/apis/SystemApi.d.ts
vendored
File diff suppressed because one or more lines are too long
11
ui/api-client/dist/apis/SystemApi.js
vendored
11
ui/api-client/dist/apis/SystemApi.js
vendored
@ -82,20 +82,15 @@ class SystemApi extends runtime.BaseAPI {
|
||||
/**
|
||||
* Activate a flagged feature.
|
||||
*/
|
||||
activationFlagsActivate_2Raw(requestParameters, initOverrides) {
|
||||
activationFlagsActivate_2Raw(initOverrides) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (requestParameters['activationFlagsActivateRequest'] == null) {
|
||||
throw new runtime.RequiredError('activationFlagsActivateRequest', 'Required parameter "activationFlagsActivateRequest" was null or undefined when calling activationFlagsActivate_2().');
|
||||
}
|
||||
const queryParameters = {};
|
||||
const headerParameters = {};
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
const response = yield this.request({
|
||||
path: `/sys/activation-flags/secrets-sync/activate`,
|
||||
method: 'POST',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: (0, index_1.ActivationFlagsActivateRequestToJSON)(requestParameters['activationFlagsActivateRequest']),
|
||||
}, initOverrides);
|
||||
return new runtime.VoidApiResponse(response);
|
||||
});
|
||||
@ -103,9 +98,9 @@ class SystemApi extends runtime.BaseAPI {
|
||||
/**
|
||||
* Activate a flagged feature.
|
||||
*/
|
||||
activationFlagsActivate_2(activationFlagsActivateRequest, initOverrides) {
|
||||
activationFlagsActivate_2(initOverrides) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const response = yield this.activationFlagsActivate_2Raw({ activationFlagsActivateRequest: activationFlagsActivateRequest }, initOverrides);
|
||||
const response = yield this.activationFlagsActivate_2Raw(initOverrides);
|
||||
return yield response.value();
|
||||
});
|
||||
}
|
||||
|
||||
9
ui/api-client/dist/esm/apis/SystemApi.d.ts
vendored
9
ui/api-client/dist/esm/apis/SystemApi.d.ts
vendored
File diff suppressed because one or more lines are too long
13
ui/api-client/dist/esm/apis/SystemApi.js
vendored
13
ui/api-client/dist/esm/apis/SystemApi.js
vendored
File diff suppressed because one or more lines are too long
1
ui/api-client/dist/esm/models/index.d.ts
vendored
1
ui/api-client/dist/esm/models/index.d.ts
vendored
@ -1,4 +1,3 @@
|
||||
export * from './ActivationFlagsActivateRequest';
|
||||
export * from './AliCloudConfigureRequest';
|
||||
export * from './AliCloudLoginRequest';
|
||||
export * from './AliCloudWriteAuthRoleRequest';
|
||||
|
||||
1
ui/api-client/dist/esm/models/index.js
vendored
1
ui/api-client/dist/esm/models/index.js
vendored
@ -1,6 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export * from './ActivationFlagsActivateRequest';
|
||||
export * from './AliCloudConfigureRequest';
|
||||
export * from './AliCloudLoginRequest';
|
||||
export * from './AliCloudWriteAuthRoleRequest';
|
||||
|
||||
1
ui/api-client/dist/models/index.d.ts
vendored
1
ui/api-client/dist/models/index.d.ts
vendored
@ -1,4 +1,3 @@
|
||||
export * from './ActivationFlagsActivateRequest';
|
||||
export * from './AliCloudConfigureRequest';
|
||||
export * from './AliCloudLoginRequest';
|
||||
export * from './AliCloudWriteAuthRoleRequest';
|
||||
|
||||
1
ui/api-client/dist/models/index.js
vendored
1
ui/api-client/dist/models/index.js
vendored
@ -16,7 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
__exportStar(require("./ActivationFlagsActivateRequest"), exports);
|
||||
__exportStar(require("./AliCloudConfigureRequest"), exports);
|
||||
__exportStar(require("./AliCloudLoginRequest"), exports);
|
||||
__exportStar(require("./AliCloudWriteAuthRoleRequest"), exports);
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
|
||||
import * as runtime from '../runtime';
|
||||
import type {
|
||||
ActivationFlagsActivateRequest,
|
||||
AuditingCalculateHashRequest,
|
||||
AuditingCalculateHashResponse,
|
||||
AuditingEnableDeviceRequest,
|
||||
@ -264,8 +263,6 @@ import type {
|
||||
WellKnownReadLabelResponse,
|
||||
} from '../models/index';
|
||||
import {
|
||||
ActivationFlagsActivateRequestFromJSON,
|
||||
ActivationFlagsActivateRequestToJSON,
|
||||
AuditingCalculateHashRequestFromJSON,
|
||||
AuditingCalculateHashRequestToJSON,
|
||||
AuditingCalculateHashResponseFromJSON,
|
||||
@ -760,10 +757,6 @@ import {
|
||||
WellKnownReadLabelResponseToJSON,
|
||||
} from '../models/index';
|
||||
|
||||
export interface SystemApiActivationFlagsActivate1Request {
|
||||
activationFlagsActivateRequest: ActivationFlagsActivateRequest;
|
||||
}
|
||||
|
||||
export interface SystemApiAuditingCalculateHashOperationRequest {
|
||||
path: string;
|
||||
auditingCalculateHashRequest: AuditingCalculateHashRequest;
|
||||
@ -1893,26 +1886,16 @@ export class SystemApi extends runtime.BaseAPI {
|
||||
/**
|
||||
* Activate a flagged feature.
|
||||
*/
|
||||
async activationFlagsActivate_2Raw(requestParameters: SystemApiActivationFlagsActivate1Request, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<runtime.VoidResponse>> {
|
||||
if (requestParameters['activationFlagsActivateRequest'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'activationFlagsActivateRequest',
|
||||
'Required parameter "activationFlagsActivateRequest" was null or undefined when calling activationFlagsActivate_2().'
|
||||
);
|
||||
}
|
||||
|
||||
async activationFlagsActivate_2Raw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<runtime.VoidResponse>> {
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
const response = await this.request({
|
||||
path: `/sys/activation-flags/secrets-sync/activate`,
|
||||
method: 'POST',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: ActivationFlagsActivateRequestToJSON(requestParameters['activationFlagsActivateRequest']),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.VoidApiResponse(response);
|
||||
@ -1921,8 +1904,8 @@ export class SystemApi extends runtime.BaseAPI {
|
||||
/**
|
||||
* Activate a flagged feature.
|
||||
*/
|
||||
async activationFlagsActivate_2(activationFlagsActivateRequest: ActivationFlagsActivateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.VoidResponse> {
|
||||
const response = await this.activationFlagsActivate_2Raw({ activationFlagsActivateRequest: activationFlagsActivateRequest }, initOverrides);
|
||||
async activationFlagsActivate_2(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.VoidResponse> {
|
||||
const response = await this.activationFlagsActivate_2Raw(initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export * from './ActivationFlagsActivateRequest';
|
||||
export * from './AliCloudConfigureRequest';
|
||||
export * from './AliCloudLoginRequest';
|
||||
export * from './AliCloudWriteAuthRoleRequest';
|
||||
|
||||
@ -9,22 +9,21 @@ import { service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { keyIsFolder } from 'core/utils/key-utils';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
import type SyncDestinationModel from 'vault/models/sync/destination';
|
||||
import type { Destination } from 'vault/sync';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type Store from '@ember-data/store';
|
||||
import type ApiService from 'vault/services/api';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type { SearchSelectOption } from 'vault/vault/app-types';
|
||||
import type { SearchSelectOption } from 'vault/app-types';
|
||||
|
||||
interface Args {
|
||||
destination: SyncDestinationModel;
|
||||
destination: Destination;
|
||||
}
|
||||
|
||||
export default class DestinationSyncPageComponent extends Component<Args> {
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: Store;
|
||||
@service declare readonly api: ApiService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
|
||||
@ -47,21 +46,20 @@ export default class DestinationSyncPageComponent extends Component<Args> {
|
||||
return !this.mountPath || !this.secretPath || this.isSecretDirectory || this.setAssociation.isRunning;
|
||||
}
|
||||
|
||||
willDestroy(): void {
|
||||
this.pagination.clearDataset('sync/association');
|
||||
super.willDestroy();
|
||||
}
|
||||
|
||||
// unable to use built-in fetch functionality of SearchSelect since we need to filter by kv type
|
||||
async fetchMounts() {
|
||||
const mounts = [];
|
||||
try {
|
||||
const secretEngines = await this.store.query('secret-engine', {});
|
||||
this.mounts = secretEngines.reduce((filtered: SearchSelectOption[], model) => {
|
||||
if (model.type === 'kv' && model.version === 2) {
|
||||
filtered.push({ name: model.path, id: model.path });
|
||||
const { secret } = await this.api.sys.internalUiListEnabledVisibleMounts();
|
||||
if (secret) {
|
||||
for (const path in secret) {
|
||||
const { type, options } = secret[path as keyof typeof secret];
|
||||
if (type === 'kv' && options?.['version'] === '2') {
|
||||
mounts.push({ name: path, id: path });
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}, []);
|
||||
}
|
||||
this.mounts = mounts;
|
||||
} catch (error) {
|
||||
// the user is still able to manually enter the mount path
|
||||
// InputSearch component will render in this case
|
||||
@ -83,20 +81,16 @@ export default class DestinationSyncPageComponent extends Component<Args> {
|
||||
this.error = ''; // reset error
|
||||
try {
|
||||
this.syncedSecret = '';
|
||||
const { name: destinationName, type: destinationType } = this.args.destination;
|
||||
const { name, type } = this.args.destination;
|
||||
const mount = keyIsFolder(this.mountPath) ? this.mountPath.slice(0, -1) : this.mountPath; // strip trailing slash from mount path
|
||||
const association = this.store.createRecord('sync/association', {
|
||||
destinationName,
|
||||
destinationType,
|
||||
mount,
|
||||
secretName: this.secretPath,
|
||||
});
|
||||
await association.save({ adapterOptions: { action: 'set' } });
|
||||
const payload = { mount, secretName: this.secretPath };
|
||||
await this.api.sys.systemWriteSyncDestinationsTypeNameAssociationsSet(name, type, payload);
|
||||
this.syncedSecret = this.secretPath;
|
||||
// reset the secret path to help make it clear that the sync was successful
|
||||
this.secretPath = '';
|
||||
} catch (error) {
|
||||
this.error = `Sync operation error: \n ${errorMessage(error)}`;
|
||||
const { message } = await this.api.parseError(error);
|
||||
this.error = `Sync operation error: \n ${message}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -8,12 +8,11 @@ import { tracked } from '@glimmer/tracking';
|
||||
import { service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { waitFor } from '@ember/test-waiters';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type Store from '@ember-data/store';
|
||||
import type ApiService from 'vault/services/api';
|
||||
|
||||
interface Args {
|
||||
onClose: () => void;
|
||||
@ -25,7 +24,7 @@ export default class SyncActivationModal extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: Store;
|
||||
@service declare readonly api: ApiService;
|
||||
|
||||
@tracked hasConfirmedDocs = false;
|
||||
|
||||
@ -36,19 +35,16 @@ export default class SyncActivationModal extends Component<Args> {
|
||||
this.args.onConfirm();
|
||||
|
||||
// sync activation is managed by the root/administrative namespace so child namespaces are not sent.
|
||||
// for non-managed clusters the root namespace path is technically an empty string so we pass null
|
||||
// otherwise we pass 'admin' if HVD managed.
|
||||
const namespace = this.flags.hvdManagedNamespaceRoot;
|
||||
|
||||
// for non-managed clusters the root namespace path is technically an empty string, otherwise we pass 'admin' if HVD managed.
|
||||
const namespace = this.flags.hvdManagedNamespaceRoot || '';
|
||||
try {
|
||||
yield this.store
|
||||
.adapterFor('application')
|
||||
.ajax('/v1/sys/activation-flags/secrets-sync/activate', 'POST', { namespace });
|
||||
yield this.api.sys.activationFlagsActivate_2(this.api.buildHeaders({ namespace }));
|
||||
// must refresh and not transition because transition does not refresh the model from within a namespace
|
||||
yield this.router.refresh('vault.cluster');
|
||||
} catch (error) {
|
||||
this.args.onError(errorMessage(error));
|
||||
this.flashMessages.danger(`Error enabling feature \n ${errorMessage(error)}`);
|
||||
const { message } = yield this.api.parseError(error);
|
||||
this.args.onError(message);
|
||||
this.flashMessages.danger(`Error enabling feature \n ${message}`);
|
||||
} finally {
|
||||
this.args.onClose();
|
||||
}
|
||||
|
||||
@ -4,11 +4,9 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { findDestination } from 'core/helpers/sync-destinations';
|
||||
import formResolver from 'vault/forms/sync/resolver';
|
||||
|
||||
import type Store from '@ember-data/store';
|
||||
import type { DestinationType } from 'vault/sync';
|
||||
|
||||
type Params = {
|
||||
@ -16,8 +14,6 @@ type Params = {
|
||||
};
|
||||
|
||||
export default class SyncSecretsDestinationsCreateDestinationRoute extends Route {
|
||||
@service declare readonly store: Store;
|
||||
|
||||
model(params: Params) {
|
||||
const { type } = params;
|
||||
const { defaultValues } = findDestination(type);
|
||||
|
||||
@ -3,4 +3,4 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Secrets::Page::Destinations::Destination::Sync @destination={{this.model}} />
|
||||
<Secrets::Page::Destinations::Destination::Sync @destination={{this.model.destination}} />
|
||||
@ -13,8 +13,6 @@
|
||||
"ember-cli-typescript": "*",
|
||||
"ember-auto-import": "*",
|
||||
"@types/ember": "latest",
|
||||
"@types/ember-data": "latest",
|
||||
"@types/ember-data__store": "latest",
|
||||
"@types/ember__array": "latest",
|
||||
"@types/ember__component": "latest",
|
||||
"@types/ember__controller": "latest",
|
||||
|
||||
@ -212,7 +212,7 @@ module('Acceptance | sync | overview', function (hooks) {
|
||||
});
|
||||
this.server.post('/sys/activation-flags/secrets-sync/activate', (_, req) => {
|
||||
assert.strictEqual(
|
||||
req.requestHeaders['X-Vault-Namespace'],
|
||||
req.requestHeaders['x-vault-namespace'],
|
||||
'admin',
|
||||
'Request is made to the admin namespace'
|
||||
);
|
||||
|
||||
@ -9,10 +9,9 @@ import { setupEngine } from 'ember-engines/test-support';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { setupDataStubs } from 'vault/tests/helpers/sync/setup-hooks';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import { render, click, fillIn, settled } from '@ember/test-helpers';
|
||||
import { render, click, fillIn } from '@ember/test-helpers';
|
||||
import { PAGE } from 'vault/tests/helpers/sync/sync-selectors';
|
||||
import { selectChoose } from 'ember-power-select/test-support';
|
||||
import sinon from 'sinon';
|
||||
import { Response } from 'miragejs';
|
||||
|
||||
const { destinations, searchSelect, messageError, kvSuggestion } = PAGE;
|
||||
@ -138,25 +137,4 @@ module('Integration | Component | sync | Secrets::Page::Destinations::Destinatio
|
||||
|
||||
assert.dom(messageError).hasTextContaining(error, 'Error renders in alert banner');
|
||||
});
|
||||
|
||||
test('it should clear sync associations from store in willDestroy hook', async function (assert) {
|
||||
const clearDatasetStub = sinon.stub(this.owner.lookup('service:pagination'), 'clearDataset');
|
||||
|
||||
this.renderComponent = true;
|
||||
await render(
|
||||
hbs`
|
||||
{{#if this.renderComponent}}
|
||||
<Secrets::Page::Destinations::Destination::Sync @destination={{this.destination}} />
|
||||
{{/if}}
|
||||
`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
this.set('renderComponent', false);
|
||||
await settled();
|
||||
|
||||
assert.true(
|
||||
clearDatasetStub.calledWith('sync/association'),
|
||||
'Sync associations are cleared from store on component teardown'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -17,6 +17,7 @@ import { PAGE } from 'vault/tests/helpers/sync/sync-selectors';
|
||||
import { Response } from 'miragejs';
|
||||
import { dateFormat } from 'core/helpers/date-format';
|
||||
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
|
||||
import { listDestinationsTransform } from 'sync/utils/api-transforms';
|
||||
|
||||
const { title, tab, overviewCard, cta, overview, pagination, emptyStateTitle, emptyStateMessage } = PAGE;
|
||||
|
||||
@ -29,13 +30,14 @@ module('Integration | Component | sync | Page::Overview', function (hooks) {
|
||||
// allow capabilities as root by default to allow users to POST to the secrets-sync/activate endpoint
|
||||
this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.api = this.owner.lookup('service:api');
|
||||
this.flags = this.owner.lookup('service:flags');
|
||||
|
||||
syncScenario(this.server);
|
||||
syncHandlers(this.server);
|
||||
|
||||
this.destinations = await this.store.query('sync/destination', {});
|
||||
const destinations = await this.api.sys.systemListSyncDestinations(true);
|
||||
this.destinations = listDestinationsTransform(destinations);
|
||||
|
||||
this.setup = ({
|
||||
canActivate = false,
|
||||
|
||||
@ -93,7 +93,7 @@ module('Integration | Component | Secrets::SyncActivationModal', function (hooks
|
||||
|
||||
this.server.post('/sys/activation-flags/secrets-sync/activate', (_, req) => {
|
||||
assert.strictEqual(
|
||||
req.requestHeaders['X-Vault-Namespace'],
|
||||
req.requestHeaders['x-vault-namespace'],
|
||||
'admin',
|
||||
'POST to secrets-sync/activate is called with admin namespace'
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user