const PortainerClient = require('portainer-api-client') const fs = require('fs') const figlet = require('figlet') const TYPES = { swarm: 1, compose: 2 } const createClient = (config) => { config = Object.assign({ url: null, username: null, password: null }, config) const log = config.log || console.log const warning = `# ${figlet.textSync('WARNING!', 'Colossal').split('\n').join('\n# ')}\n# This stack was deployed automatically. Any changes made here may be overwritten.\n#\n\n` const client = new PortainerClient(config.url, config.username, config.password); const call = async (method, path, data) => { if (config.verbose) log(method, path, data ? JSON.stringify(data, null, 4) : '') const result = await client.callApiWithKey(method, path, data) if (config.verbose) log(JSON.stringify(result, null, 4)) return result } const getEndpointSwarm = async (endpointId) => await call('get', `/api/endpoints/${encodeURIComponent(endpointId)}/docker/swarm`) const getEndpoints = async () => await call('get', '/api/endpoints') const getSwarms = async () => await call('get', '/api/swarms') const getStacks = async () => await call('get', '/api/stacks') const deleteStack = async (opts) => { opts = Object.assign({ name: null, id: null, endpointId: null }, opts) if (!opts.id) { if (!opts.name) { throw new Error('Either name or id required') } const stacks = await getStacks() const stack = stacks.find(x => x.Name === opts.name) if (!stack) throw new Error(`Stack ${opts.name} not found.`) opts.id = stack.Id } if (!opts.endpointId) { const endpoints = await getEndpoints() if (!endpoints || !endpoints.length) throw new Error('Could not find any endpoints') opts.endpointId = endpoints[0].Id } console.log(`Deleting stack ${opts.id}`) return call('delete', `/api/stacks/${encodeURIComponent(opts.id)}`) } const upsertStack = async (opts) => { opts = Object.assign({ name: null, stackFile: null, stackFileContent: null, swarmId: null, endpointId: null, type: 'swarm', method: 'string', prune: true, warning: true }, opts) if (!opts.name) throw new Error('name required') if (!opts.stackFileContent) { if (!opts.stackFile) { throw new Error('stackFile or stackFileContent required') } opts.stackFileContent = fs.readFileSync(opts.stackFile, 'utf-8') } const type = TYPES[opts.type] || opts.type if (!opts.endpointId) { const endpoints = await getEndpoints() if (!endpoints || !endpoints.length) throw new Error('Could not find any endpoints') opts.endpointId = endpoints[0].Id } if (!opts.swarmId) { const swarm = await getEndpointSwarm(opts.endpointId) opts.swarmId = swarm.ID } const stacks = await getStacks() const stack = stacks.find(x => x.Name === opts.name) if (stack) { log(`Found existing stack ${opts.name}. Updating...`) return await call('PUT', `/api/stacks/${encodeURIComponent(stack.Id)}?method=${encodeURIComponent(opts.method)}&type=${encodeURIComponent(type)}&endpointId=${encodeURIComponent(opts.endpointId)}`, { Name: opts.name, StackFileContent: (opts.warning ? warning : '') + opts.stackFileContent, SwarmID: opts.swarmId, Prune: opts.prune }) } else { log(`New stack ${opts.name}`) const stack = await call('POST', `/api/stacks?method=${encodeURIComponent(opts.method)}&type=${encodeURIComponent(type)}&endpointId=${encodeURIComponent(opts.endpointId)}`, { Name: opts.name, StackFileContent: (opts.warning ? warning : '') + opts.stackFileContent, SwarmID: opts.swarmId }) await call('POST', '/api/resource_controls', { Type: 'stack', Public: true, ResourceID: opts.name, Users: [], Teams: [], SubResourceIDs: [] }) return stack } } return { getEndpoints, getSwarms, getStacks, upsertStack, deleteStack } } module.exports = { createClient }