Add HashiCorp Nomad provider (#483)

* provider: adding Nomad provider

* updating CONTRIBUTING.md with Nomad provider

* updated README.md by adding the Nomad provider

* fix typo

* adding nomad/api and nomad/testutil deps

* adding Nomad binary dependency for provider tests

* fixed the nomad binary download command step and added tolerations to the nomad provider.

* adding nomad provider demo gif

* adding my name to authors

* adding two missing go-rootcerts files after dep ensure

* delete pod comment
This commit is contained in:
Anubhav Mishra
2019-01-08 01:18:11 +05:30
committed by Robbie Zhang
parent 5796be449b
commit a46e1dd2ce
332 changed files with 126455 additions and 2 deletions

View File

@@ -0,0 +1,36 @@
import ClusterAdapter from './cluster';
export default ClusterAdapter.extend({
queryRecord() {
return this._super(...arguments).then(resp => {
resp.data.id = resp.data.license_id;
return resp.data;
});
},
createRecord(store, type, snapshot) {
let id = snapshot.attr('licenseId');
return this._super(...arguments).then(() => {
return {
id,
};
});
},
updateRecord(store, type, snapshot) {
let id = snapshot.attr('licenseId');
return this._super(...arguments).then(() => {
return {
id,
};
});
},
pathForType() {
return 'license';
},
urlForUpdateRecord() {
return this.buildURL() + '/license';
},
});

View File

@@ -0,0 +1,32 @@
import Component from '@ember/component';
import { allFeatures } from 'vault/helpers/all-features';
import { computed } from '@ember/object';
export default Component.extend({
expirationTime: '',
startTime: '',
licenseId: '',
features: null,
text: '',
showForm: false,
isTemporary: computed('licenseId', function() {
return this.licenseId === 'temporary';
}),
featuresInfo: computed('features', function() {
let info = [];
allFeatures().forEach(feature => {
let active = this.features.includes(feature) ? true : false;
info.push({ name: feature, active: active });
});
return info;
}),
saveModel() {},
actions: {
saveModel(text) {
this.saveModel(text);
},
toggleForm() {
this.toggleProperty('showForm');
},
},
});

View File

@@ -0,0 +1,15 @@
import Controller from '@ember/controller';
export default Controller.extend({
licenseSuccess() {
this.send('doRefresh');
},
licenseError() {
//eat the error (handled in MessageError component)
},
actions: {
saveModel({ text }) {
this.model.save({ text }).then(() => this.licenseSuccess(), () => this.licenseError());
},
},
});

View File

@@ -0,0 +1,29 @@
import DS from 'ember-data';
const { attr } = DS;
/* sample response
{
"data": {
"expiration_time": "2017-11-14T16:34:36.546753-05:00",
"features": [
"UI",
"HSM",
"Performance Replication",
"DR Replication"
],
"license_id": "temporary",
"start_time": "2017-11-14T16:04:36.546753-05:00"
},
"warnings": [
"time left on license is 29m33s"
]
}
*/
export default DS.Model.extend({
expirationTime: attr('string'),
features: attr('array'),
licenseId: attr('string'),
startTime: attr('string'),
text: attr('string'),
});

View File

@@ -0,0 +1,22 @@
import Route from '@ember/routing/route';
import ClusterRoute from 'vault/mixins/cluster-route';
import { inject as service } from '@ember/service';
export default Route.extend(ClusterRoute, {
version: service(),
beforeModel() {
if (this.version.isOSS) {
this.transitionTo('vault.cluster');
}
},
model() {
return this.store.queryRecord('license', {});
},
actions: {
doRefresh() {
this.refresh();
},
},
});

View File

@@ -0,0 +1,81 @@
<PageHeader as |p|>
<p.levelLeft>
<h1 class="title is-3">License</h1>
</p.levelLeft>
</PageHeader>
<MessageError @model={{model}} />
{{#if isTemporary}}
<section class="box is-sideless is-fullwidth">
<AlertBanner
@type="warning"
@message="Your temporary license expires in {{date-from-now expirationTime}} and your vault will seal. Please enter a valid license below."
@class="license-warning"
data-test-cluster-status
data-test-warning-text
/>
<span class="title is-5" data-test-temp-license>Temporary License</span>
<form {{action "saveModel" text on="submit"}}>
<div class="box is-shadowless is-fullwidth is-marginless">
<div class="field">
<label for="license-id" class="is-label">License</label>
<div class="control">
{{input id="license-id" value=text autocomplete="off" class="input" data-test-text-input="data-test-text-input"}}
</div>
</div>
</div>
<div class="field">
<div class="control">
<button type="submit" class="button is-primary" data-test-save-button>Save</button>
</div>
</div>
</form>
</section>
{{else}}
<section class="box is-sideless is-fullwidth">
<span class="title is-5">Details</span>
{{#if showForm}}
<form {{action "saveModel" text on="submit"}}>
<div class="field">
<label for="license-id" class="is-label">License</label>
<div class="control">
{{input id="license-id" value=text autocomplete="off" class="input" data-test-text-input="data-test-text-input"}}
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button type="submit" class="button is-primary" data-test-save-button>Save</button>
</div>
<div class="control">
<button type="button" {{action "toggleForm"}} class="button" data-test-cancel-button>Cancel</button>
</div>
</div>
</form>
{{else}}
<div class="field box is-fullwidth is-shadowless is-paddingless is-marginless">
{{info-table-row label="License ID" value=licenseId}}
{{#info-table-row label="Valid from" value=startTime}}
{{date-format model.startTime 'MMM DD, YYYY hh:mm:ss A'}} to {{date-format expirationTime 'MMM DD, YYYY hh:mm:ss A'}}
{{/info-table-row}}
</div>
<div class="field box is-fullwidth is-shadowless is-paddingless is-marginless">
<div class="control">
<button type="button" {{action "toggleForm"}} class="button" data-test-enter-button>Enter new license</button>
</div>
</div>
{{/if}}
</section>
{{/if}}
<section class="box is-sideless is-marginless is-shadowless is-fullwidth">
<span class="title is-5">Features</span>
<div class="field box is-fullwidth is-shadowless is-paddingless is-marginless">
{{#each featuresInfo as |info|}}
{{#info-table-row label=info.name value=(if info.active "Active" "Not Active") data-test-feature-row="data-test-feature-row"}}
{{#if info.active}}
<ICon @size=28 @glyph="true" /> <span data-test-feature-status>Active</span>
{{else}}
<ICon @size=28 @glyph="false" /> <span data-test-feature-status>Not Active</span>
{{/if}}
{{/info-table-row}}
{{/each}}
</div>
</section>

View File

@@ -0,0 +1,9 @@
<LicenseInfo
@startTime={{model.startTime}}
@expirationTime={{model.expirationTime}}
@licenseId={{model.licenseId}}
@features={{model.features}}
@text={{model.text}}
@saveModel={{action "saveModel"}}
@model={{model}}
/>

View File

@@ -0,0 +1,117 @@
import { addMinutes } from 'date-fns';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import sinon from 'sinon';
import { create } from 'ember-cli-page-object';
import license from '../../pages/components/license-info';
import { allFeatures } from 'vault/helpers/all-features';
const FEATURES = allFeatures();
const component = create(license);
module('Integration | Component | license info', function(hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function() {
component.setContext(this);
});
hooks.afterEach(function() {
component.removeContext();
});
const LICENSE_WARNING_TEXT = `Warning Your temporary license expires in 30 minutes and your vault will seal. Please enter a valid license below.`;
test('it renders properly for temporary license', async function(assert) {
const now = Date.now();
this.set('licenseId', 'temporary');
this.set('expirationTime', addMinutes(now, 30));
this.set('startTime', now);
this.set('features', ['HSM', 'Namespaces']);
await render(
hbs`<LicenseInfo @licenseId={{this.licenseId}} @expirationTime={{this.expirationTime}} @startTime={{this.startTime}} @features={{this.features}}/>`
);
assert.equal(component.warning, LICENSE_WARNING_TEXT, 'it renders warning text including time left');
assert.equal(component.hasSaveButton, true, 'it renders the save button');
assert.equal(component.hasTextInput, true, 'it renders text input for new license');
assert.equal(component.featureRows.length, FEATURES.length, 'it renders all of the features');
assert.equal(component.featureRows[0].featureName, 'HSM', 'it renders HSM feature');
assert.equal(component.featureRows[0].featureStatus, 'Active', 'it renders Active for HSM feature');
assert.equal(
component.featureRows[1].featureName,
'Performance Replication',
'it renders Performance Replication feature name'
);
assert.equal(
component.featureRows[1].featureStatus,
'Not Active',
'it renders Not Active for Performance Replication'
);
});
test('it renders feature status properly for features associated with license', async function(assert) {
const now = Date.now();
this.set('licenseId', 'temporary');
this.set('expirationTime', addMinutes(now, 30));
this.set('startTime', now);
this.set('features', ['HSM', 'Namespaces']);
await render(
hbs`<LicenseInfo @licenseId={{this.licenseId}} @expirationTime={{this.expirationTime}} @startTime={{this.startTime}} @features={{this.features}}/>`
);
assert.equal(component.featureRows.length, FEATURES.length, 'it renders all of the features');
let activeFeatures = component.featureRows.filter(f => f.featureStatus === 'Active');
assert.equal(activeFeatures.length, 2);
});
test('it renders properly for non-temporary license', async function(assert) {
const now = Date.now();
this.set('licenseId', 'test');
this.set('expirationTime', addMinutes(now, 30));
this.set('startTime', now);
this.set('features', ['HSM', 'Namespaces']);
await render(
hbs`<LicenseInfo @licenseId={{this.licenseId}} @expirationTime={{this.expirationTime}} @startTime={{this.startTime}} @features={{this.features}}/>`
);
assert.equal(component.hasWarning, false, 'it does not have a warning');
assert.equal(component.hasSaveButton, false, 'it does not render the save button');
assert.equal(component.hasTextInput, false, 'it does not render the text input for new license');
assert.equal(component.hasEnterButton, true, 'it renders the button to toggle license form');
});
test('it shows and hides license form when enter and cancel buttons are clicked', async function(assert) {
const now = Date.now();
this.set('licenseId', 'test');
this.set('expirationTime', addMinutes(now, 30));
this.set('startTime', now);
this.set('features', ['HSM', 'Namespaces']);
await render(
hbs`<LicenseInfo @licenseId={{this.licenseId}} @expirationTime={{this.expirationTime}} @startTime={{this.startTime}} @features={{this.features}}/>`
);
await component.enterButton();
assert.equal(component.hasSaveButton, true, 'it does not render the save button');
assert.equal(component.hasTextInput, true, 'it does not render the text input for new license');
assert.equal(component.hasEnterButton, false, 'it renders the button to toggle license form');
await component.cancelButton();
assert.equal(component.hasSaveButton, false, 'it does not render the save button');
assert.equal(component.hasTextInput, false, 'it does not render the text input for new license');
assert.equal(component.hasEnterButton, true, 'it renders the button to toggle license form');
});
test('it calls saveModel when save button is clicked', async function(assert) {
const now = Date.now();
this.set('licenseId', 'temporary');
this.set('expirationTime', addMinutes(now, 30));
this.set('startTime', now);
this.set('features', ['HSM', 'Namespaces']);
this.set('saveModel', sinon.spy());
await render(
hbs`<LicenseInfo @licenseId={{this.licenseId}} @expirationTime={{this.expirationTime}} @startTime={{this.startTime}} @features={{this.features}} @saveModel={{this.saveModel}}/>`
);
await component.text('ABCDE12345');
await component.saveButton();
assert.ok(this.get('saveModel').calledOnce);
});
});

View File

@@ -0,0 +1,18 @@
import { clickable, fillable, text, isPresent, collection } from 'ember-cli-page-object';
export default {
text: fillable('[data-test-text-input]'),
isTemp: isPresent('[data-test-temp-license]'),
hasTextInput: isPresent('[data-test-text-input]'),
saveButton: clickable('[data-test-save-button]'),
hasSaveButton: isPresent('[data-test-save-button]'),
enterButton: clickable('[data-test-enter-button]'),
hasEnterButton: isPresent('[data-test-enter-button]'),
cancelButton: clickable('[data-test-cancel-button]'),
hasWarning: isPresent('[data-test-warning-text]'),
warning: text('[data-test-warning-text]'),
featureRows: collection('[data-test-feature-row]', {
featureName: text('[data-test-row-label]'),
featureStatus: text('[data-test-feature-status]'),
}),
};