UNPKG

@npm-wharf/fabrik8

Version:

provision a new Kubernetes cluster and deploy software to it from a single API

698 lines (640 loc) 18.8 kB
const createReconciler = require('../lib/reconcile') const SERVICE_ACCOUNTS = [{ totally: 'a', service: 'account-json', private_key: 'd34db33f', client_email: 'my-sa@iam.google.com' }, { totally: 'a', service: 'account-json', private_key: 'd34db33f', client_email: 'my-sa2@iam.google.com' }] const CLUSTER_DEFAULTS = { worker: { cores: 2, memory: '13GB', count: 3, min: 3, max: 6, storage: { persistent: '160GB' } }, flags: { alphaFeatures: false, maintenanceWindow: '08:00:00Z', networkPolicy: true }, manager: { distributed: false }, managers: 1 } const TOKEN_DEFAULTS = { nginx_upstream1: 'frontdoor.svc.cluster.local:5000', nginx_upstream2: 'rewrite.svc.cluster.local:5001' } const DEFAULT_PROPS = { billingAccount: '234523452345', organizationId: '123412341234', user: 'admin', password: '{{ uuid() }}', version: '1.10.11-gke.1', basicAuth: true, zones: ['us-central1-a'] } const UUID = '12341234-1234-1234-1234-123412341234' describe('reconciler', () => { describe('processArgv', () => { describe('with full defaults', () => { const DEFAULTS = { allowedDomains: ['npme.io', 'google.io'], common: { ...DEFAULT_PROPS, credentials: SERVICE_ACCOUNTS[0].client_email }, applicationCredentials: SERVICE_ACCOUNTS[1].client_email, serviceAccounts: { applicationCredentials: SERVICE_ACCOUNTS[1].client_email, service_account: SERVICE_ACCOUNTS[1].client_email, credentials: SERVICE_ACCOUNTS[0].client_email }, tokens: { ...TOKEN_DEFAULTS }, cluster: { ...CLUSTER_DEFAULTS } } let processArgv before(() => { processArgv = createReconciler( { async getCommon () { return DEFAULTS }, async getServiceAccount (email) { return SERVICE_ACCOUNTS.find(json => json.client_email === email) } }, { v4 () { return UUID } } ).processArgv }) it('should process argv and get options', async () => { const result = await processArgv({ name: 'mycluster', specification: '.' }) const expectedCommon = { ...DEFAULTS.common, name: 'mycluster', slug: 'mycluster', url: 'mycluster.npme.io', domain: 'npme.io', projectId: 'mycluster', environment: 'production', user: 'admin', password: UUID } result.should.eql({ specification: '.', kubeformSettings: { ...CLUSTER_DEFAULTS, ...expectedCommon, clusterName: 'mycluster', credentials: SERVICE_ACCOUNTS[0], applicationCredentials: SERVICE_ACCOUNTS[1] }, hikaruSettings: { ...TOKEN_DEFAULTS, ...expectedCommon, credentials: SERVICE_ACCOUNTS[0], awsZone: expectedCommon.domain, subdomain: expectedCommon.name } }) }) }) describe('with templated defaults', () => { const DEFAULTS = { allowedDomains: ['npme.io', 'google.io'], common: { ...DEFAULT_PROPS, projectId: 'asdf-{{ slug }}', password: '{{ uuid() }}', credentials: SERVICE_ACCOUNTS[0].client_email }, applicationCredentials: SERVICE_ACCOUNTS[1].client_email, serviceAccounts: { applicationCredentials: SERVICE_ACCOUNTS[1].client_email, service_account: SERVICE_ACCOUNTS[1].client_email, credentials: SERVICE_ACCOUNTS[0].client_email }, tokens: { ...TOKEN_DEFAULTS, secretSalt: '{{ randomBytes() }}' }, cluster: { ...CLUSTER_DEFAULTS } } let processArgv before(() => { processArgv = createReconciler( { async getCommon () { return DEFAULTS }, async getServiceAccount (email) { return SERVICE_ACCOUNTS.find(json => json.client_email === email) } }, { v4 () { return UUID } } ).processArgv }) it('should process argv and get options', async () => { const result = await processArgv({ name: 'mycluster', specification: '.' }) const { kubeformSettings, hikaruSettings } = result kubeformSettings.password.should.match(/[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}/i) hikaruSettings.secretSalt.should.match(/[0-9a-f]{64}/i) kubeformSettings.projectId.should.equal('asdf-mycluster') hikaruSettings.projectId.should.equal('asdf-mycluster') }) }) describe('with less defaults', () => { const DEFAULTS = { allowedDomains: ['npme.io', 'google.io'], ...DEFAULT_PROPS, credentials: SERVICE_ACCOUNTS[0].client_email, serviceAccounts: { service_account: SERVICE_ACCOUNTS[1].client_email, applicationCredentials: SERVICE_ACCOUNTS[1].client_email, credentials: SERVICE_ACCOUNTS[0].client_email }, cluster: { ...CLUSTER_DEFAULTS }, tokens: { prop: '{{slug}}-prop' }, common: {} } let processArgv before(() => { processArgv = createReconciler( { async getCommon () { return DEFAULTS }, async getServiceAccount (email) { return SERVICE_ACCOUNTS.find(json => json.client_email === email) } }, { v4 () { return UUID } } ).processArgv }) it('should process argv and get options', async () => { const result = await processArgv({ name: 'mycluster', clusterName: 'mycluster-01', zone: ['us-west2-a'], specification: '.' }) const expectedCommon = { name: 'mycluster', slug: 'mycluster', url: 'mycluster.npme.io', domain: 'npme.io', projectId: 'mycluster', zones: ['us-west2-a'], environment: 'production' } result.should.eql({ specification: '.', kubeformSettings: { ...CLUSTER_DEFAULTS, ...expectedCommon, clusterName: 'mycluster-01' }, hikaruSettings: { ...expectedCommon, prop: 'mycluster-prop', awsZone: expectedCommon.domain, subdomain: expectedCommon.name } }) }) }) describe('with existing cluster data', () => { const DEFAULTS = { allowedDomains: ['npme.io', 'google.io'], ...DEFAULT_PROPS, credentials: SERVICE_ACCOUNTS[0].client_email, applicationCredentials: SERVICE_ACCOUNTS[0].client_email, serviceAccounts: { service_account: SERVICE_ACCOUNTS[1].client_email, credentials: SERVICE_ACCOUNTS[0].client_email, applicationCredentials: SERVICE_ACCOUNTS[0].client_email }, tokens: { ...TOKEN_DEFAULTS }, cluster: { ...CLUSTER_DEFAULTS }, common: {} } const COMMON = { name: 'mycluster', slug: 'mycluster', url: 'mycluster.google.io', domain: 'npme.io', projectId: 'mycluster', environment: 'production', user: 'admin', zones: ['us-central1-a'], password: UUID } const STORED = { specification: '.', cluster: { ...CLUSTER_DEFAULTS, ...COMMON, clusterName: 'mycluster', credentials: SERVICE_ACCOUNTS[0].client_email }, common: COMMON, tokens: { ...TOKEN_DEFAULTS, ...COMMON, awsZone: COMMON.domain, subdomain: COMMON.name }, credentials: SERVICE_ACCOUNTS[0].client_email } let processArgv before(() => { processArgv = createReconciler( { async getCommon () { return DEFAULTS }, async getServiceAccount (email) { return SERVICE_ACCOUNTS.find(json => json.client_email === email) }, async getCluster (name) { name.should.equal('mycluster') return { value: STORED } } }, { v4 () { return UUID } } ).processArgv }) it('should process argv and get options', async () => { const result = await processArgv({ name: 'mycluster', specification: '.' }) result.should.eql({ specification: STORED.specification, kubeformSettings: { ...STORED.cluster, credentials: SERVICE_ACCOUNTS[0], applicationCredentials: SERVICE_ACCOUNTS[0] }, hikaruSettings: { ...STORED.tokens } }) }) }) describe('with existing cluster data in slug mode', () => { const DEFAULTS = { allowedDomains: ['npme.io', 'google.io'], ...DEFAULT_PROPS, credentials: SERVICE_ACCOUNTS[0].client_email, applicationCredentials: SERVICE_ACCOUNTS[0].client_email, serviceAccounts: { service_account: SERVICE_ACCOUNTS[1].client_email, credentials: SERVICE_ACCOUNTS[0].client_email, applicationCredentials: SERVICE_ACCOUNTS[0].client_email }, tokens: { ...TOKEN_DEFAULTS }, cluster: { ...CLUSTER_DEFAULTS }, common: {} } const COMMON = { name: 'mycluster', slug: 'mycluster', url: 'mycluster.google.io', domain: 'npme.io', projectId: 'mycluster', environment: 'production', user: 'admin', password: UUID } const STORED = { specification: '.', cluster: { ...CLUSTER_DEFAULTS, ...COMMON, clusterName: 'mycluster', credentials: SERVICE_ACCOUNTS[0].client_email }, common: COMMON, tokens: { ...TOKEN_DEFAULTS, ...COMMON, awsZone: COMMON.domain, subdomain: COMMON.name }, credentials: SERVICE_ACCOUNTS[0].client_email } let processArgv before(() => { processArgv = createReconciler( { async getCommon () { return DEFAULTS }, async getServiceAccount (email) { return SERVICE_ACCOUNTS.find(json => json.client_email === email) }, async getCluster (name) { name.should.equal('mycluster') return { value: STORED } } }, { v4 () { return UUID } } ).processArgv }) it('should process argv and get options', async () => { const result = await processArgv({ slug: 'mycluster', specification: '.' }) result.should.eql({ specification: STORED.specification, kubeformSettings: { ...STORED.cluster, credentials: SERVICE_ACCOUNTS[0], applicationCredentials: SERVICE_ACCOUNTS[0] }, hikaruSettings: { ...STORED.tokens } }) }) it('should throw if the cluster does not exist', async () => { await processArgv({ slug: 'gone', specification: '.' }).should.be.rejectedWith(Error) }) }) }) describe('_reconcileName', () => { let _reconcileName before(() => { _reconcileName = createReconciler({})._reconcileName }) it('should process an url', () => { const result = _reconcileName({ url: 'mycluster.npme.io' }) result.should.eql({ name: 'mycluster', domain: 'npme.io', url: 'mycluster.npme.io' }) }) it('should balk on bad domains', () => { (() => _reconcileName({ url: 'mycluster.google.com' })).should.throw() }) it('should allow overriding allowed domains', () => { const result = _reconcileName({ url: 'mycluster.google.io' }, ['google.io']) result.should.eql({ name: 'mycluster', domain: 'google.io', url: 'mycluster.google.io' }) }) it('should balk on bad domains (overridden)', () => { (() => _reconcileName({ url: 'mycluster.npme.io' }, ['google.io'])).should.throw() }) it('should work with just a name', () => { const result = _reconcileName({ name: 'mycluster' }, ['google.io']) result.should.eql({ name: 'mycluster', domain: 'google.io', url: 'mycluster.google.io' }) }) it('should work with a name and domain', () => { const result = _reconcileName({ name: 'mycluster', domain: 'npme.io' }, ['google.io', 'npme.io']) result.should.eql({ name: 'mycluster', domain: 'npme.io', url: 'mycluster.npme.io' }) }) it('should allow a name unrelated to the domain', () => { const result = _reconcileName({ name: 'mycluster', url: 'asdfasdf.npme.io' }) result.should.eql({ name: 'mycluster', domain: 'npme.io', url: 'asdfasdf.npme.io' }) }) it('should throw if nothing relevant is passed', () => { (() => _reconcileName({ foo: 'bar' })).should.throw() }) }) describe('_yargsOverrides', () => { let _yargsOverrides before(() => { _yargsOverrides = createReconciler({})._yargsOverrides }) it('should override deep properties', async () => { const input = { common: { a: 1, b: 1, c: { d: 1 } }, tokens: { e: 1 }, cluster: { f: 1, g: { h: 1 } } } const argv = { 'arg-a': 'a', 'arg-tokens.e': 2, 'arg-cluster.g.h': 2 } const result = _yargsOverrides(input, argv) result.should.eql({ common: { a: 'a', b: 1, c: { d: 1 } }, tokens: { e: 2 }, cluster: { f: 1, g: { h: 2 } } }) }) it('should override complete objects and arrays', async () => { const input = { common: { a: 1 } } const argv = { 'arg-a': '["a"]' } const result = _yargsOverrides(input, argv) result.should.eql({ common: { a: [ 'a' ] } }) }) it('should ignore non-existend objects', async () => { const result = _yargsOverrides({}, { 'arg-foo.bar': 1 }) result.should.eql({}) }) }) describe('_reifyServiceAccounts', () => { let _reifyServiceAccounts before(() => { _reifyServiceAccounts = createReconciler({ async getServiceAccount (email) { return SERVICE_ACCOUNTS.find(json => json.client_email === email) } })._reifyServiceAccounts }) it('should turn service accounts into full jsons and insert them into the options', async () => { const result = await _reifyServiceAccounts({ serviceAccounts: { my_sa: SERVICE_ACCOUNTS[0].client_email, auth: SERVICE_ACCOUNTS[1].client_email }, other: 'bar', common: { auth: SERVICE_ACCOUNTS[1].client_email }, auth: SERVICE_ACCOUNTS[1].client_email, tokens: { my_sa: SERVICE_ACCOUNTS[0].client_email, other: 'foo' } }) result.should.eql({ other: 'bar', auth: JSON.stringify(SERVICE_ACCOUNTS[1]), common: { auth: JSON.stringify(SERVICE_ACCOUNTS[1]) }, tokens: { my_sa: JSON.stringify(SERVICE_ACCOUNTS[0]), other: 'foo' } }) }) it('should noop if no serviceAccounts in tokens', async () => { const result = await _reifyServiceAccounts({ foo: 'bar', serviceAccounts: { my_sa: 'my-sa@iam.google.com' } }) result.should.eql({ foo: 'bar' }) }) it('should noop if no serviceAccounts', async () => { const result = await _reifyServiceAccounts({ foo: 'bar' }) result.should.eql({ foo: 'bar' }) }) }) describe('storeResult', () => { describe('with a bunch of info', () => { let storeResult const serviceAccountsCalls = [] const registerClusterCalls = [] before(() => { storeResult = createReconciler( { async addServiceAccount (sa) { serviceAccountsCalls.push(sa) }, async registerCluster (...args) { registerClusterCalls.push(args) } } ).storeResult }) it('should work', async () => { const common = { ...DEFAULT_PROPS, projectId: 'myproject', user: 'admin', password: 'hunter2', slug: 'newcluster', name: 'newcluster', url: 'newcluster.npme.io', environment: 'production' } await storeResult({ cluster: { ...CLUSTER_DEFAULTS, ...common, auth: JSON.stringify(SERVICE_ACCOUNTS[0]) }, tokens: { credentials: SERVICE_ACCOUNTS[1], ...TOKEN_DEFAULTS, ...common }, specification: '.' }) serviceAccountsCalls.should.eql([SERVICE_ACCOUNTS[0], SERVICE_ACCOUNTS[1]]) registerClusterCalls.should.eql([ [ 'newcluster', 'production', { serviceAccounts: { auth: SERVICE_ACCOUNTS[0].client_email, credentials: SERVICE_ACCOUNTS[1].client_email }, spec: '.', environment: 'production', common, cluster: { auth: SERVICE_ACCOUNTS[0].client_email, ...CLUSTER_DEFAULTS, ...common }, tokens: { ...TOKEN_DEFAULTS, ...common, credentials: SERVICE_ACCOUNTS[1].client_email } }, ['production'] ] ]) }) }) }) })