From 6828f8fb2cef3a240f81f021e4e01f4199323a14 Mon Sep 17 00:00:00 2001 From: Stone Date: Wed, 5 Apr 2023 15:52:38 -0400 Subject: [PATCH 1/4] Add query view create/delete, document index attribute creation --- README.md | 42 ++++++++++++++ bin/morgue.js | 153 +++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 3 files changed, 188 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f4b0308..afe3f2c 100644 --- a/README.md +++ b/README.md @@ -1514,3 +1514,45 @@ For example: ``` morgue actions upload --project myproj myconfig.json ``` + + +## Attributes + +Morgue can be used to create or delete project index attributes. + +The provided commands are as follows: +- `morgue attribute create --description --type --format ` : Create a project index attribute +- `morgue attribute delete ` : Delete a project index attribute + +Formats +- `bitmap`, `uint8`, `uint16`, `uint32`, `uint64`, `uint128`, `uuid`, `dictionary` + +Types +- `none`, `commit`, `semver`, `callstack`, `hostname`, `bytes`, `kilobytes`, `gigabytes`, `nanoseconds`, `milliseconds`, `seconds`, `unix_timestamp`, `js_timestamp`, `gps_timestamp`, `memory_address`, `labels`, `sha256`, `uuid`, `ipv4`, `ipv6` + +For example: + +``` +morgue attribute create myProject myAttribute --description='My Description' --type='uint64' --format='bytes' +``` +``` +morgue attribute delete myProject myAttribute +``` + +## Views + +Morgue can be used to create or delete project query views. + +The provided commands are as follows: + +- `morgue view create ` : Create a query view. +- `morgue view delete ` : Delete query view. + +For example: + +``` +morgue view create myProject myQueryViewName --queries=queries.json --payload=payload.json +``` +``` +morgue view delete myProject myQueryViewName +``` \ No newline at end of file diff --git a/bin/morgue.js b/bin/morgue.js index fbeea7b..33e428b 100755 --- a/bin/morgue.js +++ b/bin/morgue.js @@ -195,6 +195,7 @@ var commands = { actions: coronerActions, attachment: coronerAttachment, attribute: coronerAttribute, + view: coronerView, audit: coronerAudit, log: coronerLog, bpg: coronerBpg, @@ -4573,15 +4574,103 @@ function subcmdProcess(argv, config, opts) { opts.usageFn("Invalid subcommand '" + subcmd + "'."); } +function viewUsageFn(str){ + if (str) { + err(str + "\n"); + } + console.error("Usage: morgue view "); + process.exit(1); +} + +function viewSetupFn(config, argv, opts, subcmd) { + if (argv.length < 4) { + return attributeUsageFn("Incomplete command."); + } + + opts.params = { + attrname: argv._[2], + }; + if (!opts.params.attrname) + return viewUsageFn("Missing attribute name."); + + opts.state.bpg = coronerBpgSetup(opts.state.coroner, argv); + opts.state.model = opts.state.bpg.get(); + opts.state.context = coronerParams(argv, config); + + const ctx = opts.state.context; + opts.state.universe = opts.state.model.universe.find((univ) => { + return univ.fields.name === ctx.universe; + }); + if (!opts.state.universe) + return viewUsageFn(`Universe ${ctx.universe} not found.`); + opts.state.project = opts.state.model.project.find((proj) => + proj.fields.universe === opts.state.universe.fields.id && + proj.fields.name === ctx.project + ); + if (!opts.state.project) { + return viewUsageFn( `Project ${ctx.universe}/${ctx.project} not found.`); + } + + if (subcmd !== 'create') { + opts.state.query = opts.state.model.query.find((query) => + query.fields.name === opts.params.attrname + ); + if (!opts.state.query) + return viewUsageFn("View not found."); + opts.state.attr_key = { + project: opts.state.query.fields.project, + name: opts.state.query.fields.name, + }; + } +} + function attributeUsageFn(str) { + const formats = [ + 'none', + 'commit', + 'semver', + 'callstack', + 'hostname', + 'bytes', + 'kilobytes', + 'gigabytes', + 'nanoseconds', + 'milliseconds', + 'seconds', + 'unix_timestamp', + 'js_timestamp', + 'gps_timestamp', + 'memory_address', + 'labels', + 'sha256', + 'uuid', + 'ipv4', + 'ipv6', + ] + + const types = [ + 'bitmap', + 'uint8', + 'uint16', + 'uint32', + 'uint64', + 'uint128', + 'uuid', + 'dictionary', + ] + if (str) err(str + "\n"); console.error("Usage: morgue attribute [options]"); console.error(""); - console.error("Options for create (all but format are required):"); - console.error(" --type=T Specify type."); + console.error("Options for create (all but format are required): "); console.error(" --description=D Specify description."); - console.error(" --format=F Specify formatting hint."); + console.error(" --type=T Specify type. Can be of the following: "); + console.error(" " + types.join(', ')) + console.error(" --format=F Specify formatting hint. Can be of the following: "); + console.error(" " + formats.slice(0, 10).join(', ') + ', ') + console.error(" " + formats.slice(10).join(', ')) + process.exit(1); } @@ -4606,10 +4695,10 @@ function attributeSetupFn(config, argv, opts, subcmd) { }); if (!opts.state.universe) return attributeUsageFn(`Universe ${ctx.universe} not found.`); - opts.state.project = opts.state.model.project.find((proj) => { - return proj.fields.universe === opts.state.universe.fields.id && - proj.fields.name === ctx.project; - }); + opts.state.project = opts.state.model.project.find((proj) => { + return proj.fields.universe === opts.state.universe.fields.id && + proj.fields.name === ctx.project; + }); if (!opts.state.project) { return attributeUsageFn( `Project ${ctx.universe}/${ctx.project} not found.`); } @@ -4656,7 +4745,44 @@ function attributeSet(argv, config, opts) { }, }); - bpgPost(state.bpg, request, bpgCbFn('Attribute', 'update')); + bpgPost(state.bpg, request, bpgCbFn('View', 'update')); +} + +function viewCreate(argv, config, opts) { + const state = opts.state; + + if (!argv.queries) return viewUsageFn("Must specify queries."); + if (!argv.payload) return viewUsageFn("Must specify payload."); + + // json parse or keep input as json + const queries = typeof argv.queries === 'string' ? JSON.parse(argv.queries) : argv.queries + const payload = typeof argv.payload === 'string' ? JSON.parse(argv.payload) : argv.payload + // update name + payload.name = opts.params.attrname + + const request = bpgSingleRequest({ + action: "create", + type: "configuration/query", + object: { + name: opts.params.attrname, + project: state.project.fields.pid, + owner: config.config.uid, + queries: JSON.stringify(queries), + payload: JSON.stringify(payload), + }, + }); + + bpgPost(state.bpg, request, bpgCbFn('View', 'create')); +} + +function viewDelete(argv, config, opts) { + const state = opts.state; + const request = bpgSingleRequest({ + action: "delete", + type: "configuration/query", + key: state.attr_key, + }); + bpgPost(state.bpg, request, bpgCbFn('View', 'delete')); } function attributeDelete(argv, config, opts) { @@ -4702,6 +4828,17 @@ function coronerAttribute(argv, config) { }); } +function coronerView(argv, config) { + subcmdProcess(argv, config, { + usageFn: viewUsageFn, + setupFn: viewSetupFn, + subcmds: { + create: viewCreate, + delete: viewDelete, + }, + }); +} + /* * Print frames in j, relative to availability in k. Different functions * are bolded accordingly. diff --git a/package.json b/package.json index 87ae151..15880fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "backtrace-morgue", - "version": "1.21.7", + "version": "1.21.8", "description": "command line interface to the Backtrace object store", "main": "./lib/coroner.js", "keywords": [ From 4b437ddb61b251900b09478cd5b6fb9bc0a6b0b8 Mon Sep 17 00:00:00 2001 From: Stone Date: Wed, 5 Apr 2023 16:41:47 -0400 Subject: [PATCH 2/4] Fix version number, fix log message --- bin/morgue.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/morgue.js b/bin/morgue.js index fe64b0c..afefdb8 100755 --- a/bin/morgue.js +++ b/bin/morgue.js @@ -4808,7 +4808,7 @@ function attributeSet(argv, config, opts) { }, }); - bpgPost(state.bpg, request, bpgCbFn('View', 'update')); + bpgPost(state.bpg, request, bpgCbFn('Attribute', 'update')); } function viewCreate(argv, config, opts) { diff --git a/package.json b/package.json index cf6c3bb..6afa26c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "backtrace-morgue", - "version": "1.23.1", + "version": "1.23.0", "description": "command line interface to the Backtrace object store", "main": "./lib/coroner.js", "keywords": [ From 11ef3586ebf6bbb34bb1e46debaaebcc13ae2b36 Mon Sep 17 00:00:00 2001 From: Stone Date: Wed, 5 Apr 2023 16:46:48 -0400 Subject: [PATCH 3/4] Validate input values, fix indentation --- bin/morgue.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bin/morgue.js b/bin/morgue.js index afefdb8..ca2928e 100755 --- a/bin/morgue.js +++ b/bin/morgue.js @@ -4758,10 +4758,10 @@ function attributeSetupFn(config, argv, opts, subcmd) { }); if (!opts.state.universe) return attributeUsageFn(`Universe ${ctx.universe} not found.`); - opts.state.project = opts.state.model.project.find((proj) => { - return proj.fields.universe === opts.state.universe.fields.id && - proj.fields.name === ctx.project; - }); + opts.state.project = opts.state.model.project.find((proj) => { + return proj.fields.universe === opts.state.universe.fields.id && + proj.fields.name === ctx.project; + }); if (!opts.state.project) { return attributeUsageFn( `Project ${ctx.universe}/${ctx.project} not found.`); } @@ -4816,6 +4816,8 @@ function viewCreate(argv, config, opts) { if (!argv.queries) return viewUsageFn("Must specify queries."); if (!argv.payload) return viewUsageFn("Must specify payload."); + if(!state.project.fields.pid) return viewUsageFn("Invalid Project."); + if(!config.config.uid) return viewUsageFn("Invalid user."); // json parse or keep input as json const queries = typeof argv.queries === 'string' ? JSON.parse(argv.queries) : argv.queries From d1203d2abb047d8ca0e12e0cb5b3cc5430f14c69 Mon Sep 17 00:00:00 2001 From: Stone Date: Wed, 5 Apr 2023 16:56:59 -0400 Subject: [PATCH 4/4] Improve input validation --- bin/morgue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/morgue.js b/bin/morgue.js index ca2928e..c109609 100755 --- a/bin/morgue.js +++ b/bin/morgue.js @@ -4816,7 +4816,7 @@ function viewCreate(argv, config, opts) { if (!argv.queries) return viewUsageFn("Must specify queries."); if (!argv.payload) return viewUsageFn("Must specify payload."); - if(!state.project.fields.pid) return viewUsageFn("Invalid Project."); + if(!state.project.fields && !state.project.fields.pid) return viewUsageFn("Invalid Project."); if(!config.config.uid) return viewUsageFn("Invalid user."); // json parse or keep input as json