@containernerds/incus-client
Version:
A Node.js client for automating Incus (LXD) servers.
176 lines (160 loc) • 5.83 kB
text/typescript
import axios, { AxiosInstance } from 'axios';
import https from 'https';
import fs from 'fs';
import {
InstanceSource,
InstanceSpec,
defaultInstanceSource,
InstanceRunningState
} from '../types';
import { asyncPoll } from '../utils';
export const createInstanceSource = (overrides: InstanceSource): InstanceSource => {
return {
...defaultInstanceSource,
...overrides,
};
};
export const createInstanceSpec = (overrides: InstanceSpec): InstanceSpec => {
return {
architecture: 'x86_64',
config: {},
devices: {},
ephemeral: false,
instance_type: 't1.micro',
profiles: ['default'],
restore: '',
source: createInstanceSource({ alias: 'ubuntu/22.04' }),
start: true,
stateful: false,
type: 'container',
...overrides,
};
};
export class InstancesAPI {
private client: AxiosInstance;
private defaultInstanceSource: InstanceSource;
private project: string;
constructor(baseURL: string, httpsAgent: https.Agent, project: string) {
this.client = axios.create({ baseURL, httpsAgent});
this.defaultInstanceSource = {
allow_inconsistent: false,
instance_only: false,
live: false,
mode: 'pull',
protocol: 'simplestreams',
server: 'https://images.linuxcontainers.org',
type: 'image',
};
this.project = project;
};
async listInstances() {
const response = await this.client.get(`/1.0/instances?project=${this.project}`);
return response.data;
};
async createInstance(instanceSpec: InstanceSpec) {
let instanceSource: InstanceSource = {
...this.defaultInstanceSource,
};
const response = await this.client.post(`/1.0/instances?project=${this.project}`, {
...this.defaultInstanceSource,
...instanceSpec
});
return response.data;
};
// TODO: NEED TO TEST THIS, NOT SURE IF IT WORKS
async createInstanceFromBackup(backupFile: string) {
const backup = fs.readFileSync(backupFile);
const response = await this.client.post(`/1.0/instances?project=${this.project}`, {
backup,
});
return response.data;
};
/**
* Gets a specific instance (basic struct).
*
* @param {string} instanceName
* @returns {Promise<any>} A promise that resolves with the response data.
* @throws {Error} If the request fails.
*/
async getInstance(instanceName: string) {
const response = await this.client.get(`/1.0/instances/${instanceName}?project=${this.project}`);
return response.data;
};
/**
* Deletes an instance by its name.
* This also deletes anything owned by the instance such as snapshots and backups.
*
* @param {string} instanceName - The name of the instance to delete.
* @returns {Promise<any>} A promise that resolves with the response data.
* @throws {Error} If the request fails.
*/
async deleteInstance(instanceName: string, force: boolean = false): Promise<any> {
if (force) {
await this.changeInstanceState(instanceName, InstanceRunningState.STOP, force);
};
const response = await this.client.delete(`/1.0/instances/${instanceName}?project=${this.project}`);
return response.data;
};
/**
* Gets the state of an instance.
*
* @param {string} instanceName - The name of the instance to get the state of.
* @returns {Promise<any>} A promise that resolves with the response data.
* @throws {Error} If the request fails.
*/
async getInstanceState(instanceName: string) {
const response = await this.client.get(`/1.0/instances/${instanceName}/state?project=${this.project}`);
return response.data;
};
/**
* Changes the state of an instance.
*
* @param {string} instanceName - The name of the instance to change the state of.
* @param {string} state - The state to change the instance to.
* @param {boolean} force - Whether to force the state change.
* @param {boolean} stateful - Whether to perform a stateful change.
* @param {number} timeout - The time to wait for the state change to complete.
* @returns {Promise<any>} A promise that resolves with the response data.
* @throws {Error} If the request fails.
*/
async changeInstanceState(
instanceName: string,
state: string,
force: boolean = false,
stateful: boolean = false,
timeout: number = 30
): Promise<any> {
const response = await this.client.put(`/1.0/instances/${instanceName}/state?project=${this.project}`, {
action: state,
force,
stateful,
timeout,
});
// stop:Stopped
// start:Running
// restart:Running
// TODO support freeze, unfreeze, migrate, snapshot, restore, delete
const linked: any = {
'stop': 'Stopped',
'start': 'Running',
'restart': 'Running',
}
if (response.status === 202) {
const pollForStatus = await asyncPoll(() => {
const res = this.getInstanceState(instanceName).then((data) => {
if (data.metadata.status === linked[state]) {
return true;
} else {
return false;
};
}).catch((error) => {
return false;
});
return res;
}, 5, 5000);
return pollForStatus;
} else {
return response.data;
}
};
};