#!/usr/bin/node const path = require('path'); const fs = require('fs'); const YAML = dougal_require("yaml"); const db = dougal_require("db"); const { deepSet } = dougal_require("utils"); function dougal_require(id) { try { return require(path.join(__dirname, "../lib/www/server/lib", id)); } catch (err) { if (err.code == "MODULE_NOT_FOUND") { console.log("Trying alternative path"); return require(path.join(__dirname, "../lib/www/server/node_modules", id)); } else { console.error(err); throw err; } } } // // https://gitlab.com/wgp/dougal/software/-/work_items/291 // function check_asaqc (cfg) { if (!cfg.cloud?.asaqc?.id) { return apply_cloud_asaqc; } } function apply_cloud_asaqc (cfg) { const asaqc = cfg.asaqc; if (asaqc) { console.log("Applying ASAQC changes"); deepSet(cfg, [ "cloud", "asaqc" ], asaqc); } else { console.log("ASAQC configuration not found. Will create empty ASAQC object"); deepSet(cfg, [ "cloud", "asaqc" ], { id: null, imo: null, mmsi: null }); } return cfg; } // // https://gitlab.com/wgp/dougal/software/-/work_items/296 // function check_asaqc_subscription_key (cfg) { if (!cfg.cloud?.asaqc?.subscriptionKey) { return apply_asaqc_subscription_key; } } function apply_asaqc_subscription_key (cfg) { console.log("Adding subscriptionKey to ASAQC configuration"); const subscriptionKey = process.env.DOUGAL_ASAQC_SUBSCRIPTION_KEY; if (subscriptionKey) { deepSet(cfg, [ "cloud", "asaqc", "subscriptionKey" ] , subscriptionKey); } else { throw new Error("The ASAQC subscription key must be supplied via the DOUGAL_ASAQC_SUBSCRIPTION_KEY environment variable"); } return cfg; } // // https://gitlab.com/wgp/dougal/software/-/work_items/297 // function check_online_line_name_info (cfg) { if (!cfg.online?.line?.lineNameInfo?.fields) { return apply_online_line_name_info; } } function apply_online_line_name_info (cfg) { console.log("Applying online line name info changes"); let lineNameInfo = { example: null, fields: { line: { offset: null, length: 4, type: "int", }, sequence: { offset: null, length: 3, type: "int" }, incr: { offset: null, length: 2, type: "bool", enum: {} }, attempt: { offset: null, length: 1, type: "int" } } }; switch (process.env.HOST) { case "dougal04": lineNameInfo = { "example": "EQ22200-2213130-007", "fields": { "line": { "length": 4, "type": "int", "offset": 10 }, "sequence": { "length": 3, "type": "int", "offset": 16 }, "incr": { "enum": { "1": true, "2": false }, "length": 1, "type": "bool", "offset": 8 }, "attempt": { "length": 1, "type": "int", "offset": 14 }, "file_no": { "length": 3, "type": "int", "offset": 20 }, "year": { "offset": 2, "length": 2, "type": "int" }, "survey_type": { "enum": { "0": "Marine", "2": "OBS/PRM" }, "offset": 4, "length": 1, "default": "Unknown", "type": "str" }, "project_number": { "offset": 5, "length": 2, "type": "int" }, "num_sources": { "enum": { "0": "2", "1": "1", "2": "3" }, "offset": 9, "length": 1, "type": "int" } } } break; case "dougal03": // Don't know what they use break; case "dougal02": case "dougal01": default: // Includes dev servers lineNameInfo = { example: "1054282180S00000", fields: { line: { offset: 2, length: 4, type: "int", }, sequence: { offset: 7, length: 3, type: "int" }, incr: { offset: 0, length: 2, type: "bool", enum: { "10": true, "20": false } }, attempt: { offset: 6, length: 1, type: "int" } } }; } deepSet(cfg, [ "online", "line", "lineNameInfo" ], lineNameInfo); return cfg; } // // https://gitlab.com/wgp/dougal/software/-/work_items/292 // function check_raw_p111_line_name_info (cfg) { if (!cfg.raw?.p111?.lineNameInfo?.fields) { return apply_raw_p111_line_name_info; } } function apply_raw_p111_line_name_info (cfg) { console.log("Applying raw P1/11 name info changes"); let lineNameInfo = { example: null, fields: { line: { offset: null, length: 4, type: "int", }, sequence: { offset: null, length: 3, type: "int" }, incr: { offset: null, length: 2, type: "bool", enum: {} }, attempt: { offset: null, length: 1, type: "int" } } }; switch (process.env.HOST) { case "dougal04": lineNameInfo = { "example": "EQ22200-2213130-007.000.P111", "fields": { "line": { "length": 4, "type": "int", "offset": 10 }, "sequence": { "length": 3, "type": "int", "offset": 16 }, "incr": { "enum": { "1": true, "2": false }, "length": 1, "type": "bool", "offset": 8 }, "attempt": { "length": 1, "type": "int", "offset": 14 }, "file_no": { "length": 3, "type": "int", "offset": 20 }, "year": { "offset": 2, "length": 2, "type": "int" }, "survey_type": { "enum": { "0": "Marine", "2": "OBS/PRM" }, "offset": 4, "length": 1, "default": "Unknown", "type": "str" }, "project_number": { "offset": 5, "length": 2, "type": "int" }, "num_sources": { "enum": { "0": "2", "1": "1", "2": "3" }, "offset": 9, "length": 1, "type": "int" } } } break; case "dougal03": // Don't know what they use break; case "dougal02": case "dougal01": default: // Includes dev servers lineNameInfo = { example: "1054282180S00000.000.p111", fields: { line: { offset: 2, length: 4, type: "int", }, sequence: { offset: 7, length: 3, type: "int" }, incr: { offset: 0, length: 2, type: "bool", enum: { "10": true, "20": false } }, attempt: { offset: 6, length: 1, type: "int" } } }; } deepSet(cfg, [ "raw", "p111", "lineNameInfo" ], lineNameInfo); return cfg; } // // https://gitlab.com/wgp/dougal/software/-/work_items/293 // function check_final_p111_line_name_info (cfg) { if (!cfg.final?.p111?.lineNameInfo?.fields) { return apply_final_p111_line_name_info; } } function apply_final_p111_line_name_info (cfg) { console.log("Applying final P1/11 name info changes"); let lineNameInfo = { example: null, fields: { line: { offset: null, length: 4, type: "int", }, sequence: { offset: null, length: 3, type: "int" }, incr: { offset: null, length: 2, type: "bool", enum: {} }, attempt: { offset: null, length: 1, type: "int" } } }; switch (process.env.HOST) { case "dougal04": lineNameInfo = { "example": "EQ22200-2213130-007.000.P111", "fields": { "line": { "length": 4, "type": "int", "offset": 10 }, "sequence": { "length": 3, "type": "int", "offset": 16 }, "incr": { "enum": { "1": true, "2": false }, "length": 1, "type": "bool", "offset": 8 }, "attempt": { "length": 1, "type": "int", "offset": 14 }, "file_no": { "length": 3, "type": "int", "offset": 20 }, "year": { "offset": 2, "length": 2, "type": "int" }, "survey_type": { "enum": { "0": "Marine", "2": "OBS/PRM" }, "offset": 4, "length": 1, "default": "Unknown", "type": "str" }, "project_number": { "offset": 5, "length": 2, "type": "int" }, "num_sources": { "enum": { "0": "2", "1": "1", "2": "3" }, "offset": 9, "length": 1, "type": "int" } } } break; case "dougal03": // Don't know what they use break; case "dougal02": case "dougal01": default: // Includes dev servers lineNameInfo = { example: "1054282180S00000.000.p111", fields: { line: { offset: 2, length: 4, type: "int", }, sequence: { offset: 7, length: 3, type: "int" }, incr: { offset: 0, length: 2, type: "bool", enum: { "10": true, "20": false } }, attempt: { offset: 6, length: 1, type: "int" } } }; } deepSet(cfg, [ "final", "p111", "lineNameInfo" ], lineNameInfo); return cfg; } // // https://gitlab.com/wgp/dougal/software/-/work_items/294 // function check_smsrc_headers_glob_path (cfg) { if (!cfg.raw?.source?.smsrc?.header?.glob?.length) { return apply_smsrc_headers_glob_path; } } function apply_smsrc_headers_glob_path (cfg) { console.log("Copying Smartsource header glob and path values to new location"); const globs = cfg?.raw?.smsrc?.globs; const paths = cfg?.raw?.smsrc?.paths; if (globs) { deepSet(cfg, [ "raw", "source", "smsrc", "header", "globs" ], globs); } if (paths) { deepSet(cfg, [ "raw", "source", "smsrc", "header", "paths" ], paths); } return cfg; } // // https://gitlab.com/wgp/dougal/software/-/work_items/294 // function check_smsrc_headers_line_name_info (cfg) { if (!cfg.raw?.source?.smsrc?.header?.lineNameInfo?.fields) { return apply_smsrc_headers_line_name_info; } } function apply_smsrc_headers_line_name_info (cfg) { console.log("Applying raw P1/11 name info changes"); let lineNameInfo = { example: null, fields: { line: { offset: null, length: 4, type: "int", }, sequence: { offset: null, length: 3, type: "int" }, incr: { offset: null, length: 2, type: "bool", enum: {} }, attempt: { offset: null, length: 1, type: "int" } } }; switch (process.env.HOST) { case "dougal04": lineNameInfo = { "example": "EQ22200-2213130-007.000.P111", "fields": { "line": { "length": 4, "type": "int", "offset": 10 }, "sequence": { "length": 3, "type": "int", "offset": 16 }, "incr": { "enum": { "1": true, "2": false }, "length": 1, "type": "bool", "offset": 8 }, "attempt": { "length": 1, "type": "int", "offset": 14 }, "file_no": { "length": 3, "type": "int", "offset": 20 }, "year": { "offset": 2, "length": 2, "type": "int" }, "survey_type": { "enum": { "0": "Marine", "2": "OBS/PRM" }, "offset": 4, "length": 1, "default": "Unknown", "type": "str" }, "project_number": { "offset": 5, "length": 2, "type": "int" }, "num_sources": { "enum": { "0": "2", "1": "1", "2": "3" }, "offset": 9, "length": 1, "type": "int" } } } break; case "dougal03": // Don't know what they use break; case "dougal02": case "dougal01": default: // Includes dev servers lineNameInfo = { example: "1054282180S00000.HDR", fields: { line: { offset: 2, length: 4, type: "int", }, sequence: { offset: 7, length: 3, type: "int" }, incr: { offset: 0, length: 2, type: "bool", enum: { "10": true, "20": false } }, attempt: { offset: 6, length: 1, type: "int" } } }; } deepSet(cfg, [ "raw", "source", "smsrc", "header", "lineNameInfo" ], lineNameInfo); return cfg; } // // https://gitlab.com/wgp/dougal/software/-/work_items/295 // function check_smsrc_segy (cfg) { // We only do this on installations where we know there is, or there // might be, SEG-Y data available. const supported_hosts = [ "dougal02", "dougal01" ]; if (supported_hosts.includes(process.env.HOST)) { if (!cfg.raw?.source?.smsrc?.segy?.lineNameInfo?.fields) { return apply_smsrc_segy; } } } function apply_smsrc_segy (cfg) { // We don't need to run a switch() for hosts here, since // we've already done that in check_smsrc_segy(). // Use the paths for *.HDR files as a reference const paths = cfg.raw?.source?.smsrc?.header?.paths?.map( p => path.join(path.dirname(p), "10 SEG-Y")); const globs = [ "**/*-hyd.sgy" ]; const lineNameInfo = { "example": "1051460070S00000-hyd.sgy", "fields": { "sequence": { "length": 3, "type": "int", "offset": 7 }, "line": { "length": 4, "offset": 2 } } }; const segy = { paths, globs, lineNameInfo }; deepSet(cfg, [ "raw", "source", "smsrc", "segy" ], segy); return cfg; } // // https://gitlab.com/wgp/dougal/software/-/work_items/298 // function check_preplots_fields (cfg) { if (cfg.preplots?.length) { const indices = []; for (const idx in cfg.preplots) { const preplot = cfg.preplots[idx]; if (!preplot?.fields?.line_name) { indices.push(idx); } } if (indices.length) { return apply_preplots_fieldsλ(indices); } } } function apply_preplots_fieldsλ (indices) { function fix_preplot (preplot) { const names = preplot.format.names; const types = preplot.format.types; const widths = preplot.format.widths; const offsets_widths = widths.reduce ((acc, cur) => { if (cur < 0) { acc.p -= cur; // Advances the position by -cur } else { acc.f.push({offset: acc.p, width: cur}); acc.p += cur; } return acc; }, {f: [], p: 0}) const fields = {}; names.forEach( (name, ι) => { const field = { type: types[ι], ...offsets_widths[ι] }; fields[name] = field; }); preplot.fields = fields; return preplot; } return function apply_preplots_fields (cfg) { for (const idx of indices) { console.log("Fixing preplot", idx); const preplot = fix_preplot(cfg.preplots[idx]); cfg.preplots.splice(idx, 1, preplot); } } } /* Template for more upgrade actions // // https://gitlab.com/wgp/dougal/software/-/work_items/ // function check_ (cfg) { } function apply_ (cfg) { } */ const checkers = [ check_asaqc, check_asaqc_subscription_key, check_online_line_name_info, check_raw_p111_line_name_info, check_final_p111_line_name_info, check_smsrc_headers_glob_path, check_smsrc_headers_line_name_info, check_smsrc_segy, check_preplots_fields ] const now = new Date(); const tstamp = now.toISOString().substr(0, 19)+"Z"; function fnames(pid) { return { backup: `${pid}-configuration-${tstamp}.yaml`, upgrade: `NEW-${pid}-configuration-${tstamp}.yaml` }; } function save_scripts (pid) { const cwd = process.cwd(); const fn_backup = path.resolve(path.join(cwd, fnames(pid).backup)); const fn_upgrade = path.resolve(path.join(cwd, fnames(pid).upgrade)); console.log("Creating script to restore old / new configurations"); const backup = `# Restore pre-upgrade configuration for ${pid} curl -vs "http://localhost:3000/api/project/${pid}/configuration" -X PUT -H "Content-Type: application/yaml" --data-binary @${fn_backup}\n`; const upgrade = `# Restore post-upgrade configuration for ${pid} curl -vs "http://localhost:3000/api/project/${pid}/configuration" -X PUT -H "Content-Type: application/yaml" --data-binary @${fn_upgrade}\n`; fs.writeFileSync(`restore-20231113-pre-${pid}.sh`, backup); fs.writeFileSync(`restore-20231113-post-${pid}.sh`, upgrade); } async function backup (pid, cfg) { const fname = fnames(pid).backup; console.log(`Backing up configuration for ${pid} as ${fname} into current directory`); const text = YAML.stringify(cfg); fs.writeFileSync(fname, text); } async function save_configuration (pid, cfg) { console.log("Saving configuration for", pid); console.log("Saving copy of NEW configuration to file"); const fname = fnames(pid).upgrade; const text = YAML.stringify(cfg); fs.writeFileSync(fname, text); save_scripts(pid); //console.log("Uploading configuration to server"); try { //await db.project.configuration.put(pid); } catch (err) { console.log("Configuration upload failed"); console.error(err); throw err; } } async function upgrade_configuration (pid) { const configuration = await db.project.configuration.get(pid); console.log(`Checking configuration for ${configuration.id} (${configuration.schema})`); const appliers = checkers.map( checker => checker(configuration) ).filter( i => !!i ); if (appliers.length) { console.log("Configuration needs changes."); await backup(pid, configuration); console.log("Applying changes"); console.log(appliers); for (const applier of appliers) { applier(configuration); } await save_configuration(pid, configuration); } } async function main () { const cla = process.argv.slice(2).map(i => i.toLowerCase()); function project_filter (project) { if (cla.length == 0) return true; return cla.includes(project.pid.toLowerCase()); } const projects = (await db.project.get()).filter(project_filter); projects.sort( (a, b) => a.pid > b.pid ? 1 : a.pid < b.pid ? -1 : 0); console.log(projects); for (const project of projects) { await upgrade_configuration(project.pid); } console.log("All done"); process.exit(0); } main();