From 2c94c8312233a40a536d0f98ca513e2bbe8bf720 Mon Sep 17 00:00:00 2001 From: Brandt Keller <43887158+brandtkeller@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:27:50 -0700 Subject: [PATCH] feat(configuration): add initial support for Viper command initialization (#607) * feat(config): initial common package for cmds with viper * feat(viper): begin integrating viper into commands * feat(viper): print config used * feat(template): initial templating of a validation * feat(template): testing functions in templating * fix(e2e): update function use for proper arguments * chore(docs): update docs and cleanup * fix(dev): cleanup proto templating logic * fix(pkg): cleanup the templating proto work * fix(cli): update hooks for nested commands * fix(docs): Update src/cmd/common/viper.go Co-authored-by: Megan Wolf <97549300+meganwolf0@users.noreply.github.com> * fix(dev): update alias for dev to d * chore(deps): run go mod tidy to resolve updates to deps --------- Co-authored-by: Megan Wolf <97549300+meganwolf0@users.noreply.github.com> --- demo/simple/oscal-component-opa.yaml | 10 +-- docs/reference/configuration.md | 20 +++++ go.mod | 15 +++- go.sum | 33 +++++++ lula-config.yaml | 1 + src/cmd/common/setup.go | 42 +++++++++ src/cmd/common/viper.go | 95 ++++++++++++++++++++ src/cmd/dev/common.go | 10 ++- src/cmd/dev/get-resources.go | 68 +++++++------- src/cmd/dev/lint.go | 15 ++-- src/cmd/dev/validate.go | 128 +++++++++++++------------- src/cmd/evaluate/evaluate.go | 12 ++- src/cmd/generate/generate.go | 18 ++-- src/cmd/root.go | 38 ++------ src/cmd/tools/common.go | 8 ++ src/cmd/tools/compose.go | 58 ++++++------ src/cmd/tools/lint.go | 129 +++++++++++++-------------- src/cmd/tools/upgrade.go | 81 +++++++++-------- src/cmd/tools/uuid.go | 2 +- src/cmd/validate/validate.go | 13 ++- src/test/e2e/dev_validation_test.go | 2 - 21 files changed, 501 insertions(+), 297 deletions(-) create mode 100644 docs/reference/configuration.md create mode 100644 lula-config.yaml create mode 100644 src/cmd/common/setup.go create mode 100644 src/cmd/common/viper.go diff --git a/demo/simple/oscal-component-opa.yaml b/demo/simple/oscal-component-opa.yaml index 2afc71b5..b360dbc7 100644 --- a/demo/simple/oscal-component-opa.yaml +++ b/demo/simple/oscal-component-opa.yaml @@ -47,13 +47,13 @@ component-definition: type: kubernetes kubernetes-spec: resources: - - name: podsvt - resource-rule: - group: + - name: podsvt + resource-rule: + group: version: v1 resource: pods - namespaces: [validation-test] - provider: + namespaces: [validation-test] + provider: type: opa opa-spec: rego: | diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md new file mode 100644 index 00000000..40632156 --- /dev/null +++ b/docs/reference/configuration.md @@ -0,0 +1,20 @@ +# Config File + +Lula allows the use and specification of a config file in the following ways: +- Checking current working directory for a `lula-config.yaml` file +- Specification with environment variable `LULA_CONFIG=` + +## Identification + +If identified, Lula will log which configuration file is used to stdout: +```bash +Using config file /home/dev/work/lula/lula-config.yaml +``` +## Support + +Modification of `log level` can be set in the configuration file by specifying: + +lula-config.yaml +```yaml +log_level: debug +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 3b8ed819..260fe124 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/pterm/pterm v0.12.79 github.com/sergi/go-diff v1.3.1 github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.18.2 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.30.3 k8s.io/apimachinery v0.30.3 @@ -44,6 +45,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/camelcase v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-ini/ini v1.67.0 // indirect @@ -66,6 +68,7 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/copier v0.4.0 // indirect @@ -79,9 +82,11 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lithammer/dedent v1.1.0 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -90,6 +95,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.19.1 // indirect @@ -99,10 +105,15 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.4.6 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/vladimirvivien/gexe v0.2.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -116,6 +127,7 @@ require ( go.opentelemetry.io/otel/sdk v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.starlark.net v0.0.0-20240123142251-f86470692795 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.27.0 // indirect @@ -129,6 +141,7 @@ require ( google.golang.org/protobuf v1.34.2 // indirect gopkg.in/evanphx/json-patch.v5 v5.9.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/component-base v0.30.3 // indirect k8s.io/component-helpers v0.30.3 // indirect diff --git a/go.sum b/go.sum index 27c287c8..4529548d 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,10 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= @@ -167,6 +171,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -209,6 +215,8 @@ github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffkt github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -218,6 +226,8 @@ github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -241,6 +251,8 @@ github.com/open-policy-agent/opa v0.67.1 h1:rzy26J6g1X+CKknAcx0Vfbt41KqjuSzx4E0A github.com/open-policy-agent/opa v0.67.1/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -274,6 +286,10 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -285,19 +301,34 @@ github.com/smarty/assertions v1.15.1 h1:812oFiXI+G55vxsFf+8bIZ1ux30qtkdqzKbEFwyX github.com/smarty/assertions v1.15.1/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/vladimirvivien/gexe v0.2.0 h1:nbdAQ6vbZ+ZNsolCgSVb9Fno60kzSuvtzVh6Ytqi/xY= @@ -442,6 +473,8 @@ gopkg.in/evanphx/json-patch.v5 v5.9.0 h1:hx1VU2SGj4F8r9b8GUwJLdc8DNO8sy79ZGui0G0 gopkg.in/evanphx/json-patch.v5 v5.9.0/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/lula-config.yaml b/lula-config.yaml new file mode 100644 index 00000000..6a44c079 --- /dev/null +++ b/lula-config.yaml @@ -0,0 +1 @@ +log_level: debug \ No newline at end of file diff --git a/src/cmd/common/setup.go b/src/cmd/common/setup.go new file mode 100644 index 00000000..fd662cba --- /dev/null +++ b/src/cmd/common/setup.go @@ -0,0 +1,42 @@ +package common + +import ( + "os" + + "github.com/defenseunicorns/lula/src/config" + "github.com/defenseunicorns/lula/src/pkg/message" +) + +func SetupClI(logLevel string) error { + + match := map[string]message.LogLevel{ + "warn": message.WarnLevel, + "info": message.InfoLevel, + "debug": message.DebugLevel, + "trace": message.TraceLevel, + } + + // No log level set, so use the default + if logLevel != "" { + if lvl, ok := match[logLevel]; ok { + message.SetLogLevel(lvl) + message.Info("Log level set to " + logLevel) + } else { + message.Warn("Invalid log level. Valid options are: warn, info, debug, trace.") + } + } + + // Disable progress bars for CI envs + if os.Getenv("CI") == "true" { + message.Debug("CI environment detected, disabling progress bars") + message.NoProgress = true + } + + if !config.SkipLogFile { + message.UseLogFile() + } + + printViperConfigUsed() + + return nil +} diff --git a/src/cmd/common/viper.go b/src/cmd/common/viper.go new file mode 100644 index 00000000..9433e520 --- /dev/null +++ b/src/cmd/common/viper.go @@ -0,0 +1,95 @@ +package common + +import ( + "errors" + "os" + "strings" + + "github.com/defenseunicorns/lula/src/pkg/message" + "github.com/spf13/viper" +) + +const ( + VLogLevel = "log_level" +) + +var ( + // Viper instance used by commands + v *viper.Viper + + // Viper configuration error + vConfigError error +) + +// InitViper initializes the viper singleton for the CLI +func InitViper() *viper.Viper { + // Already initialized by some other command + if v != nil { + return v + } + + v = viper.New() + + // Skip for the version command + if isVersionCmd() { + return v + } + + // Specify an alternate config file + cfgFile := os.Getenv("LULA_CONFIG") + + // Don't forget to read config either from cfgFile or from home directory! + if cfgFile != "" { + // Use config file from the flag. + v.SetConfigFile(cfgFile) + } else { + // Search config paths in the current directory and $HOME/.lula. + v.AddConfigPath(".") + v.AddConfigPath("$HOME/.lula") + v.SetConfigName("lula-config") + } + + // E.g. LULA_LOG_LEVEL=debug + v.SetEnvPrefix("lula") + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.AutomaticEnv() + + // Optional, so ignore errors + vConfigError = v.ReadInConfig() + + // Set default values for viper + setDefaults() + + return v +} + +// GetViper returns the viper singleton +func GetViper() *viper.Viper { + return v +} + +func isVersionCmd() bool { + args := os.Args + return len(args) > 1 && (args[1] == "version" || args[1] == "v") +} + +func setDefaults() { + v.SetDefault(VLogLevel, "info") +} + +func printViperConfigUsed() { + // Only print config info if viper is initialized. + vInitialized := v != nil + if !vInitialized { + return + } + var notFoundErr viper.ConfigFileNotFoundError + if errors.As(vConfigError, ¬FoundErr) { + return + } + if vConfigError != nil { + message.WarnErrf(vConfigError, "failed to load config file: %s", vConfigError.Error()) + return + } + message.Notef("Using config file %s", v.ConfigFileUsed()) +} diff --git a/src/cmd/dev/common.go b/src/cmd/dev/common.go index 695facb5..2cf406be 100644 --- a/src/cmd/dev/common.go +++ b/src/cmd/dev/common.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/defenseunicorns/lula/src/config" "github.com/defenseunicorns/lula/src/pkg/common" "github.com/defenseunicorns/lula/src/pkg/message" "github.com/defenseunicorns/lula/src/types" @@ -19,8 +20,15 @@ const DEFAULT_TIMEOUT = 1 var devCmd = &cobra.Command{ Use: "dev", - Aliases: []string{"t"}, + Aliases: []string{"d"}, Short: "Collection of dev commands to make dev life easier", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + config.SkipLogFile = true + // Call the parent's (root) PersistentPreRun + if parentPreRun := cmd.Parent().PersistentPreRun; parentPreRun != nil { + parentPreRun(cmd.Parent(), args) + } + }, } type flags struct { diff --git a/src/cmd/dev/get-resources.go b/src/cmd/dev/get-resources.go index 277b334e..a3e2ac4a 100644 --- a/src/cmd/dev/get-resources.go +++ b/src/cmd/dev/get-resources.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/defenseunicorns/lula/src/config" + "github.com/defenseunicorns/lula/src/cmd/common" "github.com/defenseunicorns/lula/src/pkg/message" "github.com/defenseunicorns/lula/src/types" "github.com/spf13/cobra" @@ -28,40 +28,40 @@ To hang for timeout of 5 seconds: lula get-resources -t 5 ` +var getResourcesCmd = &cobra.Command{ + Use: "get-resources", + Short: "Get Resources from a Lula Validation Manifest", + Long: "Get the JSON resources specified in a Lula Validation Manifest", + Example: getResourcesHelp, + Run: func(cmd *cobra.Command, args []string) { + spinnerMessage := fmt.Sprintf("Getting Resources from %s", getResourcesOpts.InputFile) + spinner := message.NewProgressSpinner(spinnerMessage) + defer spinner.Stop() + + ctx := context.Background() + var validationBytes []byte + var err error + + // Read the validation data from STDIN or provided file + validationBytes, err = ReadValidation(cmd, spinner, getResourcesOpts.InputFile, getResourcesOpts.Timeout) + if err != nil { + message.Fatalf(err, "error reading validation: %v", err) + } + + collection, err := DevGetResources(ctx, validationBytes, spinner) + if err != nil { + message.Fatalf(err, "error running dev get-resources: %v", err) + } + + writeResources(collection, getResourcesOpts.OutputFile) + + spinner.Success() + }, +} + func init() { - getResourcesCmd := &cobra.Command{ - Use: "get-resources", - Short: "Get Resources from a Lula Validation Manifest", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - config.SkipLogFile = true - }, - Long: "Get the JSON resources specified in a Lula Validation Manifest", - Example: getResourcesHelp, - Run: func(cmd *cobra.Command, args []string) { - spinnerMessage := fmt.Sprintf("Getting Resources from %s", getResourcesOpts.InputFile) - spinner := message.NewProgressSpinner(spinnerMessage) - defer spinner.Stop() - - ctx := context.Background() - var validationBytes []byte - var err error - - // Read the validation data from STDIN or provided file - validationBytes, err = ReadValidation(cmd, spinner, getResourcesOpts.InputFile, getResourcesOpts.Timeout) - if err != nil { - message.Fatalf(err, "error reading validation: %v", err) - } - - collection, err := DevGetResources(ctx, validationBytes, spinner) - if err != nil { - message.Fatalf(err, "error running dev get-resources: %v", err) - } - - writeResources(collection, getResourcesOpts.OutputFile) - - spinner.Success() - }, - } + + common.InitViper() devCmd.AddCommand(getResourcesCmd) diff --git a/src/cmd/dev/lint.go b/src/cmd/dev/lint.go index 5627be9f..8995cade 100644 --- a/src/cmd/dev/lint.go +++ b/src/cmd/dev/lint.go @@ -5,8 +5,8 @@ import ( "strings" oscalValidation "github.com/defenseunicorns/go-oscal/src/pkg/validation" - "github.com/defenseunicorns/lula/src/config" - "github.com/defenseunicorns/lula/src/pkg/common" + "github.com/defenseunicorns/lula/src/cmd/common" + pkgCommon "github.com/defenseunicorns/lula/src/pkg/common" "github.com/defenseunicorns/lula/src/pkg/common/network" "github.com/defenseunicorns/lula/src/pkg/message" "github.com/spf13/cobra" @@ -25,11 +25,8 @@ To lint existing validation files: ` var lintCmd = &cobra.Command{ - Use: "lint", - Short: "Lint validation files against schema", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - config.SkipLogFile = true - }, + Use: "lint", + Short: "Lint validation files against schema", Long: "Validate validation files are properly configured against the schema, file paths can be local or URLs (https://)", Example: lintHelp, Run: func(cmd *cobra.Command, args []string) { @@ -87,7 +84,7 @@ func DevLintCommand(inputFiles []string) []oscalValidation.ValidationResult { break } - validations, err := common.ReadValidationsFromYaml(validationBytes) + validations, err := pkgCommon.ReadValidationsFromYaml(validationBytes) if err != nil { handleFail(err) break @@ -119,6 +116,8 @@ func DevLintCommand(inputFiles []string) []oscalValidation.ValidationResult { func init() { + common.InitViper() + devCmd.AddCommand(lintCmd) lintCmd.Flags().StringSliceVarP(&lintOpts.InputFiles, "input-files", "f", []string{}, "the paths to validation files (comma-separated)") diff --git a/src/cmd/dev/validate.go b/src/cmd/dev/validate.go index 40d9637e..b99c40fc 100644 --- a/src/cmd/dev/validate.go +++ b/src/cmd/dev/validate.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/defenseunicorns/go-oscal/src/pkg/files" - "github.com/defenseunicorns/lula/src/config" - "github.com/defenseunicorns/lula/src/pkg/common" + "github.com/defenseunicorns/lula/src/cmd/common" + pkgCommon "github.com/defenseunicorns/lula/src/pkg/common" "github.com/defenseunicorns/lula/src/pkg/message" "github.com/defenseunicorns/lula/src/types" "github.com/spf13/cobra" @@ -38,76 +38,76 @@ type ValidateFlags struct { var validateOpts = &ValidateFlags{} -func init() { - validateCmd := &cobra.Command{ - Use: "validate", - Short: "Run an individual Lula validation.", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - config.SkipLogFile = true - }, - Long: "Run an individual Lula validation for quick testing and debugging of a Lula Validation. This command is intended for development purposes only.", - Example: validateHelp, - Run: func(cmd *cobra.Command, args []string) { - spinnerMessage := fmt.Sprintf("Validating %s", validateOpts.InputFile) - spinner := message.NewProgressSpinner(spinnerMessage) - defer spinner.Stop() - - ctx := context.Background() - var validationBytes []byte - var resourcesBytes []byte - var err error - - // Read the validation data from STDIN or provided file - validationBytes, err = ReadValidation(cmd, spinner, validateOpts.InputFile, validateOpts.Timeout) - if err != nil { - message.Fatalf(err, "error reading validation: %v", err) - } +var validateCmd = &cobra.Command{ + Use: "validate", + Short: "Run an individual Lula validation.", + Long: "Run an individual Lula validation for quick testing and debugging of a Lula Validation. This command is intended for development purposes only.", + Example: validateHelp, + Run: func(cmd *cobra.Command, args []string) { + spinnerMessage := fmt.Sprintf("Validating %s", validateOpts.InputFile) + spinner := message.NewProgressSpinner(spinnerMessage) + defer spinner.Stop() + + ctx := context.Background() + var validationBytes []byte + var resourcesBytes []byte + var err error + + // Read the validation data from STDIN or provided file + validationBytes, err = ReadValidation(cmd, spinner, validateOpts.InputFile, validateOpts.Timeout) + if err != nil { + message.Fatalf(err, "error reading validation: %v", err) + } - // Reset the spinner message - spinner.Updatef(spinnerMessage) - - // If a resources file is provided, read the resources file - if validateOpts.ResourcesFile != "" { - if !strings.HasSuffix(validateOpts.ResourcesFile, ".json") { - message.Fatalf(fmt.Errorf("resource file must be a json file"), "resource file must be a json file") - } else { - // Read the resources data - resourcesBytes, err = common.ReadFileToBytes(validateOpts.ResourcesFile) - if err != nil { - message.Fatalf(err, "error reading file: %v", err) - } + // Reset the spinner message + spinner.Updatef(spinnerMessage) + + // If a resources file is provided, read the resources file + if validateOpts.ResourcesFile != "" { + if !strings.HasSuffix(validateOpts.ResourcesFile, ".json") { + message.Fatalf(fmt.Errorf("resource file must be a json file"), "resource file must be a json file") + } else { + // Read the resources data + resourcesBytes, err = pkgCommon.ReadFileToBytes(validateOpts.ResourcesFile) + if err != nil { + message.Fatalf(err, "error reading file: %v", err) } } + } - validation, err := DevValidate(ctx, validationBytes, resourcesBytes, spinner) - if err != nil { - message.Fatalf(err, "error running dev validate: %v", err) - } + validation, err := DevValidate(ctx, validationBytes, resourcesBytes, spinner) + if err != nil { + message.Fatalf(err, "error running dev validate: %v", err) + } - // Write the validation result to a file if an output file is provided - // Otherwise, print the result to the debug console - err = writeValidation(validation, validateOpts.OutputFile) - if err != nil { - message.Fatalf(err, "error writing result: %v", err) - } + // Write the validation result to a file if an output file is provided + // Otherwise, print the result to the debug console + err = writeValidation(validation, validateOpts.OutputFile) + if err != nil { + message.Fatalf(err, "error writing result: %v", err) + } - // Print observations if there are any - if len(validation.Result.Observations) > 0 { - message.Infof("Observations:") - for key, observation := range validation.Result.Observations { - message.Infof("--> %s: %s", key, observation) - } + // Print observations if there are any + if len(validation.Result.Observations) > 0 { + message.Infof("Observations:") + for key, observation := range validation.Result.Observations { + message.Infof("--> %s: %s", key, observation) } + } - result := validation.Result.Passing > 0 && validation.Result.Failing <= 0 - // If the expected result is not equal to the actual result, return an error - if validateOpts.ExpectedResult != result { - message.Fatalf(fmt.Errorf("validation failed"), "expected result to be %t got %t", validateOpts.ExpectedResult, result) - } - // Print the number of passing and failing results - message.Infof("Validation completed with %d passing and %d failing results", validation.Result.Passing, validation.Result.Failing) - }, - } + result := validation.Result.Passing > 0 && validation.Result.Failing <= 0 + // If the expected result is not equal to the actual result, return an error + if validateOpts.ExpectedResult != result { + message.Fatalf(fmt.Errorf("validation failed"), "expected result to be %t got %t", validateOpts.ExpectedResult, result) + } + // Print the number of passing and failing results + message.Infof("Validation completed with %d passing and %d failing results", validation.Result.Passing, validation.Result.Failing) + }, +} + +func init() { + + common.InitViper() devCmd.AddCommand(validateCmd) diff --git a/src/cmd/evaluate/evaluate.go b/src/cmd/evaluate/evaluate.go index 8e643065..351611a0 100644 --- a/src/cmd/evaluate/evaluate.go +++ b/src/cmd/evaluate/evaluate.go @@ -6,7 +6,8 @@ import ( "github.com/defenseunicorns/go-oscal/src/pkg/files" oscalTypes_1_1_2 "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-2" - "github.com/defenseunicorns/lula/src/pkg/common" + "github.com/defenseunicorns/lula/src/cmd/common" + pkgCommon "github.com/defenseunicorns/lula/src/pkg/common" "github.com/defenseunicorns/lula/src/pkg/common/oscal" "github.com/defenseunicorns/lula/src/pkg/common/result" "github.com/defenseunicorns/lula/src/pkg/message" @@ -51,13 +52,16 @@ var evaluateCmd = &cobra.Command{ }, } -func EvaluateCommand() *cobra.Command { +func init() { + common.InitViper() evaluateCmd.Flags().StringSliceVarP(&opts.InputFile, "input-file", "f", []string{}, "Path to the file to be evaluated") evaluateCmd.MarkFlagRequired("input-file") evaluateCmd.Flags().StringVarP(&opts.Target, "target", "t", "", "the specific control implementations or framework to validate against") evaluateCmd.Flags().BoolVarP(&opts.summary, "summary", "s", false, "Print a summary of the evaluation") - // insert flag options here +} + +func EvaluateCommand() *cobra.Command { return evaluateCmd } @@ -211,7 +215,7 @@ func readManyAssessmentResults(fileArray []string) (map[string]*oscalTypes_1_1_2 return nil, fmt.Errorf("invalid file extension: %s, requires .json or .yaml", fileString) } - data, err := common.ReadFileToBytes(fileString) + data, err := pkgCommon.ReadFileToBytes(fileString) if err != nil { return nil, err } diff --git a/src/cmd/generate/generate.go b/src/cmd/generate/generate.go index cc3952a8..4ef0ebe7 100644 --- a/src/cmd/generate/generate.go +++ b/src/cmd/generate/generate.go @@ -5,6 +5,7 @@ import ( "strings" oscalTypes_1_1_2 "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-2" + "github.com/defenseunicorns/lula/src/cmd/common" "github.com/defenseunicorns/lula/src/pkg/common/network" "github.com/defenseunicorns/lula/src/pkg/common/oscal" "github.com/defenseunicorns/lula/src/pkg/message" @@ -174,28 +175,31 @@ var generateComponentCmd = &cobra.Command{ // }, // } -func GenerateCommand() *cobra.Command { +func init() { + + common.InitViper() generateCmd.AddCommand(generateComponentCmd) // generateCmd.AddCommand(generateAssessmentPlanCmd) // generateCmd.AddCommand(generateSystemSecurityPlanCmd) // generateCmd.AddCommand(generatePOAMCmd) - generateFlags() - generateComponentFlags() + bindGenerateFlags() + bindGenerateComponentFlags() + +} +func GenerateCommand() *cobra.Command { return generateCmd } -func generateFlags() { +func bindGenerateFlags() { generateFlags := generateCmd.PersistentFlags() - generateFlags.StringVarP(&opts.InputFile, "input-file", "f", "", "Path to a manifest file") generateFlags.StringVarP(&opts.OutputFile, "output-file", "o", "", "Path and Name to an output file") - } -func generateComponentFlags() { +func bindGenerateComponentFlags() { componentFlags := generateComponentCmd.Flags() componentFlags.StringVarP(&componentOpts.CatalogSource, "catalog-source", "c", "", "Catalog source location (local or remote)") diff --git a/src/cmd/root.go b/src/cmd/root.go index 75c4a3eb..0d0ac969 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -1,18 +1,15 @@ package cmd import ( - "os" - "github.com/spf13/cobra" + "github.com/defenseunicorns/lula/src/cmd/common" "github.com/defenseunicorns/lula/src/cmd/dev" "github.com/defenseunicorns/lula/src/cmd/evaluate" "github.com/defenseunicorns/lula/src/cmd/generate" "github.com/defenseunicorns/lula/src/cmd/tools" "github.com/defenseunicorns/lula/src/cmd/validate" "github.com/defenseunicorns/lula/src/cmd/version" - "github.com/defenseunicorns/lula/src/config" - "github.com/defenseunicorns/lula/src/pkg/message" ) var LogLevelCLI string @@ -20,33 +17,7 @@ var LogLevelCLI string var rootCmd = &cobra.Command{ Use: "lula", PersistentPreRun: func(cmd *cobra.Command, args []string) { - - match := map[string]message.LogLevel{ - "warn": message.WarnLevel, - "info": message.InfoLevel, - "debug": message.DebugLevel, - "trace": message.TraceLevel, - } - - // No log level set, so use the default - if LogLevelCLI != "" { - if lvl, ok := match[LogLevelCLI]; ok { - message.SetLogLevel(lvl) - message.Debug("Log level set to " + LogLevelCLI) - } else { - message.Warn("Invalid log level. Valid options are: warn, info, debug, trace.") - } - } - - // Disable progress bars for CI envs - if os.Getenv("CI") == "true" { - message.Debug("CI environment detected, disabling progress bars") - message.NoProgress = true - } - - if !config.SkipLogFile { - message.UseLogFile() - } + common.SetupClI(LogLevelCLI) }, Short: "Risk Management as Code", Long: `Real Time Risk Transparency through automated validation`, @@ -58,6 +29,9 @@ func Execute() { } func init() { + + v := common.InitViper() + commands := []*cobra.Command{ validate.ValidateCommand(), evaluate.EvaluateCommand(), @@ -69,5 +43,5 @@ func init() { version.Include(rootCmd) dev.Include(rootCmd) - rootCmd.PersistentFlags().StringVarP(&LogLevelCLI, "log-level", "l", "info", "Log level when running Lula. Valid options are: warn, info, debug, trace") + rootCmd.PersistentFlags().StringVarP(&LogLevelCLI, "log-level", "l", v.GetString(common.VLogLevel), "Log level when running Lula. Valid options are: warn, info, debug, trace") } diff --git a/src/cmd/tools/common.go b/src/cmd/tools/common.go index d517a79e..3bea63d6 100644 --- a/src/cmd/tools/common.go +++ b/src/cmd/tools/common.go @@ -1,6 +1,7 @@ package tools import ( + "github.com/defenseunicorns/lula/src/config" "github.com/spf13/cobra" ) @@ -8,6 +9,13 @@ var toolsCmd = &cobra.Command{ Use: "tools", Aliases: []string{"t"}, Short: "Collection of additional commands to make OSCAL easier", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + config.SkipLogFile = true + // Call the parent's (root) PersistentPreRun + if parentPreRun := cmd.Parent().PersistentPreRun; parentPreRun != nil { + parentPreRun(cmd.Parent(), args) + } + }, } // Include adds the tools command to the root command. diff --git a/src/cmd/tools/compose.go b/src/cmd/tools/compose.go index 3063800f..1668ae4b 100644 --- a/src/cmd/tools/compose.go +++ b/src/cmd/tools/compose.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/defenseunicorns/lula/src/cmd/common" "github.com/defenseunicorns/lula/src/pkg/common/composition" "github.com/defenseunicorns/lula/src/pkg/common/oscal" "github.com/defenseunicorns/lula/src/pkg/message" @@ -27,36 +28,37 @@ To compose an OSCAL Model: To indicate a specific output file: lula tools compose -f ./oscal-component.yaml -o composed-oscal-component.yaml ` +var composeCmd = &cobra.Command{ + Use: "compose", + Short: "compose an OSCAL component definition", + Long: "Lula Composition of an OSCAL component definition. Used to compose remote validations within a component definition in order to resolve any references for portability.", + Example: composeHelp, + Run: func(cmd *cobra.Command, args []string) { + composeSpinner := message.NewProgressSpinner("Composing %s", composeOpts.InputFile) + defer composeSpinner.Stop() + + if composeOpts.InputFile == "" { + message.Fatal(errors.New("flag input-file is not set"), + "Please specify an input file with the -f flag") + } + + outputFile := composeOpts.OutputFile + if outputFile == "" { + outputFile = GetDefaultOutputFile(composeOpts.InputFile) + } + + err := Compose(composeOpts.InputFile, outputFile) + if err != nil { + message.Fatalf(err, "Composition error: %s", err) + } + + message.Infof("Composed OSCAL Component Definition to: %s", outputFile) + composeSpinner.Success() + }, +} func init() { - composeCmd := &cobra.Command{ - Use: "compose", - Short: "compose an OSCAL component definition", - Long: "Lula Composition of an OSCAL component definition. Used to compose remote validations within a component definition in order to resolve any references for portability.", - Example: composeHelp, - Run: func(cmd *cobra.Command, args []string) { - composeSpinner := message.NewProgressSpinner("Composing %s", composeOpts.InputFile) - defer composeSpinner.Stop() - - if composeOpts.InputFile == "" { - message.Fatal(errors.New("flag input-file is not set"), - "Please specify an input file with the -f flag") - } - - outputFile := composeOpts.OutputFile - if outputFile == "" { - outputFile = GetDefaultOutputFile(composeOpts.InputFile) - } - - err := Compose(composeOpts.InputFile, outputFile) - if err != nil { - message.Fatalf(err, "Composition error: %s", err) - } - - message.Infof("Composed OSCAL Component Definition to: %s", outputFile) - composeSpinner.Success() - }, - } + common.InitViper() toolsCmd.AddCommand(composeCmd) diff --git a/src/cmd/tools/lint.go b/src/cmd/tools/lint.go index efa5ab2d..6c31f382 100644 --- a/src/cmd/tools/lint.go +++ b/src/cmd/tools/lint.go @@ -6,7 +6,7 @@ import ( "strings" oscalValidation "github.com/defenseunicorns/go-oscal/src/pkg/validation" - "github.com/defenseunicorns/lula/src/config" + "github.com/defenseunicorns/lula/src/cmd/common" "github.com/defenseunicorns/lula/src/pkg/message" "github.com/spf13/cobra" ) @@ -24,80 +24,79 @@ To lint existing OSCAL files: ` -func init() { - lintCmd := &cobra.Command{ - Use: "lint", - Short: "Validate OSCAL against schema", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - config.SkipLogFile = true - }, - Long: "Validate OSCAL documents are properly configured against the OSCAL schema", - Example: lintHelp, - Run: func(cmd *cobra.Command, args []string) { - var validationResults []oscalValidation.ValidationResult - if len(opts.InputFiles) == 0 { - message.Fatalf(nil, "No input files specified") +var lintCmd = &cobra.Command{ + Use: "lint", + Short: "Validate OSCAL against schema", + Long: "Validate OSCAL documents are properly configured against the OSCAL schema", + Example: lintHelp, + Run: func(cmd *cobra.Command, args []string) { + var validationResults []oscalValidation.ValidationResult + if len(opts.InputFiles) == 0 { + message.Fatalf(nil, "No input files specified") + } + + for _, inputFile := range opts.InputFiles { + + spinner := message.NewProgressSpinner("Linting %s", inputFile) + defer spinner.Stop() + + validationResp, err := oscalValidation.ValidationCommand(inputFile) + // fatal for non-validation errors + if err != nil { + message.Fatalf(err, "Failed to lint %s: %s", inputFile, err) } - for _, inputFile := range opts.InputFiles { + for _, warning := range validationResp.Warnings { + message.Warn(warning) + } - spinner := message.NewProgressSpinner("Linting %s", inputFile) - defer spinner.Stop() + // append the validation result to the results array + validationResults = append(validationResults, validationResp.Result) - validationResp, err := oscalValidation.ValidationCommand(inputFile) - // fatal for non-validation errors + // If result file is not specified, print the validation result + if opts.ResultFile == "" { + jsonBytes, err := json.MarshalIndent(validationResp.Result, "", " ") if err != nil { - message.Fatalf(err, "Failed to lint %s: %s", inputFile, err) - } - - for _, warning := range validationResp.Warnings { - message.Warn(warning) - } - - // append the validation result to the results array - validationResults = append(validationResults, validationResp.Result) - - // If result file is not specified, print the validation result - if opts.ResultFile == "" { - jsonBytes, err := json.MarshalIndent(validationResp.Result, "", " ") - if err != nil { - message.Fatalf(err, "Failed to marshal validation result") - } - message.Infof("Validation result for %s: %s", inputFile, string(jsonBytes)) - } - // New conditional for logging success or failed linting - if validationResp.Result.Valid { - message.Infof("Successfully validated %s is valid OSCAL version %s %s\n", inputFile, validationResp.Validator.GetSchemaVersion(), validationResp.Validator.GetModelType()) - spinner.Success() - } else { - message.WarnErrf(nil, "Failed to lint %s", inputFile) - spinner.Stop() + message.Fatalf(err, "Failed to marshal validation result") } + message.Infof("Validation result for %s: %s", inputFile, string(jsonBytes)) } - - // If result file is specified, write the validation results to the file - if opts.ResultFile != "" { - // If there is only one validation result, write it to the file - if len(validationResults) == 1 { - oscalValidation.WriteValidationResult(validationResults[0], opts.ResultFile) - } else { - // If there are multiple validation results, write them to the file - oscalValidation.WriteValidationResults(validationResults, opts.ResultFile) - } + // New conditional for logging success or failed linting + if validationResp.Result.Valid { + message.Infof("Successfully validated %s is valid OSCAL version %s %s\n", inputFile, validationResp.Validator.GetSchemaVersion(), validationResp.Validator.GetModelType()) + spinner.Success() + } else { + message.WarnErrf(nil, "Failed to lint %s", inputFile) + spinner.Stop() } - - // If there is at least one validation result that is not valid, exit with a fatal error - failedFiles := []string{} - for _, result := range validationResults { - if !result.Valid { - failedFiles = append(failedFiles, result.Metadata.DocumentPath) - } + } + + // If result file is specified, write the validation results to the file + if opts.ResultFile != "" { + // If there is only one validation result, write it to the file + if len(validationResults) == 1 { + oscalValidation.WriteValidationResult(validationResults[0], opts.ResultFile) + } else { + // If there are multiple validation results, write them to the file + oscalValidation.WriteValidationResults(validationResults, opts.ResultFile) } - if len(failedFiles) > 0 { - message.Fatal(nil, fmt.Sprintf("The following files failed linting: %s", strings.Join(failedFiles, ", "))) + } + + // If there is at least one validation result that is not valid, exit with a fatal error + failedFiles := []string{} + for _, result := range validationResults { + if !result.Valid { + failedFiles = append(failedFiles, result.Metadata.DocumentPath) } - }, - } + } + if len(failedFiles) > 0 { + message.Fatal(nil, fmt.Sprintf("The following files failed linting: %s", strings.Join(failedFiles, ", "))) + } + }, +} + +func init() { + common.InitViper() toolsCmd.AddCommand(lintCmd) diff --git a/src/cmd/tools/upgrade.go b/src/cmd/tools/upgrade.go index 46489cdb..3814fefc 100644 --- a/src/cmd/tools/upgrade.go +++ b/src/cmd/tools/upgrade.go @@ -5,7 +5,7 @@ import ( "github.com/defenseunicorns/go-oscal/src/pkg/revision" "github.com/defenseunicorns/go-oscal/src/pkg/validation" "github.com/defenseunicorns/go-oscal/src/pkg/versioning" - "github.com/defenseunicorns/lula/src/config" + "github.com/defenseunicorns/lula/src/cmd/common" "github.com/defenseunicorns/lula/src/pkg/message" "github.com/spf13/cobra" ) @@ -21,56 +21,55 @@ type upgradeOptions struct { var upgradeOpts upgradeOptions = upgradeOptions{} -func init() { - upgradeCmd := &cobra.Command{ - Use: "upgrade", - Short: "Upgrade OSCAL document to a new version if possible.", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - config.SkipLogFile = true - }, - Long: "Validate an OSCAL document against the OSCAL schema version provided. If the document is valid, upgrade it to the provided OSCAL version. Otherwise, return or write as ValidationError. Yaml formatting handled by gopkg/yaml.v3 and while objects will maintain deep equality, visual representation may be different than the input file.", - Example: upgradeHelp, - Run: func(cmd *cobra.Command, args []string) { - spinner := message.NewProgressSpinner("Upgrading %s to version %s", upgradeOpts.InputFile, upgradeOpts.Version) - defer spinner.Stop() +var upgradeCmd = &cobra.Command{ + Use: "upgrade", + Short: "Upgrade OSCAL document to a new version if possible.", + Long: "Validate an OSCAL document against the OSCAL schema version provided. If the document is valid, upgrade it to the provided OSCAL version. Otherwise, return or write as ValidationError. Yaml formatting handled by gopkg/yaml.v3 and while objects will maintain deep equality, visual representation may be different than the input file.", + Example: upgradeHelp, + Run: func(cmd *cobra.Command, args []string) { + spinner := message.NewProgressSpinner("Upgrading %s to version %s", upgradeOpts.InputFile, upgradeOpts.Version) + defer spinner.Stop() - // If no output file is specified, write to the input file - if upgradeOpts.OutputFile == "" { - upgradeOpts.OutputFile = upgradeOpts.InputFile - } + // If no output file is specified, write to the input file + if upgradeOpts.OutputFile == "" { + upgradeOpts.OutputFile = upgradeOpts.InputFile + } - revisionResponse, revisionErr := revision.RevisionCommand(&upgradeOpts.RevisionOptions) + revisionResponse, revisionErr := revision.RevisionCommand(&upgradeOpts.RevisionOptions) - if upgradeOpts.ValidationResult != "" { - err := validation.WriteValidationResult(revisionResponse.Result, upgradeOpts.ValidationResult) - if err != nil { - message.Fatalf("Failed to write validation result to %s: %s\n", upgradeOpts.ValidationResult, err) - } + if upgradeOpts.ValidationResult != "" { + err := validation.WriteValidationResult(revisionResponse.Result, upgradeOpts.ValidationResult) + if err != nil { + message.Fatalf("Failed to write validation result to %s: %s\n", upgradeOpts.ValidationResult, err) } + } - if revisionErr != nil { - message.Fatalf(revisionErr, "Failed to upgrade %s: %s", upgradeOpts.InputFile, revisionErr) - } + if revisionErr != nil { + message.Fatalf(revisionErr, "Failed to upgrade %s: %s", upgradeOpts.InputFile, revisionErr) + } - if len(revisionResponse.Warnings) > 0 { - for _, warning := range revisionResponse.Warnings { - message.Warn(warning) - } + if len(revisionResponse.Warnings) > 0 { + for _, warning := range revisionResponse.Warnings { + message.Warn(warning) } + } - err := files.WriteOutput(revisionResponse.RevisedBytes, upgradeOpts.OutputFile) - if err != nil { - message.Fatalf(err, "Failed to write upgraded %s with: %s", upgradeOpts.OutputFile, err) - } + err := files.WriteOutput(revisionResponse.RevisedBytes, upgradeOpts.OutputFile) + if err != nil { + message.Fatalf(err, "Failed to write upgraded %s with: %s", upgradeOpts.OutputFile, err) + } - if err != nil { - message.Fatalf(err, "Failed to upgrade %s to OSCAL version %s %s", upgradeOpts.InputFile, revisionResponse.Reviser.GetSchemaVersion(), revisionResponse.Reviser.GetModelType()) - } - message.Infof("Successfully upgraded %s to OSCAL version %s %s\n", upgradeOpts.InputFile, revisionResponse.Reviser.GetSchemaVersion(), revisionResponse.Reviser.GetModelType()) - spinner.Success() - }, - } + if err != nil { + message.Fatalf(err, "Failed to upgrade %s to OSCAL version %s %s", upgradeOpts.InputFile, revisionResponse.Reviser.GetSchemaVersion(), revisionResponse.Reviser.GetModelType()) + } + message.Infof("Successfully upgraded %s to OSCAL version %s %s\n", upgradeOpts.InputFile, revisionResponse.Reviser.GetSchemaVersion(), revisionResponse.Reviser.GetModelType()) + spinner.Success() + }, +} + +func init() { + common.InitViper() toolsCmd.AddCommand(upgradeCmd) upgradeCmd.Flags().StringVarP(&upgradeOpts.InputFile, "input-file", "f", "", "the path to a oscal json schema file") diff --git a/src/cmd/tools/uuid.go b/src/cmd/tools/uuid.go index f488233a..b9f357db 100644 --- a/src/cmd/tools/uuid.go +++ b/src/cmd/tools/uuid.go @@ -33,7 +33,7 @@ func init() { } else if len(args) == 1 { fmt.Println(uuid.NewUUIDWithSource(args[0])) } else { - message.Fatal(fmt.Errorf("Too many arguments"), "Too many arguments") + message.Fatal(fmt.Errorf("too many arguments"), "Too many arguments") } }, } diff --git a/src/cmd/validate/validate.go b/src/cmd/validate/validate.go index b8cad3a6..0432c50e 100644 --- a/src/cmd/validate/validate.go +++ b/src/cmd/validate/validate.go @@ -8,7 +8,8 @@ import ( "github.com/defenseunicorns/go-oscal/src/pkg/files" oscalTypes_1_1_2 "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-2" - "github.com/defenseunicorns/lula/src/pkg/common" + "github.com/defenseunicorns/lula/src/cmd/common" + pkgCommon "github.com/defenseunicorns/lula/src/pkg/common" "github.com/defenseunicorns/lula/src/pkg/common/composition" "github.com/defenseunicorns/lula/src/pkg/common/oscal" requirementstore "github.com/defenseunicorns/lula/src/pkg/common/requirement-store" @@ -72,15 +73,19 @@ var validateCmd = &cobra.Command{ }, } -func ValidateCommand() *cobra.Command { +func init() { + common.InitViper() - // insert flag options here validateCmd.Flags().StringVarP(&opts.OutputFile, "output-file", "o", "", "the path to write assessment results. Creates a new file or appends to existing files") validateCmd.Flags().StringVarP(&opts.InputFile, "input-file", "f", "", "the path to the target OSCAL component definition") validateCmd.MarkFlagRequired("input-file") validateCmd.Flags().StringVarP(&opts.Target, "target", "t", "", "the specific control implementations or framework to validate against") validateCmd.Flags().BoolVar(&ConfirmExecution, "confirm-execution", false, "confirm execution scripts run as part of the validation") validateCmd.Flags().BoolVar(&RunNonInteractively, "non-interactive", false, "run the command non-interactively") + +} + +func ValidateCommand() *cobra.Command { return validateCmd } @@ -124,7 +129,7 @@ func ValidateOnPath(path string, target string) (assessmentResult *oscalTypes_1_ // Change Cwd to the directory of the component definition dirPath := filepath.Dir(path) message.Debugf("changing cwd to %s", dirPath) - resetCwd, err := common.SetCwdToFileDir(dirPath) + resetCwd, err := pkgCommon.SetCwdToFileDir(dirPath) if err != nil { return assessmentResult, err } diff --git a/src/test/e2e/dev_validation_test.go b/src/test/e2e/dev_validation_test.go index 4c5746b2..0a69759a 100644 --- a/src/test/e2e/dev_validation_test.go +++ b/src/test/e2e/dev_validation_test.go @@ -75,7 +75,6 @@ func TestDevValidation(t *testing.T) { if err != nil { t.Errorf("Error reading file: %v", err) } - validation, err := dev.DevValidate(ctx, validationBytes, resourcesBytes, nil) if err != nil { t.Errorf("Error testing dev validate: %v", err) @@ -109,7 +108,6 @@ func TestDevValidation(t *testing.T) { if err != nil { t.Errorf("Error reading file: %v", err) } - validation, err := dev.DevValidate(ctx, validationBytes, resourcesBytes, nil) if err != nil { t.Errorf("Error testing dev validate: %v", err)