91 lines
11 KiB
JavaScript
91 lines
11 KiB
JavaScript
#!/usr/bin/env node
|
|
// fixturi-cloud/deriva.mjs (B15, 2026-10-18): fixturile adaptoarelor pe furnizori de cloud, DERIVATE determinist din sursele din
|
|
// `surse/` (copiile exacte ale exemplelor din documentatia oficiala a fiecarui CLI/API; pentru GCP, unde documentatia nu are un exemplu
|
|
// de iesire, o forma construita din schema documentata, numita `din-schema`). Pentru fiecare sursa se scriu:
|
|
// <src>.json forma pe care o scrie CLI-ul (pentru Azure: `properties` aplatizat cum face az, plus campurile pe care CLI-ul le
|
|
// adauga si pe care exemplul REST nu le are: powerState la `az vm list -d`, instanceView la `az container show`)
|
|
// <src>.oprita.json aceeasi forma plus o incarcatura OPRITA (nu are voie sa intre in jurnal)
|
|
// <src>.secrete.json aceeasi forma cu variabile de mediu CU VALORI plantate (valorile nu au voie sa apara in jurnal)
|
|
// plus cateva variante care exercita refuzurile pe nume (fara -d, fara instanceView). Tot ce e modificat fata de sursa e scris AICI,
|
|
// in cod, nu de mana in fisiere: proba re-deriva si compara octet cu octet cu ce e pe disc.
|
|
// node deriva.mjs scrie fixturile langa acest fisier
|
|
// node deriva.mjs --verifica compara cu ce e pe disc: 0 identice, 1 diferite
|
|
import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const SURSE = path.join(AICI, 'surse');
|
|
export const SECRET = 'AERE-SINTETIC-valoare-de-mediu-plantata-93c1';
|
|
export const SECRET_DATE = 'AERE-SINTETIC-customData-plantat-5e7b';
|
|
const citeste = (f) => JSON.parse(fs.readFileSync(path.join(SURSE, f), 'utf8'));
|
|
const clona = (o) => JSON.parse(JSON.stringify(o));
|
|
// az CLI scrie resursele ARM cu `properties` aplatizat in obiectul resursei (si la fel containerele dintr-un grup ACI)
|
|
const aplatizeaza = (o) => { const { properties, ...rest } = o; return { ...rest, ...(properties || {}) }; };
|
|
|
|
export function deriva() {
|
|
const out = {};
|
|
// ---- AWS: iesirile CLI sunt copiate exact; variantele adauga/planteaza ----------------------------------------------------
|
|
{ const t = citeste('aws-ecs-describe-tasks.json'); const d = citeste('aws-ecs-describe-task-definition.json');
|
|
// definitia din documentatie (hello_world:8) nu e cea a task-ului din exemplu (console-sample-app-static2:1): forma CLI primeste
|
|
// o definitie aliniata pe ARN-ul si pe containerul task-ului, cu un secret referit prin ARN (secrets[].valueFrom), nu prin valoare
|
|
const def = clona(d); def.taskDefinition.taskDefinitionArn = t.tasks[0].taskDefinitionArn; def.taskDefinition.family = 'console-sample-app-static2'; def.taskDefinition.revision = 1;
|
|
def.taskDefinition.containerDefinitions = [{ name: 'simple-app', image: 'httpd:2.4', cpu: 10, memory: 300, essential: true, command: ['httpd-foreground'],
|
|
environment: [{ name: 'HTTPD_PORT', value: '80' }], secrets: [{ name: 'API_TOKEN', valueFrom: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:api-token-AbCdEf' }],
|
|
mountPoints: [{ sourceVolume: 'www', containerPath: '/usr/local/apache2/htdocs', readOnly: true }], portMappings: [{ containerPort: 80, hostPort: 80 }] }];
|
|
out['aws-ecs.json'] = clona(t); out['aws-ecs.definitii.json'] = def;
|
|
const op = clona(t); const oprit = clona(t.tasks[0]); oprit.taskArn = 'arn:aws:ecs:us-east-1:123456789012:task/MyCluster/0000000000000000000000oprita'; oprit.lastStatus = 'STOPPED'; oprit.desiredStatus = 'STOPPED'; oprit.stoppedReason = 'Essential container in task exited'; oprit.containers[0].lastStatus = 'STOPPED'; oprit.containers[0].exitCode = 0; op.tasks.push(oprit);
|
|
out['aws-ecs.oprita.json'] = op;
|
|
const sec = clona(t); sec.tasks[0].overrides.containerOverrides[0].environment = [{ name: 'OVERRIDE_TOKEN', value: SECRET }]; out['aws-ecs.secrete.json'] = sec;
|
|
const secDef = clona(def); secDef.taskDefinition.containerDefinitions[0].environment.push({ name: 'DB_PASSWORD', value: SECRET }); out['aws-ecs.secrete.definitii.json'] = secDef; }
|
|
{ const e = citeste('aws-ec2-describe-instances.json'); out['aws-ec2.json'] = clona(e);
|
|
const op = clona(e); const oprit = clona(e.Reservations[0].Instances[0]); oprit.InstanceId = 'i-0000000000oprita00'; oprit.State = { Code: 80, Name: 'stopped' }; oprit.StateTransitionReason = 'User initiated (2026-10-17 20:00:00 GMT)'; op.Reservations[0].Instances.push(oprit); out['aws-ec2.oprita.json'] = op;
|
|
const sec = clona(e); sec.Reservations[0].Instances[0].Tags.push({ Key: 'DB_PASSWORD', Value: SECRET }); out['aws-ec2.secrete.json'] = sec; }
|
|
{ const l = citeste('aws-lambda-list-functions.json'); out['aws-lambda.json'] = clona(l);
|
|
const op = clona(l); const oprit = clona(l.Functions[0]); oprit.FunctionName = 'inactive-function'; oprit.FunctionArn = 'arn:aws:lambda:us-west-2:123456789012:function:inactive-function'; oprit.State = 'Inactive'; oprit.StateReason = 'Function is inactive'; op.Functions.push(oprit); out['aws-lambda.oprita.json'] = op;
|
|
const sec = clona(l); sec.Functions[0].Environment = { Variables: { DB_PASSWORD: SECRET, LOG_LEVEL: 'info' } }; out['aws-lambda.secrete.json'] = sec; }
|
|
// ---- Azure: exemplele REST, aduse la forma CLI --------------------------------------------------------------------------------
|
|
{ const r = citeste('azure-vm-list-rest-2026-04-01.json'); const vms = r.responses['200'].body.value.map((v) => ({ ...aplatizeaza(v), resourceGroup: /\/resourceGroups\/([^/]+)\//i.exec(v.id)[1] }));
|
|
// `az vm list -d` adauga powerState (displayStatus-ul starii PowerState/* din instanceView) si scoate instanceView (azure-cli vm/custom.py get_vm_details)
|
|
const cuD = vms.map((v) => { const { instanceView, ...rest } = v; return { ...rest, powerState: 'VM running', publicIps: '', fqdns: '', privateIps: '10.0.0.4', macAddresses: '' }; });
|
|
out['azure-vm.json'] = cuD; out['azure-vm.fara-d.json'] = vms;
|
|
const op = clona(cuD); const oprit = clona(cuD[0]); oprit.name = 'vm-oprita'; oprit.id = oprit.id.replace(/\/[^/]+$/, '/vm-oprita'); oprit.powerState = 'VM deallocated'; op.push(oprit); out['azure-vm.oprita.json'] = op;
|
|
const sec = clona(cuD); sec[0].osProfile.customData = SECRET_DATE; sec[0].userData = SECRET_DATE; sec[0].tags = { DB_PASSWORD: SECRET, env: 'prod' }; out['azure-vm.secrete.json'] = sec; }
|
|
{ const r = citeste('azure-container-groups-list-rest-2026-07-01.json');
|
|
// `az container show` poarta instanceView (ContainerPropertiesInstanceView: currentState {state, startTime, detailStatus}, restartCount); exemplul REST de list nu il are
|
|
const stare = (state, extra = {}) => ({ currentState: { state, startTime: '2026-10-18T06:00:00Z', detailStatus: '', ...extra }, events: [], restartCount: 0 });
|
|
const grupuri = r.value.map((g) => { const f = aplatizeaza(g); f.resourceGroup = /\/resourceGroups\/([^/]+)\//i.exec(g.id)[1]; f.containers = f.containers.map(aplatizeaza); return f; });
|
|
const cuStare = clona(grupuri); for (const g of cuStare) { g.instanceView = { state: 'Running', events: [] }; for (const c of g.containers) c.instanceView = stare('Running'); }
|
|
out['azure-container.json'] = cuStare; out['azure-container.fara-stare.json'] = grupuri;
|
|
const op = clona(cuStare); const oprit = clona(cuStare[0]); oprit.name = 'demo2'; oprit.id = oprit.id.replace(/\/[^/]+$/, '/demo2'); oprit.instanceView.state = 'Stopped'; oprit.containers[0].instanceView = stare('Terminated', { exitCode: 0, finishTime: '2026-10-18T06:05:00Z' }); op.push(oprit); out['azure-container.oprita.json'] = op;
|
|
const sec = clona(cuStare); sec[0].containers[0].environmentVariables = [{ name: 'DB_PASSWORD', value: SECRET }, { name: 'API_KEY', secureValue: SECRET }]; out['azure-container.secrete.json'] = sec; }
|
|
{ const r = citeste('azure-containerapps-list-rest-2026-07-01.json'); const apps = r.responses['200'].body.value; out['azure-containerapp.json'] = clona(apps);
|
|
const op = clona(apps); const oprit = clona(apps[0]); oprit.name = 'testcontainerApp1'; oprit.id = oprit.id.replace(/\/[^/]+$/, '/testcontainerApp1'); oprit.properties.runningStatus = 'Stopped'; op.push(oprit); out['azure-containerapp.oprita.json'] = op;
|
|
const sec = clona(apps); sec[0].properties.template.containers[0].env = [{ name: 'DB_PASSWORD', value: SECRET }, { name: 'API_KEY', secretRef: 'api-key' }]; sec[0].properties.configuration.secrets = [{ name: 'api-key', value: SECRET }]; out['azure-containerapp.secrete.json'] = sec; }
|
|
{ const r = citeste('azure-webapps-list-rest-2024-11-01.json'); const sites = r.value.map(aplatizeaza);
|
|
// primul sit primeste un runtime de container (cum il lasa `az webapp config container set`); exemplul REST are linuxFxVersion gol
|
|
sites[0].siteConfig.linuxFxVersion = 'DOCKER|mcr.microsoft.com/appsvc/staticsite:latest';
|
|
out['azure-webapp.json'] = clona(sites);
|
|
const op = clona(sites); const oprit = clona(sites[1]); oprit.name = 'sitef0000'; oprit.id = oprit.id.replace(/\/[^/]+$/, '/sitef0000'); oprit.state = 'Stopped'; op.push(oprit); out['azure-webapp.oprita.json'] = op;
|
|
const sec = clona(sites); sec[0].siteConfig.appSettings = [{ name: 'DB_PASSWORD', value: SECRET }, { name: 'WEBSITES_PORT', value: '8080' }]; sec[0].siteConfig.connectionStrings = [{ name: 'Main', connectionString: SECRET, type: 'SQLAzure' }]; out['azure-webapp.secrete.json'] = sec; }
|
|
// ---- GCP: formele din schema ----------------------------------------------------------------------------------------------------
|
|
{ const i = citeste('gcp-compute-instances-din-schema.json'); out['gcp-compute.json'] = clona(i);
|
|
const op = clona(i); const oprit = clona(i[0]); oprit.name = 'api-2'; oprit.id = '4218766341062832346'; oprit.status = 'TERMINATED'; oprit.selfLink = oprit.selfLink.replace(/api-1$/, 'api-2'); oprit.lastStopTimestamp = '2026-10-17T20:00:00.000-07:00'; op.push(oprit); out['gcp-compute.oprita.json'] = op;
|
|
const sec = clona(i); sec[0].metadata.items.push({ key: 'user-data', value: SECRET }); sec[0].metadata.items.find((x) => x.key === 'startup-script').value = `#! /bin/bash\nexport DB_PASSWORD=${SECRET}\n`; out['gcp-compute.secrete.json'] = sec; }
|
|
{ const s = citeste('gcp-run-services-din-schema.json'); out['gcp-run.json'] = clona(s);
|
|
const op = clona(s); const oprit = clona(s[0]); oprit.metadata.name = 'batch'; oprit.metadata.uid = '00000000-0000-4000-8000-00000000oprita'; oprit.status.conditions[0] = { type: 'Ready', status: 'False', reason: 'RevisionFailed', message: 'Revision batch-00001-abc is not ready.', lastTransitionTime: '2026-10-17T20:00:00.000000Z' }; op.push(oprit); out['gcp-run.oprita.json'] = op;
|
|
const sec = clona(s); sec[0].spec.template.spec.containers[0].env.push({ name: 'DB_PASSWORD_PLAIN', value: SECRET }); out['gcp-run.secrete.json'] = sec; }
|
|
return out;
|
|
}
|
|
export const FISIERE = Object.keys(deriva());
|
|
export const text = (o) => JSON.stringify(o, null, 2) + '\n';
|
|
|
|
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
|
const d = deriva(); let diferite = 0;
|
|
for (const [f, o] of Object.entries(d)) {
|
|
const p = path.join(AICI, f); const t = text(o);
|
|
if (process.argv.includes('--verifica')) { const pe = fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null; if (pe !== t) { diferite++; console.log('DIFERIT ' + f); } }
|
|
else fs.writeFileSync(p, t);
|
|
}
|
|
if (process.argv.includes('--verifica')) { console.log(diferite ? `${diferite} fixturi difera de derivare` : `fixturile de pe disc sunt cele derivate (${Object.keys(d).length})`); process.exitCode = diferite ? 1 : 0; }
|
|
else console.log(`scrise ${Object.keys(d).length} fixturi in ${AICI}`);
|
|
}
|