-
Notifications
You must be signed in to change notification settings - Fork 9
feat: enable Aops API in SDK #299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| import chalk from 'chalk'; | ||
| import ora from 'ora'; | ||
| import * as fs from 'fs'; | ||
|
Check warning on line 3 in packages/cli/src/actions/deploy-policy.ts
|
||
| import * as path from 'path'; | ||
|
Check warning on line 4 in packages/cli/src/actions/deploy-policy.ts
|
||
| import fetch from 'node-fetch'; | ||
| import type { EnvironmentConfig, PolicyConfig } from '../types/index.js'; | ||
| import { API_ENDPOINTS, AUTH_CONSTANTS } from '../constants/index.js'; | ||
| import { MESSAGES } from '../constants/messages.js'; | ||
| import { createHeaders } from '../utils/api.js'; | ||
| import { getEnvironmentConfig } from '../utils/env-config.js'; | ||
| import { handleHttpError } from '../utils/error-handler.js'; | ||
| import { cliTelemetryClient } from '../telemetry/index.js'; | ||
|
|
||
| export interface DeployPolicyOptions { | ||
| policyId?: string; | ||
| baseUrl?: string; | ||
| orgId?: string; | ||
| tenantId?: string; | ||
| accessToken?: string; | ||
| logger?: { log: (message: string) => void }; | ||
| } | ||
|
|
||
| interface TenantPolicy { | ||
| tenantIdentifier: string; | ||
| policyIdentifier: string | null; | ||
| productIdentifier: string; | ||
| licenseTypeIdentifier: string; | ||
| tenantName?: string; | ||
| } | ||
|
|
||
| interface TenantResponse { | ||
| name: string; | ||
| identifier: string; | ||
| url: string; | ||
| status: string; | ||
| tenantPolicies: TenantPolicy[]; | ||
| } | ||
|
|
||
| const AI_TRUST_LAYER_PRODUCT = 'AITrustLayer'; | ||
|
|
||
| function loadPolicyConfig(logger: { log: (message: string) => void }): PolicyConfig | null { | ||
| const configPath = path.join(process.cwd(), AUTH_CONSTANTS.FILES.UIPATH_DIR, AUTH_CONSTANTS.FILES.POLICY_CONFIG); | ||
| try { | ||
| if (fs.existsSync(configPath)) { | ||
| const content = fs.readFileSync(configPath, 'utf-8'); | ||
| return JSON.parse(content) as PolicyConfig; | ||
| } | ||
| } catch (error) { | ||
| logger.log(chalk.dim(`${MESSAGES.ERRORS.FAILED_TO_LOAD_POLICY_CONFIG} ${error instanceof Error ? error.message : ''}`)); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| async function getTenantPolicies( | ||
| tenantId: string, | ||
| envConfig: EnvironmentConfig | ||
| ): Promise<TenantResponse> { | ||
| const url = `${envConfig.baseUrl}/${envConfig.orgId}/${API_ENDPOINTS.TENANT_GET.replace('{tenantId}', tenantId)}`; | ||
|
|
||
| const response = await fetch(url, { | ||
| method: 'GET', | ||
| headers: createHeaders({ | ||
| bearerToken: envConfig.accessToken, | ||
| }), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| await handleHttpError(response, MESSAGES.ERROR_CONTEXT.POLICY_DEPLOY); | ||
| } | ||
|
|
||
| return (await response.json()) as TenantResponse; | ||
| } | ||
|
|
||
| async function deployTenantPolicies( | ||
| policies: TenantPolicy[], | ||
| envConfig: EnvironmentConfig | ||
| ): Promise<void> { | ||
| const url = `${envConfig.baseUrl}/${envConfig.orgId}/${API_ENDPOINTS.TENANT_SAVE}`; | ||
|
|
||
| // Remove tenantName from the payload as it's not needed for the POST request | ||
| const payload = policies.map(({ tenantName, ...policy }) => policy); | ||
|
|
||
| const response = await fetch(url, { | ||
| method: 'POST', | ||
| headers: createHeaders({ | ||
| bearerToken: envConfig.accessToken, | ||
| }), | ||
| body: JSON.stringify(payload), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| await handleHttpError(response, MESSAGES.ERROR_CONTEXT.POLICY_DEPLOY); | ||
| } | ||
| } | ||
|
|
||
| export async function executeDeployPolicy(options: DeployPolicyOptions): Promise<void> { | ||
| const logger = options.logger ?? { log: console.log }; | ||
|
|
||
| logger.log(chalk.blue(MESSAGES.INFO.POLICY_DEPLOYING)); | ||
|
|
||
| // Get policyId from options or load from stored config | ||
| let policyId = options.policyId; | ||
| if (!policyId) { | ||
| const policyConfig = loadPolicyConfig(logger); | ||
| if (policyConfig?.policyId) { | ||
| policyId = policyConfig.policyId; | ||
| logger.log(chalk.dim(`Using stored policy: ${policyConfig.policyName} (${policyId})`)); | ||
| } else { | ||
| throw new Error(MESSAGES.ERRORS.POLICY_ID_REQUIRED); | ||
| } | ||
| } | ||
|
|
||
| const envConfig = getEnvironmentConfig( | ||
| AUTH_CONSTANTS.REQUIRED_ENV_VARS.DEPLOY_POLICY, | ||
| logger, | ||
| { | ||
| baseUrl: options.baseUrl, | ||
| orgId: options.orgId, | ||
| tenantId: options.tenantId, | ||
| accessToken: options.accessToken, | ||
| } | ||
| ); | ||
| if (!envConfig) throw new Error('Missing required configuration'); | ||
|
|
||
| const spinner = ora(MESSAGES.INFO.FETCHING_TENANT_POLICIES).start(); | ||
|
|
||
| try { | ||
| // Step 1: Get current tenant policies | ||
| const tenantData = await getTenantPolicies(envConfig.tenantId, envConfig); | ||
|
|
||
| // Step 2: Find and update AITrustLayer policy | ||
| const updatedPolicies = tenantData.tenantPolicies.map((policy) => { | ||
| if (policy.productIdentifier === AI_TRUST_LAYER_PRODUCT) { | ||
| return { | ||
| ...policy, | ||
| policyIdentifier: policyId, | ||
| }; | ||
| } | ||
| return policy; | ||
| }); | ||
|
|
||
| // Check if AITrustLayer policy was found | ||
| const aiTrustLayerPolicy = updatedPolicies.find( | ||
| (p) => p.productIdentifier === AI_TRUST_LAYER_PRODUCT | ||
| ); | ||
| if (!aiTrustLayerPolicy) { | ||
| spinner.fail(chalk.red(MESSAGES.ERRORS.AI_TRUST_LAYER_POLICY_NOT_FOUND)); | ||
| throw new Error(MESSAGES.ERRORS.AI_TRUST_LAYER_POLICY_NOT_FOUND); | ||
| } | ||
|
|
||
| spinner.text = MESSAGES.INFO.DEPLOYING_POLICY_TO_TENANT; | ||
|
|
||
| // Step 3: Deploy updated policies | ||
| await deployTenantPolicies(updatedPolicies, envConfig); | ||
|
|
||
| spinner.succeed(chalk.green(MESSAGES.SUCCESS.POLICY_DEPLOYED_SUCCESS)); | ||
| cliTelemetryClient.track('Cli.DeployPolicy', { operation: 'deploy' }); | ||
|
|
||
| logger.log(''); | ||
| logger.log(` ${chalk.cyan('Tenant:')} ${tenantData.name}`); | ||
| logger.log(` ${chalk.cyan('Tenant ID:')} ${tenantData.identifier}`); | ||
| logger.log(` ${chalk.cyan('Policy ID:')} ${policyId}`); | ||
| logger.log(` ${chalk.cyan('Product:')} ${AI_TRUST_LAYER_PRODUCT}`); | ||
| } catch (error) { | ||
| spinner.fail(chalk.red(MESSAGES.ERRORS.POLICY_DEPLOY_FAILED)); | ||
| throw error; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| import chalk from 'chalk'; | ||
| import ora from 'ora'; | ||
| import * as fs from 'fs'; | ||
|
Check warning on line 3 in packages/cli/src/actions/publish-policy.ts
|
||
| import * as path from 'path'; | ||
|
Check warning on line 4 in packages/cli/src/actions/publish-policy.ts
|
||
| import fetch from 'node-fetch'; | ||
| import type { EnvironmentConfig, PolicyConfig } from '../types/index.js'; | ||
| import { API_ENDPOINTS, AUTH_CONSTANTS } from '../constants/index.js'; | ||
| import { MESSAGES } from '../constants/messages.js'; | ||
| import { createHeaders } from '../utils/api.js'; | ||
| import { getEnvironmentConfig, atomicWriteFileSync } from '../utils/env-config.js'; | ||
| import { handleHttpError } from '../utils/error-handler.js'; | ||
| import { cliTelemetryClient } from '../telemetry/index.js'; | ||
|
|
||
| export interface PublishPolicyOptions { | ||
| file: string; | ||
| baseUrl?: string; | ||
| orgId?: string; | ||
| tenantId?: string; | ||
| accessToken?: string; | ||
| logger?: { log: (message: string) => void }; | ||
| } | ||
|
|
||
| interface PolicyInputData { | ||
| 'policy-name': string; | ||
| 'product-name'?: string; | ||
| description?: string | null; | ||
| version?: string; | ||
| availability?: number; | ||
| data: Record<string, unknown>; | ||
| } | ||
|
|
||
| interface PolicyCreateResponse { | ||
| name: string; | ||
| identifier: string; | ||
| description: string | null; | ||
| priority: number; | ||
| availability: number; | ||
| } | ||
|
|
||
| const HARDCODED_PRODUCT = { | ||
| name: 'AITrustLayer', | ||
| label: 'AI Trust Layer', | ||
| consumerProducts: [ | ||
| { | ||
| name: 'Business', | ||
| label: 'StudioX', | ||
| isRestricted: false, | ||
| isCloud: false, | ||
| isRemote: false, | ||
| }, | ||
| { | ||
| name: 'Development', | ||
| label: 'Studio', | ||
| isRestricted: false, | ||
| isCloud: false, | ||
| isRemote: false, | ||
| }, | ||
| { | ||
| name: 'StudioWeb', | ||
| label: 'Studio Web', | ||
| isRestricted: false, | ||
| isCloud: true, | ||
| isRemote: false, | ||
| }, | ||
| ], | ||
| isRestricted: false, | ||
| isCloud: true, | ||
| isRemote: false, | ||
| }; | ||
|
|
||
| function loadPolicyFile(filePath: string, logger: { log: (message: string) => void }): PolicyInputData { | ||
| const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath); | ||
|
|
||
| if (!fs.existsSync(absolutePath)) { | ||
| throw new Error(`${MESSAGES.ERRORS.POLICY_FILE_NOT_FOUND}: ${absolutePath}`); | ||
| } | ||
|
|
||
| try { | ||
| const content = fs.readFileSync(absolutePath, 'utf-8'); | ||
| const parsed = JSON.parse(content) as PolicyInputData; | ||
|
|
||
| if (!parsed['policy-name']) { | ||
| throw new Error(MESSAGES.ERRORS.POLICY_NAME_REQUIRED); | ||
| } | ||
| if (!parsed.data) { | ||
| throw new Error(MESSAGES.ERRORS.POLICY_DATA_REQUIRED); | ||
| } | ||
|
|
||
| return parsed; | ||
| } catch (error) { | ||
| if (error instanceof SyntaxError) { | ||
| throw new Error(`${MESSAGES.ERRORS.POLICY_FILE_INVALID_JSON}: ${error.message}`); | ||
| } | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| function buildPolicyPayload(policyInput: PolicyInputData): Record<string, unknown> { | ||
| return { | ||
| policy: { | ||
| availability: policyInput.availability ?? 99, | ||
| name: policyInput['policy-name'], | ||
| priority: 4, | ||
| product: HARDCODED_PRODUCT, | ||
| description: policyInput.description ?? null, | ||
| }, | ||
| policyFormData: { | ||
| data: { | ||
| data: policyInput.data, | ||
| }, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| function savePolicyConfig(config: PolicyConfig, logger: { log: (message: string) => void }): void { | ||
| const configDir = path.join(process.cwd(), AUTH_CONSTANTS.FILES.UIPATH_DIR); | ||
| const configPath = path.join(configDir, AUTH_CONSTANTS.FILES.POLICY_CONFIG); | ||
| try { | ||
| if (!fs.existsSync(configDir)) { | ||
| fs.mkdirSync(configDir, { recursive: true }); | ||
| } | ||
| atomicWriteFileSync(configPath, config); | ||
| } catch (error) { | ||
| logger.log(chalk.yellow(`${MESSAGES.ERRORS.FAILED_TO_SAVE_POLICY_CONFIG} ${error instanceof Error ? error.message : MESSAGES.ERRORS.UNKNOWN_ERROR}`)); | ||
| } | ||
| } | ||
|
|
||
| async function createPolicy( | ||
| payload: Record<string, unknown>, | ||
| envConfig: EnvironmentConfig | ||
| ): Promise<PolicyCreateResponse> { | ||
| const url = `${envConfig.baseUrl}/${envConfig.orgId}/${API_ENDPOINTS.POLICY_SAVE}`; | ||
|
|
||
| const response = await fetch(url, { | ||
| method: 'POST', | ||
| headers: createHeaders({ | ||
| bearerToken: envConfig.accessToken, | ||
| tenantId: envConfig.tenantId, | ||
| }), | ||
| body: JSON.stringify(payload), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| await handleHttpError(response, MESSAGES.ERROR_CONTEXT.POLICY_PUBLISHING); | ||
| } | ||
|
|
||
| return (await response.json()) as PolicyCreateResponse; | ||
| } | ||
|
|
||
| export async function executePublishPolicy(options: PublishPolicyOptions): Promise<void> { | ||
| const logger = options.logger ?? { log: console.log }; | ||
|
|
||
| logger.log(chalk.blue(MESSAGES.INFO.POLICY_PUBLISHING)); | ||
|
|
||
| const envConfig = getEnvironmentConfig( | ||
| AUTH_CONSTANTS.REQUIRED_ENV_VARS.PUBLISH_POLICY, | ||
| logger, | ||
| { | ||
| baseUrl: options.baseUrl, | ||
| orgId: options.orgId, | ||
| tenantId: options.tenantId, | ||
| accessToken: options.accessToken, | ||
| } | ||
| ); | ||
| if (!envConfig) throw new Error('Missing required configuration'); | ||
|
|
||
| const spinner = ora(MESSAGES.INFO.READING_POLICY_FILE).start(); | ||
|
|
||
| try { | ||
| const policyInput = loadPolicyFile(options.file, logger); | ||
| spinner.text = MESSAGES.INFO.PUBLISHING_POLICY; | ||
|
|
||
| const payload = buildPolicyPayload(policyInput); | ||
| const result = await createPolicy(payload, envConfig); | ||
|
|
||
| spinner.succeed(chalk.green(MESSAGES.SUCCESS.POLICY_PUBLISHED_SUCCESS)); | ||
| cliTelemetryClient.track('Cli.PublishPolicy', { operation: 'create' }); | ||
|
|
||
| // Save policy config for later use by deploy-policy | ||
| savePolicyConfig({ | ||
| policyId: result.identifier, | ||
| policyName: result.name, | ||
| publishedAt: new Date().toISOString(), | ||
| }, logger); | ||
|
|
||
| logger.log(''); | ||
| logger.log(` ${chalk.cyan('Policy Name:')} ${result.name}`); | ||
| logger.log(` ${chalk.cyan('Policy ID:')} ${result.identifier}`); | ||
| logger.log(` ${chalk.cyan('Availability:')} ${result.availability}%`); | ||
| if (result.description) { | ||
| logger.log(` ${chalk.cyan('Description:')} ${result.description}`); | ||
| } | ||
| } catch (error) { | ||
| spinner.fail(chalk.red(MESSAGES.ERRORS.POLICY_PUBLISHING_FAILED)); | ||
| throw error; | ||
| } | ||
| } | ||
Check warning
Code scanning / CodeQL
File data in outbound network request Medium