"Fossies" - the Fresh Open Source Software Archive

Member "cli-1.1280.1/src/lib/iac/drift/driftctl.ts" (20 Feb 2024, 10135 Bytes) of package /linux/misc/snyk-cli-1.1280.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 import config from '../../config';
    2 import { EXIT_CODES } from '../../../cli/exit-codes';
    3 import envPaths from 'env-paths';
    4 import {
    5   DescribeOptions,
    6   DriftctlExecutionResult,
    7   DriftCTLOptions,
    8   FmtOptions,
    9 } from '../types';
   10 import { TimerMetricInstance } from '../../metrics';
   11 import * as analytics from '../../analytics';
   12 import { spinner } from '../../spinner';
   13 import {
   14   createIgnorePattern,
   15   verifyServiceMappingExists,
   16 } from '../service-mappings';
   17 import * as debugLib from 'debug';
   18 import { makeRequest } from '../../request';
   19 import * as child_process from 'child_process';
   20 import { StdioOptions } from 'child_process';
   21 import * as path from 'path';
   22 import * as fs from 'fs';
   23 import * as os from 'os';
   24 import * as crypto from 'crypto';
   25 import { createDirIfNotExists, isExe } from '../file-utils';
   26 import { restoreEnvProxy } from '../env-utils';
   27 
   28 const debug = debugLib('driftctl');
   29 
   30 const cachePath = config.CACHE_PATH ?? envPaths('snyk').cache;
   31 
   32 export const DCTL_EXIT_CODES = {
   33   EXIT_IN_SYNC: 0,
   34   EXIT_NOT_IN_SYNC: 1,
   35   EXIT_ERROR: 2,
   36 };
   37 
   38 export const driftctlVersion = 'v0.40.0';
   39 
   40 const driftctlChecksums = {
   41   driftctl_darwin_amd64:
   42     '4eb86bd4a1e965c2552879795434143f1db974b2d795581b9ddb69d0bd8a245a',
   43   'driftctl_windows_386.exe':
   44     'a02f079cb128ba46396db9654bc8bb8066ebde0539ebbeb401a40a81dfc8f733',
   45   driftctl_darwin_arm64:
   46     'dfdee8138eb817cc066b8bf915c808fbd53536ee1757b34ca6e518e1c2ad1ba5',
   47   driftctl_linux_arm64:
   48     '8816f1378138c2ce585c762e109b5fdd41b7144b915e97759ceae946db023540',
   49   'driftctl_windows_arm.exe':
   50     '6217151b4168e93ffdd6e005cb1cf03768f371cd6b412f53605fde46343c08d1',
   51   driftctl_linux_amd64:
   52     '84e2462454956a4df794a24e0f4d2351299212d772b8602fc5070e6174ac1324',
   53   'driftctl_windows_amd64.exe':
   54     '1561fd04e3d428c39ae95f81214517bbf62e8333156bf538a2d385005e350c8b',
   55   'driftctl_windows_arm64.exe':
   56     '76f939d836da64fa9dab63f0eeffd09a0de7e353b034296b8f1582cdff6f2a61',
   57   driftctl_linux_arm:
   58     '7f669ca49e152779a09587ff0e58dedd3996229cc8ff3e5cdc371895eaa994f6',
   59   driftctl_linux_386:
   60     'e6bbdf341148e81511d30dd5afe2fa2ef08f3b0b75079bf0bde2b790d75beb8a',
   61 };
   62 
   63 const dctlBaseUrl = 'https://static.snyk.io/cli/driftctl/';
   64 
   65 const driftctlPath: string = path.join(
   66   cachePath,
   67   'driftctl_' + driftctlVersion,
   68 );
   69 
   70 const driftctlDefaultOptions = ['--no-version-check'];
   71 
   72 let isBinaryDownloaded = false;
   73 
   74 export const generateArgs = async (
   75   options: DriftCTLOptions,
   76   driftIgnore?: string[],
   77 ): Promise<string[]> => {
   78   if (options.kind === 'describe') {
   79     return await generateScanFlags(options as DescribeOptions, driftIgnore);
   80   }
   81 
   82   if (options.kind === 'fmt') {
   83     return generateFmtFlags(options as FmtOptions);
   84   }
   85 
   86   throw 'Unsupported command';
   87 };
   88 
   89 const generateFmtFlags = (options: FmtOptions): string[] => {
   90   const args: string[] = ['fmt', ...driftctlDefaultOptions];
   91 
   92   if (options.json) {
   93     args.push('--output');
   94     args.push('json://stdout');
   95   }
   96 
   97   if (options.html) {
   98     args.push('--output');
   99     args.push('html://stdout');
  100   }
  101 
  102   if (options['html-file-output']) {
  103     args.push('--output');
  104     args.push('html://' + options['html-file-output']);
  105   }
  106 
  107   return args;
  108 };
  109 
  110 const generateScanFlags = async (
  111   options: DescribeOptions,
  112   driftIgnore?: string[],
  113 ): Promise<string[]> => {
  114   const args: string[] = ['scan', ...driftctlDefaultOptions];
  115 
  116   if (options.quiet) {
  117     args.push('--quiet');
  118   }
  119 
  120   if (options.filter) {
  121     args.push('--filter');
  122     args.push(options.filter);
  123   }
  124 
  125   args.push('--output');
  126   args.push('json://stdout');
  127 
  128   if (options['fetch-tfstate-headers']) {
  129     args.push('--headers');
  130     args.push(options['fetch-tfstate-headers']);
  131   }
  132 
  133   if (options['tfc-token']) {
  134     args.push('--tfc-token');
  135     args.push(options['tfc-token']);
  136   }
  137 
  138   if (options['tfc-endpoint']) {
  139     args.push('--tfc-endpoint');
  140     args.push(options['tfc-endpoint']);
  141   }
  142 
  143   if (options['tf-provider-version']) {
  144     args.push('--tf-provider-version');
  145     args.push(options['tf-provider-version']);
  146   }
  147 
  148   if (options.strict) {
  149     args.push('--strict');
  150   }
  151 
  152   if (options['only-unmanaged']) {
  153     args.push('--only-unmanaged');
  154   }
  155 
  156   if (options.driftignore) {
  157     args.push('--driftignore');
  158     args.push(options.driftignore);
  159   }
  160 
  161   if (options['tf-lockfile']) {
  162     args.push('--tf-lockfile');
  163     args.push(options['tf-lockfile']);
  164   }
  165 
  166   if (driftIgnore && driftIgnore.length > 0) {
  167     args.push('--ignore');
  168     args.push(driftIgnore.join(','));
  169   }
  170 
  171   let configDir = cachePath;
  172   await createDirIfNotExists(cachePath);
  173   if (options['config-dir']) {
  174     configDir = options['config-dir'];
  175   }
  176   args.push('--config-dir');
  177   args.push(configDir);
  178 
  179   if (options.from) {
  180     const froms = options.from.split(',');
  181     for (const f of froms) {
  182       args.push('--from');
  183       args.push(f);
  184     }
  185   }
  186 
  187   let to = 'aws+tf';
  188   if (options.to) {
  189     to = options.to;
  190   }
  191   args.push('--to');
  192   args.push(to);
  193 
  194   if (options.service) {
  195     const services = options.service.split(',');
  196     verifyServiceMappingExists(services);
  197     args.push('--ignore');
  198     args.push(createIgnorePattern(services));
  199   }
  200 
  201   debug(args);
  202 
  203   return args;
  204 };
  205 
  206 export function translateExitCode(exitCode: number | null): number {
  207   switch (exitCode) {
  208     case DCTL_EXIT_CODES.EXIT_IN_SYNC:
  209       return 0;
  210     case DCTL_EXIT_CODES.EXIT_NOT_IN_SYNC:
  211       return EXIT_CODES.VULNS_FOUND;
  212     case DCTL_EXIT_CODES.EXIT_ERROR:
  213       return EXIT_CODES.ERROR;
  214     default:
  215       debug('driftctl returned %d', exitCode);
  216       return EXIT_CODES.ERROR;
  217   }
  218 }
  219 
  220 export const runDriftCTL = async ({
  221   options,
  222   driftIgnore,
  223   input,
  224   stdio,
  225 }: {
  226   options: DriftCTLOptions;
  227   driftIgnore?: string[];
  228   input?: string;
  229   stdio?: StdioOptions;
  230 }): Promise<DriftctlExecutionResult> => {
  231   const path = await findOrDownload();
  232 
  233   const args = await generateArgs(options, driftIgnore);
  234 
  235   if (!stdio) {
  236     stdio = ['pipe', 'pipe', 'inherit'];
  237   }
  238 
  239   debug('running driftctl %s ', args.join(' '));
  240 
  241   const dctl_env: NodeJS.ProcessEnv = restoreEnvProxy({
  242     ...process.env,
  243     DCTL_IS_SNYK: 'true',
  244   });
  245 
  246   const p = child_process.spawn(path, args, {
  247     stdio,
  248     env: dctl_env,
  249   });
  250 
  251   let stdout = '';
  252   return new Promise<DriftctlExecutionResult>((resolve, reject) => {
  253     if (input) {
  254       p.stdin?.write(input);
  255       p.stdin?.end();
  256     }
  257     p.on('error', (error) => {
  258       reject(error);
  259     });
  260 
  261     p.stdout?.on('data', (data) => {
  262       stdout += data;
  263     });
  264 
  265     p.on('exit', (code) => {
  266       resolve({ code: translateExitCode(code), stdout });
  267     });
  268   });
  269 };
  270 
  271 async function findOrDownload(): Promise<string> {
  272   let dctl = await findDriftCtl();
  273   if (isBinaryDownloaded) {
  274     return dctl;
  275   }
  276   let downloadDuration = 0;
  277   let binaryExist = true;
  278   if (dctl === '') {
  279     binaryExist = false;
  280     try {
  281       await createDirIfNotExists(cachePath);
  282       dctl = driftctlPath;
  283 
  284       const duration = new TimerMetricInstance('driftctl_download');
  285       duration.start();
  286       await download(driftctlUrl(), dctl);
  287       duration.stop();
  288 
  289       downloadDuration = Math.round((duration.getValue() as number) / 1000);
  290     } catch (err) {
  291       return Promise.reject(err);
  292     }
  293   }
  294   analytics.add('iac-drift-binary-already-exist', binaryExist);
  295   analytics.add('iac-drift-binary-download-duration-seconds', downloadDuration);
  296   isBinaryDownloaded = true;
  297   return dctl;
  298 }
  299 
  300 async function findDriftCtl(): Promise<string> {
  301   // lookup in custom path contained in env var DRIFTCTL_PATH
  302   let dctlPath = config.DRIFTCTL_PATH;
  303   if (dctlPath != null) {
  304     const exists = await isExe(dctlPath);
  305     if (exists) {
  306       debug('Found driftctl in $DRIFTCTL_PATH: %s', dctlPath);
  307       return dctlPath;
  308     }
  309   }
  310 
  311   // lookup in app cache
  312   dctlPath = driftctlPath;
  313   const exists = await isExe(dctlPath);
  314   if (exists) {
  315     debug('Found driftctl in cache: %s', dctlPath);
  316     return dctlPath;
  317   }
  318   debug('driftctl not found');
  319   return '';
  320 }
  321 
  322 async function download(url, destination: string): Promise<boolean> {
  323   debug('downloading driftctl into %s', destination);
  324 
  325   const payload = {
  326     method: 'GET',
  327     url: url,
  328     output: destination,
  329     follow: 3,
  330   };
  331 
  332   await spinner('Downloading...');
  333   return new Promise<boolean>((resolve, reject) => {
  334     makeRequest(payload, function(err, res, body) {
  335       try {
  336         if (err) {
  337           reject(
  338             new Error('Could not download driftctl from ' + url + ': ' + err),
  339           );
  340           return;
  341         }
  342         if (res.statusCode !== 200) {
  343           reject(
  344             new Error(
  345               'Could not download driftctl from ' + url + ': ' + res.statusCode,
  346             ),
  347           );
  348           return;
  349         }
  350 
  351         validateChecksum(body);
  352 
  353         fs.writeFileSync(destination, body);
  354         debug('File saved: ' + destination);
  355 
  356         fs.chmodSync(destination, 0o744);
  357         resolve(true);
  358       } finally {
  359         spinner.clearAll();
  360       }
  361     });
  362   });
  363 }
  364 
  365 function validateChecksum(body: string) {
  366   // only validate if we downloaded the official driftctl binary
  367   if (config.DRIFTCTL_URL || config.DRIFTCTL_PATH) {
  368     return;
  369   }
  370 
  371   const computedHash = crypto
  372     .createHash('sha256')
  373     .update(body)
  374     .digest('hex');
  375   const givenHash = driftctlChecksums[driftctlFileName()];
  376 
  377   if (computedHash != givenHash) {
  378     throw new Error('Downloaded file has inconsistent checksum...');
  379   }
  380 }
  381 
  382 function driftctlFileName(): string {
  383   let platform = 'linux';
  384   switch (os.platform()) {
  385     case 'darwin':
  386       platform = 'darwin';
  387       break;
  388     case 'win32':
  389       platform = 'windows';
  390       break;
  391   }
  392 
  393   let arch = 'amd64';
  394   switch (os.arch()) {
  395     case 'ia32':
  396     case 'x32':
  397       arch = '386';
  398       break;
  399     case 'arm':
  400       arch = 'arm';
  401       break;
  402     case 'arm64':
  403       arch = 'arm64';
  404       break;
  405   }
  406 
  407   let ext = '';
  408   switch (os.platform()) {
  409     case 'win32':
  410       ext = '.exe';
  411       break;
  412   }
  413 
  414   return `driftctl_${platform}_${arch}${ext}`;
  415 }
  416 
  417 function driftctlUrl(): string {
  418   if (config.DRIFTCTL_URL) {
  419     return config.DRIFTCTL_URL;
  420   }
  421 
  422   return `${dctlBaseUrl}/${driftctlVersion}/${driftctlFileName()}`;
  423 }