diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js
index cbf0b574f..58e47471c 100644
--- a/addons/api/mirage/config.js
+++ b/addons/api/mirage/config.js
@@ -35,6 +35,21 @@ function routes() {
// make this `http://localhost:8080`, for example, if your API is on a different server
// this.urlPrefix = '';
+
+ this.get('/metadata.json', () => {
+ // this configuration is optional
+ const licensedFeatures = environmentConfig?.features?.licensedFeatures;
+ const edition = licensedFeatures ? 'enterprise' : 'oss';
+ const features = licensedFeatures ? Object.keys(licensedFeatures) : [];
+ const metadata = {
+ license: {
+ edition,
+ features,
+ },
+ };
+ return metadata;
+ });
+
// make this `/api`, for example, if your API is namespaced
this.namespace = environmentConfig.api.namespace;
// delay for each request, automatically set to 0 during testing
diff --git a/addons/core/translations/actions/en-us.yaml b/addons/core/translations/actions/en-us.yaml
index 36cb5dffe..eb8fe0e4c 100644
--- a/addons/core/translations/actions/en-us.yaml
+++ b/addons/core/translations/actions/en-us.yaml
@@ -47,3 +47,4 @@ remove-resources: Remove resources and save
do-this-later: Do this later
toggle-color-theme: Toggle Color Theme
toggle-feature-edition: Toggle Feature Edition
+toggle-licensed-features: Toggle Licensed Features
diff --git a/addons/core/translations/en-us.yaml b/addons/core/translations/en-us.yaml
index 277e0dd3b..a482f52fc 100644
--- a/addons/core/translations/en-us.yaml
+++ b/addons/core/translations/en-us.yaml
@@ -38,11 +38,6 @@ titles:
create-resources: Create Resources
choose-an-option: Choose an option
feature-edition: 'Edition: {edition}'
-editions:
- oss: OSS
- ent: Enterprise
- enterprise: Enterprise
- hcp: HCP
descriptions:
empty-set: There are no items to display yet. You may be able to add items or try back later.
cluster-url-initialization: To get started, please enter your cluster URL
diff --git a/ui/admin/app/instance-initializers/feature-edition.js b/ui/admin/app/instance-initializers/feature-edition.js
index 9a356fb46..957f7ceea 100644
--- a/ui/admin/app/instance-initializers/feature-edition.js
+++ b/ui/admin/app/instance-initializers/feature-edition.js
@@ -3,9 +3,37 @@
* SPDX-License-Identifier: MPL-2.0
*/
-export function initialize(owner) {
+import fetch from 'fetch';
+
+async function autoInitializeFeatureEdition(owner) {
const service = owner.lookup('service:feature-edition');
- service.initialize();
+ const url = '/metadata.json';
+ let edition;
+ let features;
+
+ // Attempt to load edition for the metadata JSON
+ try {
+ const response = await fetch(url);
+ const json = await response.json();
+ edition = json?.license?.edition;
+ features = json?.license?.features;
+ } catch (e) {
+ // if the request fails, the service will initialize the default edition
+ // as specified in the config
+ }
+
+ // Initialize the feature edition and its associated features
+ service.initialize(edition, features);
+}
+
+export async function initialize(owner) {
+ const config = owner.resolveRegistration('config:environment');
+ const isTesting = config.environment === 'test';
+ // Do not auto-init in a test environment.
+ // Tests should explicitly enable editions and features as needed.
+ if (!isTesting) {
+ await autoInitializeFeatureEdition(owner);
+ }
}
export default {
diff --git a/ui/admin/app/routes/application.js b/ui/admin/app/routes/application.js
index b5ca65df4..cbdbc109e 100644
--- a/ui/admin/app/routes/application.js
+++ b/ui/admin/app/routes/application.js
@@ -20,6 +20,7 @@ export default class ApplicationRoute extends Route {
@service confirm;
@service router;
@service intl;
+ @service features;
@service featureEdition;
// =attributes
@@ -132,4 +133,13 @@ export default class ApplicationRoute extends Route {
toggleEdition(edition) {
this.featureEdition.setEdition(edition);
}
+
+ @action
+ toggleFeature(feature) {
+ if (this.features.isEnabled(feature)) {
+ this.features.disable(feature);
+ } else {
+ this.features.enable(feature);
+ }
+ }
}
diff --git a/ui/admin/app/services/feature-edition.js b/ui/admin/app/services/feature-edition.js
index 840a8ea8f..294bcc680 100644
--- a/ui/admin/app/services/feature-edition.js
+++ b/ui/admin/app/services/feature-edition.js
@@ -8,8 +8,6 @@ import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import config from 'admin/config/environment';
-const { selectedEdition, featureEditions } = config;
-
export default class FeatureEditionService extends Service {
// =services
@@ -17,24 +15,34 @@ export default class FeatureEditionService extends Service {
// =attributes
+ licensedFeatures = Object.keys(config.features.licensedFeatures);
+
@tracked edition;
// array of edition strings
get editions() {
- return Object.keys(featureEditions);
+ return Object.keys(config.features.featureEditions);
}
// =methods
/**
* Initializes the selected edition from the config.
+ * @param {string} edition
+ * @param {?string[]} enabledFeatures - list of extra features to enable
*/
- initialize() {
- this.setEdition(selectedEdition);
+ initialize(edition, enabledFeatures) {
+ this.setEdition(edition || config.features.defaultEdition, enabledFeatures);
}
- setEdition(edition) {
- const editionFlags = featureEditions[edition];
+ /**
+ * Enables the specified edition.
+ * @param {string} edition
+ * @param {?string[]} enabledFeatures - list of extra features to enable
+ */
+ setEdition(edition, enabledFeatures) {
+ this.edition = edition;
+ const editionFlags = config.features.featureEditions[edition];
Object.keys(editionFlags).forEach((flag) => {
if (editionFlags[flag]) {
this.features.enable(flag);
@@ -42,6 +50,8 @@ export default class FeatureEditionService extends Service {
this.features.disable(flag);
}
});
- this.edition = edition;
+ if (enabledFeatures) {
+ enabledFeatures.forEach((flag) => this.features.enable(flag));
+ }
}
}
diff --git a/ui/admin/app/templates/application.hbs b/ui/admin/app/templates/application.hbs
index 4c4b34805..8bd7a7574 100644
--- a/ui/admin/app/templates/application.hbs
+++ b/ui/admin/app/templates/application.hbs
@@ -73,7 +73,7 @@
data-test-dev-edition-toggle
@text={{t
'titles.feature-edition'
- edition=(t (concat 'editions.' this.featureEdition.edition))
+ edition=this.featureEdition.edition
}}
@icon='flight-icons/svg/layers-16'
as |dropdown|
@@ -89,7 +89,7 @@
{{#each this.featureEdition.editions as |edition|}}
@@ -97,6 +97,20 @@
+
+
+
+ {{#each this.featureEdition.licensedFeatures as |feature|}}
+
+
+
+ {{/each}}
+
+
{{/if}}
diff --git a/ui/admin/config/environment.js b/ui/admin/config/environment.js
index 1638b2c99..577b7b6e3 100644
--- a/ui/admin/config/environment.js
+++ b/ui/admin/config/environment.js
@@ -5,12 +5,27 @@
'use strict';
-const features = require('./features');
+const v8 = require('v8');
+const {
+ defaultEdition,
+ featureEditions,
+ licensedFeatures,
+} = require('./features');
const APP_NAME = process.env.APP_NAME || 'Boundary';
const API_HOST = process.env.API_HOST || '';
+const clone = (obj) => v8.deserialize(v8.serialize(obj));
+
module.exports = function (environment) {
+ // Start with a fresh copy of the features config every run, since it
+ // could be modified by an environment.
+ const features = {
+ defaultEdition,
+ featureEditions: clone(featureEditions),
+ licensedFeatures: clone(licensedFeatures),
+ };
+
let ENV = {
modulePrefix: 'admin',
environment,
@@ -98,8 +113,7 @@ module.exports = function (environment) {
timeout: 4000,
},
- selectedEdition: features.selectedEdition,
- featureEditions: features.featureEditions,
+ features: features,
featureFlags: {},
};
@@ -125,14 +139,16 @@ module.exports = function (environment) {
};
// Default edition in development
- ENV.selectedEdition = 'enterprise';
- features.enableFeaturesInAllEditions(
- {
- // Show edition toggle in UI in development
- 'dev-edition-toggle': true,
- },
- ENV
- );
+ ENV.features.defaultEdition = 'enterprise';
+ // Enable development-only features
+ ENV.features.featureEditions.oss['dev-edition-toggle'] = true;
+ ENV.features.featureEditions.enterprise['dev-edition-toggle'] = true;
+ ENV.features.featureEditions.hcp['dev-edition-toggle'] = true;
+ // Enable licensed features by default in enterprise and hcp
+ Object.keys(ENV.features.licensedFeatures).forEach((feature) => {
+ ENV.features.featureEditions.enterprise[feature] = true;
+ ENV.features.featureEditions.hcp[feature] = true;
+ });
}
if (environment === 'test') {
@@ -150,16 +166,6 @@ module.exports = function (environment) {
ENV.flashMessageDefaults.timeout = 0;
ENV.enableConfirmService = false;
-
- /**
- * Enable additional features for testing purposes.
- * [TODO] Tests should be refactored such that each test case
- * explicitly enables the features and/or edition that it needs.
- * By default, tests should have no features enabled. This explicit
- * approach ensures that test cases are easy to read and understand,
- * since their feature requirements are embedded.
- */
- ENV.selectedEdition = 'enterprise';
}
if (environment === 'production') {
diff --git a/ui/admin/config/features.js b/ui/admin/config/features.js
index ed7f0b151..9a4382ec9 100644
--- a/ui/admin/config/features.js
+++ b/ui/admin/config/features.js
@@ -5,14 +5,22 @@
const EDITION = process.env.EDITION || 'oss'; // Default edition is OSS
-const selectedEdition = EDITION;
+const defaultEdition = EDITION;
+const devFeatures = {
+ 'dev-edition-toggle': false,
+};
+const licensedFeatures = {
+ 'multi-hop-sessions': false,
+ 'ssh-target': false,
+};
// Base edition declares available features, disabled by default.
const baseEdition = {
+ ...devFeatures,
+ ...licensedFeatures,
byow: false,
'byow-pki-hcp-cluster-id': false,
'json-credentials': false,
- 'ssh-target': false,
'static-credentials': false,
'target-worker-filters-v2': false,
'target-worker-filters-v2-ingress': false,
@@ -32,7 +40,6 @@ featureEditions.oss = {
};
featureEditions.enterprise = {
...featureEditions.oss,
- 'ssh-target': true,
'target-worker-filters-v2-ingress': true,
'vault-worker-filter': true,
};
@@ -42,18 +49,8 @@ featureEditions.hcp = {
'target-worker-filters-v2-hcp': true,
};
-/**
- * Takes a set of features and enables them in all editions.
- * For use in development and testing only.
- */
-const enableFeaturesInAllEditions = (features = {}, ENV) => {
- Object.entries(featureEditions).forEach(
- ([key, edition]) => (ENV.featureEditions[key] = { ...features, ...edition })
- );
-};
-
module.exports = {
featureEditions,
- selectedEdition,
- enableFeaturesInAllEditions,
+ defaultEdition,
+ licensedFeatures,
};
diff --git a/ui/admin/tests/acceptance/credential-library/create-test.js b/ui/admin/tests/acceptance/credential-library/create-test.js
index 7f0343574..dc9cfe2d1 100644
--- a/ui/admin/tests/acceptance/credential-library/create-test.js
+++ b/ui/admin/tests/acceptance/credential-library/create-test.js
@@ -23,6 +23,7 @@ module('Acceptance | credential-libraries | create', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
+ let featuresService;
let getCredentialLibraryCount;
const instances = {
@@ -76,6 +77,7 @@ module('Acceptance | credential-libraries | create', function (hooks) {
getCredentialLibraryCount = () =>
this.server.schema.credentialLibraries.all().models.length;
authenticateSession({});
+ featuresService = this.owner.lookup('service:features');
});
test('visiting credential libraries', async function (assert) {
assert.expect(2);
@@ -98,6 +100,7 @@ module('Acceptance | credential-libraries | create', function (hooks) {
test('can create a new credential library of type vault ssh cert', async function (assert) {
assert.expect(12);
+ featuresService.enable('ssh-target');
const count = getCredentialLibraryCount();
await visit(urls.newCredentialLibrary);
await click('[value="vault-ssh-certificate"]');
@@ -156,6 +159,7 @@ module('Acceptance | credential-libraries | create', function (hooks) {
test('ecdsa and rsa key types bring up a key bits field', async function (assert) {
assert.expect(3);
+ featuresService.enable('ssh-target');
await visit(urls.newCredentialLibrary);
await click('[value="vault-ssh-certificate"]');
await select('[name="key_type"]', 'ed25519');
@@ -224,11 +228,9 @@ module('Acceptance | credential-libraries | create', function (hooks) {
});
test('cannot select vault ssh cert when feature is disabled', async function (assert) {
- const featuresService = this.owner.lookup('service:features');
- featuresService.disable('ssh-target');
- assert.expect(1);
+ assert.expect(2);
await visit(urls.newCredentialLibrary);
-
+ assert.false(featuresService.isEnabled('ssh-target'));
assert.dom('[value="vault-ssh-certificate"]').doesNotExist();
});
});
diff --git a/ui/admin/tests/acceptance/credential-library/read-test.js b/ui/admin/tests/acceptance/credential-library/read-test.js
index 38cf3f406..3194db43a 100644
--- a/ui/admin/tests/acceptance/credential-library/read-test.js
+++ b/ui/admin/tests/acceptance/credential-library/read-test.js
@@ -85,7 +85,7 @@ module('Acceptance | credential-libraries | read', function (hooks) {
});
test('cannot navigate to vault ssh cert form when feature is not enabled', async function (assert) {
- assert.expect(1);
+ assert.expect(2);
instances.credentialLibrary = this.server.create('credential-library', {
scope: instances.scopes.project,
credentialStore: instances.credentialStore,
@@ -94,10 +94,9 @@ module('Acceptance | credential-libraries | read', function (hooks) {
await visit(
`${urls.credentialLibraries}/${instances.credentialLibrary.id}`
);
- const featuresService = this.owner.lookup('service:features');
- featuresService.disable('ssh-target');
await visit(urls.credentialLibraries);
-
+ const featuresService = this.owner.lookup('service:features');
+ assert.false(featuresService.isEnabled('ssh-target'));
assert.dom('.rose-table-body tr:nth-of-type(2) a').doesNotExist();
});
diff --git a/ui/admin/tests/acceptance/credential-store/create-test.js b/ui/admin/tests/acceptance/credential-store/create-test.js
index 780023fdd..6fc90f3f9 100644
--- a/ui/admin/tests/acceptance/credential-store/create-test.js
+++ b/ui/admin/tests/acceptance/credential-store/create-test.js
@@ -14,6 +14,7 @@ module('Acceptance | credential-stores | create', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
+ let featuresService;
let getCredentialStoresCount;
let getStaticCredentialStoresCount;
let getVaultCredentialStoresCount;
@@ -62,10 +63,12 @@ module('Acceptance | credential-stores | create', function (hooks) {
.length;
};
authenticateSession({});
+ featuresService = this.owner.lookup('service:features');
});
test('Users can create a new credential store of default type static', async function (assert) {
assert.expect(1);
+ featuresService.enable('static-credentials');
const count = getStaticCredentialStoresCount();
await visit(urls.newCredentialStore);
await fillIn('[name="name"]', 'random string');
@@ -75,6 +78,7 @@ module('Acceptance | credential-stores | create', function (hooks) {
test('Users can create a new credential store of type vault', async function (assert) {
assert.expect(1);
+ featuresService.enable('static-credentials');
const count = getVaultCredentialStoresCount();
await visit(urls.newCredentialStore);
await fillIn('[name="name"]', 'random string');
diff --git a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js
index 1276bf965..67133fb92 100644
--- a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js
+++ b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js
@@ -117,6 +117,7 @@ module(
test('users can create a new json credential', async function (assert) {
assert.expect(2);
+ featuresService.enable('json-credentials');
const credentialsCount = getCredentialsCount();
const jsonCredentialCount = getJsonCredentialCount();
await visit(urls.credentials);
@@ -159,6 +160,7 @@ module(
test('users can cancel create new json credential', async function (assert) {
assert.expect(2);
+ featuresService.enable('json-credentials');
const credentialsCount = getCredentialsCount();
await visit(urls.credentials);
@@ -173,6 +175,7 @@ module(
test('users can switch away from JSON type credentials and the json_object value will be cleared', async function (assert) {
assert.expect(2);
+ featuresService.enable('json-credentials');
const editorSelector = '[data-test-code-editor-field-editor]';
const newSecret = '{"test": "value"}';
@@ -191,6 +194,7 @@ module(
test('users cannot navigate to new credential route without proper authorization', async function (assert) {
assert.expect(2);
+ featuresService.enable('static-credentials');
instances.staticCredentialStore.authorized_collection_actions.credentials =
instances.staticCredentialStore.authorized_collection_actions.credentials.filter(
(item) => {
@@ -282,6 +286,7 @@ module(
test('saving a new json credential with invalid fields displays error messages', async function (assert) {
assert.expect(1);
+ featuresService.enable('json-credentials');
await visit(urls.credentials);
this.server.post('/credentials', () => {
return new Response(
@@ -313,12 +318,11 @@ module(
});
test('cannot navigate to json credential when feature is disabled', async function (assert) {
- featuresService.disable('json-credentials');
- assert.expect(4);
+ assert.expect(5);
await visit(urls.credentials);
await click(`[href="${urls.newCredential}"]`);
-
+ assert.false(featuresService.isEnabled('json-credentials'));
assert.true(
instances.staticCredentialStore.authorized_collection_actions.credentials.includes(
'create'
diff --git a/ui/admin/tests/acceptance/credential-store/credentials/read-test.js b/ui/admin/tests/acceptance/credential-store/credentials/read-test.js
index 6739b54ea..09ccfb301 100644
--- a/ui/admin/tests/acceptance/credential-store/credentials/read-test.js
+++ b/ui/admin/tests/acceptance/credential-store/credentials/read-test.js
@@ -14,6 +14,8 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
+ let featuresService;
+
const instances = {
scopes: {
org: null,
@@ -75,6 +77,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) {
urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`;
urls.unknownCredential = `${urls.credentials}/foo`;
authenticateSession({});
+ featuresService = this.owner.lookup('service:features');
});
test('visiting username & password credential', async function (assert) {
@@ -101,6 +104,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) {
test('visiting JSON credential', async function (assert) {
assert.expect(2);
+ featuresService.enable('json-credentials');
await visit(urls.staticCredentialStore);
await click(`[href="${urls.credentials}"]`);
await a11yAudit();
@@ -141,10 +145,9 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) {
});
test('cannot navigate to a JSON credential form when feature not enabled', async function (assert) {
- assert.expect(1);
- const featuresService = this.owner.lookup('service:features');
- featuresService.disable('json-credentials');
+ assert.expect(2);
await visit(urls.credentials);
+ assert.false(featuresService.isEnabled('json-credentials'));
assert.dom('.rose-table-body tr:nth-child(3) a').doesNotExist();
});
diff --git a/ui/admin/tests/acceptance/credential-store/read-test.js b/ui/admin/tests/acceptance/credential-store/read-test.js
index b7661aef7..f8d7bc38f 100644
--- a/ui/admin/tests/acceptance/credential-store/read-test.js
+++ b/ui/admin/tests/acceptance/credential-store/read-test.js
@@ -14,6 +14,8 @@ module('Acceptance | credential-stores | read', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
+ let featuresService;
+
const instances = {
scopes: {
global: null,
@@ -63,10 +65,12 @@ module('Acceptance | credential-stores | read', function (hooks) {
urls.unknownCredentialStore = `${urls.credentialStores}/foo`;
authenticateSession({});
+ featuresService = this.owner.lookup('service:features');
});
test('visiting static credential store', async function (assert) {
assert.expect(2);
+ featuresService.enable('static-credentials');
await visit(urls.credentialStores);
await a11yAudit();
assert.strictEqual(currentURL(), urls.credentialStores);
@@ -104,6 +108,7 @@ module('Acceptance | credential-stores | read', function (hooks) {
test('cannot navigate to a vault credential store form without proper authorization', async function (assert) {
assert.expect(2);
+ featuresService.enable('static-credentials');
await visit(urls.projectScope);
instances.vaultCredentialStore.authorized_actions =
instances.vaultCredentialStore.authorized_actions.filter(
diff --git a/ui/admin/tests/acceptance/develop-test.js b/ui/admin/tests/acceptance/develop-test.js
index 3148ff3d8..f70fc1992 100644
--- a/ui/admin/tests/acceptance/develop-test.js
+++ b/ui/admin/tests/acceptance/develop-test.js
@@ -20,12 +20,14 @@ module('Acceptance | develop', function (hooks) {
});
test('edition toggle is hidden when `dev-edition-toggle` is false', async function (assert) {
+ assert.expect(2);
await visit('/');
assert.false(featuresService.isEnabled('dev-edition-toggle'));
assert.dom('data-dev-edition-toggle').doesNotExist();
});
test('edition toggle is visible when `dev-edition-toggle` is true', async function (assert) {
+ assert.expect(2);
assert.false(featuresService.isEnabled('dev-edition-toggle'));
featuresService.enable('dev-edition-toggle');
await visit('/');
diff --git a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js
index 5f556fd6f..eb6e47021 100644
--- a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js
+++ b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js
@@ -148,14 +148,13 @@ module('Acceptance | targets | brokered credential sources', function (hooks) {
});
test('cannot navigate to a json type credential when feature is disabled', async function (assert) {
- featuresService.disable('json-credentials');
- assert.expect(2);
+ assert.expect(3);
const jsonCredential = instances.credentials[2];
instances.target.update({
brokeredCredentialSourceIds: [...randomlySelectedCredentials],
});
await visit(urls.brokeredCredentialSources);
-
+ assert.false(featuresService.isEnabled('json-credentials'));
assert
.dom('.rose-table-row:nth-child(3)')
.includesText(jsonCredential.name);
diff --git a/ui/admin/tests/acceptance/targets/create-test.js b/ui/admin/tests/acceptance/targets/create-test.js
index d5ff06eab..86f3ced0d 100644
--- a/ui/admin/tests/acceptance/targets/create-test.js
+++ b/ui/admin/tests/acceptance/targets/create-test.js
@@ -85,13 +85,17 @@ module('Acceptance | targets | create', function (hooks) {
test('defaults to type `ssh` when no query param provided', async function (assert) {
assert.expect(1);
+ featuresService.enable('ssh-target');
await visit(urls.newTarget);
assert.strictEqual(find('[name="type"]:checked').value, TYPE_TARGET_SSH);
});
test('can create type `ssh` when `target-worker-filters-v2` is disabled', async function (assert) {
- featuresService.disable('target-worker-filters-v2');
- assert.expect(4);
+ assert.expect(5);
+ featuresService.enable('ssh-target');
+
+ assert.false(featuresService.isEnabled('target-worker-filters-v2'));
+
const targetCount = getTargetCount();
const sshTargetCount = getSSHTargetCount();
await visit(urls.targets);
@@ -116,8 +120,10 @@ module('Acceptance | targets | create', function (hooks) {
});
test('can create type `ssh` when `target-worker-filters-v2` is enabled', async function (assert) {
- featuresService.enable('target-worker-filters-v2');
assert.expect(4);
+ featuresService.enable('ssh-target');
+ featuresService.enable('target-worker-filters-v2');
+
const targetCount = getTargetCount();
const sshTargetCount = getSSHTargetCount();
await visit(urls.targets);
@@ -146,8 +152,11 @@ module('Acceptance | targets | create', function (hooks) {
});
test('can create type `tcp` when `target-worker-filters-v2` is disabled', async function (assert) {
- featuresService.disable('target-worker-filters-v2');
- assert.expect(4);
+ assert.expect(5);
+ featuresService.enable('ssh-target');
+
+ assert.false(featuresService.isEnabled('target-worker-filters-v2'));
+
const targetCount = getTargetCount();
const tcpTargetCount = getTCPTargetCount();
await visit(urls.targets);
@@ -172,8 +181,10 @@ module('Acceptance | targets | create', function (hooks) {
});
test('can create type `tcp` when `target-worker-filters-v2` is enabled', async function (assert) {
- featuresService.enable('target-worker-filters-v2');
assert.expect(4);
+ featuresService.enable('ssh-target');
+ featuresService.enable('target-worker-filters-v2');
+
const targetCount = getTargetCount();
const tcpTargetCount = getTCPTargetCount();
await visit(urls.targets);
@@ -203,9 +214,11 @@ module('Acceptance | targets | create', function (hooks) {
});
test('can create type `tcp` when `target-worker-filters-v2` and `target-worker-filters-v2-ingress` is enabled', async function (assert) {
+ assert.expect(4);
+ featuresService.enable('ssh-target');
featuresService.enable('target-worker-filters-v2');
featuresService.enable('target-worker-filters-v2-ingress');
- assert.expect(4);
+
const targetCount = getTargetCount();
const tcpTargetCount = getTCPTargetCount();
await visit(urls.targets);
@@ -239,15 +252,16 @@ module('Acceptance | targets | create', function (hooks) {
test('default port is not marked required for SSH targets', async function (assert) {
assert.expect(1);
+ featuresService.enable('ssh-target');
await visit(urls.newTarget);
assert.dom('[data-test-default-port-label]').includesText('Optional');
});
test('default port is marked required for TCP targets', async function (assert) {
assert.expect(1);
+ featuresService.enable('ssh-target');
await visit(urls.newTarget);
await click('[value="tcp"]');
-
assert.dom('[data-test-default-port-label]').includesText('Required');
});
@@ -284,12 +298,11 @@ module('Acceptance | targets | create', function (hooks) {
});
test('cannot navigate to new SSH targets route when ssh feature is disabled', async function (assert) {
- assert.expect(3);
- featuresService.disable('ssh-target');
+ assert.expect(4);
await visit(urls.targets);
await click(`[href="${urls.newTarget}"]`);
-
+ assert.false(featuresService.isEnabled('ssh-target'));
assert.true(
instances.scopes.project.authorized_collection_actions.targets.includes(
'create'
@@ -301,6 +314,9 @@ module('Acceptance | targets | create', function (hooks) {
test('can cancel create new TCP target', async function (assert) {
assert.expect(3);
+ featuresService.enable('target-worker-filters-v2');
+ featuresService.enable('target-worker-filters-v2-ingress');
+
const targetCount = getTargetCount();
const tcpTargetCount = getTCPTargetCount();
await visit(urls.targets);
@@ -321,6 +337,10 @@ module('Acceptance | targets | create', function (hooks) {
test('can cancel create new SSH target', async function (assert) {
assert.expect(3);
+ featuresService.enable('ssh-target');
+ featuresService.enable('target-worker-filters-v2');
+ featuresService.enable('target-worker-filters-v2-ingress');
+
const targetCount = getTargetCount();
const sshTargetCount = getSSHTargetCount();
await visit(urls.targets);
@@ -398,10 +418,13 @@ module('Acceptance | targets | create', function (hooks) {
test('can save address', async function (assert) {
assert.expect(2);
+ featuresService.enable('target-network-address');
+
const targetCount = getTargetCount();
await visit(urls.targets);
await click(`[href="${urls.newTarget}"]`);
+
await fillIn('[name="name"]', 'random string');
await fillIn('[name="address"]', '0.0.0.0');
await click('[type="submit"]');
@@ -414,12 +437,10 @@ module('Acceptance | targets | create', function (hooks) {
});
test('address field does not exist when target network address feature is disabled', async function (assert) {
- assert.expect(1);
- featuresService.disable('target-network-address');
+ assert.expect(2);
await visit(urls.targets);
-
await click(`[href="${urls.newTarget}"]`);
-
+ assert.false(featuresService.isEnabled('target-network-address'));
assert.dom('[name="address"]').doesNotExist();
});
});
diff --git a/ui/admin/tests/acceptance/targets/read-test.js b/ui/admin/tests/acceptance/targets/read-test.js
index e6205e150..10d792b4b 100644
--- a/ui/admin/tests/acceptance/targets/read-test.js
+++ b/ui/admin/tests/acceptance/targets/read-test.js
@@ -74,6 +74,8 @@ module('Acceptance | targets | read', function (hooks) {
test('visiting ssh target', async function (assert) {
assert.expect(2);
+ featuresService.enable('ssh-target');
+
await visit(urls.targets);
await a11yAudit();
assert.strictEqual(currentURL(), urls.targets);
@@ -132,23 +134,22 @@ module('Acceptance | targets | read', function (hooks) {
});
test('visiting an ssh target does not show the worker_filter deprecation message when "target-worker-filters-v2" is disabled', async function (assert) {
- featuresService.disable('target-worker-filters-v2');
- assert.expect(1);
+ assert.expect(2);
await visit(urls.sshTarget);
-
+ assert.false(featuresService.isEnabled('target-worker-filters-v2'));
assert.dom('.hds-alert').doesNotExist();
});
test('visiting a tcp target does not show the worker_filter deprecation message when "target-worker-filters-v2" is disabled', async function (assert) {
- featuresService.disable('target-worker-filters-v2');
- assert.expect(1);
+ assert.expect(2);
await visit(urls.tcpTarget);
-
+ assert.false(featuresService.isEnabled('target-worker-filters-v2'));
assert.dom('.hds-alert').doesNotExist();
});
- test('cannot navigate to a ssh target form without proper authorization', async function (assert) {
+ test('cannot navigate to an ssh target form without proper authorization', async function (assert) {
assert.expect(2);
+ featuresService.enable('ssh-target');
await visit(urls.projectScope);
instances.sshTarget.authorized_actions =
instances.sshTarget.authorized_actions.filter((item) => item !== 'read');
@@ -161,6 +162,7 @@ module('Acceptance | targets | read', function (hooks) {
test('cannot navigate to a tcp target form without proper authorization', async function (assert) {
assert.expect(2);
+ featuresService.enable('ssh-target');
await visit(urls.projectScope);
instances.tcpTarget.authorized_actions =
instances.tcpTarget.authorized_actions.filter((item) => item !== 'read');
diff --git a/ui/admin/tests/acceptance/targets/update-test.js b/ui/admin/tests/acceptance/targets/update-test.js
index ac7455b66..02f0b597e 100644
--- a/ui/admin/tests/acceptance/targets/update-test.js
+++ b/ui/admin/tests/acceptance/targets/update-test.js
@@ -39,7 +39,6 @@ module('Acceptance | targets | update', function (hooks) {
hooks.beforeEach(function () {
featuresService = this.owner.lookup('service:features');
- featuresService.disable('target-worker-filters-v2');
// Generate resources
instances.scopes.global = this.server.create('scope', { id: 'global' });
instances.scopes.org = this.server.create('scope', {
@@ -96,10 +95,9 @@ module('Acceptance | targets | update', function (hooks) {
});
test('updating a target does not show the worker_filter deprecation message when "target-worker-filters-v2" is disabled', async function (assert) {
- featuresService.disable('target-worker-filters-v2');
- assert.expect(1);
+ assert.expect(2);
await visit(urls.target);
-
+ assert.false(featuresService.isEnabled('target-worker-filters-v2'));
assert.dom('.hds-alert').doesNotExist();
});
@@ -366,6 +364,8 @@ module('Acceptance | targets | update', function (hooks) {
test('saving address with existing host sources brings up confirmation modal and removes host sources', async function (assert) {
assert.expect(4);
+ featuresService.enable('ssh-target');
+ featuresService.enable('target-network-address');
const confirmService = this.owner.lookup('service:confirm');
confirmService.enabled = true;
this.server.createList(
@@ -410,6 +410,8 @@ module('Acceptance | targets | update', function (hooks) {
test('saving address with existing host sources brings up confirmation modal and can cancel', async function (assert) {
assert.expect(4);
+ featuresService.enable('ssh-target');
+ featuresService.enable('target-network-address');
const confirmService = this.owner.lookup('service:confirm');
confirmService.enabled = true;
this.server.createList(
diff --git a/ui/admin/tests/acceptance/workers/create-test.js b/ui/admin/tests/acceptance/workers/create-test.js
index 7d98fe115..fefe6584c 100644
--- a/ui/admin/tests/acceptance/workers/create-test.js
+++ b/ui/admin/tests/acceptance/workers/create-test.js
@@ -54,21 +54,21 @@ module('Acceptance | workers | create', function (hooks) {
});
test('initial upstreams input field is visible for `oss` binary', async function (assert) {
- assert.expect(2);
+ assert.expect(3);
const featuresService = this.owner.lookup('service:features');
- featuresService.disable('byow-pki-hcp-cluster-id');
await visit(newWorkerURL);
const labels = findAll('label.rose-form-label');
+ assert.false(featuresService.isEnabled('byow-pki-hcp-cluster-id'));
assert.dom(labels[0]).doesNotIncludeText('Boundary Cluster ID');
assert.dom(labels[2]).hasText('Initial Upstreams');
});
test('download and install step shows correct oss instructions', async function (assert) {
- assert.expect(2);
+ assert.expect(3);
const featuresService = this.owner.lookup('service:features');
- featuresService.disable('byow-pki-hcp-cluster-id');
await visit(newWorkerURL);
const createSection = findAll('.worker-create-section');
+ assert.false(featuresService.isEnabled('byow-pki-hcp-cluster-id'));
assert.dom(createSection[1]).includesText('curl -fsSL');
assert.dom(createSection[1]).doesNotIncludeText('wget -q');
});
diff --git a/ui/admin/tests/acceptance/workers/list-test.js b/ui/admin/tests/acceptance/workers/list-test.js
index 404741a53..2899b01d3 100644
--- a/ui/admin/tests/acceptance/workers/list-test.js
+++ b/ui/admin/tests/acceptance/workers/list-test.js
@@ -18,6 +18,8 @@ module('Acceptance | workers | list', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
+ let featuresService;
+
const instances = {
scopes: {
global: null,
@@ -31,14 +33,15 @@ module('Acceptance | workers | list', function (hooks) {
hooks.beforeEach(function () {
instances.scopes.global = this.server.create('scope', { id: 'global' });
-
urls.globalScope = `/scopes/global/scopes`;
urls.workers = `/scopes/global/workers`;
authenticateSession({});
+ featuresService = this.owner.lookup('service:features');
});
test('Users can navigate to workers with proper authorization', async function (assert) {
assert.expect(2);
+ featuresService.enable('byow');
await visit(urls.globalScope);
assert.ok(
instances.scopes.global.authorized_collection_actions.workers.includes(
@@ -50,6 +53,7 @@ module('Acceptance | workers | list', function (hooks) {
test('Users cannot navigate to workers without list or create permissions', async function (assert) {
assert.expect(1);
+ featuresService.enable('byow');
instances.scopes.global.authorized_collection_actions.workers = [];
await visit(urls.globalScope);
@@ -58,6 +62,7 @@ module('Acceptance | workers | list', function (hooks) {
test('Users can navigate to workers with only create permission', async function (assert) {
assert.expect(1);
+ featuresService.enable('byow');
instances.scopes.global.authorized_collection_actions.workers = [
'create:worker-led',
];
@@ -68,6 +73,7 @@ module('Acceptance | workers | list', function (hooks) {
test('Users can navigate to workers with only list permission', async function (assert) {
assert.expect(1);
+ featuresService.enable('byow');
instances.scopes.global.authorized_collection_actions.workers = ['list'];
await visit(urls.globalScope);
diff --git a/ui/admin/tests/integration/components/worker-diagram/dual-filter/hcp/index-test.js b/ui/admin/tests/integration/components/worker-diagram/dual-filter/hcp/index-test.js
index fa56d0aa1..3b7b20625 100644
--- a/ui/admin/tests/integration/components/worker-diagram/dual-filter/hcp/index-test.js
+++ b/ui/admin/tests/integration/components/worker-diagram/dual-filter/hcp/index-test.js
@@ -16,13 +16,11 @@ module(
setupIntl(hooks);
let featuresService;
- const targetWorkerFilterIngress = 'target-worker-filters-v2-ingress';
const targetWorkerFilterHCP = 'target-worker-filters-v2-hcp';
hooks.beforeEach(function () {
featuresService = this.owner.lookup('service:features');
featuresService.enable(targetWorkerFilterHCP);
- featuresService.disable(targetWorkerFilterIngress);
});
test('it renders the correct diagram when egress and ingress filter is false', async function (assert) {
diff --git a/ui/admin/tests/integration/components/worker-diagram/dual-filter/index-test.js b/ui/admin/tests/integration/components/worker-diagram/dual-filter/index-test.js
index 0312201ee..fa620972f 100644
--- a/ui/admin/tests/integration/components/worker-diagram/dual-filter/index-test.js
+++ b/ui/admin/tests/integration/components/worker-diagram/dual-filter/index-test.js
@@ -17,12 +17,10 @@ module(
let featuresService;
const targetWorkerFilterIngress = 'target-worker-filters-v2-ingress';
- const targetWorkerFilterHCP = 'target-worker-filters-v2-hcp';
hooks.beforeEach(function () {
featuresService = this.owner.lookup('service:features');
featuresService.enable(targetWorkerFilterIngress);
- featuresService.disable(targetWorkerFilterHCP);
});
test('it renders the correct diagram when egress and ingress filter is false', async function (assert) {
diff --git a/ui/admin/tests/integration/components/worker-diagram/index-test.js b/ui/admin/tests/integration/components/worker-diagram/index-test.js
index 058c699ec..3310a90a8 100644
--- a/ui/admin/tests/integration/components/worker-diagram/index-test.js
+++ b/ui/admin/tests/integration/components/worker-diagram/index-test.js
@@ -19,31 +19,32 @@ module('Integration | Component | worker-diagram/index', function (hooks) {
test('it renders a single filter diagram', async function (assert) {
featuresService = this.owner.lookup('service:features');
- featuresService.disable(targetWorkerFilterIngress);
- featuresService.disable(targetWorkerFilterHCP);
- assert.expect(1);
+ assert.expect(3);
await render(hbs``);
-
+ assert.false(featuresService.isEnabled(targetWorkerFilterIngress));
+ assert.false(featuresService.isEnabled(targetWorkerFilterHCP));
assert.dom('[data-test-single-filter-egress-off]').isVisible();
});
test('it renders a dual filter diagram when `target-worker-filters-v2-ingress` is enabled', async function (assert) {
featuresService = this.owner.lookup('service:features');
featuresService.enable(targetWorkerFilterIngress);
- featuresService.disable(targetWorkerFilterHCP);
- assert.expect(1);
+ assert.expect(3);
+ assert.true(featuresService.isEnabled(targetWorkerFilterIngress));
+ assert.false(featuresService.isEnabled(targetWorkerFilterHCP));
await render(hbs``);
assert.dom('[data-test-dual-filter-egress-off-ingress-off]').isVisible();
});
- test('it renders a HCP dual filter diagram when `target-worker-filters-v2-hcp` is enabled', async function (assert) {
+ test('it renders an HCP dual filter diagram when `target-worker-filters-v2-hcp` is enabled', async function (assert) {
featuresService = this.owner.lookup('service:features');
featuresService.enable(targetWorkerFilterIngress);
featuresService.enable(targetWorkerFilterHCP);
- assert.expect(1);
+ assert.expect(3);
await render(hbs``);
-
+ assert.true(featuresService.isEnabled(targetWorkerFilterIngress));
+ assert.true(featuresService.isEnabled(targetWorkerFilterHCP));
assert
.dom('[data-test-dual-filter-hcp-egress-off-ingress-off]')
.isVisible();
diff --git a/ui/admin/tests/unit/abilities/credential-library-test.js b/ui/admin/tests/unit/abilities/credential-library-test.js
index b1252507f..0765bb72f 100644
--- a/ui/admin/tests/unit/abilities/credential-library-test.js
+++ b/ui/admin/tests/unit/abilities/credential-library-test.js
@@ -17,11 +17,11 @@ module('Unit | Abilities | credential-library', function (hooks) {
hooks.beforeEach(function () {
features = this.owner.lookup('service:features');
- features.enable('ssh-target');
});
test('can read credential library type when authorized and feature is enabled', function (assert) {
assert.expect(2);
+ features.enable('ssh-target');
const canService = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store');
const credentialLibrary = store.createRecord('credential-library', {
@@ -35,6 +35,7 @@ module('Unit | Abilities | credential-library', function (hooks) {
test('cannot read credential library type when unauthorized and feature is enabled', function (assert) {
assert.expect(2);
+ features.enable('ssh-target');
const canService = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store');
const credentialLibrary = store.createRecord('credential-library', {
@@ -47,30 +48,30 @@ module('Unit | Abilities | credential-library', function (hooks) {
});
test('cannot read credential library type when unauthorized and feature is disabled', function (assert) {
- assert.expect(2);
+ assert.expect(3);
const canService = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store');
const featuresService = this.owner.lookup('service:features');
- featuresService.disable('ssh-target');
const credentialLibrary = store.createRecord('credential-library', {
authorized_actions: [],
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
});
+ assert.false(featuresService.isEnabled('ssh-target'));
assert.false(canService.can('read credential-library', credentialLibrary));
credentialLibrary.type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
assert.false(canService.can('read credential-library', credentialLibrary));
});
test('can read vault-generic but not vault-ssh-certificate when authorized and feature is disabled', function (assert) {
- assert.expect(2);
+ assert.expect(3);
const canService = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store');
const featuresService = this.owner.lookup('service:features');
- featuresService.disable('ssh-target');
const credentialLibrary = store.createRecord('credential-library', {
authorized_actions: ['read'],
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
});
+ assert.false(featuresService.isEnabled('ssh-target'));
assert.false(canService.can('read credential-library', credentialLibrary));
credentialLibrary.type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
assert.true(canService.can('read credential-library', credentialLibrary));
diff --git a/ui/admin/tests/unit/abilities/credential-test.js b/ui/admin/tests/unit/abilities/credential-test.js
index 8b19f5196..b60d95a7c 100644
--- a/ui/admin/tests/unit/abilities/credential-test.js
+++ b/ui/admin/tests/unit/abilities/credential-test.js
@@ -9,16 +9,16 @@ import { setupTest } from 'ember-qunit';
module('Unit | Abilities | credential', function (hooks) {
setupTest(hooks);
- let features;
+ let featuresService;
hooks.beforeEach(function () {
// Enable json-credentials feature by default
- features = this.owner.lookup('service:features');
- features.enable('json-credentials');
+ featuresService = this.owner.lookup('service:features');
});
test('can read credentials, including JSON credentials, when authorized and json-credentials feature is enabled', function (assert) {
assert.expect(3);
+ featuresService.enable('json-credentials');
const service = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store');
const credential = store.createRecord('credential', {
@@ -34,6 +34,7 @@ module('Unit | Abilities | credential', function (hooks) {
test('cannot read credentials, including JSON credentials, when unauthorized and json-credentials feature is enabled', function (assert) {
assert.expect(3);
+ featuresService.enable('json-credentials');
const service = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store');
const credential = store.createRecord('credential', {
@@ -48,15 +49,14 @@ module('Unit | Abilities | credential', function (hooks) {
});
test('cannot read credentials, including JSON credentials, when unauthorized and json-credentials feature is disabled', function (assert) {
- assert.expect(3);
+ assert.expect(4);
const service = this.owner.lookup('service:can');
- const featuresService = this.owner.lookup('service:features');
- featuresService.disable('json-credentials');
const store = this.owner.lookup('service:store');
const credential = store.createRecord('credential', {
authorized_actions: [],
type: 'json',
});
+ assert.false(featuresService.isEnabled('json-credentials'));
assert.false(service.can('read credential', credential));
credential.type = 'username_password';
assert.false(service.can('read credential', credential));
@@ -65,14 +65,14 @@ module('Unit | Abilities | credential', function (hooks) {
});
test('can read credentials, excepting JSON credentials, when authorized and json-credentials feature is disabled', function (assert) {
- assert.expect(3);
- features.disable('json-credentials');
+ assert.expect(4);
const service = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store');
const credential = store.createRecord('credential', {
authorized_actions: ['read'],
type: 'json',
});
+ assert.false(featuresService.isEnabled('json-credentials'));
assert.false(service.can('read credential', credential));
credential.type = 'username_password';
assert.true(service.can('read credential', credential));