#!/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: // .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`) // .oprita.json aceeasi forma plus o incarcatura OPRITA (nu are voie sa intre in jurnal) // .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}`); }