Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@basaldev/nodeblocks-cloud-sdk

Package Overview
Dependencies
Maintainers
8
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@basaldev/nodeblocks-cloud-sdk - npm Package Compare versions

Comparing version
0.2.5
to
0.3.0
+160
lib/resource/apis.js
const fetch = require('node-fetch');
const _ = require('lodash');
function getAuthHeaders(makeSession, fingerprint) {
const { accessToken } = makeSession();
return {
Authorization: `Bearer ${accessToken}`,
'x-nb-fingerprint': fingerprint
};
}
function createClient(endpoint, makeBaseHeaders) {
return async (path, { method, headers, body }) => {
// console.log(`Requesting: ${method} ${path}`, { headers, body }, makeBaseHeaders?.());
const res = await fetch(`${endpoint}${path}`, {
method,
headers: {
...makeBaseHeaders?.(),
...headers
},
body
});
const data = await res.json();
// console.log(`Response: ${res.status} ${res.statusText}`, data);
if (!res.ok) {
throw new Error(data.message);
}
return data;
}
}
function createPost(makeClient) {
const client = makeClient();
return async (path, body) => await client(path, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
}
function createPatch(makeClient) {
const client = makeClient();
return async (path, body) => await client(path, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
}
function createGet(makeClient) {
const client = makeClient();
return async (path) => await client(path, {
method: 'GET',
});
}
function createLogin(makePost) {
const post = makePost();
return async (email, password, fingerprint) => await post('/login', { email, password, fingerprint });
}
function templateProjectPath(projectId) {
const id = projectId ? `/${projectId}` : '';
return `/projects${id}`;
}
function createPostProject(makePost) {
const post = makePost();
const path = templateProjectPath();
return async (values) => await post(path, values);
}
function createPatchProject(makePatch, projectId) {
const patch = makePatch();
const path = templateProjectPath(projectId);
return async (values) => await patch(path, values);
}
function createGetProject(makeGet, projectId) {
const get = makeGet();
const path = templateProjectPath(projectId);
return async () => await get(path);
}
function templateServicePath(projectId, serviceId) {
const projectPath = templateProjectPath(projectId);
const id = serviceId ? `/${serviceId}` : '';
return `${projectPath}/services${id}`;
}
function createPostService(makePost, projectId) {
const post = makePost();
const path = templateServicePath(projectId);
return async (type, values) => await post(`${path}/${type}`, values);
}
function createPatchService(makePatch, projectId, serviceId) {
const patch = makePatch();
const path = templateServicePath(projectId, serviceId);
return async (values) => await patch(path, values);
}
function createGetService(makeGet, projectId, serviceId) {
const get = makeGet();
const path = templateServicePath(projectId, serviceId);
return async () => await get(path);
}
function createApi({ endpoints, auth }) {
const fingerprint = auth.fingerprint;
const user = auth.user;
const session = {
userId: '',
accessToken: ''
};
const getSession = () => session;
const authHeaders = () => getAuthHeaders(getSession, fingerprint);
// auth client
const authClient = () => createClient(endpoints.auth);
const authPost = () => createPost(authClient);
const login = () => createLogin(authPost)(user.email, user.password, fingerprint)
.then((res) => _.assign(session, res));
// backend client
const backendClient = () => createClient(endpoints.backend, authHeaders);
const backendPost = () => createPost(backendClient);
const backendPatch = () => createPatch(backendClient);
const backendGet = () => createGet(backendClient);
// projects
const postProject = () => createPostProject(backendPost);
const patchProject = projectId => createPatchProject(backendPatch, projectId);
const getProject = projectId => createGetProject(backendGet, projectId);
// services
const postService = projectId => createPostService(backendPost, projectId);
const patchService = (projectId, serviceId) => createPatchService(backendPatch, projectId, serviceId);
const getService = (projectId, serviceId) => createGetService(backendGet, projectId, serviceId);
return {
login,
projects: {
create: postProject(),
id: projectId => ({
update: patchProject(projectId),
get: getProject(projectId),
services: {
create: postService(projectId),
id: serviceId => ({
update: patchService(projectId, serviceId),
get: getService(projectId, serviceId),
}),
},
})
},
};
}
exports.createApi = createApi;
const _ = require('lodash');
const utils = require('./utils');
const apis = require('./apis');
/**
* Apply command
*/
async function resourceApply(options) {
console.log('🚧 Applying resources...');
// Read template and state
const template = utils.readJSON(options.template);
const state = utils.readJSON(options.state);
const profile = utils.readJSON(options.profile);
state.env = template.env ?? {};
state.resources = state.resources ?? {};
state.resources.infrastructures = state.resources.infrastructures ?? {};
state.resources.projects = state.resources.projects ?? {};
state.resources.services = state.resources.services ?? {};
// Setup apis
const api = apis.createApi(profile.settings);
// get session
await getSession(api);
// TODO: Apply infrastructures
// Apply projects
state.resources.projects = await applyProjects(api, template, state);
// Apply services
state.resources.services = await applyServices(api, template, state);
// Write state
utils.writeJSON(options.state, state);
}
function parseTemplate(template, state) {
const templateString = JSON.stringify(template);
const parsedTemplateString = templateString.replace(/{{\s*?([^\s]*?)\s*?}}/g, (_match, key) => {
const value = _.get(state, key);
if (!value) {
// console.log(`Key ${key} is not found in the state`);
return '';
}
return value;
});
return JSON.parse(parsedTemplateString);
}
async function getSession(api) {
console.log('Getting session...');
return await api.login();
}
function getCreateProjectFieldMask() {
return [
'name',
'infrastructureId',
'budgetAlertEmails',
'budgetAmount',
'enableExternalIpAddress'
];
}
function getUpdateProjectFieldMask() {
return [
'name',
];
}
async function createProject(api, value) {
const mask = getCreateProjectFieldMask();
const data = _.pick(value, mask);
const body = _.omitBy(data, _.isUndefined);
return await api.projects.create(body);
}
async function updateProject(api, projectId, value) {
const mask = getUpdateProjectFieldMask();
const data = _.pick(value, mask);
const body = _.omitBy(data, _.isUndefined);
return await api.projects
.id(projectId)
.update(body);
}
async function applyProjects(api, template, state) {
const parsed = parseTemplate(template, state);
const results = {};
const create = async (key, value) => {
console.log(`- Creating project ${key}...`);
return await createProject(api, value);
}
const get = async (projectId) => {
console.log(`- Getting project:${projectId}...`);
return await api.projects.id(projectId).get();
}
const isDrifted = (projectInState, projectInDb) => {
const mask = ['budgetAmount', 'status', 'createdAt', 'updatedAt'];
return !_.isEqual(
_.omit(projectInState, mask),
_.omit(projectInDb, mask)
);
}
const hasUpdate = (projectInTemplate, projectInState) => {
const updateFieldMask = getUpdateProjectFieldMask();
return !_.isEqual(
_.pick(projectInTemplate, updateFieldMask),
_.pick(projectInState, updateFieldMask)
);
}
for (const [key, projectInTemplate] of Object.entries(parsed.resources.projects)) {
console.log(`Apply project:${key}...`);
const projectInState = state.resources.projects[key];
const projectId = projectInState?.id
if (!projectId) {
const res = await create(key, projectInTemplate);
results[key] = res;
continue;
}
const projectInDb = await get(projectId);
if (!projectInDb) {
throw new Error(`Project:${key} is not found in DB!`);
}
const drifted = isDrifted(projectInState, projectInDb);
if (drifted) {
console.log('# in state:', projectInState);
console.log('# in db:', projectInDb);
throw new Error(`Project:${key} is drifted!`);
}
const noUpdates = !hasUpdate(projectInTemplate, projectInState);
if (noUpdates) {
console.log(`- Skipping project:${projectId} as it is up to date...`);
results[key] = projectInState;
continue;
}
console.log(`- Updating project:${projectId} as it is changed...`);
const res = await updateProject(api, projectId, projectInTemplate);
results[key] = _.merge(projectInState, res);
}
return results;
}
function getCreateCustomServiceFieldMask() {
return [
'name',
'serviceRepo',
'serviceRepoBranch'
];
}
function getCreatePredefinedServiceFieldMask() {
return [
'name',
'serviceVersion',
];
}
function getUpdateServiceFieldMask() {
return [
'adapterRepo',
'adapterRepoBranch',
'envVariables',
'healthCheckPath',
'instance',
'selectedAdapterName',
'serviceRepo',
'serviceRepoBranch',
];
}
function isCustomService(value) {
return value.type === 'custom';
}
async function createService(api, value) {
const projectId = value.projectId;
const type = value.type;
const mask = isCustomService(value)
? getCreateCustomServiceFieldMask()
: getCreatePredefinedServiceFieldMask();
const data = _.pick(value, mask);
const body = _.omitBy(data, _.isUndefined);
return await api.projects
.id(projectId)
.services
.create(type, body);
}
async function updateService(api, serviceId, value) {
const projectId = value.projectId;
const mask = getUpdateServiceFieldMask();
const data = _.pick(value, mask);
const body = _.omitBy(data, _.isUndefined);
/**
* workaround for the constraint of max props = 10 in envVariables
* make a chunk of 10 props and update them one by one
*/
if (body.envVariables) {
const chunkedKeys = _.chunk(Object.keys(body.envVariables), 10);
for (const keys of chunkedKeys) {
console.log(` - Updating chunked envVariables...`);
await api.projects
.id(projectId)
.services
.id(serviceId)
.update({ envVariables: _.pick(body.envVariables, keys) });
}
delete body.envVariables;
}
return await api.projects
.id(projectId)
.services
.id(serviceId)
.update(body);
}
async function applyServices(api, template, state) {
const parsed = parseTemplate(template, state);
const results = {};
const create = async (key, value) => {
console.log(`- Creating service ${key}...`);
return await createService(api, value);
}
const get = async (projectId, serviceId) => {
console.log(`- Getting service:${serviceId}...`);
return await api.projects.id(projectId).services.id(serviceId).get();
}
const isDrifted = (serviceInState, serviceInDb) => {
const mask = ['serviceRepoDeployKey', 'lastDeployment', 'envVariables', 'status', 'createdAt', 'updatedAt'];
return !_.isEqual(
_.omit(serviceInState, mask),
_.omit(serviceInDb, mask)
);
}
const hasUpdates = (serviceInTemplate, serviceInState) => {
const updateFieldMask = getUpdateServiceFieldMask();
return !_.isEqual(
_.pick(serviceInTemplate, updateFieldMask),
_.pick(serviceInState, updateFieldMask)
);
}
for (const [key, serviceInTemplate] of Object.entries(parsed.resources.services)) {
console.log(`Apply service:${key}...`);
const serviceInState = state.resources.services[key];
const serviceId = serviceInState?.id
if (!serviceId) {
const created = await create(key, serviceInTemplate);
const updated = await updateService(api, created.id, serviceInTemplate);
results[key] = _.merge(created, updated);
continue;
}
const projectId = serviceInTemplate.projectId;
const serviceInDb = await get(projectId, serviceId);
if (!serviceInDb) {
throw new Error(`Service:${key} is not found in DB!`);
}
const drifted = isDrifted(serviceInState, serviceInDb);
if (drifted) {
console.log('# in state:', serviceInState);
console.log('# in db:', serviceInDb);
throw new Error(`Service:${key} is drifted!`);
}
const noUpdates = !hasUpdates(serviceInTemplate, serviceInState);
if (noUpdates) {
console.log(`- Skipping service:${key} as it is up to date...`);
results[key] = serviceInState;
continue;
}
console.log(`- Updating service:${serviceId}...`);
const res = await updateService(api, serviceId, serviceInTemplate);
results[key] = _.merge(serviceInState, res);
}
return results;
}
exports.resourceApply = resourceApply;
const fs = require('fs');
function readJSON(path) {
return fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, 'utf8')) : {};
}
function writeJSON(path, data) {
return fs.writeFileSync(path, JSON.stringify(data, null, 2));
}
exports.readJSON = readJSON;
exports.writeJSON = writeJSON;
+10
-0

@@ -10,2 +10,3 @@ #! /usr/bin/env node

const dotenv = require('dotenv');
const { resourceApply } = require('./resource');

@@ -32,2 +33,4 @@ const packageDir = path.join(__dirname, '..');

.description('Manage the custom adapter');
const resourceCommand = program.command('resource')
.description('Manage the NBC resource');

@@ -43,2 +46,9 @@ adapterCommand.command('start')

.action(adapterDev);
resourceCommand.command('apply')
.description('Apply the resource')
.requiredOption('-t --template <value>', 'The path to the template file')
.requiredOption('-s --state <value>', 'The path to the state file')
.requiredOption('-p --profile <value>', 'The path to the profile file')
.action(resourceApply);
await program.parseAsync(process.argv);

@@ -45,0 +55,0 @@ }

+5
-2
{
"name": "@basaldev/nodeblocks-cloud-sdk",
"version": "0.2.5",
"version": "0.3.0",
"description": "Nodeblocks cloud SDK",

@@ -12,5 +12,6 @@ "main": "./lib/index.js",

"adapter:dev": "node ./lib/index.js adapter dev",
"resource:apply": "node ./lib/index.js resource apply",
"templates:fetch": "node ./lib/clone.js",
"prepack": "zip -r assets.zip assets -x \"**/node_modules/*\"",
"postinstall": "unzip -o assets.zip"
"postinstall": "[ -f \"assets.zip\" ] && unzip -o assets.zip || echo \"skipping unzip assets.zip\""
},

@@ -27,2 +28,4 @@ "files": [

"dotenv": "^16.4.5",
"lodash": "^4.17.21",
"node-fetch": "^2.7.0",
"prompts": "^2.4.2",

@@ -29,0 +32,0 @@ "zx": "^8.1.0"

Sorry, the diff of this file is not supported yet