Merge branch 'main' into chore-upgrade-ember-cli

This commit is contained in:
Susmitha Girumala authored and GitHub committed 2021-07-16 14:01:12 -04:00
commit 3e479fc02a
40 files changed
+600 -10

No files matched your search

+1
View File
@@ -22,6 +22,7 @@ testem.log
yarn-error.log
storybook-static
**/.storybook/*.html
.watchman-cookie*
# ember-try
.node_modules.ember-try
@@ -0,0 +1,42 @@
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* A credential library is a resource that can generate and retrieve
* credentials.
*/
export default class GeneratedCredentialLibraryModel extends BaseModel {
// =attributes
@attr('string', {
description: 'The owning credential store ID.',
})
credential_store_id;
@attr('string', {
description: 'Optional name for identification purposes',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes',
})
description;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated\nOutput only.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
}
@@ -5,7 +5,7 @@ import { attr } from '@ember-data/model';
* A credential store is a resource that can retrieve, store, and potentially
* generate credentials.
*/
export default class GeneratedAccountModel extends BaseModel {
export default class GeneratedCredentialStoreModel extends BaseModel {
// =attributes
@attr('string', {
@@ -0,0 +1,23 @@
import GeneratedCredentialLibraryModel from '../generated/models/credential-library';
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel {
// =error attributes
// These attributes exist solely to capture errors on nested fields.
// See the application adapter's error normalization method for
// more information.
@attr('string', { readOnly: true }) attributes_http_method;
@attr('string', { readOnly: true }) attributes_http_request_body;
@attr('string', { readOnly: true }) attributes_path;
// =attributes
/**
* Attributes of this resource, if any, represented as a JSON fragment.
* @type {FragmentCredentialLibraryAttributesModel}
*/
@fragment('fragment-credential-library-attributes', { defaultValue: {} })
attributes;
}
@@ -0,0 +1,23 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentCredentialLibraryAttributesModel extends Fragment {
//= attributes (vault)
@attr('string', {
description:
'The HTTP method the library uses when requesting credentials from Vault.',
})
http_method;
@attr('string', {
description:
'The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.',
})
http_request_body;
@attr('string', {
description: 'The path in Vault to request credentials from.',
})
path;
}
@@ -0,0 +1,3 @@
import ApplicationSerializer from './application';
export default class CredentialLibrarySerializer extends ApplicationSerializer {}
@@ -0,0 +1,27 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentCredentialLibraryAttributesSerializer extends JSONSerializer {
/**
* If an attribute is annotated as readOnly in the model, don't serialize it.
* Otherwise delegate to default attribute serializer.
* Only serialize http_request_body when `http_method` is set to `POST`.
* @override
* @method serializeAttribute
* @param {Snapshot} snapshot
* @param {Object} json
* @param {String} key
* @param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
let value = super.serializeAttribute(...arguments);
// Convert empty strings to null.
if (type === 'string' && json[key] === '') json[key] = null;
// Do not serialize read-only attributes.
if (options.readOnly) delete json[key];
// Do not serialize http_request_body unless http_method is POST
if (key === 'http_request_body' && !json['http_method']?.match(/post/i))
delete json[key];
return value;
}
}
@@ -0,0 +1 @@
export { default } from 'api/models/credential-library';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-credential-library-attributes';
@@ -0,0 +1 @@
export { default } from 'api/serializers/credential-library';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-credential-library-attributes';
+18
View File
@@ -464,6 +464,24 @@ export default function () {
credentialStores.where({ scopeId })
);
this.get('/credential-stores/:id');
this.post('/credential-stores');
this.patch('/credential-stores/:id');
// credential-libraries
this.get(
'/credential-libraries',
(
{ credentialLibraries },
{ queryParams: { credential_store_id: credentialStoreId } }
) => credentialLibraries.where({ credentialStoreId })
);
this.get('/credential-libraries/:id');
this.post('/credential-libraries/:id');
this.del('/credential-libraries/:id');
this.patch('/credential-libraries/:id');
/* Uncomment the following line and the Response import above
* Then change the response code to simulate error responses.
* this.get('/scopes', () => new Response(505));
@@ -0,0 +1,14 @@
import factory from '../generated/factories/credential-library';
import { random, system } from 'faker';
export default factory.extend({
id: (i) => `credential-library-id-${i}`,
attributes() {
return {
http_method: 'GET',
http_request_body: random.word(),
path: system.directoryPath(),
};
},
});
@@ -1,5 +1,6 @@
import factory from '../generated/factories/credential-store';
import { random, internet, datatype } from 'faker';
import { trait } from 'ember-cli-mirage';
const types = ['vault'];
@@ -22,4 +23,14 @@ export default factory.extend({
};
}
},
withAssociations: trait({
afterCreate(credentialStore, server) {
const { scope } = credentialStore;
server.createList('credential-library', 2, {
scope,
credentialStore,
});
},
}),
});
@@ -0,0 +1,12 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedCredentialLibraryModelFactory
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
});
@@ -2,7 +2,7 @@ import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedAuthMethodModelFactory
* GeneratedCredentialStoreModelFactory
*/
export default Factory.extend({
name: () => random.words(),
@@ -0,0 +1,6 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
credentialStore: belongsTo(),
});
+1 -1
View File
@@ -49,6 +49,6 @@ export default function (server) {
server.createList('host-catalog', 2, { scope }, 'withChildren');
server.createList('target', 2, { scope }, 'withRandomHostSets');
server.createList('session', 4, { scope }, 'withAssociations');
server.createList('credential-store', 3, { scope });
server.createList('credential-store', 3, { scope }, 'withAssociations');
});
}
@@ -0,0 +1,14 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'credential-library',
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(
this,
arguments
);
json.credential_store_id = model.credentialStoreId;
return json;
},
});
@@ -0,0 +1,13 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Model | credential library', function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('credential-library', {});
assert.ok(model);
});
});
@@ -0,0 +1,19 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Model | fragment credential library attributes',
function (hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function (assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord(
'fragment-credential-library-attributes',
{}
);
assert.ok(model);
});
}
);
@@ -0,0 +1,67 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | credential library', function (hooks) {
setupTest(hooks);
test('it serializes correctly on create', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('credential-library');
const record = store.createRecord('credential-library', {
name: 'Name',
description: 'Description',
attributes: {
path: '/vault/path',
http_method: 'GET',
},
version: 1,
});
const snapshot = record._createSnapshot();
const serializedRecord = serializer.serialize(snapshot);
assert.deepEqual(serializedRecord, {
credential_store_id: null,
name: 'Name',
description: 'Description',
version: 1,
attributes: {
path: '/vault/path',
http_method: 'GET',
},
});
});
test('it serializes correctly on update', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const serializer = store.serializerFor('credential-library');
store.push({
data: {
id: '1',
type: 'credential-library',
attributes: {
name: 'Name',
description: 'Description',
attributes: {
path: '/vault/path',
http_method: 'GET',
},
version: 1,
},
},
});
const record = store.peekRecord('credential-library', '1');
const snapshot = record._createSnapshot();
const serializedRecord = serializer.serialize(snapshot);
assert.deepEqual(serializedRecord, {
credential_store_id: null,
name: 'Name',
description: 'Description',
attributes: {
path: '/vault/path',
http_method: 'GET',
},
version: 1,
});
});
});
@@ -0,0 +1,59 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Serializer | fragment credential library attributes',
function (hooks) {
setupTest(hooks);
test('it serializes empty strings to null', function (assert) {
assert.expect(1);
const store = this.owner.lookup('service:store');
const record = store.createRecord(
'fragment-credential-library-attributes',
{
http_method: '',
path: null,
}
);
let serializedRecord = record.serialize();
assert.deepEqual(serializedRecord, {
http_method: null,
path: null,
});
});
test('it does not serialize http_request_body unless http_method is set to POST', function (assert) {
assert.expect(2);
const store = this.owner.lookup('service:store');
const record = store.createRecord(
'fragment-credential-library-attributes',
{
http_method: 'GET',
http_request_body: 'body',
}
);
let serializedRecord = record.serialize();
assert.deepEqual(
serializedRecord,
{
http_method: 'GET',
path: null,
},
'http_request_body attribute is not expected'
);
record.http_method = 'POST';
serializedRecord = record.serialize();
assert.deepEqual(
serializedRecord,
{
http_method: 'POST',
http_request_body: 'body',
path: null,
},
'http_request_body attribute is expected'
);
});
}
);
@@ -0,0 +1,38 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@showEditToggle={{if @model.isNew false true}}
as |form|
>
<form.input
@value={{@model.type}}
@label={{t 'form.type.label'}}
readonly={{true}}
@disabled={{true}}
/>
<form.input
@name='name'
@type='name'
@value={{@model.name}}
@label={{t 'form.name.label'}}
@helperText={{t 'form.name.help'}}
readonly={{false}}
/>
<form.textarea
@name='description'
@type='description'
@value={{@model.description}}
@label={{t 'form.description.label'}}
@helperText={{t 'form.description.help'}}
/>
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@enableEditText={{t 'actions.edit-form'}}
@submitText={{t 'actions.save'}}
@cancelText={{t 'actions.cancel'}}
/>
</Rose::Form>
@@ -0,0 +1,13 @@
import Controller from '@ember/controller';
export default class ScopesScopeCredentialStoresCredentialStoreController extends Controller {
// =attributes
/**
* A credential store breadcrumb
* @type {string}
*/
get breadCrumb() {
return this.model.displayName;
}
}
+6 -1
View File
@@ -87,7 +87,12 @@ Router.map(function () {
});
});
this.route('credential-stores', function () {
this.route('credential-store', { path: ':credential_store_id' });
this.route(
'credential-store',
{ path: ':credential_store_id' },
function () {}
);
this.route('new');
});
});
});
@@ -1,6 +1,11 @@
import Route from '@ember/routing/route';
import { action } from '@ember/object';
import loading from 'ember-loading/decorator';
import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeCredentialStoresRoute extends Route {
// =methods
/**
* Load all credential stores under current scope
* @returns {Promise[CredentialStoreModel]}
@@ -9,4 +14,34 @@ export default class ScopesScopeCredentialStoresRoute extends Route {
const { id: scope_id } = this.modelFor('scopes.scope');
return this.store.query('credential-store', { scope_id });
}
// =actions
/**
* Handle save
* @param {CredentialStoreModel} credentialStore
*/
@action
@loading
@notifyError(({ message }) => message)
@notifySuccess('notifications.save-success')
async save(credentialStore) {
await credentialStore.save();
await this.transitionTo(
'scopes.scope.credential-stores.credential-store',
credentialStore
);
this.refresh();
}
/**
* Rollback changes on credential stores.
* @param {CredentialStoreModel} credentialStore
*/
@action
cancel(credentialStore) {
const { isNew } = credentialStore;
credentialStore.rollbackAttributes();
if (isNew) this.transitionTo('scopes.scope.credential-stores');
}
}
@@ -6,6 +6,24 @@ export default class ScopesScopeCredentialStoresCredentialStoreRoute extends Rou
* @return {Promise[CredentialStoreModel]}
*/
async model({ credential_store_id }) {
return this.route.findRecord('credential-store', credential_store_id);
return this.store.findRecord('credential-store', credential_store_id);
}
/**
* Render credential store header and navigation page sections.
* @override
*/
renderTemplate() {
super.renderTemplate(...arguments);
this.render('scopes/scope/credential-stores/credential-store/-header', {
into: 'scopes/scope/credential-stores/credential-store',
outlet: 'header',
});
this.render('scopes/scope/credential-stores/credential-store/-navigation', {
into: 'scopes/scope/credential-stores/credential-store',
outlet: 'navigation',
});
}
}
@@ -0,0 +1,3 @@
import Route from '@ember/routing/route';
export default class ScopesScopeCredentialStoresCredentialStoreIndexRoute extends Route {}
@@ -0,0 +1,15 @@
import Route from '@ember/routing/route';
export default class ScopesScopeCredentialStoresNewRoute extends Route {
// =methods
/**
* Creates a new unsaved credential-store
*/
model() {
const scopeModel = this.modelFor('scopes.scope');
return this.store.createRecord('credential-store', {
type: 'vault',
scopeModel,
});
}
}
@@ -1,2 +1,24 @@
{{page-title 'CredentialStore'}}
{{outlet}}
{{page-title @model.displayName}}
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<BreadCrumbs />
</page.breadcrumbs>
<page.header>
{{outlet 'header'}}
</page.header>
<page.navigation>
{{outlet 'navigation'}}
</page.navigation>
<page.actions>
{{!-- {{outlet 'actions'}} --}}
</page.actions>
<page.body>
{{outlet}}
</page.body>
</Rose::Layout::Page>
@@ -0,0 +1,9 @@
<h2>{{t 'resources.credential-store.title'}}</h2>
<p>{{t 'resources.credential-store.description'}}</p>
<Copyable
@text={{@model.id}}
@buttonText={{t 'actions.copy-to-clipboard'}}
@acknowledgeText={{t 'states.copied'}}
>
<code>{{@model.id}}</code>
</Copyable>
@@ -0,0 +1,5 @@
<Rose::Nav::Tabs as |nav|>
<nav.link @route='scopes.scope.credential-stores.credential-store.index'>
{{t 'titles.details'}}
</nav.link>
</Rose::Nav::Tabs>
@@ -0,0 +1,5 @@
<Form::CredentialStore
@model={{@model}}
@submit={{route-action 'save' @model}}
@cancel={{route-action 'cancel' @model}}
/>
@@ -6,11 +6,12 @@
<page.header>
<h2>
{{t 'resources.credential-store.title_plural'}}
<DocLink @doc='credential-stores' @iconSize='large' />
</h2>
{{t 'resources.credential-store.description'}}
<p>{{t 'resources.credential-store.description'}}</p>
</page.header>
{{!-- <page.actions>
<page.actions>
{{#if @model}}
<Rose::LinkButton
@route='scopes.scope.credential-stores.new'
@@ -19,7 +20,7 @@
{{t 'titles.new'}}
</Rose::LinkButton>
{{/if}}
</page.actions> --}}
</page.actions>
<page.body>
{{#if @model}}
@@ -0,0 +1,20 @@
{{page-title (t 'titles.new')}}
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<BreadCrumbs />
</page.breadcrumbs>
<page.header>
<h2>{{t 'titles.new'}}</h2>
</page.header>
<page.body>
<Form::CredentialStore
@model={{@model}}
@submit={{route-action 'save' @model}}
@cancel={{route-action 'cancel' @model}}
/>
</page.body>
</Rose::Layout::Page>
+1
View File
@@ -55,6 +55,7 @@ module.exports = function (environment) {
'host-set': '/host-sets',
'host-set.new': '/host-sets/new',
'host-set.add-hosts': '/host-sets/add-hosts',
'credential-stores': '/credential-stores',
host: '/hosts',
'host.new': '/hosts/new',
role: '/roles',
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Controller | scopes/scope/credential-stores/credential-store',
function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/credential-stores/credential-store'
);
assert.ok(controller);
});
}
);
@@ -0,0 +1,16 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Route | scopes/scope/credential-stores/credential-store/index',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup(
'route:scopes/scope/credential-stores/credential-store/index'
);
assert.ok(route);
});
}
);
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | scopes/scope/credential-stores/new', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup('route:scopes/scope/credential-stores/new');
assert.ok(route);
});
});