feat: 🎸 add support for dynamic credentials in storage bucket (#1897)
* feat: 🎸 add support for dynamic credentials in storage bucket
This commit is contained in:
1 parent
3cb8417a89
commit
4910070c95
13 files changed
+420
-96
No files matched your search
@@ -13,6 +13,14 @@ export const TYPES_STORAGE_BUCKET_PLUGIN = Object.freeze([
|
||||
TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
|
||||
]);
|
||||
|
||||
export const TYPE_CREDENTIAL_STATIC = 'static';
|
||||
export const TYPE_CREDENTIAL_DYNAMIC = 'dynamic';
|
||||
|
||||
export const TYPES_CREDENTIALS = Object.freeze([
|
||||
TYPE_CREDENTIAL_STATIC,
|
||||
TYPE_CREDENTIAL_DYNAMIC,
|
||||
]);
|
||||
|
||||
export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
||||
/**
|
||||
* True if the storage bucket is a plugin.
|
||||
@@ -22,6 +30,19 @@ export default class StorageBucketModel extends GeneratedStorageBucketModel {
|
||||
return this.type === TYPE_STORAGE_BUCKET_PLUGIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the storage bucket has role_arn, return dynamic
|
||||
* otherwise return the default static type.
|
||||
* @type {string}
|
||||
*/
|
||||
get credentialType() {
|
||||
if (this.role_arn) {
|
||||
return TYPE_CREDENTIAL_DYNAMIC;
|
||||
} else {
|
||||
return TYPE_CREDENTIAL_STATIC;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the storage bucket is an unknown type.
|
||||
* @type {boolean}
|
||||
|
||||
@@ -8,6 +8,11 @@ import ApplicationSerializer from './application';
|
||||
export default class StorageBucketSerializer extends ApplicationSerializer {
|
||||
serialize(snapshot) {
|
||||
const serialized = super.serialize(...arguments);
|
||||
//Dynamic credentials do not allow credential rotation, and the API requires this field to be
|
||||
//sent along with the dynamic credential fields
|
||||
if (serialized.attributes.role_arn) {
|
||||
serialized.attributes.disable_credential_rotation = true;
|
||||
}
|
||||
if (!snapshot.record.isNew) {
|
||||
delete serialized.bucket_name;
|
||||
delete serialized.bucket_prefix;
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3 } from 'api/models/storage-bucket';
|
||||
import {
|
||||
TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
|
||||
TYPE_CREDENTIAL_DYNAMIC,
|
||||
TYPE_CREDENTIAL_STATIC,
|
||||
} from 'api/models/storage-bucket';
|
||||
|
||||
module('Unit | Model | storage bucket', function (hooks) {
|
||||
setupTest(hooks);
|
||||
@@ -41,6 +45,21 @@ module('Unit | Model | storage bucket', function (hooks) {
|
||||
assert.true(modelB.isUnknown);
|
||||
});
|
||||
|
||||
test('it has credentialType property and returns the expected values', async function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelA = store.createRecord('storage-bucket', {
|
||||
role_arn: 'test-role-arn',
|
||||
access_key_id: null,
|
||||
});
|
||||
const modelB = store.createRecord('storage-bucket', {
|
||||
role_arn: null,
|
||||
access_key_id: 'test-access-key-id',
|
||||
});
|
||||
assert.strictEqual(modelA.credentialType, TYPE_CREDENTIAL_DYNAMIC);
|
||||
assert.strictEqual(modelB.credentialType, TYPE_CREDENTIAL_STATIC);
|
||||
});
|
||||
|
||||
test('it has isAWS property and returns the expected values', async function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
|
||||
@@ -384,6 +384,13 @@ storage-bucket:
|
||||
delete: Delete Storage Bucket
|
||||
types:
|
||||
aws_s3: AWS S3
|
||||
credential: Credential type
|
||||
static:
|
||||
title: Static
|
||||
description: Use an access key generated by AWS to use with this storage bucket
|
||||
dynamic:
|
||||
title: Dynamic
|
||||
description: Specify an Assume Role used to generate credentials to use with this AWS bucket
|
||||
form:
|
||||
scope:
|
||||
label: Scope
|
||||
@@ -399,6 +406,18 @@ storage-bucket:
|
||||
bucket_prefix:
|
||||
label: Bucket prefix
|
||||
help: The base path where session recordings will be stored.
|
||||
role_arn:
|
||||
label: Role ARN
|
||||
help: The ARN (Assume Role Name) used by this instance that the worker runs on
|
||||
role_external_id:
|
||||
label: Role external ID
|
||||
help: Used to provide access to access this account if you have this configured
|
||||
role_tags:
|
||||
label: Role tags
|
||||
help: Attributes to pass to AWS
|
||||
role_session_name:
|
||||
label: Role session name
|
||||
help: Unique identifier for the AWS session to help identify it in your logs
|
||||
region:
|
||||
label: Region
|
||||
help: The AWS region to use
|
||||
|
||||
@@ -176,93 +176,220 @@
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
{{#if @model.isNew}}
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{true}}
|
||||
@value={{@model.access_key_id}}
|
||||
@isInvalid={{@model.errors.secrets}}
|
||||
@type='password'
|
||||
name='access_key_id'
|
||||
{{on 'input' (set-from-event @model 'access_key_id')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.access_key_id.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.storage-bucket.form.access_key_id.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.secrets}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.secrets as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
<Hds::Form::RadioCard::Group @name='credential_type' as |G|>
|
||||
<G.Legend>{{t 'resources.storage-bucket.types.credential'}}</G.Legend>
|
||||
{{#each this.credentials as |credentialType|}}
|
||||
<G.RadioCard
|
||||
@name={{credentialType}}
|
||||
@value={{credentialType}}
|
||||
@checked={{eq credentialType this.selectedCredentialType}}
|
||||
@disabled={{form.disabled}}
|
||||
{{on 'input' (fn this.updateTypeSelection credentialType)}}
|
||||
as |R|
|
||||
>
|
||||
<R.Label>{{t
|
||||
(concat 'resources.storage-bucket.types.' credentialType '.title')
|
||||
}}</R.Label>
|
||||
<R.Description>{{t
|
||||
(concat
|
||||
'resources.storage-bucket.types.' credentialType '.description'
|
||||
)
|
||||
}}</R.Description>
|
||||
</G.RadioCard>
|
||||
|
||||
{{/each}}
|
||||
</Hds::Form::RadioCard::Group>
|
||||
|
||||
{{#unless this.showDynamicCredentials}}
|
||||
|
||||
{{#if @model.isNew}}
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{true}}
|
||||
@value={{@model.access_key_id}}
|
||||
@isInvalid={{@model.errors.secrets}}
|
||||
@type='password'
|
||||
name='access_key_id'
|
||||
{{on 'input' (set-from-event @model 'access_key_id')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.access_key_id.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.storage-bucket.form.access_key_id.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.secrets}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.secrets as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{true}}
|
||||
@value={{@model.secret_access_key}}
|
||||
@isInvalid={{@model.errors.secrets}}
|
||||
@type='password'
|
||||
name='secret_access_key'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'secret_access_key')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.secret_access_key.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.storage-bucket.form.secret_access_key.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.secrets}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.secrets as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
{{else}}
|
||||
<Form::Field::SecretInput
|
||||
@isRequired={{true}}
|
||||
@value={{@model.access_key_id}}
|
||||
@isInvalid={{@model.errors.access_key_id}}
|
||||
@name='access_key_id'
|
||||
@isDisabled={{form.disabled}}
|
||||
@showEditButton={{true}}
|
||||
@cancel={{fn this.rollbackSecretAttrs 'access_key_id'}}
|
||||
{{on 'input' (set-from-event @model 'access_key_id')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.access_key_id.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.storage-bucket.form.access_key_id.help'
|
||||
}}</F.HelperText>
|
||||
</Form::Field::SecretInput>
|
||||
|
||||
<Form::Field::SecretInput
|
||||
@isRequired={{true}}
|
||||
@value={{@model.secret_access_key}}
|
||||
@isInvalid={{@model.errors.secret_access_key}}
|
||||
@name='secret_access_key'
|
||||
@isDisabled={{form.disabled}}
|
||||
@showEditButton={{true}}
|
||||
@cancel={{fn this.rollbackSecretAttrs 'secret_access_key'}}
|
||||
{{on 'input' (set-from-event @model 'secret_access_key')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.secret_access_key.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.storage-bucket.form.secret_access_key.help'
|
||||
}}</F.HelperText>
|
||||
</Form::Field::SecretInput>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if this.showDynamicCredentials}}
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{true}}
|
||||
@value={{@model.secret_access_key}}
|
||||
@isInvalid={{@model.errors.secrets}}
|
||||
@type='password'
|
||||
name='secret_access_key'
|
||||
@value={{@model.role_arn}}
|
||||
@isInvalid={{@model.errors.role_arn}}
|
||||
@type='text'
|
||||
name='role_arn'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'secret_access_key')}}
|
||||
{{on 'input' (set-from-event @model 'role_arn')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.secret_access_key.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.storage-bucket.form.secret_access_key.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.secrets}}
|
||||
<F.Label>{{t 'resources.storage-bucket.form.role_arn.label'}}</F.Label>
|
||||
<F.HelperText>
|
||||
{{t 'resources.storage-bucket.form.role_arn.help'}}
|
||||
</F.HelperText>
|
||||
{{#if @model.errors.role_arn}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.secrets as |error|}}
|
||||
{{#each @model.errors.role_arn as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
{{else}}
|
||||
<Form::Field::SecretInput
|
||||
@isRequired={{true}}
|
||||
@value={{@model.access_key_id}}
|
||||
@isInvalid={{@model.errors.access_key_id}}
|
||||
@name='access_key_id'
|
||||
@isDisabled={{form.disabled}}
|
||||
@showEditButton={{true}}
|
||||
@cancel={{fn this.rollbackSecretAttrs 'access_key_id'}}
|
||||
{{on 'input' (set-from-event @model 'access_key_id')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.access_key_id.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.storage-bucket.form.access_key_id.help'
|
||||
}}</F.HelperText>
|
||||
</Form::Field::SecretInput>
|
||||
|
||||
<Form::Field::SecretInput
|
||||
@isRequired={{true}}
|
||||
@value={{@model.secret_access_key}}
|
||||
@isInvalid={{@model.errors.secret_access_key}}
|
||||
@name='secret_access_key'
|
||||
@isDisabled={{form.disabled}}
|
||||
@showEditButton={{true}}
|
||||
@cancel={{fn this.rollbackSecretAttrs 'secret_access_key'}}
|
||||
{{on 'input' (set-from-event @model 'secret_access_key')}}
|
||||
<Hds::Form::TextInput::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.role_external_id}}
|
||||
@isInvalid={{@model.errors.role_external_id}}
|
||||
@type='text'
|
||||
name='role_external_id'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'role_external_id')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.secret_access_key.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.storage-bucket.form.secret_access_key.help'
|
||||
}}</F.HelperText>
|
||||
</Form::Field::SecretInput>
|
||||
<F.Label>
|
||||
{{t 'resources.storage-bucket.form.role_external_id.label'}}
|
||||
</F.Label>
|
||||
<F.HelperText>
|
||||
{{t 'resources.storage-bucket.form.role_external_id.help'}}
|
||||
</F.HelperText>
|
||||
{{#if @model.errors.role_external_id}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.role_external_id as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.role_session_name}}
|
||||
@isInvalid={{@model.errors.role_session_name}}
|
||||
@type='text'
|
||||
name='role_session_name'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'role_session_name')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>
|
||||
{{t 'resources.storage-bucket.form.role_session_name.label'}}
|
||||
</F.Label>
|
||||
<F.HelperText>
|
||||
{{t 'resources.storage-bucket.form.role_session_name.help'}}
|
||||
</F.HelperText>
|
||||
{{#if @model.errors.role_session_name}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.role_session_name as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Form::Field::KeyValueList
|
||||
@layout='horizontal'
|
||||
@isOptional={{true}}
|
||||
@name='role_tags'
|
||||
@options={{@model.role_tags}}
|
||||
@addOption={{fn this.addOption 'role_tags'}}
|
||||
@removeOptionByIndex={{fn this.removeOptionByIndex 'role_tags'}}
|
||||
@disabled={{form.disabled}}
|
||||
as |M|
|
||||
>
|
||||
<M.Legend>
|
||||
{{t 'resources.storage-bucket.form.role_tags.label'}}
|
||||
</M.Legend>
|
||||
<M.HelperText>
|
||||
{{t 'resources.storage-bucket.form.role_tags.help'}}
|
||||
</M.HelperText>
|
||||
{{#if @model.errors.role_tags}}
|
||||
<M.Error as |E|>
|
||||
{{#each @model.errors.role_tags as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</M.Error>
|
||||
{{/if}}
|
||||
</Form::Field::KeyValueList>
|
||||
{{/if}}
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@@ -291,26 +418,27 @@
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::Checkbox::Field
|
||||
name='disable_credential_rotation'
|
||||
disabled={{form.disabled}}
|
||||
checked={{@model.disable_credential_rotation}}
|
||||
{{on 'change' this.toggleDisableCredentialRotation}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.disable_credential_rotation.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>
|
||||
{{t 'resources.storage-bucket.form.disable_credential_rotation.help'}}
|
||||
<Hds::Link::Inline
|
||||
@href={{doc-url 'storage-bucket.disable-credential-rotation'}}
|
||||
>
|
||||
{{t 'actions.learn-more'}}
|
||||
</Hds::Link::Inline>
|
||||
</F.HelperText>
|
||||
</Hds::Form::Checkbox::Field>
|
||||
|
||||
{{#unless this.showDynamicCredentials}}
|
||||
<Hds::Form::Checkbox::Field
|
||||
name='disable_credential_rotation'
|
||||
disabled={{form.disabled}}
|
||||
checked={{@model.disable_credential_rotation}}
|
||||
{{on 'change' this.toggleDisableCredentialRotation}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.storage-bucket.form.disable_credential_rotation.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>
|
||||
{{t 'resources.storage-bucket.form.disable_credential_rotation.help'}}
|
||||
<Hds::Link::Inline
|
||||
@href={{doc-url 'storage-bucket.disable-credential-rotation'}}
|
||||
>
|
||||
{{t 'actions.learn-more'}}
|
||||
</Hds::Link::Inline>
|
||||
</F.HelperText>
|
||||
</Hds::Form::Checkbox::Field>
|
||||
{{/unless}}
|
||||
{{#if (can 'save model' @model)}}
|
||||
<form.actions
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
|
||||
@@ -9,14 +9,74 @@ import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { set } from '@ember/object';
|
||||
|
||||
import {
|
||||
TYPES_CREDENTIALS,
|
||||
TYPE_CREDENTIAL_DYNAMIC,
|
||||
} from 'api/models/storage-bucket';
|
||||
|
||||
export default class FormStorageBucketComponent extends Component {
|
||||
// =actions
|
||||
// =attributes
|
||||
@service intl;
|
||||
|
||||
@tracked scopeFieldDescription = this.intl.t(
|
||||
'resources.storage-bucket.form.scope.help'
|
||||
);
|
||||
|
||||
@tracked selectedCredentialType = this.args.model.credentialType;
|
||||
|
||||
@tracked showDynamicCredentials =
|
||||
this.args.model.credentialType === TYPE_CREDENTIAL_DYNAMIC;
|
||||
|
||||
/**
|
||||
* returns an array of available credential types
|
||||
* @type {object}
|
||||
*/
|
||||
get credentials() {
|
||||
return TYPES_CREDENTIALS;
|
||||
}
|
||||
|
||||
// =actions
|
||||
/**
|
||||
* Allows to update the credential type
|
||||
* @param type {string}
|
||||
*/
|
||||
@action
|
||||
updateTypeSelection(type) {
|
||||
this.selectedCredentialType = type;
|
||||
if (type === TYPE_CREDENTIAL_DYNAMIC) {
|
||||
this.showDynamicCredentials = true;
|
||||
} else {
|
||||
this.showDynamicCredentials = false;
|
||||
}
|
||||
this.args.changeType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a key/value option object. We recreate a new array after adding
|
||||
* so that ember is aware that the array has been modified.
|
||||
* @param field {string}
|
||||
* @param key {string}
|
||||
* @param value {string}
|
||||
*/
|
||||
@action
|
||||
addOption(field, { key, value }) {
|
||||
const existingArray = this.args.model[field] ?? [];
|
||||
const newArray = [...existingArray, { key, value }];
|
||||
set(this.args.model, field, newArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an option by index. We recreate a new array after
|
||||
* splicing out the item so that ember is aware that the array has been modified.
|
||||
* @param field {string}
|
||||
* @param index {number}
|
||||
*/
|
||||
@action
|
||||
removeOptionByIndex(field, index) {
|
||||
const newArray = this.args.model[field].filter((_, i) => i !== index);
|
||||
set(this.args.model, field, newArray);
|
||||
}
|
||||
|
||||
updateScopeFieldDescription() {
|
||||
if (this.args.model.scopeModel.isGlobal) {
|
||||
this.scopeFieldDescription = this.intl.t(
|
||||
|
||||
@@ -10,6 +10,7 @@ export default class ScopesScopeStorageBucketsController extends Controller {
|
||||
// =services
|
||||
|
||||
@service intl;
|
||||
queryParams = ['credentialType'];
|
||||
|
||||
// = attributes
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,6 @@ import Controller from '@ember/controller';
|
||||
|
||||
export default class ScopesScopeStorageBucketsStorageBucketController extends Controller {
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* A storage-bucket breadcrumb
|
||||
* @type {string}
|
||||
|
||||
@@ -18,6 +18,13 @@ export default class ScopesScopeStorageBucketsRoute extends Route {
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =attributes
|
||||
|
||||
queryParams = {
|
||||
credentialType: {
|
||||
refreshModel: true,
|
||||
},
|
||||
};
|
||||
// =methods
|
||||
|
||||
/**
|
||||
@@ -88,4 +95,13 @@ export default class ScopesScopeStorageBucketsRoute extends Route {
|
||||
async delete(storageBucket) {
|
||||
await storageBucket.destroyRecord();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates type of credential
|
||||
* @param {string} credentialType
|
||||
*/
|
||||
@action
|
||||
changeType(credentialType) {
|
||||
this.router.replaceWith({ queryParams: { credentialType } });
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,27 @@ export default class ScopesScopeStorageBucketsNewRoute extends Route {
|
||||
|
||||
/**
|
||||
* Creates a new unsaved storage bucket.
|
||||
* Also rollback/destroy any new, unsaved instances from this route before
|
||||
* creating another, but reuse name/description/other fields if available.
|
||||
* @return {StorageBucket}
|
||||
*/
|
||||
model() {
|
||||
const scopeModel = this.store.peekRecord('scope', 'global');
|
||||
let name, description, scope, bucket_name, bucket_prefix, region;
|
||||
if (this.currentModel?.isNew) {
|
||||
({ name, description, scope, bucket_name, bucket_prefix, region } =
|
||||
this.currentModel);
|
||||
this.currentModel.rollbackAttributes();
|
||||
}
|
||||
const record = this.store.createRecord('storage-bucket', {
|
||||
type: TYPE_STORAGE_BUCKET_PLUGIN,
|
||||
compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3,
|
||||
name,
|
||||
description,
|
||||
scope,
|
||||
bucket_name,
|
||||
bucket_prefix,
|
||||
region,
|
||||
});
|
||||
record.scopeModel = scopeModel;
|
||||
return record;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@scopes={{this.scopes}}
|
||||
@changeType={{route-action 'changeType'}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@scopes={{this.scopes}}
|
||||
@changeType={{route-action 'changeType'}}
|
||||
/>
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
@@ -26,7 +26,9 @@ module('Acceptance | storage-buckets | create', function (hooks) {
|
||||
const NAME_FIELD_TEXT = 'random string';
|
||||
const BUCKET_NAME_FIELD_SELECTOR = '[name="bucket_name"]';
|
||||
const BUCKET_PREFIX_FIELD_SELECTOR = '[name="bucket_prefix"]';
|
||||
|
||||
const STATIC_CREDENTIAL_SELECTOR = '[value="static"]';
|
||||
const DYNAMIC_CREDENTIAL_SELECTOR = '[value="dynamic"]';
|
||||
const ROLE_ARN_SELECTOR = '[name="role_arn"]';
|
||||
const instances = {
|
||||
scopes: {
|
||||
global: null,
|
||||
@@ -105,6 +107,44 @@ module('Acceptance | storage-buckets | create', function (hooks) {
|
||||
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
|
||||
});
|
||||
|
||||
test('users can create a new storage bucket with dynamic credentials', async function (assert) {
|
||||
assert.expect(3);
|
||||
const storageBucketCount = getStorageBucketCount();
|
||||
await visit(urls.storageBuckets);
|
||||
|
||||
await click(`[href="${urls.newStorageBucket}"]`);
|
||||
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
|
||||
assert.dom('.hds-form-radio-card').exists({ count: 2 });
|
||||
|
||||
await click(DYNAMIC_CREDENTIAL_SELECTOR);
|
||||
await fillIn(ROLE_ARN_SELECTOR, 'test-arn-id');
|
||||
await click(SAVE_BTN_SELECTOR);
|
||||
const storageBucket = this.server.schema.storageBuckets.findBy({
|
||||
name: NAME_FIELD_TEXT,
|
||||
});
|
||||
assert.strictEqual(storageBucket.name, NAME_FIELD_TEXT);
|
||||
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
|
||||
});
|
||||
|
||||
test('users can create a new storage bucket with static credentials', async function (assert) {
|
||||
assert.expect(3);
|
||||
const storageBucketCount = getStorageBucketCount();
|
||||
await visit(urls.storageBuckets);
|
||||
|
||||
await click(`[href="${urls.newStorageBucket}"]`);
|
||||
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
|
||||
assert.dom('.hds-form-radio-card').exists({ count: 2 });
|
||||
|
||||
await click(STATIC_CREDENTIAL_SELECTOR);
|
||||
await click(SAVE_BTN_SELECTOR);
|
||||
|
||||
const storageBucket = this.server.schema.storageBuckets.findBy({
|
||||
name: NAME_FIELD_TEXT,
|
||||
});
|
||||
assert.strictEqual(storageBucket.name, NAME_FIELD_TEXT);
|
||||
assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1);
|
||||
});
|
||||
|
||||
test('user can cancel new storage bucket creation', async function (assert) {
|
||||
assert.expect(2);
|
||||
const storageBucketCount = getStorageBucketCount();
|
||||
|
||||
Reference in new issue
Block a user