UNPKG

node-snapd

Version:

nodejs client interface to ubuntu core snapd api

548 lines (447 loc) 14.5 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>index.js - Documentation</title> <script src="scripts/prettify/prettify.js"></script> <script src="scripts/prettify/lang-css.js"></script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc.css"> <script src="scripts/nav.js" defer></script> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger" /> <label for="nav-trigger" class="navicon-button x"> <div class="navicon"></div> </label> <label for="nav-trigger" class="overlay"></label> <nav > <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="SnapClient.html">SnapClient</a><ul class='methods'><li data-type='method'><a href="SnapClient.html#abort">abort</a></li><li data-type='method'><a href="SnapClient.html#connect">connect</a></li><li data-type='method'><a href="SnapClient.html#disable">disable</a></li><li data-type='method'><a href="SnapClient.html#disconnect">disconnect</a></li><li data-type='method'><a href="SnapClient.html#enable">enable</a></li><li data-type='method'><a href="SnapClient.html#info">info</a></li><li data-type='method'><a href="SnapClient.html#install">install</a></li><li data-type='method'><a href="SnapClient.html#listInterfaces">listInterfaces</a></li><li data-type='method'><a href="SnapClient.html#listSnaps">listSnaps</a></li><li data-type='method'><a href="SnapClient.html#login">login</a></li><li data-type='method'><a href="SnapClient.html#logout">logout</a></li><li data-type='method'><a href="SnapClient.html#modify">modify</a></li><li data-type='method'><a href="SnapClient.html#modifyInterface">modifyInterface</a></li><li data-type='method'><a href="SnapClient.html#readAuth">readAuth</a></li><li data-type='method'><a href="SnapClient.html#refresh">refresh</a></li><li data-type='method'><a href="SnapClient.html#remove">remove</a></li><li data-type='method'><a href="SnapClient.html#rest">rest</a></li><li data-type='method'><a href="SnapClient.html#revert">revert</a></li><li data-type='method'><a href="SnapClient.html#status">status</a></li><li data-type='method'><a href="SnapClient.html#switch">switch</a></li></ul></li><li><a href="SnapdError.html">SnapdError</a></li></ul> </nav> <div id="main"> <h1 class="page-title">index.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>'use strict' const http = require('http') const path = require('path') const fs = require('fs') const os = require('os') const reach = require('./reach') /** * Represents a snapd formatter error * @class * @param {object} body */ class SnapdError extends Error { constructor(body){ super(reach(body, 'result.message') || 'something went wrong') Error.captureStackTrace(this, SnapdError) this.code = reach(body, 'result.kind') } } /** * A snap client instance which makes restful call to snapd process using Unix sockets * @class * @param {string} authFile Defaults to `$HOME/.snap/auth.json` * @param {string} socketPath Defaults to `/run/snapd.socket` */ class SnapClient { constructor( authFile=path.join(os.homedir(), '.snap', 'auth.json'), socketPath=`/run/snapd.socket` ){ this.auth = undefined this.socketPath = socketPath } /** * make restful call to snapd process thru /run/snapd.socket * @method * @param {object} options * @param {object} options.auth * @param {string} options.method * @param {string} options.path * @param {string} options.data */ async rest({auth, method, path, data}) { return new Promise((resolve, reject) => { const post = method === 'POST' const headers = { 'Content-Type': 'application/json' } if (post) { headers['Content-Length'] = Buffer.byteLength(data) } if (auth &amp;&amp; typeof auth.macaroon === 'string') { headers['Authorization'] = `Macaroon root="${auth.macaroon}"` } const options = { socketPath: this.socketPath, path: path, method: post ? 'POST' : 'GET', headers: headers } const req = http.request(options, (res) => { res.setEncoding('utf8') let body = '' res.on('data', (chunk) => { body += chunk }) res.on('end', () => { if (res.statusCode === 200 || res.statusCode === 202) { if (body.length &lt; 1) { return reject(new Error('empty response')) } const json = JSON.parse(body) return resolve(json) } const json = JSON.parse(body) return reject(new SnapdError(json)) }) }) req.on('error', (error) => { return reject(error) }) // write data to request body if (post &amp;&amp; typeof data === 'string') { req.write(data) } req.end() }) } /** * read auth email &amp; macaroon from ~/.snap/auth.json * @method * @param {string} filename */ async readAuth(filename) { if(this.auth){ return Promise.resolve({ email: this.auth.email, macaroon: this.auth.macaroon }) } return new Promise((resolve, reject) => { const fn = filename || path.join(os.homedir(), '.snap', 'auth.json') fs.readFile(fn, (error, data) => { if (error) { return reject(error) } this.auth = JSON.parse(data) if (typeof this.auth.macaroon !== 'string') { return reject(new Error('failed to read macaroon from auth file')) } return resolve({ email: this.auth.email, macaroon: this.auth.macaroon }) }) }) } /** * login may require root privilege * @method * @param {object} options * @param {object} options.auth * @param {string} options.method * @param {string} options.path * @param {string} options.data */ async login({ email, password, otp }) { const data = { email: email, password: password, otp: otp // one time passkey for 2fa } const response = await this.rest({ method: 'POST', path: '/v2/login', data: JSON.stringify(data) }) if (response &amp;&amp; response['status-code'] === 200) { return { email: response.result.email, macaroon: response.result.macaroon } } return Promise.reject(new Error('malformed response')) } /** * logout may require root privilege * @method * @param {object} options * @param {object} options.auth */ async logout({ auth }) { const response = await this.rest({ auth: auth || await this.readAuth(), method: 'POST', path: '/v2/logout' }) if (response &amp;&amp; response['status-code'] === 200) { return true } return Promise.reject(new Error('malformed response')) } /** * list of installed snaps * @method */ async listSnaps() { const response = await this.rest({ method: 'GET', path: '/v2/snaps' }) if (response &amp;&amp; response['status-code'] === 200) { return response.result.map(entry => entry.name) } throw new Error('malformed response') } /** * Get detailed info about a snap * @method * @param {object} options * @param {object} options.name */ async info({ name }) { if (typeof name !== 'string') { return Promise.reject(new Error('malformed name argument')) } const response = await this.rest({ method: 'GET', path: `/v2/snaps/${name}` }) if (response &amp;&amp; response['status-code'] === 200) { return response.result } return Promise.reject(new Error('malformed response')) } /** * * @method * @param {object} options * @param {string} options.action * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async modify({ action, name, auth, ...opts }) { if (typeof name !== 'string') { return Promise.reject(new Error('malformed name argument')) } const data = { action: action } if (opts) { for (const b of ['classic', 'devmode', 'ignore-validation', 'jailmode']) { if (opts[b]) { data[b] = true } } for (const str of ['channel', 'version']) { if (typeof opts[str] === 'string') { data[str] = opts[str] } } } const response = await this.rest({ auth: auth || await this.readAuth(), method: 'POST', path: `/v2/snaps/${name}`, data: JSON.stringify(data) }) if (response &amp;&amp; response['status-code'] === 202) { return response.change } return Promise.reject(new Error('malformed response')) } /** * * @method * @param {object} options * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async install ({ name, auth, ...opts }) { return this.modify({ action: 'install', name, auth, ...opts }) } /** * * @method * @param {object} options * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async remove ({ name, auth, ...opts }) { return this.modify({ action: 'remove', name, auth, ...opts }) } /** * * @method * @param {object} options * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async switch ({ name, auth, ...opts }) { return this.modify({ action: 'switch', name, auth, ...opts }) } /** * * @method * @param {object} options * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async refresh ({ name, auth, ...opts }) { return this.modify({ action: 'refresh', name, auth, ...opts }) } /** * * @method * @param {object} options * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async revert ({ name, auth, ...opts }) { return this.modify({ action: 'revert', name, auth, ...opts }) } /** * * @method * @param {object} options * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async enable ({ name, auth, ...opts }) { return this.modify({ action: 'enable', name, auth, ...opts }) } /** * * @method * @param {object} options * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async disable ({ name, auth, ...opts }) { return this.modify({ action: 'disable', name, auth, ...opts }) } /** * check on status of change by id (or all changes without id arg) * * @method * @param {object} options * @param {string} options.id */ async status ({ id }) { const response = await this.rest({ method: 'GET', path: id ? `/v2/changes/${id}` : '/v2/changes' }) if (response &amp;&amp; response['status-code'] === 200) { return response.result } return Promise.reject(new Error('malformed response')) } /** * abort ongoing change by id * * @method * @param {object} options * @param {string} options.id * @param {object} options.auth */ async abort ({ id, auth }) { if (typeof id !== 'string') { return Promise.reject(new Error('malformed id argument')) } const response = await this.rest({ auth: auth || await this.readAuth(), method: 'POST', path: `/v2/changes/${id}`, data: JSON.stringify({ action: 'abort' }) }) if (response &amp;&amp; response['status-code'] === 200) { return response.result } return Promise.reject(new Error('malformed response')) } /** * * @method * @param {object} options * @param {object} options.auth */ async listInterfaces ({ auth }={}) { const response = await this.rest({ auth: auth || await this.readAuth(), method: 'GET', path: '/v2/interfaces' }) if (response &amp;&amp; response['status-code'] === 200) { return response.result } return Promise.reject(new Error('malformed response')) } /** * * @method * @param {object} options * @param {string} options.action * @param {object} options.slot * @param {object} options.plug * @param {object} options.auth */ async modifyInterface ({ action, slot, plug, auth }) { const data = { action: action, slots: [{ snap: slot.snap, slot: slot.slot }], plugs: [{ snap: plug.snap, plug: plug.plug }] } const response = await this.rest({ auth: auth || await this.readAuth(), method: 'POST', path: '/v2/interfaces', data: JSON.stringify(data) }) if (response &amp;&amp; response['status-code'] === 202) { return response.change } return Promise.reject(new Error('malformed response')) } /** * * @method * @param {object} options * @param {object} options.slot * @param {object} options.plug * @param {object} options.auth */ async connect ({ slot, plug, auth }) { return this.modifyInterface({ action: 'connect', slot, plug, auth }) } /** * * @method * @param {object} options * @param {string} options.name * @param {object} options.auth * @param {object} options.opts */ async disconnect ({ slot, plug, auth }) { return this.modifyInterface({ action: 'disconnect', slot, plug, auth }) } } module.exports = SnapClient</code></pre> </article> </section> </div> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.3</a> on Wed Jul 17 2019 01:12:54 GMT-0700 (Pacific Daylight Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme. </footer> <script>prettyPrint();</script> <script src="scripts/polyfill.js"></script> <script src="scripts/linenumber.js"></script> </body> </html>