From bed6fd2b6a419a6d9de240e28a49633cb891eff7 Mon Sep 17 00:00:00 2001 From: dariusz gorzeba Date: Tue, 7 Jan 2025 22:23:37 +0100 Subject: [PATCH] env-add-with-partner-portal-url --- Jenkinsfile | 1 + bin/pos-cli-deploy.js | 1 + bin/pos-cli-env-add.js | 70 +++---------------- bin/pos-cli-env-refresh-token.js | 4 +- lib/env-add/main.js | 61 ++++++++++++++++ lib/environments.js | 8 ++- lib/modules.js | 12 ++-- lib/portal.js | 30 ++++---- lib/presignUrl.js | 13 ++-- lib/settings.js | 6 +- test/env-add.test.js | 63 ++++++++++++----- .../modules_update/app/pos-modules.json | 4 +- .../modules_update/app/pos-modules.lock.json | 4 +- test/lib/commands.test.js | 4 +- test/logsv2s.test.js | 1 - 15 files changed, 162 insertions(+), 120 deletions(-) create mode 100644 lib/env-add/main.js diff --git a/Jenkinsfile b/Jenkinsfile index 295c2b62..3e5d2dd9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,6 +8,7 @@ pipeline { MPKIT_EMAIL = "darek+ci@near-me.com" MPKIT_URL = "https://qa-17263.staging.oregon.platform-os.com" POS_PORTAL_PASSWORD = credentials('POS_PORTAL_PASSWORD') + CI = 'true' } stages { diff --git a/bin/pos-cli-deploy.js b/bin/pos-cli-deploy.js index eea5acde..a50695e7 100755 --- a/bin/pos-cli-deploy.js +++ b/bin/pos-cli-deploy.js @@ -30,6 +30,7 @@ program MARKETPLACE_EMAIL: authData.email, MARKETPLACE_TOKEN: authData.token, MARKETPLACE_URL: authData.url, + PARTNER_PORTAL_HOST: authData.partner_portal_url, MARKETPLACE_ENV: environment, CI: process.env.CI === 'true', // TODO: Get rid off global system env, make it normal argument to function. diff --git a/bin/pos-cli-env-add.js b/bin/pos-cli-env-add.js index c5eadf39..fd4a6fb5 100755 --- a/bin/pos-cli-env-add.js +++ b/bin/pos-cli-env-add.js @@ -1,83 +1,31 @@ #!/usr/bin/env node const { program } = require('commander'); +const ServerError = require('../lib//ServerError'); const logger = require('../lib/logger'); -const validate = require('../lib/validators'); -const Portal = require('../lib/portal'); -const waitForStatus = require('../lib/data/waitForStatus'); -const { readPassword } = require('../lib/utils/password'); -const { storeEnvironment, deviceAuthorizationFlow } = require('../lib/environments'); -const ServerError = require('../lib/ServerError'); - -const saveToken = (settings, token) => { - storeEnvironment(Object.assign(settings, { token: token })); - logger.Success(`Environment ${settings.url} as ${settings.environment} has been added successfuly.`); -}; - -const help = () => { - program.outputHelp(); - process.exit(1); -} - -const checkParams = params => { - // validate.existence({ argumentValue: params.email, argumentName: 'email', fail: help }); - if (params.email) validate.email(params.email); - - validate.existence({ argumentValue: program.args[0], argumentName: 'environment', fail: help }); - - validate.existence({ argumentValue: params.url, argumentName: 'URL', fail: help }); - if (params.url.slice(-1) != '/') { - params.url = params.url + '/'; - } - validate.url(params.url); -}; - - -const login = async (email, password, url) => { - return Portal.login(email, password, url) - .then(response => { - if (response) return Promise.resolve(response[0].token); - }) -} +const addEnv = require('../lib/env-add/main') +program.showHelpAfterError(); program .name('pos-cli env add') - .arguments('[environment]', 'name of environment. Example: staging') + .arguments('', 'name of environment. Example: staging') .option('--email ', 'Partner Portal account email. Example: admin@example.com') - .option('--url ', 'marketplace url. Example: https://example.com') + .requiredOption('--url ', 'marketplace url. Example: https://example.com') + .option('--partner-portal-url ', 'Partner Partner URL', 'https://partners.platformos.com') .option( '--token ', 'if you have a token you can add it directly to pos-cli configuration without connecting to portal' ) .action(async (environment, params) => { try { - checkParams(params); - const settings = { url: params.url, environment: environment, email: params.email }; - - if (params.token) { - token = params.token; - } else if (!params.email){ - token = await deviceAuthorizationFlow(params.url); - } else { - logger.Info( - `Please make sure that you have a permission to deploy. \n You can verify it here: ${Portal.HOST}/me/permissions`, - { hideTimestamp: true } - ); - - const password = await readPassword(); - logger.Info(`Asking ${Portal.HOST} for access token...`); - - token = await login(params.email, password, params.url); - } - - if (token) saveToken(settings, token); + await addEnv(environment, params); } catch (e) { if (ServerError.isNetworkError(e)) ServerError.handler(e) else - logger.Error('Error'); - process.exit(1); + logger.Error(e); } + process.exit(1); }); program.parse(process.argv); diff --git a/bin/pos-cli-env-refresh-token.js b/bin/pos-cli-env-refresh-token.js index b5bc8e58..d39027cd 100644 --- a/bin/pos-cli-env-refresh-token.js +++ b/bin/pos-cli-env-refresh-token.js @@ -30,12 +30,12 @@ program token = await deviceAuthorizationFlow(authData.url); } else { logger.Info( - `Please make sure that you have a permission to deploy. \n You can verify it here: ${Portal.HOST}/me/permissions`, + `Please make sure that you have a permission to deploy. \n You can verify it here: ${Portal.url()}/me/permissions`, { hideTimestamp: true } ); const password = await readPassword(); - logger.Info(`Asking ${Portal.HOST} for access token...`); + logger.Info(`Asking ${Portal.url()} for access token...`); token = await login(authData.email, password, authData.url); } diff --git a/lib/env-add/main.js b/lib/env-add/main.js new file mode 100644 index 00000000..ff0a071b --- /dev/null +++ b/lib/env-add/main.js @@ -0,0 +1,61 @@ +const Portal = require('../portal'); +const logger = require('../logger'); +const validate = require('../validators'); +const { storeEnvironment, deviceAuthorizationFlow } = require('../environments'); +const waitForStatus = require('../data/waitForStatus'); +const { readPassword } = require('../utils/password'); + +const checkParams = (env, params) => { + if (params.email) validate.email(params.email); + + if (params.url.slice(-1) != '/') { + params.url = params.url + '/'; + } + validate.url(params.url); +}; + +const saveToken = (settings, token) => { + storeEnvironment(Object.assign(settings, { token: token })); + logger.Success(`Environment ${settings.url} as ${settings.environment} has been added successfuly.`); +}; + +const login = async (email, password, url) => { + return Portal.login(email, password, url) + .then(response => { + if (response) return Promise.resolve(response[0].token); + }) +} + +const addEnv = async (environment, params) => { + checkParams(environment, params); + if (params.partnerPortalUrl) { + process.env['PARTNER_PORTAL_HOST'] ||= params.partnerPortalUrl + } + + const settings = { + url: params.url, + environment: environment, + email: params.email, + partner_portal_url: process.env['PARTNER_PORTAL_HOST'] + }; + + if (params.token) { + token = params.token; + } else if (!params.email){ + token = await deviceAuthorizationFlow(params.url); + } else { + logger.Info( + `Please make sure that you have a permission to deploy. \n You can verify it here: ${Portal.url()}/me/permissions`, + { hideTimestamp: true } + ); + + const password = await readPassword(); + logger.Info(`Asking ${Portal.url()} for access token...`); + + token = await login(params.email, password, params.url); + } + + if (token) saveToken(settings, token); +} + +module.exports = addEnv; diff --git a/lib/environments.js b/lib/environments.js index be0efc1b..50cb7f4b 100644 --- a/lib/environments.js +++ b/lib/environments.js @@ -10,7 +10,7 @@ const initializeEsmModules = async () => { if(!open) { await import('open').then(imported => open = imported.default); } - + if (process.env['CI']) open = console.log return true; } @@ -21,7 +21,8 @@ const storeEnvironment = settings => { [settings.environment]: { url: settings.url, token: settings.token, - email: settings.email + email: settings.email, + partner_portal_url: settings.partner_portal_url } }; @@ -66,12 +67,13 @@ const deviceAuthorizationFlow = async (instanceUrl) => { const deviceAuthorizationResponse = await Portal.requestDeviceAuthorization(instanceDomain); logger.Debug('deviceAuthorizationResponse', deviceAuthorizationResponse); - const deviceAuthorization = JSON.parse(deviceAuthorizationResponse); + const deviceAuthorization = deviceAuthorizationResponse; const verificationUrl = deviceAuthorization['verification_uri_complete']; const deviceCode = deviceAuthorization['device_code'] const interval = (deviceAuthorization['interval'] || 5) * 1000; await initializeEsmModules(); + logger.Debug('verificationUrl', verificationUrl); await open(verificationUrl); const accessToken = await waitForAccessToken(deviceCode, interval); diff --git a/lib/modules.js b/lib/modules.js index 421c519e..d3b9a1b7 100644 --- a/lib/modules.js +++ b/lib/modules.js @@ -4,7 +4,7 @@ const glob = require('fast-glob'); const files = require('./files'); const logger = require('./logger'); -const portal = require('./portal'); +const Portal = require('./portal'); const prepareArchive = require('./prepareArchive'); const presignUrl = require('./presignUrl').presignUrlForPortal; const uploadFile = require('./s3UploadFile').uploadFile; @@ -68,13 +68,13 @@ const uploadArchive = async (token) => { }; const createVersion = async (token, accessUrl, moduleVersionName) => { - const version = await portal.createVersion(token, accessUrl, moduleVersionName, moduleId) + const version = await Portal.createVersion(token, accessUrl, moduleVersionName, moduleId) return version.id; }; const waitForPublishing = async (token, moduleVersionId) => { try { - await waitForStatus(() => portal.moduleVersionStatus(token, moduleId, moduleVersionId), 'pending', 'accepted'); + await waitForStatus(() => Portal.moduleVersionStatus(token, moduleId, moduleVersionId), 'pending', 'accepted'); logger.Success('Module uploaded.'); } catch(e) { throw new Error('Module not uploaded. Check email for errors.'); @@ -82,7 +82,7 @@ const waitForPublishing = async (token, moduleVersionId) => { }; const getModule = async (token, name) => { - const modules = await portal.findModules(token, name); + const modules = await Portal.findModules(token, name); const module = modules[0]; if (module){ return module; @@ -98,14 +98,14 @@ const getToken = async (params) => { } else { password = await readPassword(); } - logger.Info(`Asking ${portal.HOST} for access token...`); + logger.Info(`Asking ${Portal.url()} for access token...`); const token = await portalAuthToken(params.email, password); return token; } const portalAuthToken = async (email, password) => { try { - const token = await portal.jwtToken(email, password) + const token = await Portal.jwtToken(email, password) return token.auth_token; } catch (e) { if (ServerError.isNetworkError(e)) diff --git a/lib/portal.js b/lib/portal.js index a4b8bfe4..4a4c1657 100644 --- a/lib/portal.js +++ b/lib/portal.js @@ -1,40 +1,40 @@ const { apiRequest } = require('./apiRequest'); const logger = require('./logger'); -const HOST = process.env.PARTNER_PORTAL_HOST || 'https://partners.platformos.com'; - const Portal = { + url: () => { return process.env.PARTNER_PORTAL_HOST || 'https://partners.platformos.com' }, + login: (email, password, url) => { - logger.Debug('Portal.login ' + email + ' to ' + HOST); + logger.Debug('Portal.login ' + email + ' to ' + Portal.url()); return apiRequest({ - uri: `${HOST}/api/user_tokens`, + uri: `${Portal.url()}/api/user_tokens`, headers: { UserAuthorization: `${email}:${password}`, InstanceDomain: url }, }); }, jwtToken: (email, password) => { return apiRequest({ method: 'POST', - uri: `${HOST}/api/authenticate`, + uri: `${Portal.url()}/api/authenticate`, formData: { email: email, password: password }, }); }, findModules: (token, name) => { return apiRequest({ method: 'GET', - uri: `${HOST}/api/pos_modules/?modules=${name}`, + uri: `${Portal.url()}/api/pos_modules/?modules=${name}`, headers: { Authorization: `Bearer ${token}` }, }); }, moduleVersions(modules) { return apiRequest({ - uri: `${HOST}/api/pos_modules?modules=${modules.join(',')}`, + uri: `${Portal.url()}/api/pos_modules?modules=${modules.join(',')}`, }); }, createVersion: (token, url, name, posModuleId) => { return apiRequest({ method: 'POST', - uri: `${HOST}/api/pos_modules/${posModuleId}/pos_module_versions`, + uri: `${Portal.url()}/api/pos_modules/${posModuleId}/pos_module_versions`, body: { pos_module_version: { archive: url, name: name } }, headers: { Authorization: `Bearer ${token}` }, }); @@ -42,39 +42,37 @@ const Portal = { moduleVersionStatus: (token, posModuleId, moduleVersionId) => { return apiRequest({ method: 'GET', - uri: `${HOST}/api/pos_modules/${posModuleId}/pos_module_versions/${moduleVersionId}`, + uri: `${Portal.url()}/api/pos_modules/${posModuleId}/pos_module_versions/${moduleVersionId}`, headers: { Authorization: `Bearer ${token}` }, }); }, moduleVersionsSearch: (moduleVersionName) => { return apiRequest({ method: 'GET', - uri: `${HOST}/api/pos_module_version?name=${moduleVersionName}` + uri: `${Portal.url()}/api/pos_module_version?name=${moduleVersionName}` }); }, requestDeviceAuthorization: (instanceDomain) => { return apiRequest({ method: 'POST', - uri: `${HOST}/oauth/authorize_device`, + uri: `${Portal.url()}/oauth/authorize_device`, formData: { domain: instanceDomain }, - json: false + json: true }); }, fetchDeviceAccessToken: (deviceCode) => { return apiRequest({ method: 'POST', - uri: `${HOST}/oauth/device_token`, + uri: `${Portal.url()}/oauth/device_token`, formData: { grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code: deviceCode }, json: true }); - }, - - HOST: HOST + } }; module.exports = Portal; diff --git a/lib/presignUrl.js b/lib/presignUrl.js index f2c47058..a9cf16ed 100644 --- a/lib/presignUrl.js +++ b/lib/presignUrl.js @@ -1,10 +1,10 @@ const fs = require('fs'), - url = require('url'), - request = require('request-promise'), - mime = require('mime'); -const logger = require('./logger'); + url = require('url'), + request = require('request-promise'), + mime = require('mime'), + logger = require('./logger'), + Portal = require('./portal'); -const Portal = require('./portal'); const deployServiceUrl = () => process.env.DEPLOY_SERVICE_URL || url.resolve(process.env.MARKETPLACE_URL, '/api/private/urls'); const presignUrl = (s3FileName, fileName) => { @@ -48,8 +48,7 @@ const presignDirectory = path => { }; const presignUrlForPortal = (token, moduleName, filename) => { - const HOST = process.env.PARTNER_PORTAL_HOST || 'https://partners.platformos.com'; - const serviceUrl = `${HOST}/api/pos_modules/${moduleName}/presign_url`; + const serviceUrl = `${Portal.url()}/api/pos_modules/${moduleName}/presign_url`; logger.Debug(token); return request .get({ diff --git a/lib/settings.js b/lib/settings.js index 77255d46..e9a37d3c 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -15,7 +15,7 @@ const loadSettingsFileForModule = module => { }; const fetchSettings = (environment) => { - const settings = settingsFromEnv() || files.getConfig()[environment]; + const settings = settingsFromEnv() || settingsFromDotPos(environment); if (settings) return settings; if (environment) { @@ -32,4 +32,6 @@ const settingsFromEnv = () => { } }; -module.exports = { fetchSettings, loadSettingsFileForModule }; +const settingsFromDotPos = (env) => files.getConfig()[env]; + +module.exports = { fetchSettings, loadSettingsFileForModule, settingsFromDotPos }; diff --git a/test/env-add.test.js b/test/env-add.test.js index d4334e37..e8554015 100644 --- a/test/env-add.test.js +++ b/test/env-add.test.js @@ -1,32 +1,63 @@ /* global jest */ +process.env['CI'] = 'true' + const exec = require('./utils/exec'); const cliPath = require('./utils/cliPath'); const fs = require('fs'); const path = require('path'); require('dotenv').config(); -const run = (options) => exec(`${cliPath} env add ${options}`, { env: process.env }); -const readTokenForEnv = (envName) => { - const posFile = fs.readFileSync('.pos', 'utf8'); - const posConfig = JSON.parse(posFile); - return posConfig[envName]['token']; -} +const fetchAuthData = require('../lib/settings').settingsFromDotPos; + +const run = (options) => exec(`${cliPath} env add ${options}`); + +const addEnv = require('../lib/env-add/main') + +describe('commander env add', () => { + afterEach(() => exec(`rm .pos`)); -describe('env add', () => { test('adding with email and token', async () => { - const { stdout, stderr } = await run('--url https://example.com --email pos-cli-ci@platformos.com --token 12345 env_url_email_token'); + const { stdout, stderr } = await run('--url https://example.com --email pos-cli-ci@platformos.com --token 12345 e1'); + + expect(stdout).toMatch('Environment https://example.com/ as e1 has been added successfuly'); + + const settings = fetchAuthData('e1') + expect(settings['token']).toMatch('12345'); + }); - expect(stdout).toMatch('Environment https://example.com/ as env_url_email_token has been added successfuly'); - expect(readTokenForEnv('env_url_email_token')).toMatch('12345'); + test('adding with email and token and partner_portal_url', async () => { + const { stdout, stderr } = await run('--url https://example.com --email pos-cli-ci@platformos.com --token 12345 e2 --partner-portal-url http://portal.example.com'); + + expect(stdout).toMatch('Environment https://example.com/ as e2 has been added successfuly'); + + const settings = fetchAuthData('e2') + expect(settings['token']).toMatch('12345'); + expect(settings['partner_portal_url']).toMatch('http://portal.example.com'); }); +}); + +// const { apiRequest } = require('../lib/apiRequest'); + +jest.mock('../lib/portal', () => ({ + requestDeviceAuthorization: () => Promise.resolve({verification_uri_complete: "http://example.com/xxxx", device_code: "device_code"}), + fetchDeviceAccessToken: x => Promise.resolve({access_token: "12345"}) +})); + +describe('env add', () => { + afterEach(() => exec(`rm .pos`)); + + const environment = 'e1' + test('with --partner_portal_url', async () => { - test.skip('adding with url', async () => { - // TODO: mock requests to portal - global.open = () => console.log('bar'); - const { stdout, stderr } = await run('--url https:example.com env_url'); + const params = { + partnerPortalUrl: "http://portal.example.com", + url: "http://instance.example.com" + } - expect(stdout).toMatch('Environment https://example.com/ as env_url has been added successfuly'); - expect(readTokenForEnv('env_url_email')).toMatch('12345'); + await addEnv(environment, params) + const settings = fetchAuthData(environment) + expect(settings['token']).toMatch('12345'); + expect(settings['partner_portal_url']).toMatch('http://portal.example.com'); }); }); diff --git a/test/fixtures/deploy/modules_update/app/pos-modules.json b/test/fixtures/deploy/modules_update/app/pos-modules.json index 4a7041e7..970703eb 100644 --- a/test/fixtures/deploy/modules_update/app/pos-modules.json +++ b/test/fixtures/deploy/modules_update/app/pos-modules.json @@ -1,5 +1,5 @@ { "modules": { - "core": "1.0.0" + "core": "1.5.5" } -} +} \ No newline at end of file diff --git a/test/fixtures/deploy/modules_update/app/pos-modules.lock.json b/test/fixtures/deploy/modules_update/app/pos-modules.lock.json index 4a7041e7..970703eb 100644 --- a/test/fixtures/deploy/modules_update/app/pos-modules.lock.json +++ b/test/fixtures/deploy/modules_update/app/pos-modules.lock.json @@ -1,5 +1,5 @@ { "modules": { - "core": "1.0.0" + "core": "1.5.5" } -} +} \ No newline at end of file diff --git a/test/lib/commands.test.js b/test/lib/commands.test.js index 82a8f925..3d8485a8 100644 --- a/test/lib/commands.test.js +++ b/test/lib/commands.test.js @@ -53,8 +53,8 @@ test('should run env list', async () => { }); test('should run help on env add', async () => { - const { stdout, code } = await run('env add'); - expect(stdout).toMatch('Usage: pos-cli env add [options] [environment]'); + const { stdout, code, stderr } = await run('env add'); + expect(stderr).toMatch('Usage: pos-cli env add [options] ') expect(code).toEqual(1); }); diff --git a/test/logsv2s.test.js b/test/logsv2s.test.js index ad8b0a9c..95702ecd 100644 --- a/test/logsv2s.test.js +++ b/test/logsv2s.test.js @@ -4,7 +4,6 @@ const exec = require('./utils/exec'); const cliPath = require('./utils/cliPath'); const fs = require('fs'); const path = require('path'); -// const HTTP = require('../lib/logsv2/http'); require('dotenv').config();