From 16960e52e0c6afe981ceeaab95bbd70c595a2e90 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Thu, 21 Mar 2024 10:05:53 -0400 Subject: [PATCH 01/21] update golang and golangci-lint --- .github/workflows/lint-test.yml | 6 +++--- .golangci.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml index 7914ae1..c7b82c6 100644 --- a/.github/workflows/lint-test.yml +++ b/.github/workflows/lint-test.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: '1.21.0' + go-version: '1.22.1' - name: Install cockroach binary run: curl https://binaries.cockroachdb.com/cockroach-v23.1.11.linux-amd64.tgz | tar -xz && sudo cp -i cockroach-v23.1.11.linux-amd64/cockroach /usr/local/bin/ @@ -32,12 +32,12 @@ jobs: - name: Run golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.52.2 + version: v1.57.1 args: --timeout=5m - name: Run go tests and generate coverage report run: FLEETDB_CRDB_URI="host=localhost port=26257 user=root sslmode=disable dbname=fleetdb_test" go test -race -coverprofile=coverage.txt -covermode=atomic -tags testtools -p 1 ./... - + - name: Stop test database run: cockroach node drain --insecure --host=localhost:26257 diff --git a/.golangci.yml b/.golangci.yml index ff1fc7a..a020fd9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -31,7 +31,7 @@ linters: - govet - misspell - noctx - - revive + # - revive XXX: fix up old code caught here - stylecheck - whitespace From eb1da373a86370353a09cf6e9475465700c24843 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Thu, 21 Mar 2024 10:12:14 -0400 Subject: [PATCH 02/21] add a skeleton for incoming CIS data --- go.mod | 3 +- go.sum | 38 +++++++++++++++++++++++++ pkg/api/v1/router.go | 13 +++++++++ pkg/api/v1/router_inventory.go | 52 ++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 pkg/api/v1/router_inventory.go diff --git a/go.mod b/go.mod index 4b8fd55..c3fd886 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/metal-toolbox/fleetdb -go 1.19 +go 1.22 require ( github.com/XSAM/otelsql v0.23.0 // indirect @@ -34,6 +34,7 @@ require ( ) require ( + github.com/bmc-toolbox/common v0.0.0-20231204194243-7bcbccab7116 github.com/volatiletech/sqlboiler v3.7.1+incompatible go.hollow.sh/toolbox v0.6.3 go.infratographer.com/x v0.3.7 diff --git a/go.sum b/go.sum index 371cedb..c5e85c9 100644 --- a/go.sum +++ b/go.sum @@ -42,12 +42,16 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= +cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/kms v1.15.0 h1:xYl5WEaSekKYN5gGRyhjvZKM22GVBBCzegGNVPy+aIs= +cloud.google.com/go/kms v1.15.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -91,6 +95,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmc-toolbox/common v0.0.0-20231204194243-7bcbccab7116 h1:gqWn/cMjryKoUfITx2vRHrRHTvd9fQ+zKPwWsmMIrK4= +github.com/bmc-toolbox/common v0.0.0-20231204194243-7bcbccab7116/go.mod h1:SY//n1PJjZfbFbmAsB6GvEKbc7UXz3d30s3kWxfJQ/c= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= @@ -142,6 +148,7 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/ github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -160,6 +167,7 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 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/friendsofgo/errors v0.9.2 h1:X6NYxef4efCBdwI7BgS820zFaN7Cphrmb+Pljdzjtgk= github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4Pn44IGoTOI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -193,6 +201,7 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= @@ -204,12 +213,14 @@ github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -225,6 +236,7 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -276,6 +288,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -298,13 +311,16 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.5 h1:8IYp3w9nysqv3JH+NJgXJzGbDHzLOTj43BmSkp+O7qg= +github.com/google/s2a-go v0.1.5/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= +github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -449,6 +465,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -495,6 +512,7 @@ github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOa github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -519,7 +537,9 @@ github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4= +github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= github.com/nats-io/nats-server/v2 v2.9.17 h1:gFpUQ3hqIDJrnqog+Bl5vaXg+RhhYEZIElasEuRn2tw= +github.com/nats-io/nats-server/v2 v2.9.17/go.mod h1:eQysm3xDZmIjfkjr7DuD9DjRFpnxQc2vKVxtEg0Dp6s= github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c= github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= @@ -575,11 +595,13 @@ github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwa github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -692,6 +714,7 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0 h1:l7AmwSVqozWKKXeZHycpdmpycQECRpoGwJ1FW2sWfTo= go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0/go.mod h1:Ep4uoO2ijR0f49Pr7jAqyTjSCyS1SRL18wwttKfwqXA= go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo= +go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= go.opentelemetry.io/otel/exporters/jaeger v1.16.0 h1:YhxxmXZ011C0aDZKoNw+juVWAmEfv/0W2XBOv9aHTaA= @@ -711,6 +734,7 @@ go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxx go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= +go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -722,6 +746,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -802,6 +827,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -876,6 +902,7 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -890,6 +917,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1001,6 +1029,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1064,6 +1093,7 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1294,10 +1324,12 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= +modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= @@ -1305,6 +1337,7 @@ modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWs modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v3 v3.16.14 h1:af6KNtFgsVmnDYrWk3PQCS9XT6BXe7o3ZFJKkIKvXNQ= +modernc.org/ccgo/v3 v3.16.14/go.mod h1:mPDSujUIaTNWQSG4eqKw+atqLOEbma6Ncsa94WbC9zo= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= @@ -1315,19 +1348,23 @@ modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= +modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= +modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= +modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= @@ -1335,6 +1372,7 @@ modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/api/v1/router.go b/pkg/api/v1/router.go index 4a9439b..e65ca72 100644 --- a/pkg/api/v1/router.go +++ b/pkg/api/v1/router.go @@ -140,6 +140,19 @@ func (r *Router) Routes(rg *gin.RouterGroup) { srvBomByBmcMacAddress.GET("/:bmc_mac_address", amw.AuthRequired(readScopes("bmc-mac-address")), r.getBomFromBmcMacAddress) } } + + // inventory endpoints + srvInventory := rg.Group("/inventory") + { + srvInventory.GET("/inband/:uuid", amw.AuthRequired(readScopes("server", "inventory")), r.getInbandInventory) + srvInventory.GET("/outofband/:uuid", amw.AuthRequired(readScopes("server", "inventory")), r.getOutofbandInventory) + + srvInventory.POST("/inband/:uuid", amw.AuthRequired(createScopes("server", "inventory")), r.setInbandInventory) + srvInventory.POST("/outofband/:uuid", amw.AuthRequired(createScopes("server", "inventory")), r.setOutofbandInventory) + + srvInventory.PUT("/inband/:uuid", amw.AuthRequired(updateScopes("server", "inventory")), r.setInbandInventory) + srvInventory.PUT("/outofband/:uuid", amw.AuthRequired(updateScopes("server", "inventory")), r.setOutofbandInventory) + } } func createScopes(items ...string) []string { diff --git a/pkg/api/v1/router_inventory.go b/pkg/api/v1/router_inventory.go new file mode 100644 index 0000000..05a2bbe --- /dev/null +++ b/pkg/api/v1/router_inventory.go @@ -0,0 +1,52 @@ +//nolint:unused +package fleetdbapi + +import ( + "encoding/json" + "net/http" + + "github.com/bmc-toolbox/common" + "github.com/gin-gonic/gin" +) + +// A reminder for maintenance: this type needs to be able to contain all the +// relevant fields from Component-Inventory or Alloy. +type serverInventory struct { + Inv *common.Device `json:"inventory"` + BiosConfig map[string]string `json:"bios_config,omitempty"` +} + +func (si *serverInventory) mustJSON() []byte { + byt, err := json.Marshal(si) + if err != nil { + panic("bad inventory") + } + return byt +} + +func (si *serverInventory) fromJSON(b []byte) error { + return json.Unmarshal(b, si) +} + +func unimplemented(c *gin.Context) { + m := map[string]string{ + "err": "unimplemented", + } + c.JSON(http.StatusInternalServerError, m) +} + +func (r *Router) getInbandInventory(c *gin.Context) { + unimplemented(c) +} + +func (r *Router) getOutofbandInventory(c *gin.Context) { + unimplemented(c) +} + +func (r *Router) setInbandInventory(c *gin.Context) { + unimplemented(c) +} + +func (r *Router) setOutofbandInventory(c *gin.Context) { + unimplemented(c) +} From b8dc43d0c4c969e99ba0827f87bd2f31764809f3 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 22 Mar 2024 16:43:22 -0400 Subject: [PATCH 03/21] implement attribute upsert for incoming inventory --- internal/inventory/device_view.go | 176 +++++++++++++++++++++++++ internal/inventory/device_view_test.go | 63 +++++++++ pkg/api/v1/router.go | 12 +- pkg/api/v1/router_inventory.go | 68 ++++++---- 4 files changed, 284 insertions(+), 35 deletions(-) create mode 100644 internal/inventory/device_view.go create mode 100644 internal/inventory/device_view_test.go diff --git a/internal/inventory/device_view.go b/internal/inventory/device_view.go new file mode 100644 index 0000000..e2f3d8a --- /dev/null +++ b/internal/inventory/device_view.go @@ -0,0 +1,176 @@ +//nolint:all // XXX remove this! +package inventory + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "time" + + "github.com/bmc-toolbox/common" + "github.com/google/uuid" + "github.com/metal-toolbox/fleetdb/internal/models" + "github.com/pkg/errors" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/types" +) + +/* + XXX From the "this is why we can't have nice things" dept.: + SQLBoiler does in-fact generate Upsert methods for the objects in its ORM. However, because many of our tables + have partial constraints on them, Postgres (and by extension CRDB) requires a WHERE clause when you specify + ON CONFLICT (columns). That is the query looks like: INSERT INTO table (col1, col2) VALUES (foo, bar) ON CONFLICT + (col1, col2) WHERE col1 DO UPDATE ... + + SQLBoiler doesn't have a provision for that WHERE clause in the ON CONFLICT and they probably won't add it: cf. + https://github.com/volatiletech/sqlboiler/issues/856 + + That means we do the upserts the hard way until we change change the tables that have only partial constraints. +*/ + +var ( + versionedAttributesByServerID = "(namespace, created_at) IN (select namespace, max(created_at) from versioned_attributes where server_id=? group by namespace)" + + // historically these values were determined/set by alloy, even though they are + // internal to the data storage layer, hence the names + alloyVendorNamespace = "sh.hollow.alloy.server_vendor_attributes" + alloyMetadataNamespace = "sh.hollow.alloy.server_metadata_attributes" +) + +// A reminder for maintenance: this type needs to be able to contain all the +// relevant fields from Component-Inventory or Alloy. +type DeviceView struct { + Inv *common.Device `json:"inventory"` + BiosConfig map[string]string `json:"bios_config,omitempty"` +} + +func (dv *DeviceView) vendorAttributes() json.RawMessage { + m := map[string]string{ + "model": "unknown", + "serial": "unknown", + "vendor": "unknown", + } + + if dv.Inv.Model != "" { + m["model"] = dv.Inv.Model + } + + if dv.Inv.Serial != "" { + m["serial"] = dv.Inv.Serial + } + + if dv.Inv.Vendor != "" { + m["vendor"] = dv.Inv.Vendor + } + + byt, _ := json.Marshal(m) + + return byt +} + +func (dv *DeviceView) metadataAttributes() json.RawMessage { + m := map[string]string{} + + // filter UEFI variables -- they go in a versioned-attribute + for k, v := range dv.Inv.Metadata { + if k != "uefi-variables" { + m[k] = v + } + } + + if len(m) == 0 { + return nil + } + + byt, _ := json.Marshal(m) + return byt +} + +func (dv *DeviceView) updateAnyAttribute(ctx context.Context, exec boil.ContextExecutor, + srv uuid.UUID, namespace string, data json.RawMessage) error { + mods := []qm.QueryMod{ + qm.Where("server_id=?", srv), + qm.And(fmt.Sprintf("namespace='%s'", namespace)), + } + now := time.Now() + + existing, err := models.Attributes(mods...).One(ctx, exec) + switch err { + case nil: + // do update + existing.Data = types.JSON(data) + existing.UpdatedAt = null.TimeFrom(now) + _, updErr := existing.Update(ctx, exec, boil.Infer()) + return updErr + case sql.ErrNoRows: + // do insert + vendorAttr := models.Attribute{ + ServerID: null.StringFrom(srv.String()), + Namespace: namespace, + Data: types.JSON(data), + CreatedAt: null.TimeFrom(now), + } + return vendorAttr.Insert(ctx, exec, boil.Infer()) + default: + return err + } +} + +func (dv *DeviceView) updateVendorAttributes(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { + return dv.updateAnyAttribute(ctx, exec, srv, alloyVendorNamespace, dv.vendorAttributes()) +} + +func (dv *DeviceView) updateMetadataAttributes(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { + var err error + if md := dv.metadataAttributes(); md != nil { + err = dv.updateAnyAttribute(ctx, exec, srv, alloyMetadataNamespace, md) + } + return err +} + +func (dv *DeviceView) UpsertInventory(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID, inband bool) error { + // yes, this is a dopey repetitive style that should be easy for folks to extend or modify + if err := dv.updateVendorAttributes(ctx, exec, srv); err != nil { + return errors.Wrap(err, "vendor attributes update") + } + if err := dv.updateMetadataAttributes(ctx, exec, srv); err != nil { + return errors.Wrap(err, "metadata attribute update") + } + return nil +} + +func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { + // populate the vendor attributes + attrs, err := models.Attributes(qm.Where("server_id=?", srv)).All(ctx, exec) + if err != nil { + return err + } + + if dv.Inv == nil { + dv.Inv = &common.Device{} + } + + for _, a := range attrs { + switch a.Namespace { + case alloyVendorNamespace: + m := map[string]string{} + if err := a.Data.Unmarshal(&m); err != nil { + return errors.Wrap(err, "unmarshaling vendor attributes") + } + dv.Inv.Vendor = m["vendor"] + dv.Inv.Model = m["model"] + dv.Inv.Serial = m["serial"] + case alloyMetadataNamespace: + m := map[string]string{} + if err := a.Data.Unmarshal(&m); err != nil { + return errors.Wrap(err, "unmarshaling metadata attributes") + } + dv.Inv.Metadata = m + default: + } + } + return nil +} diff --git a/internal/inventory/device_view_test.go b/internal/inventory/device_view_test.go new file mode 100644 index 0000000..416f0a6 --- /dev/null +++ b/internal/inventory/device_view_test.go @@ -0,0 +1,63 @@ +//go:build integration + +package inventory + +import ( + "context" + "testing" + + "github.com/bmc-toolbox/common" + "github.com/google/uuid" + "github.com/metal-toolbox/fleetdb/internal/dbtools" + "github.com/metal-toolbox/fleetdb/internal/models" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + + "github.com/stretchr/testify/require" +) + +func Test_DeviceViewUpdate(t *testing.T) { + t.Parallel() + t.Run("insert and update device attributes", func(t *testing.T) { + t.Parallel() + db := dbtools.DatabaseTest(t) + dv := DeviceView{ + Inv: &common.Device{ + Common: common.Common{ + Vendor: "CoolVendor", + Model: "BestModel 420", + Serial: "0xdeadbeef", + Metadata: map[string]string{ + "uefi-variables": "shouldn't be here", + "metakey": "value", + }, + }, + }, + } + srvID := uuid.New() + // make a server for out attributes + server := models.Server{ + ID: srvID.String(), + Name: null.StringFrom("dvtest-server"), + FacilityCode: null.StringFrom("test1"), + } + + srvErr := server.Insert(context.TODO(), db, boil.Infer()) + require.NoError(t, srvErr, "server setup failed") + + err := dv.UpsertInventory(context.TODO(), db, srvID, false) + require.NoError(t, err) + + // do it again to test the update + dv.Inv.Common.Serial = "roastbeef" + err = dv.UpsertInventory(context.TODO(), db, srvID, false) + require.NoError(t, err) + + // validate the contents + read := DeviceView{} + err = read.FromDatastore(context.TODO(), db, srvID) + require.NoError(t, err) + require.Equal(t, "roastbeef", read.Inv.Serial) + require.Equal(t, 1, len(read.Inv.Metadata)) + }) +} diff --git a/pkg/api/v1/router.go b/pkg/api/v1/router.go index e65ca72..0a091a1 100644 --- a/pkg/api/v1/router.go +++ b/pkg/api/v1/router.go @@ -144,14 +144,10 @@ func (r *Router) Routes(rg *gin.RouterGroup) { // inventory endpoints srvInventory := rg.Group("/inventory") { - srvInventory.GET("/inband/:uuid", amw.AuthRequired(readScopes("server", "inventory")), r.getInbandInventory) - srvInventory.GET("/outofband/:uuid", amw.AuthRequired(readScopes("server", "inventory")), r.getOutofbandInventory) - - srvInventory.POST("/inband/:uuid", amw.AuthRequired(createScopes("server", "inventory")), r.setInbandInventory) - srvInventory.POST("/outofband/:uuid", amw.AuthRequired(createScopes("server", "inventory")), r.setOutofbandInventory) - - srvInventory.PUT("/inband/:uuid", amw.AuthRequired(updateScopes("server", "inventory")), r.setInbandInventory) - srvInventory.PUT("/outofband/:uuid", amw.AuthRequired(updateScopes("server", "inventory")), r.setOutofbandInventory) + // uuid is the server id, mode is 'inband' or 'outofband' -- the data generated by each method can differ + // significantly, so we keep both sets as separate things to make apples-to-apples comparisons easier. + srvInventory.GET("/:uuid/:mode", amw.AuthRequired(readScopes("server", "inventory")), r.getInventory) + srvInventory.PUT("/:uuid/:mode", amw.AuthRequired(updateScopes("server", "inventory")), r.setInventory) } } diff --git a/pkg/api/v1/router_inventory.go b/pkg/api/v1/router_inventory.go index 05a2bbe..1e73e2f 100644 --- a/pkg/api/v1/router_inventory.go +++ b/pkg/api/v1/router_inventory.go @@ -2,32 +2,13 @@ package fleetdbapi import ( - "encoding/json" "net/http" - "github.com/bmc-toolbox/common" + "github.com/metal-toolbox/fleetdb/internal/inventory" + "github.com/gin-gonic/gin" ) -// A reminder for maintenance: this type needs to be able to contain all the -// relevant fields from Component-Inventory or Alloy. -type serverInventory struct { - Inv *common.Device `json:"inventory"` - BiosConfig map[string]string `json:"bios_config,omitempty"` -} - -func (si *serverInventory) mustJSON() []byte { - byt, err := json.Marshal(si) - if err != nil { - panic("bad inventory") - } - return byt -} - -func (si *serverInventory) fromJSON(b []byte) error { - return json.Unmarshal(b, si) -} - func unimplemented(c *gin.Context) { m := map[string]string{ "err": "unimplemented", @@ -35,18 +16,51 @@ func unimplemented(c *gin.Context) { c.JSON(http.StatusInternalServerError, m) } -func (r *Router) getInbandInventory(c *gin.Context) { +func (r *Router) getInventory(c *gin.Context) { unimplemented(c) } -func (r *Router) getOutofbandInventory(c *gin.Context) { - unimplemented(c) +func (r *Router) setInventory(c *gin.Context) { + srvId, err := r.parseUUID(c) + if err != nil { + badRequestResponse(c, "invalid server id", err) + return + } + + var doInband bool + switch c.Param("mode") { + case "inband": + doInband = true + case "outofband": + default: + badRequestResponse(c, "invalid inventory mode", nil) + } + + var view inventory.DeviceView + if err := c.ShouldBindJSON(&view); err != nil { + badRequestResponse(c, "invalid inventory payload", err) + return + } + + if err := view.UpsertInventory(c.Request.Context(), r.DB, srvId, doInband); err != nil { + dbErrorResponse(c, err) + } } -func (r *Router) setInbandInventory(c *gin.Context) { - unimplemented(c) +/*( if err != nil { + tx.Rollback() //nolint errcheck + dbErrorResponse(c, err) + + return + } + // compose the attributes from the inventory + // - server vendor attributes + // - server metadata attributes + + // compose the components + } func (r *Router) setOutofbandInventory(c *gin.Context) { unimplemented(c) -} +}*/ From 638183e21422de0c427fbfd0567509f5b67ba817 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Tue, 26 Mar 2024 13:57:43 -0400 Subject: [PATCH 04/21] WIP: inventory updates --- Makefile | 2 +- internal/inventory/component_attributes.go | 54 ++++++++++ internal/inventory/device_components.go | 22 ++++ internal/inventory/device_view.go | 112 +++++++++++++++++---- internal/inventory/device_view_test.go | 13 ++- 5 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 internal/inventory/component_attributes.go create mode 100644 internal/inventory/device_components.go diff --git a/Makefile b/Makefile index d5a2ebe..2326204 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ unit-test: | lint ## check test coverage coverage: | test-database @echo Generating coverage report... - @FLEETDB_CRDB_URI="${TEST_DB}" go test ./... -race -coverprofile=coverage.out -covermode=atomic -tags testtools -p 1 + @FLEETDB_CRDB_URI="${TEST_DB}" go test ./... -race -coverprofile=coverage.out -covermode=atomic -tags testtools,integration -p 1 @go tool cover -func=coverage.out @go tool cover -html=coverage.out diff --git a/internal/inventory/component_attributes.go b/internal/inventory/component_attributes.go new file mode 100644 index 0000000..5b09ffd --- /dev/null +++ b/internal/inventory/component_attributes.go @@ -0,0 +1,54 @@ +package inventory + +import ( + "encoding/json" + + "github.com/bmc-toolbox/common" +) + +type attributes struct { + Capabilities []*common.Capability `json:"capabilities,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + ID string `json:"id,omitempty"` + ChassisType string `json:"chassis_type,omitempty"` + Description string `json:"description,omitempty"` + ProductName string `json:"product_name,omitempty"` + InterfaceType string `json:"interface_type,omitempty"` + Slot string `json:"slot,omitempty"` + Architecture string `json:"architecture,omitempty"` + MacAddress string `json:"macaddress,omitempty"` + SupportedControllerProtocols string `json:"supported_controller_protocol,omitempty"` + SupportedDeviceProtocols string `json:"supported_device_protocol,omitempty"` + SupportedRAIDTypes string `json:"supported_raid_types,omitempty"` + PhysicalID string `json:"physid,omitempty"` + FormFactor string `json:"form_factor,omitempty"` + PartNumber string `json:"part_number,omitempty"` + OemID string `json:"oem_id,omitempty"` + DriveType string `json:"drive_type,omitempty"` + StorageController string `json:"storage_controller,omitempty"` + BusInfo string `json:"bus_info,omitempty"` + WWN string `json:"wwn,omitempty"` + Protocol string `json:"protocol,omitempty"` + SmartStatus string `json:"smart_status,omitempty"` + SmartErrors []string `json:"smart_errors,omitempty"` + PowerCapacityWatts int64 `json:"power_capacity_watts,omitempty"` + SizeBytes int64 `json:"size_bytes,omitempty"` + CapacityBytes int64 `json:"capacity_bytes,omitempty" diff:"immutable"` + ClockSpeedHz int64 `json:"clock_speed_hz,omitempty"` + Cores int `json:"cores,omitempty"` + Threads int `json:"threads,omitempty"` + SpeedBits int64 `json:"speed_bits,omitempty"` + SpeedGbps int64 `json:"speed_gbps,omitempty"` + BlockSizeBytes int64 `json:"block_size_bytes,omitempty"` + CapableSpeedGbps int64 `json:"capable_speed_gbps,omitempty"` + NegotiatedSpeedGbps int64 `json:"negotiated_speed_gbps,omitempty"` + Oem bool `json:"oem,omitempty"` +} + +func (a *attributes) mustJSON() []byte { + byt, err := json.Marshal(a) + if err != nil { + panic("bad attributes") + } + return byt +} diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go new file mode 100644 index 0000000..6c2fcfb --- /dev/null +++ b/internal/inventory/device_components.go @@ -0,0 +1,22 @@ +package inventory + +import ( + "context" + "errors" + + "github.com/google/uuid" + "github.com/volatiletech/sqlboiler/v4/boil" +) + +func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextExecutor, + srv uuid.UUID, method string) error { + if err := dv.writeDimms(ctx, exec, srv, method); err != nil { + return err + } + return nil +} + +func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor, + srv uuid.UUID, method string) error { + return errors.New("unimplemented") +} diff --git a/internal/inventory/device_view.go b/internal/inventory/device_view.go index e2f3d8a..7d63bfe 100644 --- a/internal/inventory/device_view.go +++ b/internal/inventory/device_view.go @@ -38,6 +38,13 @@ var ( // internal to the data storage layer, hence the names alloyVendorNamespace = "sh.hollow.alloy.server_vendor_attributes" alloyMetadataNamespace = "sh.hollow.alloy.server_metadata_attributes" + alloyUefiVarsNamespace = "sh.hollow.alloy.server_uefi_variables" // this is a versioned attribute, we expect it to change + + // metadata keys + modelKey = "model" + vendorKey = "vendor" + serialKey = "serial" + uefiVarsKey = "uefi-variables" ) // A reminder for maintenance: this type needs to be able to contain all the @@ -49,21 +56,21 @@ type DeviceView struct { func (dv *DeviceView) vendorAttributes() json.RawMessage { m := map[string]string{ - "model": "unknown", - "serial": "unknown", - "vendor": "unknown", + modelKey: "unknown", + serialKey: "unknown", + vendorKey: "unknown", } if dv.Inv.Model != "" { - m["model"] = dv.Inv.Model + m[modelKey] = dv.Inv.Model } if dv.Inv.Serial != "" { - m["serial"] = dv.Inv.Serial + m[serialKey] = dv.Inv.Serial } if dv.Inv.Vendor != "" { - m["vendor"] = dv.Inv.Vendor + m[vendorKey] = dv.Inv.Vendor } byt, _ := json.Marshal(m) @@ -76,7 +83,7 @@ func (dv *DeviceView) metadataAttributes() json.RawMessage { // filter UEFI variables -- they go in a versioned-attribute for k, v := range dv.Inv.Metadata { - if k != "uefi-variables" { + if k != uefiVarsKey { m[k] = v } } @@ -89,6 +96,23 @@ func (dv *DeviceView) metadataAttributes() json.RawMessage { return byt } +func (dv *DeviceView) uefiVariables() (json.RawMessage, error) { + var varString string + + varString, ok := dv.Inv.Metadata[uefiVarsKey] + if !ok { + return nil, nil + } + + // sanity check the data coming in from the caller + m := map[string]any{} + if err := json.Unmarshal([]byte(varString), &m); err != nil { + return nil, errors.Wrap(err, "unmarshaling uefi-variables") + } + + return []byte(varString), nil +} + func (dv *DeviceView) updateAnyAttribute(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID, namespace string, data json.RawMessage) error { mods := []qm.QueryMod{ @@ -107,13 +131,13 @@ func (dv *DeviceView) updateAnyAttribute(ctx context.Context, exec boil.ContextE return updErr case sql.ErrNoRows: // do insert - vendorAttr := models.Attribute{ + attr := models.Attribute{ ServerID: null.StringFrom(srv.String()), Namespace: namespace, Data: types.JSON(data), CreatedAt: null.TimeFrom(now), } - return vendorAttr.Insert(ctx, exec, boil.Infer()) + return attr.Insert(ctx, exec, boil.Infer()) default: return err } @@ -131,26 +155,68 @@ func (dv *DeviceView) updateMetadataAttributes(ctx context.Context, exec boil.Co return err } +// write a versioned-attribute containing the UEFI variables from this server +func (dv *DeviceView) updateUefiVariables(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { + mods := []qm.QueryMod{ + qm.Where("server_id=?", srv), + qm.And(fmt.Sprintf("namespace='%s'", alloyUefiVarsNamespace)), + } + now := time.Now() + + varData, err := dv.uefiVariables() + if err != nil { + return err + } + + existing, err := models.VersionedAttributes(mods...).One(ctx, exec) + switch err { + case nil: + // do update + existing.Data = types.JSON(varData) + existing.Tally = existing.Tally + 1 + existing.UpdatedAt = null.TimeFrom(now) + _, updErr := existing.Update(ctx, exec, boil.Infer()) + return updErr + case sql.ErrNoRows: + // do insert + va := models.VersionedAttribute{ + ServerID: null.StringFrom(srv.String()), + Namespace: alloyUefiVarsNamespace, + CreatedAt: null.TimeFrom(now), + Data: types.JSON(varData), + } + return va.Insert(ctx, exec, boil.Infer()) + default: + return err + } +} + func (dv *DeviceView) UpsertInventory(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID, inband bool) error { - // yes, this is a dopey repetitive style that should be easy for folks to extend or modify + // yes, this is a dopey, repetitive style that should be easy for folks to extend or modify if err := dv.updateVendorAttributes(ctx, exec, srv); err != nil { return errors.Wrap(err, "vendor attributes update") } if err := dv.updateMetadataAttributes(ctx, exec, srv); err != nil { return errors.Wrap(err, "metadata attribute update") } + if err := dv.updateUefiVariables(ctx, exec, srv); err != nil { + return errors.Wrap(err, "uefi variables update") + } return nil } func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { - // populate the vendor attributes attrs, err := models.Attributes(qm.Where("server_id=?", srv)).All(ctx, exec) if err != nil { return err } if dv.Inv == nil { - dv.Inv = &common.Device{} + dv.Inv = &common.Device{ + Common: common.Common{ + Metadata: map[string]string{}, + }, + } } for _, a := range attrs { @@ -160,17 +226,27 @@ func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecut if err := a.Data.Unmarshal(&m); err != nil { return errors.Wrap(err, "unmarshaling vendor attributes") } - dv.Inv.Vendor = m["vendor"] - dv.Inv.Model = m["model"] - dv.Inv.Serial = m["serial"] + dv.Inv.Vendor = m[vendorKey] + dv.Inv.Model = m[modelKey] + dv.Inv.Serial = m[serialKey] case alloyMetadataNamespace: - m := map[string]string{} - if err := a.Data.Unmarshal(&m); err != nil { + if err := a.Data.Unmarshal(&dv.Inv.Metadata); err != nil { return errors.Wrap(err, "unmarshaling metadata attributes") } - dv.Inv.Metadata = m default: } } + + uefiVarsAttr, err := models.VersionedAttributes( + qm.Where("server_id=?", srv), + qm.And(fmt.Sprintf("namespace='%s'", alloyUefiVarsNamespace)), + qm.OrderBy("tally DESC"), + ).One(ctx, exec) + + if err != nil { + return err + } + + dv.Inv.Metadata[uefiVarsKey] = uefiVarsAttr.Data.String() return nil } diff --git a/internal/inventory/device_view_test.go b/internal/inventory/device_view_test.go index 416f0a6..95e2848 100644 --- a/internal/inventory/device_view_test.go +++ b/internal/inventory/device_view_test.go @@ -4,6 +4,7 @@ package inventory import ( "context" + "encoding/json" "testing" "github.com/bmc-toolbox/common" @@ -28,7 +29,7 @@ func Test_DeviceViewUpdate(t *testing.T) { Model: "BestModel 420", Serial: "0xdeadbeef", Metadata: map[string]string{ - "uefi-variables": "shouldn't be here", + "uefi-variables": `{ "msg":"hi there" }`, "metakey": "value", }, }, @@ -50,6 +51,7 @@ func Test_DeviceViewUpdate(t *testing.T) { // do it again to test the update dv.Inv.Common.Serial = "roastbeef" + dv.Inv.Metadata["uefi-variables"] = `{ "msg": "hi again" }` err = dv.UpsertInventory(context.TODO(), db, srvID, false) require.NoError(t, err) @@ -58,6 +60,13 @@ func Test_DeviceViewUpdate(t *testing.T) { err = read.FromDatastore(context.TODO(), db, srvID) require.NoError(t, err) require.Equal(t, "roastbeef", read.Inv.Serial) - require.Equal(t, 1, len(read.Inv.Metadata)) + require.Equal(t, 2, len(read.Inv.Metadata)) + + varStr, ok := read.Inv.Metadata["uefi-variables"] + require.True(t, ok) + + uefiVar := map[string]any{} + require.NoError(t, json.Unmarshal([]byte(varStr), &uefiVar)) + require.Equal(t, "hi again", uefiVar["msg"]) }) } From aaaf52173fea991f23d8579c498bca4152124d6b Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Wed, 3 Apr 2024 17:04:20 -0400 Subject: [PATCH 05/21] move component type initialization to FleetDB Currently alloy creates the server component types on startup if they haven't been populated previously. This change imports bmc-toolbox's component slugs and uses them to populate the server_component_types table using an upsert. This should be able to capture changes to known component types on an upgrade too. --- cmd/serve.go | 7 ++ internal/dbtools/component_types.go | 79 ++++++++++++++++++++++ internal/dbtools/component_types_test.go | 36 ++++++++++ internal/inventory/component_attributes.go | 1 + internal/inventory/device_components.go | 1 + internal/inventory/device_view.go | 2 - pkg/api/v1/router_inventory.go | 4 +- 7 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 internal/dbtools/component_types.go create mode 100644 internal/dbtools/component_types_test.go diff --git a/cmd/serve.go b/cmd/serve.go index 94b6600..beb5e1f 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -13,6 +13,7 @@ import ( "go.infratographer.com/x/crdbx" "go.infratographer.com/x/otelx" "go.infratographer.com/x/viperx" + "go.uber.org/zap" "gocloud.dev/secrets" // import gocdk secret drivers @@ -92,6 +93,12 @@ func serve(ctx context.Context) { dbtools.RegisterHooks() + if err := dbtools.SetupComponentTypes(ctx, db); err != nil { + logger.With( + zap.Error(err), + ).Fatal("set up component types") + } + keeper, err := secrets.OpenKeeper(ctx, viper.GetString("db.encryption_driver")) if err != nil { logger.Fatalw("failed to open secrets keeper", "error", err) diff --git a/internal/dbtools/component_types.go b/internal/dbtools/component_types.go new file mode 100644 index 0000000..97d37e3 --- /dev/null +++ b/internal/dbtools/component_types.go @@ -0,0 +1,79 @@ +package dbtools + +import ( + "context" + "database/sql" + + "github.com/bmc-toolbox/common" + "github.com/gosimple/slug" + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + + "github.com/metal-toolbox/fleetdb/internal/models" +) + +var errAddTypes = errors.New("unable to add component types") + +// XXX: if bmc-toolbox exported this as a list, we could just import it. +var knownComponentTypes = []string{ + common.SlugBackplaneExpander, + common.SlugChassis, + common.SlugTPM, + common.SlugGPU, + common.SlugCPU, + common.SlugPhysicalMem, + common.SlugStorageController, + common.SlugBMC, + common.SlugBIOS, + common.SlugDrive, + common.SlugDriveTypePCIeNVMEeSSD, + common.SlugDriveTypeSATASSD, + common.SlugDriveTypeSATAHDD, + common.SlugNIC, + common.SlugPSU, + common.SlugCPLD, + common.SlugEnclosure, + common.SlugUnknown, + common.SlugMainboard, +} + +// SetupComponentTypes upserts all known component types to the database. +// Despite the descriptor, in the database the Name field of the component type +// is the verbatim value of the string, and the Slug is computed as a lower-case +// english-localized variant. +func SetupComponentTypes(ctx context.Context, db *sqlx.DB) error { + txn := db.MustBeginTx(ctx, &sql.TxOptions{}) + for _, typ := range knownComponentTypes { + sct := &models.ServerComponentType{ + Name: typ, + Slug: slug.Make(typ), + } + if err := sct.Upsert(ctx, txn, false, []string{"slug"}, boil.None(), boil.Infer()); err != nil { + _ = txn.Rollback() + return errors.Wrap(errAddTypes, err.Error()) + } + } + return txn.Commit() +} + +// ComponentTypeIDFromName expects the name of the component (as defined in +// bmc-toolbox) and will return the internal database ID for that name. +func ComponentTypeIDFromName(ctx context.Context, exec boil.ContextExecutor, name string) (string, error) { + sct, err := models.ServerComponentTypes( + models.ServerComponentTypeWhere.Name.EQ(name), + ).One(ctx, exec) + if err != nil { + return "", err + } + return sct.ID, nil +} + +// MustComponentTypeID returns the component type id for the given component name or panics +func MustComponentTypeID(ctx context.Context, exec boil.ContextExecutor, name string) string { + id, err := ComponentTypeIDFromName(ctx, exec, name) + if err != nil { + panic(err) + } + return id +} diff --git a/internal/dbtools/component_types_test.go b/internal/dbtools/component_types_test.go new file mode 100644 index 0000000..b3e9f9e --- /dev/null +++ b/internal/dbtools/component_types_test.go @@ -0,0 +1,36 @@ +//go:build testtools && integration + +package dbtools + +import ( + "context" + "testing" + + "github.com/bmc-toolbox/common" + "github.com/stretchr/testify/require" +) + +func TestServerComponentTypes(t *testing.T) { + t.Parallel() + db := DatabaseTest(t) + ctx := context.TODO() + + err := SetupComponentTypes(ctx, db) + require.NoError(t, err) + + for _, typ := range knownComponentTypes { + _, err := ComponentTypeIDFromName(ctx, db, typ) + require.NoError(t, err, "couldn't find %s", typ) + } + + require.NotPanics(t, func() { _ = MustComponentTypeID(ctx, db, common.SlugBackplaneExpander) }) + + require.Panics(t, func() { _ = MustComponentTypeID(ctx, db, "bogus") }) + + _, err = ComponentTypeIDFromName(ctx, db, "bogus") + require.Error(t, err, "no error on bogus") + + err = SetupComponentTypes(ctx, db) + require.NoError(t, err, "duplicated setup call") + +} diff --git a/internal/inventory/component_attributes.go b/internal/inventory/component_attributes.go index 5b09ffd..ff46426 100644 --- a/internal/inventory/component_attributes.go +++ b/internal/inventory/component_attributes.go @@ -1,3 +1,4 @@ +//nolint:all // XXX remove this! package inventory import ( diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go index 6c2fcfb..78dfb52 100644 --- a/internal/inventory/device_components.go +++ b/internal/inventory/device_components.go @@ -1,3 +1,4 @@ +//nolint:all // XXX remove this! package inventory import ( diff --git a/internal/inventory/device_view.go b/internal/inventory/device_view.go index 7d63bfe..fd85d70 100644 --- a/internal/inventory/device_view.go +++ b/internal/inventory/device_view.go @@ -32,8 +32,6 @@ import ( */ var ( - versionedAttributesByServerID = "(namespace, created_at) IN (select namespace, max(created_at) from versioned_attributes where server_id=? group by namespace)" - // historically these values were determined/set by alloy, even though they are // internal to the data storage layer, hence the names alloyVendorNamespace = "sh.hollow.alloy.server_vendor_attributes" diff --git a/pkg/api/v1/router_inventory.go b/pkg/api/v1/router_inventory.go index 1e73e2f..4edf9c1 100644 --- a/pkg/api/v1/router_inventory.go +++ b/pkg/api/v1/router_inventory.go @@ -21,7 +21,7 @@ func (r *Router) getInventory(c *gin.Context) { } func (r *Router) setInventory(c *gin.Context) { - srvId, err := r.parseUUID(c) + srvID, err := r.parseUUID(c) if err != nil { badRequestResponse(c, "invalid server id", err) return @@ -42,7 +42,7 @@ func (r *Router) setInventory(c *gin.Context) { return } - if err := view.UpsertInventory(c.Request.Context(), r.DB, srvId, doInband); err != nil { + if err := view.UpsertInventory(c.Request.Context(), r.DB, srvID, doInband); err != nil { dbErrorResponse(c, err) } } From cb2a8290100555d6dc3c4821a1f6a160d6b10d10 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Thu, 4 Apr 2024 13:55:35 -0400 Subject: [PATCH 06/21] refactor updateAnyAttribute so we can do component attributes too --- internal/inventory/device_view.go | 44 +++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/internal/inventory/device_view.go b/internal/inventory/device_view.go index fd85d70..ced6cd6 100644 --- a/internal/inventory/device_view.go +++ b/internal/inventory/device_view.go @@ -111,30 +111,40 @@ func (dv *DeviceView) uefiVariables() (json.RawMessage, error) { return []byte(varString), nil } +// the "either server or server-component" facet of attributes makes this function a +// little complicated func (dv *DeviceView) updateAnyAttribute(ctx context.Context, exec boil.ContextExecutor, - srv uuid.UUID, namespace string, data json.RawMessage) error { - mods := []qm.QueryMod{ - qm.Where("server_id=?", srv), - qm.And(fmt.Sprintf("namespace='%s'", namespace)), + isServerAttr bool, id uuid.UUID, namespace string, data json.RawMessage) error { + var mods []qm.QueryMod + + idStr := null.StringFrom(id.String()) + attrData := types.JSON(data) + currentTime := null.TimeFrom(time.Now()) + + // create an attribute in the event we need to make an insert + attr := models.Attribute{ + Namespace: namespace, + Data: attrData, + CreatedAt: currentTime, } - now := time.Now() + + if isServerAttr { + attr.ServerID = idStr + mods = append(mods, models.AttributeWhere.ServerID.EQ(idStr)) + } else { + attr.ServerComponentID = idStr + mods = append(mods, models.AttributeWhere.ServerComponentID.EQ(idStr)) + } + mods = append(mods, models.AttributeWhere.Namespace.EQ(namespace)) existing, err := models.Attributes(mods...).One(ctx, exec) switch err { case nil: - // do update - existing.Data = types.JSON(data) - existing.UpdatedAt = null.TimeFrom(now) + existing.Data = attrData + existing.UpdatedAt = currentTime _, updErr := existing.Update(ctx, exec, boil.Infer()) return updErr case sql.ErrNoRows: - // do insert - attr := models.Attribute{ - ServerID: null.StringFrom(srv.String()), - Namespace: namespace, - Data: types.JSON(data), - CreatedAt: null.TimeFrom(now), - } return attr.Insert(ctx, exec, boil.Infer()) default: return err @@ -142,13 +152,13 @@ func (dv *DeviceView) updateAnyAttribute(ctx context.Context, exec boil.ContextE } func (dv *DeviceView) updateVendorAttributes(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { - return dv.updateAnyAttribute(ctx, exec, srv, alloyVendorNamespace, dv.vendorAttributes()) + return dv.updateAnyAttribute(ctx, exec, true, srv, alloyVendorNamespace, dv.vendorAttributes()) } func (dv *DeviceView) updateMetadataAttributes(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { var err error if md := dv.metadataAttributes(); md != nil { - err = dv.updateAnyAttribute(ctx, exec, srv, alloyMetadataNamespace, md) + err = dv.updateAnyAttribute(ctx, exec, true, srv, alloyMetadataNamespace, md) } return err } From 70b5431ebd4989d27229973efce146d02e9d2cf7 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 5 Apr 2024 18:16:17 -0400 Subject: [PATCH 07/21] add real component initialization to setup and fix test fallout --- internal/dbtools/fixtures.go | 4 ++++ pkg/api/v1/router_server_component_type_test.go | 5 ++--- pkg/api/v1/router_server_components_test.go | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/dbtools/fixtures.go b/internal/dbtools/fixtures.go index d599a2b..413f510 100644 --- a/internal/dbtools/fixtures.go +++ b/internal/dbtools/fixtures.go @@ -137,6 +137,10 @@ func addFixtures(t *testing.T) error { return err } + if err := SetupComponentTypes(ctx, testDB); err != nil { + return err + } + // excluding Chuckles here since that server is deleted FixtureServers = models.ServerSlice{FixtureNemo, FixtureDory, FixtureMarlin} FixtureDeletedServers = models.ServerSlice{FixtureChuckles} diff --git a/pkg/api/v1/router_server_component_type_test.go b/pkg/api/v1/router_server_component_type_test.go index 167d6de..bab1cb0 100644 --- a/pkg/api/v1/router_server_component_type_test.go +++ b/pkg/api/v1/router_server_component_type_test.go @@ -42,9 +42,8 @@ func TestIntegrationListServerComponentTypes(t *testing.T) { r, resp, err := s.Client.ListServerComponentTypes(ctx, nil) if !expectError { require.NoError(t, err) - assert.Len(t, r, 1) - assert.Equal(t, dbtools.FixtureFinType.Slug, r[0].Slug) - assert.Equal(t, dbtools.FixtureFinType.Name, r[0].Name) + assert.GreaterOrEqual(t, len(r), 1) + assert.NotNil(t, r.ByName(dbtools.FixtureFinType.Name)) assert.NotNil(t, resp) assert.NotNil(t, resp.Links.Self) } diff --git a/pkg/api/v1/router_server_components_test.go b/pkg/api/v1/router_server_components_test.go index 842dd30..b2f0004 100644 --- a/pkg/api/v1/router_server_components_test.go +++ b/pkg/api/v1/router_server_components_test.go @@ -199,7 +199,7 @@ func TestIntegrationServerGetComponents(t *testing.T) { } // expect atleast 1 component type to proceed - assert.Len(t, componentTypeSlice, 1) + assert.GreaterOrEqual(t, len(componentTypeSlice), 1) // fixture to create a server components csFixtureCreate := fleetdbapi.ServerComponentSlice{ From bff98be43e34ee14ee6a31e118231748f0544ec9 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 5 Apr 2024 18:17:34 -0400 Subject: [PATCH 08/21] flesh out the incoming inventory API a bit --- pkg/api/v1/router_inventory.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/pkg/api/v1/router_inventory.go b/pkg/api/v1/router_inventory.go index 4edf9c1..30bdda1 100644 --- a/pkg/api/v1/router_inventory.go +++ b/pkg/api/v1/router_inventory.go @@ -4,9 +4,10 @@ package fleetdbapi import ( "net/http" - "github.com/metal-toolbox/fleetdb/internal/inventory" - "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "github.com/metal-toolbox/fleetdb/internal/inventory" ) func unimplemented(c *gin.Context) { @@ -36,13 +37,36 @@ func (r *Router) setInventory(c *gin.Context) { badRequestResponse(c, "invalid inventory mode", nil) } - var view inventory.DeviceView + view := inventory.DeviceView{} if err := c.ShouldBindJSON(&view); err != nil { badRequestResponse(c, "invalid inventory payload", err) return } - if err := view.UpsertInventory(c.Request.Context(), r.DB, srvID, doInband); err != nil { + view.DeviceID = srvID + view.Inband = doInband + + txn := r.DB.MustBegin() + + // XXX what about BIOS config? + if err := view.UpsertInventory(c.Request.Context(), txn); err != nil { + if err := txn.Rollback(); err != nil { + r.Logger.With( + zap.Error(err), + zap.String("device_id", srvID.String()), + zap.Bool("inband", doInband), + ).Warn("rollback error") + // increment error metrics + } + dbErrorResponse(c, err) + } + if err := txn.Commit(); err != nil { + r.Logger.With( + zap.Error(err), + zap.String("device_id", srvID.String()), + zap.Bool("inband", doInband), + ).Warn("commit error") + // increment error metrics dbErrorResponse(c, err) } } From fd4e08013fb3cdff19543ea1f7b3a206098a1f79 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 5 Apr 2024 18:19:22 -0400 Subject: [PATCH 09/21] parallel tests are problematic with a big global database --- internal/dbtools/component_types_test.go | 1 - internal/inventory/device_view_test.go | 25 +++++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/internal/dbtools/component_types_test.go b/internal/dbtools/component_types_test.go index b3e9f9e..bd8c7cc 100644 --- a/internal/dbtools/component_types_test.go +++ b/internal/dbtools/component_types_test.go @@ -11,7 +11,6 @@ import ( ) func TestServerComponentTypes(t *testing.T) { - t.Parallel() db := DatabaseTest(t) ctx := context.TODO() diff --git a/internal/inventory/device_view_test.go b/internal/inventory/device_view_test.go index 95e2848..1e536d5 100644 --- a/internal/inventory/device_view_test.go +++ b/internal/inventory/device_view_test.go @@ -18,10 +18,9 @@ import ( ) func Test_DeviceViewUpdate(t *testing.T) { - t.Parallel() t.Run("insert and update device attributes", func(t *testing.T) { - t.Parallel() db := dbtools.DatabaseTest(t) + srvID := uuid.New() dv := DeviceView{ Inv: &common.Device{ Common: common.Common{ @@ -34,8 +33,8 @@ func Test_DeviceViewUpdate(t *testing.T) { }, }, }, + DeviceID: srvID, } - srvID := uuid.New() // make a server for out attributes server := models.Server{ ID: srvID.String(), @@ -46,18 +45,30 @@ func Test_DeviceViewUpdate(t *testing.T) { srvErr := server.Insert(context.TODO(), db, boil.Infer()) require.NoError(t, srvErr, "server setup failed") - err := dv.UpsertInventory(context.TODO(), db, srvID, false) + err := dv.UpsertInventory(context.TODO(), db) require.NoError(t, err) // do it again to test the update dv.Inv.Common.Serial = "roastbeef" dv.Inv.Metadata["uefi-variables"] = `{ "msg": "hi again" }` - err = dv.UpsertInventory(context.TODO(), db, srvID, false) + err = dv.UpsertInventory(context.TODO(), db) require.NoError(t, err) + // there should be 2 records for the UEFI variables versioned attribute + + count, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerID.EQ(null.StringFrom(srvID.String())), + models.VersionedAttributeWhere.Namespace.EQ(alloyUefiVarsNamespace), + ).Count(context.TODO(), db) + + require.NoError(t, err, "counting uefi versioned attributes") + require.Equal(t, int64(2), count) + // validate the contents - read := DeviceView{} - err = read.FromDatastore(context.TODO(), db, srvID) + read := DeviceView{ + DeviceID: srvID, + } + err = read.FromDatastore(context.TODO(), db) require.NoError(t, err) require.Equal(t, "roastbeef", read.Inv.Serial) require.Equal(t, 2, len(read.Inv.Metadata)) From c6935f7883cc87f1b36b6436d567086454b0360e Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 5 Apr 2024 18:24:05 -0400 Subject: [PATCH 10/21] generalize the routines for upserting attributes and versioned attributes --- internal/inventory/device_view.go | 99 +++++++++++++++++-------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/internal/inventory/device_view.go b/internal/inventory/device_view.go index ced6cd6..0c2fe02 100644 --- a/internal/inventory/device_view.go +++ b/internal/inventory/device_view.go @@ -6,7 +6,6 @@ import ( "database/sql" "encoding/json" "fmt" - "time" "github.com/bmc-toolbox/common" "github.com/google/uuid" @@ -50,6 +49,8 @@ var ( type DeviceView struct { Inv *common.Device `json:"inventory"` BiosConfig map[string]string `json:"bios_config,omitempty"` + Inband bool // the method of inventory collection + DeviceID uuid.UUID } func (dv *DeviceView) vendorAttributes() json.RawMessage { @@ -113,19 +114,17 @@ func (dv *DeviceView) uefiVariables() (json.RawMessage, error) { // the "either server or server-component" facet of attributes makes this function a // little complicated -func (dv *DeviceView) updateAnyAttribute(ctx context.Context, exec boil.ContextExecutor, - isServerAttr bool, id uuid.UUID, namespace string, data json.RawMessage) error { +func updateAnyAttribute(ctx context.Context, exec boil.ContextExecutor, + isServerAttr bool, id, namespace string, data json.RawMessage) error { var mods []qm.QueryMod - idStr := null.StringFrom(id.String()) + idStr := null.StringFrom(id) attrData := types.JSON(data) - currentTime := null.TimeFrom(time.Now()) // create an attribute in the event we need to make an insert attr := models.Attribute{ Namespace: namespace, Data: attrData, - CreatedAt: currentTime, } if isServerAttr { @@ -140,9 +139,8 @@ func (dv *DeviceView) updateAnyAttribute(ctx context.Context, exec boil.ContextE existing, err := models.Attributes(mods...).One(ctx, exec) switch err { case nil: - existing.Data = attrData - existing.UpdatedAt = currentTime - _, updErr := existing.Update(ctx, exec, boil.Infer()) + attr.ID = existing.ID + _, updErr := attr.Update(ctx, exec, boil.Infer()) return updErr case sql.ErrNoRows: return attr.Insert(ctx, exec, boil.Infer()) @@ -151,70 +149,80 @@ func (dv *DeviceView) updateAnyAttribute(ctx context.Context, exec boil.ContextE } } -func (dv *DeviceView) updateVendorAttributes(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { - return dv.updateAnyAttribute(ctx, exec, true, srv, alloyVendorNamespace, dv.vendorAttributes()) +func (dv *DeviceView) updateVendorAttributes(ctx context.Context, exec boil.ContextExecutor) error { + return updateAnyAttribute(ctx, exec, true, dv.DeviceID.String(), alloyVendorNamespace, dv.vendorAttributes()) } -func (dv *DeviceView) updateMetadataAttributes(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { +func (dv *DeviceView) updateMetadataAttributes(ctx context.Context, exec boil.ContextExecutor) error { var err error if md := dv.metadataAttributes(); md != nil { - err = dv.updateAnyAttribute(ctx, exec, true, srv, alloyMetadataNamespace, md) + err = updateAnyAttribute(ctx, exec, true, dv.DeviceID.String(), alloyMetadataNamespace, md) } return err } -// write a versioned-attribute containing the UEFI variables from this server -func (dv *DeviceView) updateUefiVariables(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { - mods := []qm.QueryMod{ - qm.Where("server_id=?", srv), - qm.And(fmt.Sprintf("namespace='%s'", alloyUefiVarsNamespace)), +// insert a new versioned attribute record with the provided data. if this is not the first +// time we've seen a id/namespace tuple, increment the tally +func updateAnyVersionedAttribute(ctx context.Context, exec boil.ContextExecutor, + isServerAttr bool, id, namespace string, data json.RawMessage) error { + var mods []qm.QueryMod + + idStr := null.StringFrom(id) + attrData := types.JSON(data) + + // we will always insert a new versioned attribute, just incrementing the tally + vattr := models.VersionedAttribute{ + Namespace: namespace, + Data: attrData, } - now := time.Now() - varData, err := dv.uefiVariables() - if err != nil { - return err + if isServerAttr { + vattr.ServerID = idStr + mods = append(mods, models.VersionedAttributeWhere.ServerID.EQ(idStr)) + } else { + vattr.ServerComponentID = idStr + mods = append(mods, models.VersionedAttributeWhere.ServerComponentID.EQ(idStr)) } + mods = append(mods, models.VersionedAttributeWhere.Namespace.EQ(namespace), qm.OrderBy("tally DESC")) - existing, err := models.VersionedAttributes(mods...).One(ctx, exec) + lastVA, err := models.VersionedAttributes(mods...).One(ctx, exec) switch err { case nil: - // do update - existing.Data = types.JSON(varData) - existing.Tally = existing.Tally + 1 - existing.UpdatedAt = null.TimeFrom(now) - _, updErr := existing.Update(ctx, exec, boil.Infer()) - return updErr + vattr.Tally = lastVA.Tally + 1 case sql.ErrNoRows: - // do insert - va := models.VersionedAttribute{ - ServerID: null.StringFrom(srv.String()), - Namespace: alloyUefiVarsNamespace, - CreatedAt: null.TimeFrom(now), - Data: types.JSON(varData), - } - return va.Insert(ctx, exec, boil.Infer()) + // first time we've seen this vattr default: return err } + return vattr.Insert(ctx, exec, boil.Infer()) } -func (dv *DeviceView) UpsertInventory(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID, inband bool) error { +// write a versioned-attribute containing the UEFI variables from this server +func (dv *DeviceView) updateUefiVariables(ctx context.Context, exec boil.ContextExecutor) error { + uefiVarData, err := dv.uefiVariables() + if err != nil { + return err + } + return updateAnyVersionedAttribute(ctx, exec, true, + dv.DeviceID.String(), alloyUefiVarsNamespace, uefiVarData) +} + +func (dv *DeviceView) UpsertInventory(ctx context.Context, exec boil.ContextExecutor) error { // yes, this is a dopey, repetitive style that should be easy for folks to extend or modify - if err := dv.updateVendorAttributes(ctx, exec, srv); err != nil { + if err := dv.updateVendorAttributes(ctx, exec); err != nil { return errors.Wrap(err, "vendor attributes update") } - if err := dv.updateMetadataAttributes(ctx, exec, srv); err != nil { + if err := dv.updateMetadataAttributes(ctx, exec); err != nil { return errors.Wrap(err, "metadata attribute update") } - if err := dv.updateUefiVariables(ctx, exec, srv); err != nil { + if err := dv.updateUefiVariables(ctx, exec); err != nil { return errors.Wrap(err, "uefi variables update") } return nil } -func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecutor, srv uuid.UUID) error { - attrs, err := models.Attributes(qm.Where("server_id=?", srv)).All(ctx, exec) +func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecutor) error { + attrs, err := models.Attributes(qm.Where("server_id=?", dv.DeviceID)).All(ctx, exec) if err != nil { return err } @@ -246,7 +254,7 @@ func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecut } uefiVarsAttr, err := models.VersionedAttributes( - qm.Where("server_id=?", srv), + qm.Where("server_id=?", dv.DeviceID), qm.And(fmt.Sprintf("namespace='%s'", alloyUefiVarsNamespace)), qm.OrderBy("tally DESC"), ).One(ctx, exec) @@ -256,5 +264,8 @@ func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecut } dv.Inv.Metadata[uefiVarsKey] = uefiVarsAttr.Data.String() + + // XXX: get components and component attributes and populate the dv.Inv + return nil } From 9b2c286d0fd1fab5f611cbdef0fa361023573d84 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Mon, 8 Apr 2024 16:40:04 -0400 Subject: [PATCH 11/21] implement bios and dimms --- internal/dbtools/component_types_test.go | 4 - internal/inventory/component_attributes.go | 67 ++-- internal/inventory/device_components.go | 174 ++++++++++- internal/inventory/device_components_test.go | 305 +++++++++++++++++++ 4 files changed, 511 insertions(+), 39 deletions(-) create mode 100644 internal/inventory/device_components_test.go diff --git a/internal/dbtools/component_types_test.go b/internal/dbtools/component_types_test.go index bd8c7cc..3ecaebd 100644 --- a/internal/dbtools/component_types_test.go +++ b/internal/dbtools/component_types_test.go @@ -28,8 +28,4 @@ func TestServerComponentTypes(t *testing.T) { _, err = ComponentTypeIDFromName(ctx, db, "bogus") require.Error(t, err, "no error on bogus") - - err = SetupComponentTypes(ctx, db) - require.NoError(t, err, "duplicated setup call") - } diff --git a/internal/inventory/component_attributes.go b/internal/inventory/component_attributes.go index ff46426..7c07de3 100644 --- a/internal/inventory/component_attributes.go +++ b/internal/inventory/component_attributes.go @@ -8,48 +8,61 @@ import ( ) type attributes struct { + Architecture string `json:"architecture,omitempty"` + BlockSizeBytes int64 `json:"block_size_bytes,omitempty"` + BusInfo string `json:"bus_info,omitempty"` Capabilities []*common.Capability `json:"capabilities,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - ID string `json:"id,omitempty"` + CapableSpeedGbps int64 `json:"capable_speed_gbps,omitempty"` + CapacityBytes int64 `json:"capacity_bytes,omitempty" diff:"immutable"` ChassisType string `json:"chassis_type,omitempty"` + ClockSpeedHz int64 `json:"clock_speed_hz,omitempty"` + Cores int `json:"cores,omitempty"` Description string `json:"description,omitempty"` - ProductName string `json:"product_name,omitempty"` + DriveType string `json:"drive_type,omitempty"` + FormFactor string `json:"form_factor,omitempty"` + ID string `json:"id,omitempty"` InterfaceType string `json:"interface_type,omitempty"` - Slot string `json:"slot,omitempty"` - Architecture string `json:"architecture,omitempty"` MacAddress string `json:"macaddress,omitempty"` - SupportedControllerProtocols string `json:"supported_controller_protocol,omitempty"` - SupportedDeviceProtocols string `json:"supported_device_protocol,omitempty"` - SupportedRAIDTypes string `json:"supported_raid_types,omitempty"` - PhysicalID string `json:"physid,omitempty"` - FormFactor string `json:"form_factor,omitempty"` - PartNumber string `json:"part_number,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + NegotiatedSpeedGbps int64 `json:"negotiated_speed_gbps,omitempty"` + Oem bool `json:"oem,omitempty"` OemID string `json:"oem_id,omitempty"` - DriveType string `json:"drive_type,omitempty"` - StorageController string `json:"storage_controller,omitempty"` - BusInfo string `json:"bus_info,omitempty"` - WWN string `json:"wwn,omitempty"` - Protocol string `json:"protocol,omitempty"` - SmartStatus string `json:"smart_status,omitempty"` - SmartErrors []string `json:"smart_errors,omitempty"` + PartNumber string `json:"part_number,omitempty"` + PhysicalID string `json:"physid,omitempty"` PowerCapacityWatts int64 `json:"power_capacity_watts,omitempty"` + ProductName string `json:"product_name,omitempty"` + Protocol string `json:"protocol,omitempty"` SizeBytes int64 `json:"size_bytes,omitempty"` - CapacityBytes int64 `json:"capacity_bytes,omitempty" diff:"immutable"` - ClockSpeedHz int64 `json:"clock_speed_hz,omitempty"` - Cores int `json:"cores,omitempty"` - Threads int `json:"threads,omitempty"` + Slot string `json:"slot,omitempty"` + SmartErrors []string `json:"smart_errors,omitempty"` + SmartStatus string `json:"smart_status,omitempty"` SpeedBits int64 `json:"speed_bits,omitempty"` SpeedGbps int64 `json:"speed_gbps,omitempty"` - BlockSizeBytes int64 `json:"block_size_bytes,omitempty"` - CapableSpeedGbps int64 `json:"capable_speed_gbps,omitempty"` - NegotiatedSpeedGbps int64 `json:"negotiated_speed_gbps,omitempty"` - Oem bool `json:"oem,omitempty"` + StorageController string `json:"storage_controller,omitempty"` + SupportedControllerProtocols string `json:"supported_controller_protocol,omitempty"` + SupportedDeviceProtocols string `json:"supported_device_protocol,omitempty"` + SupportedRAIDTypes string `json:"supported_raid_types,omitempty"` + Threads int `json:"threads,omitempty"` + WWN string `json:"wwn,omitempty"` } -func (a *attributes) mustJSON() []byte { +func (a *attributes) MustJSON() []byte { byt, err := json.Marshal(a) if err != nil { panic("bad attributes") } return byt } + +type versionedAttributes struct { + Firmware *common.Firmware `json:"firmware,omitempty"` + Status *common.Status `json:"status,omitempty"` +} + +func (va *versionedAttributes) MustJSON() []byte { + byt, err := json.Marshal(va) + if err != nil { + panic("bad attributes") + } + return byt +} diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go index 78dfb52..bb5875a 100644 --- a/internal/inventory/device_components.go +++ b/internal/inventory/device_components.go @@ -3,21 +3,179 @@ package inventory import ( "context" - "errors" + "database/sql" + "fmt" + "log" + "strings" - "github.com/google/uuid" + "github.com/bmc-toolbox/common" + "github.com/metal-toolbox/fleetdb/internal/dbtools" + "github.com/metal-toolbox/fleetdb/internal/models" + "github.com/pkg/errors" + "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" ) -func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextExecutor, - srv uuid.UUID, method string) error { - if err := dv.writeDimms(ctx, exec, srv, method); err != nil { +var ( + inbandComponentNamespace = "sh.hollow.alloy.inband.metadata" + outofbandComponentNamespace = "sh.hollow.alloy.outofband.metadata" + + errComponent = errors.New("component error") + errAttribute = errors.New("attribute error") + errVersionedAttr = errors.New("versioned attribute error") +) + +func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextExecutor) error { + if err := dv.writeBios(ctx, exec); err != nil { + return err + } + if err := dv.writeDimms(ctx, exec); err != nil { + return err + } + return nil +} + +func createOrUpdateComponent(ctx context.Context, exec boil.ContextExecutor, sc *models.ServerComponent) error { + existing, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(sc.Name), + models.ServerComponentWhere.ServerID.EQ(sc.ServerID), + models.ServerComponentWhere.Serial.EQ(sc.Serial), + models.ServerComponentWhere.ServerComponentTypeID.EQ(sc.ServerComponentTypeID), + ).One(ctx, exec) + + switch err { + case nil: + sc.ID = existing.ID + _, updErr := sc.Update(ctx, exec, boil.Infer()) + return updErr + case sql.ErrNoRows: + return sc.Insert(ctx, exec, boil.Infer()) + default: return err } +} + +func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) error { + typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugBIOS) + + bios := dv.Inv.BIOS + sc := &models.ServerComponent{ + Name: null.StringFrom(common.SlugBIOS), + Vendor: null.NewString(bios.Vendor, bios.Vendor != ""), + Model: null.NewString(bios.Model, bios.Model != ""), + Serial: null.NewString(bios.Serial, bios.Serial != ""), + ServerID: dv.DeviceID.String(), + ServerComponentTypeID: typeID, + } + + prodName := strings.TrimSpace(bios.ProductName) + if sc.Model.IsZero() && prodName != "" { + sc.Model.SetValid(prodName) + } + if err := createOrUpdateComponent(ctx, exec, sc); err != nil { + return errors.Wrap(errComponent, "bios: "+err.Error()) + } + + namespace := inbandComponentNamespace + if !dv.Inband { + namespace = outofbandComponentNamespace + } + + attrData := (&attributes{ + Capabilities: bios.Capabilities, + CapacityBytes: bios.CapacityBytes, + Description: bios.Description, + Metadata: bios.Metadata, + Oem: bios.Oem, + ProductName: prodName, + SizeBytes: bios.SizeBytes, + }).MustJSON() + + log.Printf("attribute data: %v", string(attrData)) + // update the component attribute + if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { + return errors.Wrap(errAttribute, "bios: "+err.Error()) + } + + // compose the versioned attributes + biosVA := &versionedAttributes{ + Firmware: bios.Firmware, + Status: bios.Status, + } + + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, biosVA.MustJSON()); err != nil { + return errors.Wrap(errVersionedAttr, "bios: "+err.Error()) + } + return nil } -func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor, - srv uuid.UUID, method string) error { - return errors.New("unimplemented") +func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) error { + typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugPhysicalMem) + + for idx, dimm := range dv.Inv.Memory { + // skip bogus dimms + if dimm.Vendor == "" && + dimm.ProductName == "" && + dimm.SizeBytes == 0 && + dimm.ClockSpeedHz == 0 { + continue + } + + sc := &models.ServerComponent{ + Name: null.StringFrom(common.SlugPhysicalMem), + Vendor: null.NewString(dimm.Vendor, dimm.Vendor != ""), + Model: null.NewString(dimm.Model, dimm.Model != ""), + Serial: null.NewString(dimm.Serial, dimm.Serial != ""), + ServerID: dv.DeviceID.String(), + ServerComponentTypeID: typeID, + } + + // set incrementing serial when one isn't found + if sc.Serial.IsZero() { + sc.Serial.SetValid(fmt.Sprintf("%d", idx)) + } + + prodName := strings.TrimSpace(dimm.ProductName) + if sc.Model.IsZero() && prodName != "" { + sc.Model.SetValid(prodName) + } + + if err := createOrUpdateComponent(ctx, exec, sc); err != nil { + return errors.Wrap(errComponent, "dimm: "+err.Error()) + } + + namespace := inbandComponentNamespace + if !dv.Inband { + namespace = outofbandComponentNamespace + } + + attrData := (&attributes{ + Capabilities: dimm.Capabilities, + ClockSpeedHz: dimm.ClockSpeedHz, + Description: dimm.Description, + FormFactor: dimm.FormFactor, + Metadata: dimm.Metadata, // maybe this should be versioned? + PartNumber: dimm.PartNumber, + ProductName: prodName, + SizeBytes: dimm.SizeBytes, + Slot: strings.TrimPrefix(dimm.Slot, "DIMM.Socket."), + }).MustJSON() + + // update the component attribute + if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { + return errors.Wrap(errAttribute, "dimm: "+err.Error()) + } + + // compose the versioned attributes for this dimm + dimmVA := &versionedAttributes{ + Firmware: dimm.Firmware, + Status: dimm.Status, + } + + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, dimmVA.MustJSON()); err != nil { + return errors.Wrap(errVersionedAttr, "dimm: "+err.Error()) + } + } + return nil } diff --git a/internal/inventory/device_components_test.go b/internal/inventory/device_components_test.go new file mode 100644 index 0000000..697fa4c --- /dev/null +++ b/internal/inventory/device_components_test.go @@ -0,0 +1,305 @@ +//go:build integration + +package inventory + +import ( + "context" + "encoding/json" + "testing" + + "github.com/bmc-toolbox/common" + "github.com/google/uuid" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + + "github.com/metal-toolbox/fleetdb/internal/dbtools" + "github.com/metal-toolbox/fleetdb/internal/models" +) + +func mustCreateServerRecord(t *testing.T, db *sqlx.DB, name string) uuid.UUID { + t.Helper() + // we need to create a server in order to fulfill the foreign-key requirement + // for server-components + srv := models.Server{ + Name: null.StringFrom(name), + FacilityCode: null.StringFrom("tf2"), + } + + err := srv.Insert(context.TODO(), db, boil.Infer()) + require.NoError(t, err, "server setup") + srvUUID := uuid.MustParse(srv.ID) + return srvUUID +} + +func TestComponents(t *testing.T) { + db := dbtools.DatabaseTest(t) + t.Run("writeBios", func(t *testing.T) { + srvUUID := mustCreateServerRecord(t, db, "write-bios") + + // XXX: If the serial number changes, we will insert a component record instead of updating it + orig := &common.BIOS{ + Common: common.Common{ + Oem: true, + Vendor: "coolguy", + Model: "xxxx", + ProductName: "the-product", + Serial: "some-serial", + Firmware: &common.Firmware{ + Installed: "some-version", + }, + Status: &common.Status{ + State: "OK", + Health: "decent", + }, + }, + SizeBytes: int64(1111), + CapacityBytes: int64(2222), + } + + update := &common.BIOS{ + Common: common.Common{ + Oem: false, + Vendor: "OpenBios", + ProductName: "super-cool", + Serial: "some-serial", + Firmware: &common.Firmware{ + Installed: "installed-version", + }, + Status: &common.Status{ + State: "contented", + Health: "healthy", + }, + }, + SizeBytes: int64(3333), + CapacityBytes: int64(4444), + } + + device := &DeviceView{ + DeviceID: srvUUID, + Inv: &common.Device{ + BIOS: orig, + }, + } // inband = false + + tx := db.MustBegin() + err := device.writeBios(context.TODO(), tx) + require.NoError(t, err) + _ = tx.Commit() + + scc, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBIOS)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + scr, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBIOS)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).One(context.TODO(), db) + require.NoError(t, err) + + ac, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac) + + vac, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), vac) + + // update the BIOS + device.Inv.BIOS = update + tx2 := db.MustBegin() + err = device.writeBios(context.TODO(), tx2) + require.NoError(t, err) + err = tx2.Commit() + require.NoError(t, err) + + scc, err = models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBIOS)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + ac, err = models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac) + + ar, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).One(context.TODO(), db) + require.NoError(t, err) + // unpack the Data to validate the update + var attr attributes + err = json.Unmarshal([]byte(ar.Data), &attr) + require.NoError(t, err) + require.Equal(t, int64(4444), attr.CapacityBytes) + require.Equal(t, int64(3333), attr.SizeBytes) + + vac, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(2), vac) + + vrec, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), + qm.OrderBy("tally DESC"), + ).One(context.TODO(), db) + require.NoError(t, err) + + vattr := versionedAttributes{} + err = json.Unmarshal([]byte(vrec.Data), &vattr) + require.NoError(t, err) + require.Equal(t, "contented", vattr.Status.State) + }) + t.Run("writeDimms", func(t *testing.T) { + srvUUID := mustCreateServerRecord(t, db, "write-dimms") + + device := &DeviceView{ + DeviceID: srvUUID, + Inv: &common.Device{ + Memory: []*common.Memory{ + &common.Memory{ + Common: common.Common{ // stutter much? + Vendor: "Elephant", + ProductName: "ivory", + Serial: "my-serial-number", + Firmware: &common.Firmware{ + Installed: "installed-version", + }, + Status: &common.Status{ + State: "carceral", + Health: "meh", + }, + }, + ID: "first", + Slot: "DIMM.Socket.4", + SizeBytes: int64(1024), + ClockSpeedHz: int64(60), + }, + &common.Memory{ + ID: "bogus", + }, + }, + }, + Inband: true, + } + + tx := db.MustBegin() + err := device.writeDimms(context.TODO(), tx) + require.NoError(t, err) + _ = tx.Commit() + + // interrogate the database to validate our transformation + // we expect a server component record, an attributes record, + // and a versioned attributes record + scc, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugPhysicalMem)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + scr, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugPhysicalMem)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).One(context.TODO(), db) + require.NoError(t, err) + + ac, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(inbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac) + + vac, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), vac) + + // update the DIMM record + device.Inv.Memory = []*common.Memory{ + &common.Memory{ + Common: common.Common{ + Vendor: "Elephant", + ProductName: "ivory", + Serial: "my-serial-number", + Firmware: &common.Firmware{ + Installed: "installed-version", + }, + Status: &common.Status{ + State: "contented", + Health: "healthy", + }, + }, + ID: "first", + Slot: "DIMM.Socket.4", + SizeBytes: int64(4096), + ClockSpeedHz: int64(120), + }, + } + + tx = db.MustBegin() + err = device.writeDimms(context.TODO(), tx) + require.NoError(t, err) + _ = tx.Commit() + + ac, err = models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(inbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac) + + ar, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(inbandComponentNamespace), + ).One(context.TODO(), db) + require.NoError(t, err) + // unpack the Data to validate the update + var attr attributes + err = json.Unmarshal([]byte(ar.Data), &attr) + require.NoError(t, err) + require.Equal(t, int64(120), attr.ClockSpeedHz) + require.Equal(t, int64(4096), attr.SizeBytes) + + vac, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(2), vac) + + vrec, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), + qm.OrderBy("tally DESC"), + ).One(context.TODO(), db) + require.NoError(t, err) + + vattr := versionedAttributes{} + err = json.Unmarshal([]byte(vrec.Data), &vattr) + require.NoError(t, err) + require.Equal(t, "contented", vattr.Status.State) + }) +} From 7ccad4fdb3d2d47e1fce1ada51831937565ae8a1 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Tue, 9 Apr 2024 10:41:05 -0400 Subject: [PATCH 12/21] implement bmc and mainboard --- internal/inventory/device_components.go | 136 +++++++++++-- internal/inventory/device_components_test.go | 196 +++++++++++++++++++ 2 files changed, 320 insertions(+), 12 deletions(-) diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go index bb5875a..8a55bd2 100644 --- a/internal/inventory/device_components.go +++ b/internal/inventory/device_components.go @@ -5,7 +5,6 @@ import ( "context" "database/sql" "fmt" - "log" "strings" "github.com/bmc-toolbox/common" @@ -25,16 +24,6 @@ var ( errVersionedAttr = errors.New("versioned attribute error") ) -func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextExecutor) error { - if err := dv.writeBios(ctx, exec); err != nil { - return err - } - if err := dv.writeDimms(ctx, exec); err != nil { - return err - } - return nil -} - func createOrUpdateComponent(ctx context.Context, exec boil.ContextExecutor, sc *models.ServerComponent) error { existing, err := models.ServerComponents( models.ServerComponentWhere.Name.EQ(sc.Name), @@ -55,6 +44,22 @@ func createOrUpdateComponent(ctx context.Context, exec boil.ContextExecutor, sc } } +func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextExecutor) error { + if err := dv.writeBios(ctx, exec); err != nil { + return err + } + if err := dv.writeBMC(ctx, exec); err != nil { + return err + } + if err := dv.writeMainboard(ctx, exec); err != nil { + return err + } + if err := dv.writeDimms(ctx, exec); err != nil { + return err + } + return nil +} + func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) error { typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugBIOS) @@ -91,7 +96,6 @@ func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) SizeBytes: bios.SizeBytes, }).MustJSON() - log.Printf("attribute data: %v", string(attrData)) // update the component attribute if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { return errors.Wrap(errAttribute, "bios: "+err.Error()) @@ -110,6 +114,114 @@ func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) return nil } +func (dv *DeviceView) writeBMC(ctx context.Context, exec boil.ContextExecutor) error { + typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugBMC) + + bmc := dv.Inv.BMC + sc := &models.ServerComponent{ + Name: null.StringFrom(common.SlugBMC), + Vendor: null.NewString(bmc.Vendor, bmc.Vendor != ""), + Model: null.NewString(bmc.Model, bmc.Model != ""), + Serial: null.NewString(bmc.Serial, bmc.Serial != ""), + ServerID: dv.DeviceID.String(), + ServerComponentTypeID: typeID, + } + + prodName := strings.TrimSpace(bmc.ProductName) + if sc.Model.IsZero() && prodName != "" { + sc.Model.SetValid(prodName) + } + if err := createOrUpdateComponent(ctx, exec, sc); err != nil { + return errors.Wrap(errComponent, "bmc: "+err.Error()) + } + + namespace := inbandComponentNamespace + if !dv.Inband { + namespace = outofbandComponentNamespace + } + + attrData := (&attributes{ + Capabilities: bmc.Capabilities, + Description: bmc.Description, + Metadata: bmc.Metadata, + Oem: bmc.Oem, + ProductName: prodName, + }).MustJSON() + + if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { + return errors.Wrap(errAttribute, "bmc: "+err.Error()) + } + + // compose the versioned attributes + bmcVA := &versionedAttributes{ + Firmware: bmc.Firmware, + Status: bmc.Status, + } + + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, bmcVA.MustJSON()); err != nil { + return errors.Wrap(errVersionedAttr, "bmc: "+err.Error()) + } + + return nil +} + +func (dv *DeviceView) writeMainboard(ctx context.Context, exec boil.ContextExecutor) error { + typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugMainboard) + + mb := dv.Inv.Mainboard + sc := &models.ServerComponent{ + Name: null.StringFrom(common.SlugMainboard), + Vendor: null.NewString(mb.Vendor, mb.Vendor != ""), + Model: null.NewString(mb.Model, mb.Model != ""), + Serial: null.NewString(mb.Serial, mb.Serial != ""), + ServerID: dv.DeviceID.String(), + ServerComponentTypeID: typeID, + } + + prodName := strings.TrimSpace(mb.ProductName) + if sc.Model.IsZero() && prodName != "" { + sc.Model.SetValid(prodName) + } + + if sc.Serial.IsZero() { + sc.Serial = null.StringFrom("0") + } + + if err := createOrUpdateComponent(ctx, exec, sc); err != nil { + return errors.Wrap(errComponent, "mb: "+err.Error()) + } + + namespace := inbandComponentNamespace + if !dv.Inband { + namespace = outofbandComponentNamespace + } + + attrData := (&attributes{ + Capabilities: mb.Capabilities, + Description: mb.Description, + Metadata: mb.Metadata, + Oem: mb.Oem, + PhysicalID: mb.PhysicalID, + ProductName: prodName, + }).MustJSON() + + if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { + return errors.Wrap(errAttribute, "mb: "+err.Error()) + } + + // compose the versioned attributes + mbVA := &versionedAttributes{ + Firmware: mb.Firmware, + Status: mb.Status, + } + + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, mbVA.MustJSON()); err != nil { + return errors.Wrap(errVersionedAttr, "mb: "+err.Error()) + } + + return nil +} + func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) error { typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugPhysicalMem) diff --git a/internal/inventory/device_components_test.go b/internal/inventory/device_components_test.go index 697fa4c..acf3538 100644 --- a/internal/inventory/device_components_test.go +++ b/internal/inventory/device_components_test.go @@ -169,6 +169,202 @@ func TestComponents(t *testing.T) { require.NoError(t, err) require.Equal(t, "contented", vattr.Status.State) }) + t.Run("writeBMC", func(t *testing.T) { + srvUUID := mustCreateServerRecord(t, db, "write-bmc") + + orig := &common.BMC{ + Common: common.Common{ + Oem: true, + Vendor: "coolguy", + Model: "xxxx", + ProductName: "the-product", + Serial: "some-serial", + Firmware: &common.Firmware{ + Installed: "some-version", + }, + Status: &common.Status{ + State: "OK", + Health: "decent", + }, + }, + // BMC contains a NIC and ID string as well, but not populated or stored? + } + + update := &common.BMC{ + Common: common.Common{ + Oem: false, + Vendor: "coolguy", + ProductName: "super-cool", + Serial: "some-serial", + Firmware: &common.Firmware{ + Installed: "installed-version", + }, + Status: &common.Status{ + State: "contented", + Health: "healthy", + }, + }, + } + + device := &DeviceView{ + DeviceID: srvUUID, + Inv: &common.Device{ + BMC: orig, + }, + } // inband = false + + tx := db.MustBegin() + err := device.writeBMC(context.TODO(), tx) + require.NoError(t, err) + _ = tx.Commit() + + scc, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBMC)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + scr, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBMC)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).One(context.TODO(), db) + require.NoError(t, err) + + ac, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac) + + vac, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), vac) + + // update the BMC + device.Inv.BMC = update + tx2 := db.MustBegin() + err = device.writeBMC(context.TODO(), tx2) + require.NoError(t, err) + err = tx2.Commit() + require.NoError(t, err) + + scc, err = models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBMC)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + ac, err = models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac) + + vac, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(2), vac) + + vrec, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), + qm.OrderBy("tally DESC"), + ).One(context.TODO(), db) + require.NoError(t, err) + + vattr := versionedAttributes{} + err = json.Unmarshal([]byte(vrec.Data), &vattr) + require.NoError(t, err) + require.Equal(t, "contented", vattr.Status.State) + }) + t.Run("writeMainboard", func(t *testing.T) { + srvUUID := mustCreateServerRecord(t, db, "write-mainboard") + + device := &DeviceView{ + DeviceID: srvUUID, + Inband: true, + Inv: &common.Device{ + Mainboard: &common.Mainboard{ + Common: common.Common{ + Oem: true, + Vendor: "theVendor", + Model: "theBest", + ProductName: "super-board", + Description: "a mainboard", + Firmware: &common.Firmware{ + Installed: "old-version", + }, + Status: &common.Status{ + State: "OK", + Health: "meh", + }, + }, + }, + }, + } + + tx := db.MustBegin() + err := device.writeMainboard(context.TODO(), tx) + require.NoError(t, err) + _ = tx.Commit() + + scc, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugMainboard)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + scr, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugMainboard)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).One(context.TODO(), db) + require.NoError(t, err) + + ac, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(inbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac) + + vac, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), vac) + + device.Inv.Mainboard.Firmware = &common.Firmware{ + Installed: "new-version", + } + + tx = db.MustBegin() + err = device.writeMainboard(context.TODO(), tx) + require.NoError(t, err) + _ = tx.Commit() + + vrec, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), + qm.OrderBy("tally DESC"), + ).One(context.TODO(), db) + require.NoError(t, err) + + vattr := versionedAttributes{} + err = json.Unmarshal([]byte(vrec.Data), &vattr) + require.NoError(t, err) + require.Equal(t, "new-version", vattr.Firmware.Installed) + }) t.Run("writeDimms", func(t *testing.T) { srvUUID := mustCreateServerRecord(t, db, "write-dimms") From 6cbaf7bfce9553e7708b69db0506008c1dbe9202 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Tue, 9 Apr 2024 13:21:45 -0400 Subject: [PATCH 13/21] refactor the common parts out of the update functions --- internal/inventory/device_components.go | 211 +++++++----------------- 1 file changed, 62 insertions(+), 149 deletions(-) diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go index 8a55bd2..7bf20e8 100644 --- a/internal/inventory/device_components.go +++ b/internal/inventory/device_components.go @@ -44,6 +44,57 @@ func createOrUpdateComponent(ctx context.Context, exec boil.ContextExecutor, sc } } +// This encapsulates much of the repetitive work of getting a component to the database layer. +// The caller needs to compose the correct attributes for its given component. +func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common.Common, + deviceID, namespace, slug string, attr *attributes) error { + typeID := dbtools.MustComponentTypeID(ctx, exec, slug) + + sc := &models.ServerComponent{ + Name: null.StringFrom(slug), + Vendor: null.NewString(cmn.Vendor, cmn.Vendor != ""), + Model: null.NewString(cmn.Model, cmn.Model != ""), + Serial: null.NewString(cmn.Serial, cmn.Serial != ""), + ServerID: deviceID, + ServerComponentTypeID: typeID, + } + + prodName := strings.TrimSpace(cmn.ProductName) + if sc.Model.IsZero() && prodName != "" { + sc.Model.SetValid(prodName) + } + + if sc.Serial.IsZero() { + sc.Serial = null.StringFrom("0") + } + + if err := createOrUpdateComponent(ctx, exec, sc); err != nil { + return errors.Wrap(errComponent, slug+": "+err.Error()) + } + + // avoid computing this twice + attr.ProductName = prodName + + attrData := attr.MustJSON() + + // update the component attribute + if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { + return errors.Wrap(errAttribute, slug+": "+err.Error()) + } + + // compose the versioned attributes + vattr := &versionedAttributes{ + Firmware: cmn.Firmware, + Status: cmn.Status, + } + + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, vattr.MustJSON()); err != nil { + return errors.Wrap(errVersionedAttr, slug+": "+err.Error()) + } + + return nil +} + func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextExecutor) error { if err := dv.writeBios(ctx, exec); err != nil { return err @@ -61,170 +112,63 @@ func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextEx } func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) error { - typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugBIOS) - bios := dv.Inv.BIOS - sc := &models.ServerComponent{ - Name: null.StringFrom(common.SlugBIOS), - Vendor: null.NewString(bios.Vendor, bios.Vendor != ""), - Model: null.NewString(bios.Model, bios.Model != ""), - Serial: null.NewString(bios.Serial, bios.Serial != ""), - ServerID: dv.DeviceID.String(), - ServerComponentTypeID: typeID, - } - - prodName := strings.TrimSpace(bios.ProductName) - if sc.Model.IsZero() && prodName != "" { - sc.Model.SetValid(prodName) - } - if err := createOrUpdateComponent(ctx, exec, sc); err != nil { - return errors.Wrap(errComponent, "bios: "+err.Error()) - } namespace := inbandComponentNamespace if !dv.Inband { namespace = outofbandComponentNamespace } - attrData := (&attributes{ + attr := &attributes{ Capabilities: bios.Capabilities, CapacityBytes: bios.CapacityBytes, Description: bios.Description, Metadata: bios.Metadata, Oem: bios.Oem, - ProductName: prodName, SizeBytes: bios.SizeBytes, - }).MustJSON() - - // update the component attribute - if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { - return errors.Wrap(errAttribute, "bios: "+err.Error()) - } - - // compose the versioned attributes - biosVA := &versionedAttributes{ - Firmware: bios.Firmware, - Status: bios.Status, - } - - if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, biosVA.MustJSON()); err != nil { - return errors.Wrap(errVersionedAttr, "bios: "+err.Error()) } - return nil + return composeRecords(ctx, exec, &bios.Common, dv.DeviceID.String(), namespace, common.SlugBIOS, attr) } func (dv *DeviceView) writeBMC(ctx context.Context, exec boil.ContextExecutor) error { - typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugBMC) - bmc := dv.Inv.BMC - sc := &models.ServerComponent{ - Name: null.StringFrom(common.SlugBMC), - Vendor: null.NewString(bmc.Vendor, bmc.Vendor != ""), - Model: null.NewString(bmc.Model, bmc.Model != ""), - Serial: null.NewString(bmc.Serial, bmc.Serial != ""), - ServerID: dv.DeviceID.String(), - ServerComponentTypeID: typeID, - } - - prodName := strings.TrimSpace(bmc.ProductName) - if sc.Model.IsZero() && prodName != "" { - sc.Model.SetValid(prodName) - } - if err := createOrUpdateComponent(ctx, exec, sc); err != nil { - return errors.Wrap(errComponent, "bmc: "+err.Error()) - } namespace := inbandComponentNamespace if !dv.Inband { namespace = outofbandComponentNamespace } - attrData := (&attributes{ + attr := &attributes{ Capabilities: bmc.Capabilities, Description: bmc.Description, Metadata: bmc.Metadata, Oem: bmc.Oem, - ProductName: prodName, - }).MustJSON() - - if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { - return errors.Wrap(errAttribute, "bmc: "+err.Error()) } - // compose the versioned attributes - bmcVA := &versionedAttributes{ - Firmware: bmc.Firmware, - Status: bmc.Status, - } - - if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, bmcVA.MustJSON()); err != nil { - return errors.Wrap(errVersionedAttr, "bmc: "+err.Error()) - } - - return nil + return composeRecords(ctx, exec, &bmc.Common, dv.DeviceID.String(), namespace, common.SlugBMC, attr) } func (dv *DeviceView) writeMainboard(ctx context.Context, exec boil.ContextExecutor) error { - typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugMainboard) - mb := dv.Inv.Mainboard - sc := &models.ServerComponent{ - Name: null.StringFrom(common.SlugMainboard), - Vendor: null.NewString(mb.Vendor, mb.Vendor != ""), - Model: null.NewString(mb.Model, mb.Model != ""), - Serial: null.NewString(mb.Serial, mb.Serial != ""), - ServerID: dv.DeviceID.String(), - ServerComponentTypeID: typeID, - } - - prodName := strings.TrimSpace(mb.ProductName) - if sc.Model.IsZero() && prodName != "" { - sc.Model.SetValid(prodName) - } - - if sc.Serial.IsZero() { - sc.Serial = null.StringFrom("0") - } - - if err := createOrUpdateComponent(ctx, exec, sc); err != nil { - return errors.Wrap(errComponent, "mb: "+err.Error()) - } namespace := inbandComponentNamespace if !dv.Inband { namespace = outofbandComponentNamespace } - attrData := (&attributes{ + attr := &attributes{ Capabilities: mb.Capabilities, Description: mb.Description, Metadata: mb.Metadata, Oem: mb.Oem, PhysicalID: mb.PhysicalID, - ProductName: prodName, - }).MustJSON() - - if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { - return errors.Wrap(errAttribute, "mb: "+err.Error()) - } - - // compose the versioned attributes - mbVA := &versionedAttributes{ - Firmware: mb.Firmware, - Status: mb.Status, } - if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, mbVA.MustJSON()); err != nil { - return errors.Wrap(errVersionedAttr, "mb: "+err.Error()) - } - - return nil + return composeRecords(ctx, exec, &mb.Common, dv.DeviceID.String(), namespace, common.SlugMainboard, attr) } func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) error { - typeID := dbtools.MustComponentTypeID(ctx, exec, common.SlugPhysicalMem) - for idx, dimm := range dv.Inv.Memory { // skip bogus dimms if dimm.Vendor == "" && @@ -234,27 +178,8 @@ func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) continue } - sc := &models.ServerComponent{ - Name: null.StringFrom(common.SlugPhysicalMem), - Vendor: null.NewString(dimm.Vendor, dimm.Vendor != ""), - Model: null.NewString(dimm.Model, dimm.Model != ""), - Serial: null.NewString(dimm.Serial, dimm.Serial != ""), - ServerID: dv.DeviceID.String(), - ServerComponentTypeID: typeID, - } - - // set incrementing serial when one isn't found - if sc.Serial.IsZero() { - sc.Serial.SetValid(fmt.Sprintf("%d", idx)) - } - - prodName := strings.TrimSpace(dimm.ProductName) - if sc.Model.IsZero() && prodName != "" { - sc.Model.SetValid(prodName) - } - - if err := createOrUpdateComponent(ctx, exec, sc); err != nil { - return errors.Wrap(errComponent, "dimm: "+err.Error()) + if strings.TrimSpace(dimm.Serial) == "" { + dimm.Serial = fmt.Sprintf("%d", idx) } namespace := inbandComponentNamespace @@ -262,31 +187,19 @@ func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) namespace = outofbandComponentNamespace } - attrData := (&attributes{ + attr := &attributes{ Capabilities: dimm.Capabilities, ClockSpeedHz: dimm.ClockSpeedHz, Description: dimm.Description, FormFactor: dimm.FormFactor, Metadata: dimm.Metadata, // maybe this should be versioned? PartNumber: dimm.PartNumber, - ProductName: prodName, SizeBytes: dimm.SizeBytes, Slot: strings.TrimPrefix(dimm.Slot, "DIMM.Socket."), - }).MustJSON() - - // update the component attribute - if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { - return errors.Wrap(errAttribute, "dimm: "+err.Error()) - } - - // compose the versioned attributes for this dimm - dimmVA := &versionedAttributes{ - Firmware: dimm.Firmware, - Status: dimm.Status, } - if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, dimmVA.MustJSON()); err != nil { - return errors.Wrap(errVersionedAttr, "dimm: "+err.Error()) + if err := composeRecords(ctx, exec, &dimm.Common, dv.DeviceID.String(), namespace, common.SlugPhysicalMem, attr); err != nil { + return err } } return nil From 3128379c658ef5545d46e708b30a0b80f2cb26c5 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 12 Apr 2024 07:44:51 -0400 Subject: [PATCH 14/21] change the namespacing and fimware/status handling for vattr --- internal/inventory/component_attributes.go | 43 ++- internal/inventory/device_components.go | 73 ++-- internal/inventory/device_components_test.go | 355 ++++++++++++------- 3 files changed, 309 insertions(+), 162 deletions(-) diff --git a/internal/inventory/component_attributes.go b/internal/inventory/component_attributes.go index 7c07de3..46c3f42 100644 --- a/internal/inventory/component_attributes.go +++ b/internal/inventory/component_attributes.go @@ -54,15 +54,46 @@ func (a *attributes) MustJSON() []byte { return byt } -type versionedAttributes struct { - Firmware *common.Firmware `json:"firmware,omitempty"` - Status *common.Status `json:"status,omitempty"` +func (a *attributes) FromJSON(byt []byte) error { + return json.Unmarshal(byt, a) } -func (va *versionedAttributes) MustJSON() []byte { - byt, err := json.Marshal(va) +func mustFirmwareJSON(fw *common.Firmware) []byte { + if fw == nil { + panic("missing firmware payload") + } + byt, err := json.Marshal(fw) if err != nil { - panic("bad attributes") + panic("bad firmware payload") + } + return byt +} + +func firmwareFromJSON(byt []byte) (*common.Firmware, error) { + fw := &common.Firmware{} + err := json.Unmarshal(byt, fw) + if err != nil { + return nil, err + } + return fw, nil +} + +func mustStatusJSON(st *common.Status) []byte { + if st == nil { + panic("missing status payload") + } + byt, err := json.Marshal(st) + if err != nil { + panic("bad status payload") } return byt } + +func statusFromJSON(byt []byte) (*common.Status, error) { + st := &common.Status{} + err := json.Unmarshal(byt, st) + if err != nil { + return nil, err + } + return st, nil +} diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go index 7bf20e8..ad0214f 100644 --- a/internal/inventory/device_components.go +++ b/internal/inventory/device_components.go @@ -16,14 +16,25 @@ import ( ) var ( - inbandComponentNamespace = "sh.hollow.alloy.inband.metadata" - outofbandComponentNamespace = "sh.hollow.alloy.outofband.metadata" - errComponent = errors.New("component error") errAttribute = errors.New("attribute error") errVersionedAttr = errors.New("versioned attribute error") ) +// conventions for data namespacing: +// 1. namespaces begin with a common prefix denoting whether this data +// was collected in-band or not +// 2. attributes get namespaced by including their component slug +// 3. versioned attributes will add a suffix (like '.firmware' or '.status') +// to the namespace so it's clear what the payload of those records refers to. +func getAttributeNamespace(inband bool, slug string) string { + mode := "outofband" + if inband { + mode = "inband" + } + return strings.Join([]string{slug, mode}, ".") +} + func createOrUpdateComponent(ctx context.Context, exec boil.ContextExecutor, sc *models.ServerComponent) error { existing, err := models.ServerComponents( models.ServerComponentWhere.Name.EQ(sc.Name), @@ -47,9 +58,11 @@ func createOrUpdateComponent(ctx context.Context, exec boil.ContextExecutor, sc // This encapsulates much of the repetitive work of getting a component to the database layer. // The caller needs to compose the correct attributes for its given component. func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common.Common, - deviceID, namespace, slug string, attr *attributes) error { + inband bool, deviceID, slug string, attr *attributes) error { typeID := dbtools.MustComponentTypeID(ctx, exec, slug) + namespace := getAttributeNamespace(inband, slug) + sc := &models.ServerComponent{ Name: null.StringFrom(slug), Vendor: null.NewString(cmn.Vendor, cmn.Vendor != ""), @@ -59,7 +72,7 @@ func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common. ServerComponentTypeID: typeID, } - prodName := strings.TrimSpace(cmn.ProductName) + prodName := strings.ToLower(strings.TrimSpace(cmn.ProductName)) if sc.Model.IsZero() && prodName != "" { sc.Model.SetValid(prodName) } @@ -82,14 +95,24 @@ func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common. return errors.Wrap(errAttribute, slug+": "+err.Error()) } - // compose the versioned attributes - vattr := &versionedAttributes{ - Firmware: cmn.Firmware, - Status: cmn.Status, + // every component with firmware gets a firmware versioned attribute + if cmn.Firmware != nil { + payload := mustFirmwareJSON(cmn.Firmware) + fwns := namespace + ".firmware" + + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, fwns, payload); err != nil { + return errors.Wrap(errVersionedAttr, slug+"-firmware: "+err.Error()) + } } - if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, namespace, vattr.MustJSON()); err != nil { - return errors.Wrap(errVersionedAttr, slug+": "+err.Error()) + // every component with status gets a status versioned attribute + if cmn.Status != nil { + payload := mustStatusJSON(cmn.Status) + sns := namespace + ".status" + + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, sns, payload); err != nil { + return errors.Wrap(errVersionedAttr, slug+"-status: "+err.Error()) + } } return nil @@ -114,11 +137,6 @@ func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextEx func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) error { bios := dv.Inv.BIOS - namespace := inbandComponentNamespace - if !dv.Inband { - namespace = outofbandComponentNamespace - } - attr := &attributes{ Capabilities: bios.Capabilities, CapacityBytes: bios.CapacityBytes, @@ -128,17 +146,12 @@ func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) SizeBytes: bios.SizeBytes, } - return composeRecords(ctx, exec, &bios.Common, dv.DeviceID.String(), namespace, common.SlugBIOS, attr) + return composeRecords(ctx, exec, &bios.Common, dv.Inband, dv.DeviceID.String(), common.SlugBIOS, attr) } func (dv *DeviceView) writeBMC(ctx context.Context, exec boil.ContextExecutor) error { bmc := dv.Inv.BMC - namespace := inbandComponentNamespace - if !dv.Inband { - namespace = outofbandComponentNamespace - } - attr := &attributes{ Capabilities: bmc.Capabilities, Description: bmc.Description, @@ -146,17 +159,12 @@ func (dv *DeviceView) writeBMC(ctx context.Context, exec boil.ContextExecutor) e Oem: bmc.Oem, } - return composeRecords(ctx, exec, &bmc.Common, dv.DeviceID.String(), namespace, common.SlugBMC, attr) + return composeRecords(ctx, exec, &bmc.Common, dv.Inband, dv.DeviceID.String(), common.SlugBMC, attr) } func (dv *DeviceView) writeMainboard(ctx context.Context, exec boil.ContextExecutor) error { mb := dv.Inv.Mainboard - namespace := inbandComponentNamespace - if !dv.Inband { - namespace = outofbandComponentNamespace - } - attr := &attributes{ Capabilities: mb.Capabilities, Description: mb.Description, @@ -165,7 +173,7 @@ func (dv *DeviceView) writeMainboard(ctx context.Context, exec boil.ContextExecu PhysicalID: mb.PhysicalID, } - return composeRecords(ctx, exec, &mb.Common, dv.DeviceID.String(), namespace, common.SlugMainboard, attr) + return composeRecords(ctx, exec, &mb.Common, dv.Inband, dv.DeviceID.String(), common.SlugMainboard, attr) } func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) error { @@ -182,11 +190,6 @@ func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) dimm.Serial = fmt.Sprintf("%d", idx) } - namespace := inbandComponentNamespace - if !dv.Inband { - namespace = outofbandComponentNamespace - } - attr := &attributes{ Capabilities: dimm.Capabilities, ClockSpeedHz: dimm.ClockSpeedHz, @@ -198,7 +201,7 @@ func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) Slot: strings.TrimPrefix(dimm.Slot, "DIMM.Socket."), } - if err := composeRecords(ctx, exec, &dimm.Common, dv.DeviceID.String(), namespace, common.SlugPhysicalMem, attr); err != nil { + if err := composeRecords(ctx, exec, &dimm.Common, dv.Inband, dv.DeviceID.String(), common.SlugPhysicalMem, attr); err != nil { return err } } diff --git a/internal/inventory/device_components_test.go b/internal/inventory/device_components_test.go index acf3538..b821626 100644 --- a/internal/inventory/device_components_test.go +++ b/internal/inventory/device_components_test.go @@ -4,7 +4,6 @@ package inventory import ( "context" - "encoding/json" "testing" "github.com/bmc-toolbox/common" @@ -34,12 +33,216 @@ func mustCreateServerRecord(t *testing.T, db *sqlx.DB, name string) uuid.UUID { return srvUUID } +func TestComposeComponentRecords(t *testing.T) { + db := dbtools.DatabaseTest(t) + t.Run("common case", func(t *testing.T) { + srvUUID := mustCreateServerRecord(t, db, "common-case") + + // this can be any real slug, but *must* be a real slug, otherwise + // we will panic on the slug -> type-id lookup. + slug := common.SlugBIOS + + var inband bool + attributeNS := getAttributeNamespace(inband, slug) + require.Equal(t, "BIOS.outofband", attributeNS) + fwns := attributeNS + ".firmware" + sns := attributeNS + ".status" + + orig := &common.Common{ + Oem: true, + Vendor: "the-vendor", + ProductName: "super-product", + Firmware: &common.Firmware{ + Installed: "old-version", + }, + Status: &common.Status{ + State: "OK", + Health: "decent", + }, + } + + attr := &attributes{ + WWN: "string payload", + } + + // we will add the product name to the attributes en-passant as part of + // normalizing any missing model information + expAttr := &attributes{ + WWN: "string payload", + ProductName: "super-product", + } + + tx := db.MustBegin() + err := composeRecords(context.TODO(), tx, orig, inband, srvUUID.String(), slug, attr) + require.NoError(t, err) + _ = tx.Commit() + + // interrogate the DB for our records + scc, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + scr, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).One(context.TODO(), db) + require.NoError(t, err) + require.False(t, scr.Model.IsZero()) + require.Equal(t, "super-product", scr.Model.String) + require.False(t, scr.Serial.IsZero()) + require.Equal(t, "0", scr.Serial.String) + + // validate the record counts of the attributes/versioned-attributes + ac, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(attributeNS), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac, "attribute record") + + ar, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(attributeNS), + ).One(context.TODO(), db) + require.NoError(t, err) + recordData := &attributes{} + require.NoError(t, recordData.FromJSON(ar.Data)) + require.Equal(t, expAttr, recordData) + + vac, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(fwns), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), vac, "firmware record") + + fwr, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(fwns), + ).One(context.TODO(), db) + require.NoError(t, err) + fwData, err := firmwareFromJSON(fwr.Data) + require.NoError(t, err) + require.Equal(t, orig.Firmware, fwData) + + vac, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(sns), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), vac, "status record") + + sr, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(sns), + ).One(context.TODO(), db) + require.NoError(t, err) + srData, err := statusFromJSON(sr.Data) + require.NoError(t, err) + require.Equal(t, orig.Status, srData) + + // now do the update + // XXX: If the serial number changes, we will insert a component record instead of updating it + update := &common.Common{ + Oem: true, + Vendor: "the-vendor", + ProductName: "super-new-product", + Firmware: &common.Firmware{ + Installed: "new-version", + }, + Status: &common.Status{ + State: "happy", + Health: "content", + }, + } + + expAttr.ProductName = "super-new-product" + + tx = db.MustBegin() + err = composeRecords(context.TODO(), tx, update, inband, srvUUID.String(), slug, attr) + require.NoError(t, err) + _ = tx.Commit() + + scc, err = models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + scr, err = models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).One(context.TODO(), db) + require.NoError(t, err) + require.False(t, scr.Model.IsZero()) + require.Equal(t, "super-new-product", scr.Model.String) + require.False(t, scr.Serial.IsZero()) + require.Equal(t, "0", scr.Serial.String) + + // validate the record counts of the attributes/versioned-attributes + ac, err = models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(attributeNS), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), ac, "attribute record") + + ar, err = models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(attributeNS), + ).One(context.TODO(), db) + require.NoError(t, err) + recordData = &attributes{} + require.NoError(t, recordData.FromJSON(ar.Data)) + require.Equal(t, expAttr, recordData) + + vac, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(fwns), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(2), vac, "firmware record") + + fwr, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(fwns), + qm.OrderBy("tally DESC"), + ).One(context.TODO(), db) + require.NoError(t, err) + fwData, err = firmwareFromJSON(fwr.Data) + require.NoError(t, err) + require.Equal(t, update.Firmware, fwData) + + vac, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(sns), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(2), vac, "status record") + + sr, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(sns), + qm.OrderBy("tally DESC"), + ).One(context.TODO(), db) + require.NoError(t, err) + srData, err = statusFromJSON(sr.Data) + require.NoError(t, err) + require.Equal(t, update.Status, srData) + }) +} + +// We've tested the Common->component/attribute/versioned-attribute thing already +// so here we only check the component-specific attributes func TestComponents(t *testing.T) { db := dbtools.DatabaseTest(t) t.Run("writeBios", func(t *testing.T) { srvUUID := mustCreateServerRecord(t, db, "write-bios") - // XXX: If the serial number changes, we will insert a component record instead of updating it orig := &common.BIOS{ Common: common.Common{ Oem: true, @@ -84,18 +287,13 @@ func TestComponents(t *testing.T) { }, } // inband = false + attributeNS := getAttributeNamespace(device.Inband, common.SlugBIOS) + tx := db.MustBegin() err := device.writeBios(context.TODO(), tx) require.NoError(t, err) _ = tx.Commit() - scc, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBIOS)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), scc) - scr, err := models.ServerComponents( models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBIOS)), models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), @@ -104,17 +302,21 @@ func TestComponents(t *testing.T) { ac, err := models.Attributes( models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + models.AttributeWhere.Namespace.EQ(attributeNS), ).Count(context.TODO(), db) require.NoError(t, err) - require.Equal(t, int64(1), ac) + require.Equal(t, int64(1), ac, "attribute record") - vac, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), - ).Count(context.TODO(), db) + ar, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(attributeNS), + ).One(context.TODO(), db) require.NoError(t, err) - require.Equal(t, int64(1), vac) + // unpack the Data to validate the update + attr := &attributes{} + require.NoError(t, attr.FromJSON(ar.Data)) + require.Equal(t, int64(2222), attr.CapacityBytes) + require.Equal(t, int64(1111), attr.SizeBytes) // update the BIOS device.Inv.BIOS = update @@ -124,52 +326,27 @@ func TestComponents(t *testing.T) { err = tx2.Commit() require.NoError(t, err) - scc, err = models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBIOS)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), scc) - ac, err = models.Attributes( models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + models.AttributeWhere.Namespace.EQ(attributeNS), ).Count(context.TODO(), db) require.NoError(t, err) require.Equal(t, int64(1), ac) - ar, err := models.Attributes( + ar, err = models.Attributes( models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + models.AttributeWhere.Namespace.EQ(attributeNS), ).One(context.TODO(), db) require.NoError(t, err) // unpack the Data to validate the update - var attr attributes - err = json.Unmarshal([]byte(ar.Data), &attr) - require.NoError(t, err) + attr = &attributes{} + require.NoError(t, attr.FromJSON(ar.Data)) require.Equal(t, int64(4444), attr.CapacityBytes) require.Equal(t, int64(3333), attr.SizeBytes) - vac, err = models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(2), vac) - - vrec, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), - qm.OrderBy("tally DESC"), - ).One(context.TODO(), db) - require.NoError(t, err) - - vattr := versionedAttributes{} - err = json.Unmarshal([]byte(vrec.Data), &vattr) - require.NoError(t, err) - require.Equal(t, "contented", vattr.Status.State) }) t.Run("writeBMC", func(t *testing.T) { + // writeBMC is basically a re-run of composeRecords() so skip testing the update srvUUID := mustCreateServerRecord(t, db, "write-bmc") orig := &common.BMC{ @@ -190,22 +367,6 @@ func TestComponents(t *testing.T) { // BMC contains a NIC and ID string as well, but not populated or stored? } - update := &common.BMC{ - Common: common.Common{ - Oem: false, - Vendor: "coolguy", - ProductName: "super-cool", - Serial: "some-serial", - Firmware: &common.Firmware{ - Installed: "installed-version", - }, - Status: &common.Status{ - State: "contented", - Health: "healthy", - }, - }, - } - device := &DeviceView{ DeviceID: srvUUID, Inv: &common.Device{ @@ -213,18 +374,13 @@ func TestComponents(t *testing.T) { }, } // inband = false + attributeNS := getAttributeNamespace(device.Inband, common.SlugBMC) + tx := db.MustBegin() err := device.writeBMC(context.TODO(), tx) require.NoError(t, err) _ = tx.Commit() - scc, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBMC)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), scc) - scr, err := models.ServerComponents( models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBMC)), models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), @@ -233,60 +389,12 @@ func TestComponents(t *testing.T) { ac, err := models.Attributes( models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), ac) - - vac, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), vac) - - // update the BMC - device.Inv.BMC = update - tx2 := db.MustBegin() - err = device.writeBMC(context.TODO(), tx2) - require.NoError(t, err) - err = tx2.Commit() - require.NoError(t, err) - - scc, err = models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBMC)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), scc) - - ac, err = models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(outofbandComponentNamespace), + models.AttributeWhere.Namespace.EQ(attributeNS), ).Count(context.TODO(), db) require.NoError(t, err) require.Equal(t, int64(1), ac) - - vac, err = models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(2), vac) - - vrec, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(outofbandComponentNamespace), - qm.OrderBy("tally DESC"), - ).One(context.TODO(), db) - require.NoError(t, err) - - vattr := versionedAttributes{} - err = json.Unmarshal([]byte(vrec.Data), &vattr) - require.NoError(t, err) - require.Equal(t, "contented", vattr.Status.State) }) - t.Run("writeMainboard", func(t *testing.T) { + /*t.Run("writeMainboard", func(t *testing.T) { srvUUID := mustCreateServerRecord(t, db, "write-mainboard") device := &DeviceView{ @@ -312,6 +420,11 @@ func TestComponents(t *testing.T) { }, } + expAttr := { + ProductName: "super-board", + Description: "a mainboard", + } + tx := db.MustBegin() err := device.writeMainboard(context.TODO(), tx) require.NoError(t, err) @@ -497,5 +610,5 @@ func TestComponents(t *testing.T) { err = json.Unmarshal([]byte(vrec.Data), &vattr) require.NoError(t, err) require.Equal(t, "contented", vattr.Status.State) - }) + })*/ } From f85fd819f671f65ed1e471cce63ce4ff64a04a54 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Tue, 16 Apr 2024 10:24:15 -0400 Subject: [PATCH 15/21] implement generic deserialization routines --- internal/inventory/device_components.go | 192 +++++++++++++++++-- internal/inventory/device_components_test.go | 95 ++++----- 2 files changed, 214 insertions(+), 73 deletions(-) diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go index ad0214f..bb320e9 100644 --- a/internal/inventory/device_components.go +++ b/internal/inventory/device_components.go @@ -13,26 +13,36 @@ import ( "github.com/pkg/errors" "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" ) var ( errComponent = errors.New("component error") errAttribute = errors.New("attribute error") errVersionedAttr = errors.New("versioned attribute error") + + inbandNSTag = "sh.hollow.alloy.inband" + outofbandNSTag = "sh.hollow.alloy.outofband" ) -// conventions for data namespacing: -// 1. namespaces begin with a common prefix denoting whether this data -// was collected in-band or not -// 2. attributes get namespaced by including their component slug -// 3. versioned attributes will add a suffix (like '.firmware' or '.status') -// to the namespace so it's clear what the payload of those records refers to. -func getAttributeNamespace(inband bool, slug string) string { - mode := "outofband" +func getNamespace(inband bool) string { + ns := outofbandNSTag if inband { - mode = "inband" + ns = inbandNSTag } - return strings.Join([]string{slug, mode}, ".") + return ns +} + +func getAttributeNamespace(inband bool) string { + return getNamespace(inband) + ".metadata" +} + +func getFirmwareNamespace(inband bool) string { + return getNamespace(inband) + ".firmware" +} + +func getStatusNamespace(inband bool) string { + return getNamespace(inband) + ".status" } func createOrUpdateComponent(ctx context.Context, exec boil.ContextExecutor, sc *models.ServerComponent) error { @@ -61,8 +71,6 @@ func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common. inband bool, deviceID, slug string, attr *attributes) error { typeID := dbtools.MustComponentTypeID(ctx, exec, slug) - namespace := getAttributeNamespace(inband, slug) - sc := &models.ServerComponent{ Name: null.StringFrom(slug), Vendor: null.NewString(cmn.Vendor, cmn.Vendor != ""), @@ -91,16 +99,15 @@ func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common. attrData := attr.MustJSON() // update the component attribute - if err := updateAnyAttribute(ctx, exec, false, sc.ID, namespace, attrData); err != nil { + if err := updateAnyAttribute(ctx, exec, false, sc.ID, getAttributeNamespace(inband), attrData); err != nil { return errors.Wrap(errAttribute, slug+": "+err.Error()) } // every component with firmware gets a firmware versioned attribute if cmn.Firmware != nil { payload := mustFirmwareJSON(cmn.Firmware) - fwns := namespace + ".firmware" - if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, fwns, payload); err != nil { + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, getFirmwareNamespace(inband), payload); err != nil { return errors.Wrap(errVersionedAttr, slug+"-firmware: "+err.Error()) } } @@ -108,9 +115,8 @@ func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common. // every component with status gets a status versioned attribute if cmn.Status != nil { payload := mustStatusJSON(cmn.Status) - sns := namespace + ".status" - if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, sns, payload); err != nil { + if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, getStatusNamespace(inband), payload); err != nil { return errors.Wrap(errVersionedAttr, slug+"-status: "+err.Error()) } } @@ -118,6 +124,134 @@ func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common. return nil } +func retrieveComponentAttributes(ctx context.Context, exec boil.ContextExecutor, + componentID, namespace string) (*attributes, error) { + ar, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(componentID)), + models.AttributeWhere.Namespace.EQ(namespace), + ).One(ctx, exec) + + if err != nil { + return nil, err + } + + attr := &attributes{} + if err := attr.FromJSON(ar.Data); err != nil { + return nil, err + } + return attr, nil +} + +// we rely on the caller to know what v-attributes to retrieve and how to deserialize that data +func retrieveVersionedAttribute(ctx context.Context, exec boil.ContextExecutor, + parentID, namespace string, isServer bool) ([]byte, error) { + var mods []qm.QueryMod + if isServer { + mods = append(mods, models.VersionedAttributeWhere.ServerID.EQ(null.StringFrom(parentID))) + } else { + mods = append(mods, models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(parentID))) + } + mods = append(mods, + models.VersionedAttributeWhere.Namespace.EQ(namespace), + qm.OrderBy("tally DESC"), // get the most recent record + ) + + fwr, err := models.VersionedAttributes(mods...).One(ctx, exec) + if err != nil { + return nil, err + } + return fwr.Data, nil +} + +func retrieveComponentFirmwareVA(ctx context.Context, exec boil.ContextExecutor, + parentID, slug, namespace string) (*common.Firmware, error) { + data, err := retrieveVersionedAttribute(ctx, exec, parentID, namespace, false) + if err != nil { + return nil, err + } + fw, err := firmwareFromJSON(data) + if err != nil { + return nil, err + } + return fw, nil +} + +func retrieveComponentStatusVA(ctx context.Context, exec boil.ContextExecutor, parentID, + slug, namespace string) (*common.Status, error) { + data, err := retrieveVersionedAttribute(ctx, exec, parentID, namespace, false) + if err != nil { + return nil, err + } + st, err := statusFromJSON(data) + if err != nil { + return nil, err + } + return st, nil +} + +// this is returned by more-or-less generic database routines and the caller can specialize into a type +type dbComponent struct { + cmn *common.Common + attr *attributes +} + +// As generically as possible, retrieve this component from the database. Status and Firmware +// are composed into the *Common. We return the attributes so that the caller can reconsitute +// the specific device type. This is basically the reverse of composeRecords. +func componentsFromDatabase(ctx context.Context, exec boil.ContextExecutor, + inband bool, deviceID, slug string) ([]*dbComponent, error) { + records, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), + models.ServerComponentWhere.ServerID.EQ(deviceID), + qm.OrderBy(models.ServerComponentColumns.CreatedAt+" DESC"), + ).All(ctx, exec) + + if err != nil { + return nil, err + } + + comps := []*dbComponent{} + + for _, rec := range records { + // We should always have attributes, even if it's only "ProductName" (b/c it comes from common) + attr, err := retrieveComponentAttributes(ctx, exec, rec.ID, getAttributeNamespace(inband)) + if err != nil { + return nil, err + } + + // Either firmware or status might have no stored data. That's fine. + fw, err := retrieveComponentFirmwareVA(ctx, exec, rec.ID, slug, getFirmwareNamespace(inband)) + switch err { + case nil, sql.ErrNoRows: + default: + return nil, err + } + + st, err := retrieveComponentStatusVA(ctx, exec, rec.ID, slug, getStatusNamespace(inband)) + switch err { + case nil, sql.ErrNoRows: + default: + return nil, err + } + // Despite the schema, serial is required. It is set on storing the component if it was empty coming in. + serial := rec.Serial.String + comp := &dbComponent{ + cmn: &common.Common{ + Vendor: rec.Vendor.String, + Model: rec.Model.String, + Serial: serial, + ProductName: attr.ProductName, + Firmware: fw, + Status: st, + }, + attr: attr, + } + comps = append(comps, comp) + } + + return comps, nil +} + func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextExecutor) error { if err := dv.writeBios(ctx, exec); err != nil { return err @@ -149,6 +283,27 @@ func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) return composeRecords(ctx, exec, &bios.Common, dv.Inband, dv.DeviceID.String(), common.SlugBIOS, attr) } +func (dv *DeviceView) getBios(ctx context.Context, exec boil.ContextExecutor) error { + bios := &common.BIOS{} + components, err := componentsFromDatabase(ctx, exec, dv.Inband, dv.DeviceID.String(), common.SlugBIOS) + if err != nil { + return err + } + // We should never have more BIOS component, but a defect could result in multiple records. The + // components slice should come back in order of most recent records first, so first record wins. + for _, comp := range components { + bios.Common = *comp.cmn + bios.Capabilities = comp.attr.Capabilities + bios.CapacityBytes = comp.attr.CapacityBytes + bios.Description = comp.attr.Description + bios.Oem = comp.attr.Oem + bios.SizeBytes = comp.attr.SizeBytes + break + } + dv.Inv.BIOS = bios + return nil +} + func (dv *DeviceView) writeBMC(ctx context.Context, exec boil.ContextExecutor) error { bmc := dv.Inv.BMC @@ -162,6 +317,9 @@ func (dv *DeviceView) writeBMC(ctx context.Context, exec boil.ContextExecutor) e return composeRecords(ctx, exec, &bmc.Common, dv.Inband, dv.DeviceID.String(), common.SlugBMC, attr) } +func (dv *DeviceView) getBMC(ctx context.Context, exec boil.ContextExecutor) error { +} + func (dv *DeviceView) writeMainboard(ctx context.Context, exec boil.ContextExecutor) error { mb := dv.Inv.Mainboard diff --git a/internal/inventory/device_components_test.go b/internal/inventory/device_components_test.go index b821626..ee7b991 100644 --- a/internal/inventory/device_components_test.go +++ b/internal/inventory/device_components_test.go @@ -4,6 +4,7 @@ package inventory import ( "context" + "database/sql" "testing" "github.com/bmc-toolbox/common" @@ -43,10 +44,9 @@ func TestComposeComponentRecords(t *testing.T) { slug := common.SlugBIOS var inband bool - attributeNS := getAttributeNamespace(inband, slug) - require.Equal(t, "BIOS.outofband", attributeNS) - fwns := attributeNS + ".firmware" - sns := attributeNS + ".status" + attributeNS := getAttributeNamespace(inband) + fwns := getFirmwareNamespace(inband) + sns := getStatusNamespace(inband) orig := &common.Common{ Oem: true, @@ -233,6 +233,13 @@ func TestComposeComponentRecords(t *testing.T) { srData, err = statusFromJSON(sr.Data) require.NoError(t, err) require.Equal(t, update.Status, srData) + + // validate that we can get all the component data we expect + comps, err := componentsFromDatabase(context.TODO(), db, inband, srvUUID.String(), slug) + require.NoError(t, err) + require.Len(t, comps, 1) + require.NotNil(t, comps[0].cmn) + require.NotNil(t, comps[0].attr) }) } @@ -287,36 +294,23 @@ func TestComponents(t *testing.T) { }, } // inband = false - attributeNS := getAttributeNamespace(device.Inband, common.SlugBIOS) + //attributeNS := getAttributeNamespace(device.Inband) tx := db.MustBegin() err := device.writeBios(context.TODO(), tx) require.NoError(t, err) _ = tx.Commit() - scr, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBIOS)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).One(context.TODO(), db) - require.NoError(t, err) - - ac, err := models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(attributeNS), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), ac, "attribute record") - - ar, err := models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(attributeNS), - ).One(context.TODO(), db) + reader := &DeviceView{ + DeviceID: srvUUID, + Inv: &common.Device{}, + } + err = reader.getBios(context.TODO(), db) require.NoError(t, err) - // unpack the Data to validate the update - attr := &attributes{} - require.NoError(t, attr.FromJSON(ar.Data)) - require.Equal(t, int64(2222), attr.CapacityBytes) - require.Equal(t, int64(1111), attr.SizeBytes) + require.Equal(t, orig.Firmware, reader.Inv.BIOS.Firmware) + require.Equal(t, orig.Status, reader.Inv.BIOS.Status) + require.Equal(t, orig.SizeBytes, reader.Inv.BIOS.SizeBytes) + require.Equal(t, orig.CapacityBytes, reader.Inv.BIOS.CapacityBytes) // update the BIOS device.Inv.BIOS = update @@ -326,24 +320,23 @@ func TestComponents(t *testing.T) { err = tx2.Commit() require.NoError(t, err) - ac, err = models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(attributeNS), - ).Count(context.TODO(), db) + err = reader.getBios(context.TODO(), db) require.NoError(t, err) - require.Equal(t, int64(1), ac) + require.Equal(t, update.Firmware, reader.Inv.BIOS.Firmware) + require.Equal(t, update.Status, reader.Inv.BIOS.Status) + require.Equal(t, update.SizeBytes, reader.Inv.BIOS.SizeBytes) + require.Equal(t, update.CapacityBytes, reader.Inv.BIOS.CapacityBytes) - ar, err = models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(attributeNS), - ).One(context.TODO(), db) - require.NoError(t, err) - // unpack the Data to validate the update - attr = &attributes{} - require.NoError(t, attr.FromJSON(ar.Data)) - require.Equal(t, int64(4444), attr.CapacityBytes) - require.Equal(t, int64(3333), attr.SizeBytes) + // failed lookup + nogo := &DeviceView{ + DeviceID: srvUUID, + Inv: &common.Device{}, + Inband: true, + } + err = nogo.getBios(context.TODO(), db) + require.Error(t, err) + require.ErrorIs(t, err, sql.ErrNoRows) }) t.Run("writeBMC", func(t *testing.T) { // writeBMC is basically a re-run of composeRecords() so skip testing the update @@ -374,25 +367,15 @@ func TestComponents(t *testing.T) { }, } // inband = false - attributeNS := getAttributeNamespace(device.Inband, common.SlugBMC) - tx := db.MustBegin() err := device.writeBMC(context.TODO(), tx) require.NoError(t, err) _ = tx.Commit() - scr, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugBMC)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).One(context.TODO(), db) - require.NoError(t, err) - - ac, err := models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(attributeNS), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), ac) + reader := &DeviceView{ + DeviceID: srvUUID, + Inv: &common.Device{}, + } }) /*t.Run("writeMainboard", func(t *testing.T) { srvUUID := mustCreateServerRecord(t, db, "write-mainboard") From 7a51fc39a277add044f8bbecad38a0a91fe1fcb3 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Tue, 16 Apr 2024 10:30:42 -0400 Subject: [PATCH 16/21] update deps --- go.mod | 106 ++++++++++---------- go.sum | 301 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 200 insertions(+), 207 deletions(-) diff --git a/go.mod b/go.mod index c3fd886..7b373e6 100644 --- a/go.mod +++ b/go.mod @@ -4,57 +4,59 @@ go 1.22 require ( github.com/XSAM/otelsql v0.23.0 // indirect - github.com/cockroachdb/cockroach-go/v2 v2.3.5 + github.com/cockroachdb/cockroach-go/v2 v2.3.6 github.com/friendsofgo/errors v0.9.2 - github.com/gin-contrib/cors v1.4.0 + github.com/gin-contrib/cors v1.5.0 github.com/gin-contrib/zap v1.1.0 github.com/gin-gonic/gin v1.9.1 - github.com/google/uuid v1.3.1 - github.com/gosimple/slug v1.13.1 + github.com/google/uuid v1.6.0 + github.com/gosimple/slug v1.14.0 github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/pkg/errors v0.9.1 github.com/pressly/goose/v3 v3.15.0 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/spf13/cobra v1.7.0 + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.16.0 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 github.com/volatiletech/null/v8 v8.1.2 github.com/volatiletech/randomize v0.0.1 - github.com/volatiletech/sqlboiler/v4 v4.15.0 - github.com/volatiletech/strmangle v0.0.5 + github.com/volatiletech/sqlboiler/v4 v4.16.2 + github.com/volatiletech/strmangle v0.0.6 github.com/zsais/go-gin-prometheus v0.1.0 - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0 - go.opentelemetry.io/otel v1.16.0 + go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.49.0 + go.opentelemetry.io/otel v1.25.0 go.opentelemetry.io/otel/exporters/jaeger v1.16.0 // indirect - go.opentelemetry.io/otel/sdk v1.16.0 // indirect + go.opentelemetry.io/otel/sdk v1.25.0 // indirect go.uber.org/zap v1.27.0 gopkg.in/square/go-jose.v2 v2.6.0 ) require ( github.com/bmc-toolbox/common v0.0.0-20231204194243-7bcbccab7116 + github.com/metal-toolbox/rivets v1.0.3 github.com/volatiletech/sqlboiler v3.7.1+incompatible go.hollow.sh/toolbox v0.6.3 - go.infratographer.com/x v0.3.7 - gocloud.dev v0.33.0 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 + go.infratographer.com/x v0.3.9 + gocloud.dev v0.36.0 + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 ) require ( + cloud.google.com/go/kms v1.15.7 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.11.3 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/ericlagergren/decimal v0.0.0-20240305081647-93d586550569 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -62,70 +64,68 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/gosimple/unidecode v1.0.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx/v4 v4.18.1 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgtype v1.14.2 // indirect + github.com/jackc/pgx/v4 v4.18.2 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/nats-io/nats.go v1.28.0 // indirect - github.com/nats-io/nkeys v0.4.4 // indirect + github.com/nats-io/nats.go v1.33.1 // indirect + github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.49.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/afero v1.9.5 // 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/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/volatiletech/inflect v0.0.1 // indirect github.com/volatiletech/null v8.0.0+incompatible // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 // indirect - go.opentelemetry.io/otel/metric v1.16.0 // indirect - go.opentelemetry.io/otel/trace v1.16.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/otel/metric v1.25.0 // indirect + go.opentelemetry.io/otel/trace v1.25.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/api v0.137.0 // indirect - google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230815205213-6bfd019c3878 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect - google.golang.org/grpc v1.57.0 // indirect + google.golang.org/api v0.168.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect + google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index c5e85c9..712fcf2 100644 --- a/go.sum +++ b/go.sum @@ -29,7 +29,7 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -41,17 +41,17 @@ cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJW cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= -cloud.google.com/go/kms v1.15.0 h1:xYl5WEaSekKYN5gGRyhjvZKM22GVBBCzegGNVPy+aIs= -cloud.google.com/go/kms v1.15.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= +cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -101,8 +101,8 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1 github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -132,18 +132,20 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/cockroach-go/v2 v2.3.5 h1:Khtm8K6fTTz/ZCWPzU9Ne3aOW9VyAnj4qIPCJgKtwK0= -github.com/cockroachdb/cockroach-go/v2 v2.3.5/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8= +github.com/cockroachdb/cockroach-go/v2 v2.3.6 h1:Wlv9TzkrG9V7i6u8dEtmXPrBzvfFp+CgJNs696rAajM= +github.com/cockroachdb/cockroach-go/v2 v2.3.6/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -160,29 +162,30 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ericlagergren/decimal v0.0.0-20190420051523-6335edbaa640/go.mod h1:mdYyfAkzn9kyJ/kMk/7WE9ufl9lflh+2NvecQ5mAghs= -github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 h1:S92OBrGuLLZsyM5ybUzgc/mPjIYk2AZqufieooe98uw= -github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= +github.com/ericlagergren/decimal v0.0.0-20240305081647-93d586550569 h1:PNe5U6Gc0FAVbptp1BXy1ZYi8MyBoeIBWLU2DBH9YnA= +github.com/ericlagergren/decimal v0.0.0-20240305081647-93d586550569/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 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/friendsofgo/errors v0.9.2 h1:X6NYxef4efCBdwI7BgS820zFaN7Cphrmb+Pljdzjtgk= github.com/friendsofgo/errors v0.9.2/go.mod h1:yCvFW5AkDIL9qn7suHVLiI/gH228n7PC4Pn44IGoTOI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= -github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= +github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/zap v1.1.0 h1:GWzL9+zmK8OJdiycaK2SK1/D3SZIYpieJDD0QCNAU1o= github.com/gin-contrib/zap v1.1.0/go.mod h1:KzROP9rAL7ofFd1P8lx7Oo2lerwPWNL5vv4f6U/mAk8= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -195,27 +198,22 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -235,8 +233,6 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2V github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -268,8 +264,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -287,8 +283,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -310,17 +306,17 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.5 h1:8IYp3w9nysqv3JH+NJgXJzGbDHzLOTj43BmSkp+O7qg= -github.com/google/s2a-go v0.1.5/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= -github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= -github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -328,17 +324,17 @@ github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0 github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= -github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es= +github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -392,9 +388,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= -github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= -github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -410,23 +405,23 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.2 h1:QBdZQTKpPdBlw2AdKwHEyqUcm/lrl2cwWAHjCMyln/o= +github.com/jackc/pgtype v1.14.2/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -445,12 +440,13 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12/go.mod h1:u9MdXq/QageOOSGp7qG4XAQsYUMP+V5zEel/Vrl6OOc= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -462,7 +458,6 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -472,7 +467,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -506,8 +500,8 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/metal-toolbox/rivets v1.0.3 h1:ZW9q8V3vz6VxAczC4eR1YJdl+kapHF3ebVc+4r3NmR8= +github.com/metal-toolbox/rivets v1.0.3/go.mod h1:EMQJRT1mjIyFRXxvKNaBlz7Z4Sp88rTaGO8W18olN2I= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -536,14 +530,14 @@ github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3P github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4= -github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= -github.com/nats-io/nats-server/v2 v2.9.17 h1:gFpUQ3hqIDJrnqog+Bl5vaXg+RhhYEZIElasEuRn2tw= -github.com/nats-io/nats-server/v2 v2.9.17/go.mod h1:eQysm3xDZmIjfkjr7DuD9DjRFpnxQc2vKVxtEg0Dp6s= -github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c= -github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= -github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= -github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= +github.com/nats-io/jwt/v2 v2.5.5 h1:ROfXb50elFq5c9+1ztaUbdlrArNFl2+fQWP6B8HGEq4= +github.com/nats-io/jwt/v2 v2.5.5/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= +github.com/nats-io/nats-server/v2 v2.10.11 h1:yKUiLVincZISpo3A4YljJQ+HfLltGAgoNNJl99KL8I0= +github.com/nats-io/nats-server/v2 v2.10.11/go.mod h1:dXtOqVWzbMTEj+tUyC/itXjJhW37xh0tUBrTAlqAfx8= +github.com/nats-io/nats.go v1.33.1 h1:8TxLZZ/seeEfR97qV0/Bl939tpDnt2Z2fK3HkPypj70= +github.com/nats-io/nats.go v1.33.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -555,14 +549,14 @@ github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeB github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pressly/goose/v3 v3.15.0 h1:6tY5aDqFknY6VZkorFGgZtWygodZQxfmmEF4rqyJW9k= @@ -572,34 +566,33 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI= +github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -608,6 +601,10 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= +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/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -620,25 +617,26 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +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.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 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.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -666,8 +664,6 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU= @@ -680,11 +676,11 @@ github.com/volatiletech/randomize v0.0.1 h1:eE5yajattWqTB2/eN8df4dw+8jwAzBtbdo5s github.com/volatiletech/randomize v0.0.1/go.mod h1:GN3U0QYqfZ9FOJ67bzax1cqZ5q2xuj2mXrXBjWaRTlY= github.com/volatiletech/sqlboiler v3.7.1+incompatible h1:dm9/NjDskQVwAarmpeZ2UqLn1NKE8M3WHSHBS4jw2x8= github.com/volatiletech/sqlboiler v3.7.1+incompatible/go.mod h1:jLfDkkHWPbS2cWRLkyC20vQWaIQsASEY7gM7zSo11Yw= -github.com/volatiletech/sqlboiler/v4 v4.15.0 h1:+twm3mA34SaUF6wB9U6QkXxkK8AKkV5EfgMSvcKWeY4= -github.com/volatiletech/sqlboiler/v4 v4.15.0/go.mod h1:s643wqYyCQ7Ak2hMVxH7kTS0+lFPNlj+gHKUIukJ0YA= +github.com/volatiletech/sqlboiler/v4 v4.16.2 h1:PcV2bxjE+S+GwPKCyX7/AjlY3aiTKsOEjciLhpWQImc= +github.com/volatiletech/sqlboiler/v4 v4.16.2/go.mod h1:B14BPBGTrJ2X6l7lwnvV/iXgYR48+ozGSlzHI3frl6U= github.com/volatiletech/strmangle v0.0.1/go.mod h1:F6RA6IkB5vq0yTG4GQ0UsbbRcl3ni9P76i+JrTBKFFg= -github.com/volatiletech/strmangle v0.0.5 h1:CompJPy+lAi9h+YU/IzBR4X2RDRuAuEIP+kjFdyZXcU= -github.com/volatiletech/strmangle v0.0.5/go.mod h1:ycDvbDkjDvhC0NUU8w3fWwl5JEMTV56vTKXzR3GeR+0= +github.com/volatiletech/strmangle v0.0.6 h1:AdOYE3B2ygRDq4rXDij/MMwq6KVK/pWAYxpC7CLrkKQ= +github.com/volatiletech/strmangle v0.0.6/go.mod h1:ycDvbDkjDvhC0NUU8w3fWwl5JEMTV56vTKXzR3GeR+0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -700,8 +696,8 @@ go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+ go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.hollow.sh/toolbox v0.6.3 h1:IJOjiGdiwWwXJ2QfOkJuSucSIqrdXJbUBFst3u6T6z4= go.hollow.sh/toolbox v0.6.3/go.mod h1:nl+5RDDyYY/+wukOUzHHX2mOyWKRjlTOXUcGxny+tns= -go.infratographer.com/x v0.3.7 h1:kkykoVtC8XrmvC4oZwHWa/15+dv9RhQHgSm8KoEb/Nc= -go.infratographer.com/x v0.3.7/go.mod h1:/zbDM9njbWzUDCA9pkbi1z/v4VZjGsVHx+SPycSgIhg= +go.infratographer.com/x v0.3.9 h1:fsfF/w5zHgiNAHvYmvsWlICNha2X53WNLVSKOkyPnWo= +go.infratographer.com/x v0.3.9/go.mod h1:n/61MZRKFbGlS8xUwAhTyDhqcL2Wk6uPsXADC2n5t1I= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -711,35 +707,37 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0 h1:l7AmwSVqozWKKXeZHycpdmpycQECRpoGwJ1FW2sWfTo= -go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0/go.mod h1:Ep4uoO2ijR0f49Pr7jAqyTjSCyS1SRL18wwttKfwqXA= -go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo= -go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.49.0 h1:1f31+6grJmV3X4lxcEvUy13i5/kfDw1nJZwhd8mA4tg= +go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.49.0/go.mod h1:1P/02zM3OwkX9uki+Wmxw3a5GVb6KUXRsa7m7bOC9Fg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/propagators/b3 v1.24.0 h1:n4xwCdTx3pZqZs2CjS/CUZAs03y3dZcGhC/FepKtEUY= +go.opentelemetry.io/contrib/propagators/b3 v1.24.0/go.mod h1:k5wRxKRU2uXx2F8uNJ4TaonuEO/V7/5xoz7kdsDACT8= +go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= +go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= go.opentelemetry.io/otel/exporters/jaeger v1.16.0 h1:YhxxmXZ011C0aDZKoNw+juVWAmEfv/0W2XBOv9aHTaA= go.opentelemetry.io/otel/exporters/jaeger v1.16.0/go.mod h1:grYbBo/5afWlPpdPZYhyn78Bk04hnvxn2+hvxQhKIQM= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 h1:TVQp/bboR4mhZSav+MdgXB8FaRho1RC8UwVn3T0vjVc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0/go.mod h1:I33vtIe0sR96wfrUcilIzLoA3mLHhRmz9S9Te0S3gDo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 h1:iqjq9LAB8aK++sKVcELezzn655JnBNdsDhghU4G/So8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0/go.mod h1:hGXzO5bhhSHZnKvrDaXB82Y9DRFour0Nz/KrBh7reWw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 h1:vOL89uRfOCCNIjkisd0r7SEdJF3ZJFyCNY34fdZs8eU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0/go.mod h1:8GlBGcDk8KKi7n+2S4BT/CPZQYH3erLu0/k64r1MYgo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 h1:Mbi5PKN7u322woPa85d7ebZ+SOvEoPvoiBu+ryHWgfA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 h1:+XWJd3jf75RXJq29mxbuXhCXFDG3S3R4vBUeSI2P7tE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0/go.mod h1:hqgzBPTf4yONMFgdZvL/bK42R/iinTyVQtiWihs3SZc= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= -go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= -go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= +go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= +go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= +go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -760,8 +758,8 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -gocloud.dev v0.33.0 h1:ET5z49jm1+eUhY5BkuGk2d7czfgGeXKd4vtg1Jcg9OQ= -gocloud.dev v0.33.0/go.mod h1:z6W8qorjrfM09H8t1MDk8KLPj3Xi26aFBzDKAHWIgLU= +gocloud.dev v0.36.0 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus= +gocloud.dev v0.36.0/go.mod h1:bLxah6JQVKBaIxzsr5BQLYB4IYdWHkMZdzCXlo6F0gg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= @@ -783,11 +781,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -798,8 +794,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -826,8 +822,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -878,9 +874,8 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -901,8 +896,8 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -916,8 +911,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1004,15 +999,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1022,14 +1015,13 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -1092,8 +1084,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1144,16 +1136,17 @@ google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRR google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= -google.golang.org/api v0.137.0 h1:QrKX6uNvzJLr0Fd3vWVqcyrcmFoYi036VUAsZbiF4+s= -google.golang.org/api v0.137.0/go.mod h1:4xyob8CxC+0GChNBvEUAk8VBKNvYOTWM9T3v3UfRxuY= +google.golang.org/api v0.168.0 h1:MBRe+Ki4mMN93jhDDbpuRLjRddooArz4FeSObvUMmjY= +google.golang.org/api v0.168.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1232,12 +1225,12 @@ google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878 h1:Iveh6tGCJkHAjJgEqUQYGDGgbwmhjoAOz8kO/ajxefY= -google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230815205213-6bfd019c3878 h1:WGq4lvB/mlicysM/dUT3SBvijH4D3sm/Ny1A4wmt2CI= -google.golang.org/genproto/googleapis/api v0.0.0-20230815205213-6bfd019c3878/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1268,8 +1261,8 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 7eeae1918611ecfceaa20d81fd2a86f044f21b80 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Thu, 18 Apr 2024 11:30:22 -0400 Subject: [PATCH 17/21] convert to rivets.Server --- internal/dbtools/component_types.go | 52 +- internal/dbtools/component_types_test.go | 9 +- internal/inventory/attribute_utils.go | 86 ++++ internal/inventory/component_attributes.go | 70 +-- internal/inventory/device_components.go | 250 +++------- internal/inventory/device_components_test.go | 481 ++++--------------- internal/inventory/device_view.go | 231 +++------ internal/inventory/device_view_test.go | 80 +-- 8 files changed, 413 insertions(+), 846 deletions(-) create mode 100644 internal/inventory/attribute_utils.go diff --git a/internal/dbtools/component_types.go b/internal/dbtools/component_types.go index 97d37e3..d886988 100644 --- a/internal/dbtools/component_types.go +++ b/internal/dbtools/component_types.go @@ -13,7 +13,10 @@ import ( "github.com/metal-toolbox/fleetdb/internal/models" ) -var errAddTypes = errors.New("unable to add component types") +var ( + errAddTypes = errors.New("unable to add component types") + errBadName = errors.New("unknown component name") +) // XXX: if bmc-toolbox exported this as a list, we could just import it. var knownComponentTypes = []string{ @@ -38,6 +41,8 @@ var knownComponentTypes = []string{ common.SlugMainboard, } +var componentTypeIDCache = map[string]string{} + // SetupComponentTypes upserts all known component types to the database. // Despite the descriptor, in the database the Name field of the component type // is the verbatim value of the string, and the Slug is computed as a lower-case @@ -45,12 +50,24 @@ var knownComponentTypes = []string{ func SetupComponentTypes(ctx context.Context, db *sqlx.DB) error { txn := db.MustBeginTx(ctx, &sql.TxOptions{}) for _, typ := range knownComponentTypes { - sct := &models.ServerComponentType{ - Name: typ, - Slug: slug.Make(typ), - } - if err := sct.Upsert(ctx, txn, false, []string{"slug"}, boil.None(), boil.Infer()); err != nil { - _ = txn.Rollback() + existing, err := models.ServerComponentTypes( + models.ServerComponentTypeWhere.Name.EQ(typ), + ).One(ctx, txn) + + switch err { + case nil: + componentTypeIDCache[typ] = existing.ID + case sql.ErrNoRows: + sct := &models.ServerComponentType{ + Name: typ, + Slug: slug.Make(typ), + } + if err := sct.Insert(ctx, txn, boil.Infer()); err != nil { + _ = txn.Rollback() + return errors.Wrap(errAddTypes, err.Error()) + } + componentTypeIDCache[typ] = sct.ID + default: return errors.Wrap(errAddTypes, err.Error()) } } @@ -59,21 +76,10 @@ func SetupComponentTypes(ctx context.Context, db *sqlx.DB) error { // ComponentTypeIDFromName expects the name of the component (as defined in // bmc-toolbox) and will return the internal database ID for that name. -func ComponentTypeIDFromName(ctx context.Context, exec boil.ContextExecutor, name string) (string, error) { - sct, err := models.ServerComponentTypes( - models.ServerComponentTypeWhere.Name.EQ(name), - ).One(ctx, exec) - if err != nil { - return "", err - } - return sct.ID, nil -} - -// MustComponentTypeID returns the component type id for the given component name or panics -func MustComponentTypeID(ctx context.Context, exec boil.ContextExecutor, name string) string { - id, err := ComponentTypeIDFromName(ctx, exec, name) - if err != nil { - panic(err) +func ComponentTypeIDFromName(name string) (string, error) { + id, ok := componentTypeIDCache[name] + if !ok { + return "", errors.Wrap(errBadName, name) } - return id + return id, nil } diff --git a/internal/dbtools/component_types_test.go b/internal/dbtools/component_types_test.go index 3ecaebd..8985f87 100644 --- a/internal/dbtools/component_types_test.go +++ b/internal/dbtools/component_types_test.go @@ -6,7 +6,6 @@ import ( "context" "testing" - "github.com/bmc-toolbox/common" "github.com/stretchr/testify/require" ) @@ -18,14 +17,10 @@ func TestServerComponentTypes(t *testing.T) { require.NoError(t, err) for _, typ := range knownComponentTypes { - _, err := ComponentTypeIDFromName(ctx, db, typ) + _, err := ComponentTypeIDFromName(typ) require.NoError(t, err, "couldn't find %s", typ) } - require.NotPanics(t, func() { _ = MustComponentTypeID(ctx, db, common.SlugBackplaneExpander) }) - - require.Panics(t, func() { _ = MustComponentTypeID(ctx, db, "bogus") }) - - _, err = ComponentTypeIDFromName(ctx, db, "bogus") + _, err = ComponentTypeIDFromName("bogus") require.Error(t, err, "no error on bogus") } diff --git a/internal/inventory/attribute_utils.go b/internal/inventory/attribute_utils.go new file mode 100644 index 0000000..90d58de --- /dev/null +++ b/internal/inventory/attribute_utils.go @@ -0,0 +1,86 @@ +package inventory + +import ( + "context" + "database/sql" + "encoding/json" + + "github.com/metal-toolbox/fleetdb/internal/models" + "github.com/volatiletech/null/v8" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/types" +) + +// the "either server or server-component" facet of attributes makes this function a +// little complicated +func updateAnyAttribute(ctx context.Context, exec boil.ContextExecutor, + isServerAttr bool, id, namespace string, data json.RawMessage) error { + var mods []qm.QueryMod + + idStr := null.StringFrom(id) + attrData := types.JSON(data) + + // create an attribute in the event we need to make an insert + attr := models.Attribute{ + Namespace: namespace, + Data: attrData, + } + + if isServerAttr { + attr.ServerID = idStr + mods = append(mods, models.AttributeWhere.ServerID.EQ(idStr)) + } else { + attr.ServerComponentID = idStr + mods = append(mods, models.AttributeWhere.ServerComponentID.EQ(idStr)) + } + mods = append(mods, models.AttributeWhere.Namespace.EQ(namespace)) + + existing, err := models.Attributes(mods...).One(ctx, exec) + switch err { + case nil: + attr.ID = existing.ID + _, updErr := attr.Update(ctx, exec, boil.Infer()) + return updErr + case sql.ErrNoRows: + return attr.Insert(ctx, exec, boil.Infer()) + default: + return err + } +} + +// insert a new versioned attribute record with the provided data. if this is not the first +// time we've seen a id/namespace tuple, increment the tally +func updateAnyVersionedAttribute(ctx context.Context, exec boil.ContextExecutor, + isServerAttr bool, id, namespace string, data json.RawMessage) error { + var mods []qm.QueryMod + + idStr := null.StringFrom(id) + attrData := types.JSON(data) + + // we will always insert a new versioned attribute, just incrementing the tally + vattr := models.VersionedAttribute{ + Namespace: namespace, + Data: attrData, + } + + if isServerAttr { + vattr.ServerID = idStr + mods = append(mods, models.VersionedAttributeWhere.ServerID.EQ(idStr)) + } else { + vattr.ServerComponentID = idStr + mods = append(mods, models.VersionedAttributeWhere.ServerComponentID.EQ(idStr)) + } + mods = append(mods, models.VersionedAttributeWhere.Namespace.EQ(namespace), qm.OrderBy("tally DESC")) + + lastVA, err := models.VersionedAttributes(mods...).One(ctx, exec) + switch err { + case nil: + vattr.Tally = lastVA.Tally + 1 + case sql.ErrNoRows: + // first time we've seen this vattr + default: + return err + } + return vattr.Insert(ctx, exec, boil.Infer()) +} diff --git a/internal/inventory/component_attributes.go b/internal/inventory/component_attributes.go index 46c3f42..bc017c0 100644 --- a/internal/inventory/component_attributes.go +++ b/internal/inventory/component_attributes.go @@ -5,62 +5,34 @@ import ( "encoding/json" "github.com/bmc-toolbox/common" + rivets "github.com/metal-toolbox/rivets/types" ) -type attributes struct { - Architecture string `json:"architecture,omitempty"` - BlockSizeBytes int64 `json:"block_size_bytes,omitempty"` - BusInfo string `json:"bus_info,omitempty"` - Capabilities []*common.Capability `json:"capabilities,omitempty"` - CapableSpeedGbps int64 `json:"capable_speed_gbps,omitempty"` - CapacityBytes int64 `json:"capacity_bytes,omitempty" diff:"immutable"` - ChassisType string `json:"chassis_type,omitempty"` - ClockSpeedHz int64 `json:"clock_speed_hz,omitempty"` - Cores int `json:"cores,omitempty"` - Description string `json:"description,omitempty"` - DriveType string `json:"drive_type,omitempty"` - FormFactor string `json:"form_factor,omitempty"` - ID string `json:"id,omitempty"` - InterfaceType string `json:"interface_type,omitempty"` - MacAddress string `json:"macaddress,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - NegotiatedSpeedGbps int64 `json:"negotiated_speed_gbps,omitempty"` - Oem bool `json:"oem,omitempty"` - OemID string `json:"oem_id,omitempty"` - PartNumber string `json:"part_number,omitempty"` - PhysicalID string `json:"physid,omitempty"` - PowerCapacityWatts int64 `json:"power_capacity_watts,omitempty"` - ProductName string `json:"product_name,omitempty"` - Protocol string `json:"protocol,omitempty"` - SizeBytes int64 `json:"size_bytes,omitempty"` - Slot string `json:"slot,omitempty"` - SmartErrors []string `json:"smart_errors,omitempty"` - SmartStatus string `json:"smart_status,omitempty"` - SpeedBits int64 `json:"speed_bits,omitempty"` - SpeedGbps int64 `json:"speed_gbps,omitempty"` - StorageController string `json:"storage_controller,omitempty"` - SupportedControllerProtocols string `json:"supported_controller_protocol,omitempty"` - SupportedDeviceProtocols string `json:"supported_device_protocol,omitempty"` - SupportedRAIDTypes string `json:"supported_raid_types,omitempty"` - Threads int `json:"threads,omitempty"` - WWN string `json:"wwn,omitempty"` -} - -func (a *attributes) MustJSON() []byte { - byt, err := json.Marshal(a) +func mustAttributesJSON(ca *rivets.ComponentAttributes) []byte { + if ca == nil { + return nil + } + byt, err := json.Marshal(ca) if err != nil { - panic("bad attributes") + panic("bad component attributes payload") } return byt } -func (a *attributes) FromJSON(byt []byte) error { - return json.Unmarshal(byt, a) +func componentAttributesFromJSON(byt []byte) (*rivets.ComponentAttributes, error) { + if byt == nil { + return nil, nil + } + ca := &rivets.ComponentAttributes{} + if err := json.Unmarshal(byt, ca); err != nil { + return nil, err + } + return ca, nil } func mustFirmwareJSON(fw *common.Firmware) []byte { if fw == nil { - panic("missing firmware payload") + return nil } byt, err := json.Marshal(fw) if err != nil { @@ -70,6 +42,9 @@ func mustFirmwareJSON(fw *common.Firmware) []byte { } func firmwareFromJSON(byt []byte) (*common.Firmware, error) { + if byt == nil { + return nil, nil + } fw := &common.Firmware{} err := json.Unmarshal(byt, fw) if err != nil { @@ -80,7 +55,7 @@ func firmwareFromJSON(byt []byte) (*common.Firmware, error) { func mustStatusJSON(st *common.Status) []byte { if st == nil { - panic("missing status payload") + return nil } byt, err := json.Marshal(st) if err != nil { @@ -90,6 +65,9 @@ func mustStatusJSON(st *common.Status) []byte { } func statusFromJSON(byt []byte) (*common.Status, error) { + if byt == nil { + return nil, nil + } st := &common.Status{} err := json.Unmarshal(byt, st) if err != nil { diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go index bb320e9..c4fd6a1 100644 --- a/internal/inventory/device_components.go +++ b/internal/inventory/device_components.go @@ -4,12 +4,11 @@ package inventory import ( "context" "database/sql" - "fmt" - "strings" "github.com/bmc-toolbox/common" "github.com/metal-toolbox/fleetdb/internal/dbtools" "github.com/metal-toolbox/fleetdb/internal/models" + rivets "github.com/metal-toolbox/rivets/types" "github.com/pkg/errors" "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" @@ -17,6 +16,7 @@ import ( ) var ( + errComponentType = errors.New("component type error") errComponent = errors.New("component error") errAttribute = errors.New("attribute error") errVersionedAttr = errors.New("versioned attribute error") @@ -66,58 +66,51 @@ func createOrUpdateComponent(ctx context.Context, exec boil.ContextExecutor, sc } // This encapsulates much of the repetitive work of getting a component to the database layer. -// The caller needs to compose the correct attributes for its given component. -func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common.Common, - inband bool, deviceID, slug string, attr *attributes) error { - typeID := dbtools.MustComponentTypeID(ctx, exec, slug) +func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmp *rivets.Component, + inband bool, deviceID string) error { + name := cmp.Name + typeID, err := dbtools.ComponentTypeIDFromName(name) + if err != nil { + return errors.Wrap(errComponentType, name+" not found") + } sc := &models.ServerComponent{ - Name: null.StringFrom(slug), - Vendor: null.NewString(cmn.Vendor, cmn.Vendor != ""), - Model: null.NewString(cmn.Model, cmn.Model != ""), - Serial: null.NewString(cmn.Serial, cmn.Serial != ""), + Name: null.StringFrom(name), + Vendor: null.NewString(cmp.Vendor, cmp.Vendor != ""), + Model: null.NewString(cmp.Model, cmp.Model != ""), + Serial: null.NewString(cmp.Serial, cmp.Serial != ""), ServerID: deviceID, ServerComponentTypeID: typeID, } - prodName := strings.ToLower(strings.TrimSpace(cmn.ProductName)) - if sc.Model.IsZero() && prodName != "" { - sc.Model.SetValid(prodName) - } - - if sc.Serial.IsZero() { - sc.Serial = null.StringFrom("0") - } - if err := createOrUpdateComponent(ctx, exec, sc); err != nil { - return errors.Wrap(errComponent, slug+": "+err.Error()) + return errors.Wrap(errComponent, name+": "+err.Error()) } - // avoid computing this twice - attr.ProductName = prodName - - attrData := attr.MustJSON() + if cmp.Attributes != nil { + attrData := mustAttributesJSON(cmp.Attributes) - // update the component attribute - if err := updateAnyAttribute(ctx, exec, false, sc.ID, getAttributeNamespace(inband), attrData); err != nil { - return errors.Wrap(errAttribute, slug+": "+err.Error()) + // update the component attribute + if err := updateAnyAttribute(ctx, exec, false, sc.ID, getAttributeNamespace(inband), attrData); err != nil { + return errors.Wrap(errAttribute, name+": "+err.Error()) + } } // every component with firmware gets a firmware versioned attribute - if cmn.Firmware != nil { - payload := mustFirmwareJSON(cmn.Firmware) + if cmp.Firmware != nil { + payload := mustFirmwareJSON(cmp.Firmware) if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, getFirmwareNamespace(inband), payload); err != nil { - return errors.Wrap(errVersionedAttr, slug+"-firmware: "+err.Error()) + return errors.Wrap(errVersionedAttr, name+"-firmware: "+err.Error()) } } // every component with status gets a status versioned attribute - if cmn.Status != nil { - payload := mustStatusJSON(cmn.Status) + if cmp.Status != nil { + payload := mustStatusJSON(cmp.Status) if err := updateAnyVersionedAttribute(ctx, exec, false, sc.ID, getStatusNamespace(inband), payload); err != nil { - return errors.Wrap(errVersionedAttr, slug+"-status: "+err.Error()) + return errors.Wrap(errVersionedAttr, name+"-status: "+err.Error()) } } @@ -125,21 +118,21 @@ func composeRecords(ctx context.Context, exec boil.ContextExecutor, cmn *common. } func retrieveComponentAttributes(ctx context.Context, exec boil.ContextExecutor, - componentID, namespace string) (*attributes, error) { + componentID, namespace string) (*rivets.ComponentAttributes, error) { ar, err := models.Attributes( models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(componentID)), models.AttributeWhere.Namespace.EQ(namespace), ).One(ctx, exec) - if err != nil { + switch err { + case sql.ErrNoRows: + return nil, nil + case nil: + default: return nil, err } - attr := &attributes{} - if err := attr.FromJSON(ar.Data); err != nil { - return nil, err - } - return attr, nil + return componentAttributesFromJSON(ar.Data) } // we rely on the caller to know what v-attributes to retrieve and how to deserialize that data @@ -164,11 +157,16 @@ func retrieveVersionedAttribute(ctx context.Context, exec boil.ContextExecutor, } func retrieveComponentFirmwareVA(ctx context.Context, exec boil.ContextExecutor, - parentID, slug, namespace string) (*common.Firmware, error) { + parentID, namespace string) (*common.Firmware, error) { data, err := retrieveVersionedAttribute(ctx, exec, parentID, namespace, false) - if err != nil { + switch err { + case sql.ErrNoRows: + return nil, nil + case nil: + default: return nil, err } + fw, err := firmwareFromJSON(data) if err != nil { return nil, err @@ -177,11 +175,16 @@ func retrieveComponentFirmwareVA(ctx context.Context, exec boil.ContextExecutor, } func retrieveComponentStatusVA(ctx context.Context, exec boil.ContextExecutor, parentID, - slug, namespace string) (*common.Status, error) { + namespace string) (*common.Status, error) { data, err := retrieveVersionedAttribute(ctx, exec, parentID, namespace, false) - if err != nil { + switch err { + case sql.ErrNoRows: + return nil, nil + case nil: + default: return nil, err } + st, err := statusFromJSON(data) if err != nil { return nil, err @@ -189,19 +192,13 @@ func retrieveComponentStatusVA(ctx context.Context, exec boil.ContextExecutor, p return st, nil } -// this is returned by more-or-less generic database routines and the caller can specialize into a type -type dbComponent struct { - cmn *common.Common - attr *attributes -} - // As generically as possible, retrieve this component from the database. Status and Firmware // are composed into the *Common. We return the attributes so that the caller can reconsitute // the specific device type. This is basically the reverse of composeRecords. func componentsFromDatabase(ctx context.Context, exec boil.ContextExecutor, - inband bool, deviceID, slug string) ([]*dbComponent, error) { + inband bool, deviceID string) ([]*rivets.Component, error) { records, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), + //models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), models.ServerComponentWhere.ServerID.EQ(deviceID), qm.OrderBy(models.ServerComponentColumns.CreatedAt+" DESC"), ).All(ctx, exec) @@ -210,158 +207,39 @@ func componentsFromDatabase(ctx context.Context, exec boil.ContextExecutor, return nil, err } - comps := []*dbComponent{} + var comps []*rivets.Component for _, rec := range records { - // We should always have attributes, even if it's only "ProductName" (b/c it comes from common) + // attributes/firmware/status might not be stored because it was missing in the original data. attr, err := retrieveComponentAttributes(ctx, exec, rec.ID, getAttributeNamespace(inband)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "retrieving "+rec.Name.String+"-"+rec.ID+" attributes"+":"+err.Error()) } - // Either firmware or status might have no stored data. That's fine. - fw, err := retrieveComponentFirmwareVA(ctx, exec, rec.ID, slug, getFirmwareNamespace(inband)) + fw, err := retrieveComponentFirmwareVA(ctx, exec, rec.ID, getFirmwareNamespace(inband)) switch err { case nil, sql.ErrNoRows: default: - return nil, err + return nil, errors.Wrap(err, "retrieving "+rec.Name.String+"-"+rec.ID+" firmware"+":"+err.Error()) } - st, err := retrieveComponentStatusVA(ctx, exec, rec.ID, slug, getStatusNamespace(inband)) + st, err := retrieveComponentStatusVA(ctx, exec, rec.ID, getStatusNamespace(inband)) switch err { case nil, sql.ErrNoRows: default: - return nil, err + return nil, errors.Wrap(err, "retrieving "+rec.Name.String+"-"+rec.ID+" status"+":"+err.Error()) } - // Despite the schema, serial is required. It is set on storing the component if it was empty coming in. - serial := rec.Serial.String - comp := &dbComponent{ - cmn: &common.Common{ - Vendor: rec.Vendor.String, - Model: rec.Model.String, - Serial: serial, - ProductName: attr.ProductName, - Firmware: fw, - Status: st, - }, - attr: attr, + comp := &rivets.Component{ + Name: rec.Name.String, + Vendor: rec.Vendor.String, + Model: rec.Model.String, + Serial: rec.Serial.String, + Firmware: fw, + Status: st, + Attributes: attr, } comps = append(comps, comp) } return comps, nil } - -func (dv *DeviceView) ComposeComponents(ctx context.Context, exec boil.ContextExecutor) error { - if err := dv.writeBios(ctx, exec); err != nil { - return err - } - if err := dv.writeBMC(ctx, exec); err != nil { - return err - } - if err := dv.writeMainboard(ctx, exec); err != nil { - return err - } - if err := dv.writeDimms(ctx, exec); err != nil { - return err - } - return nil -} - -func (dv *DeviceView) writeBios(ctx context.Context, exec boil.ContextExecutor) error { - bios := dv.Inv.BIOS - - attr := &attributes{ - Capabilities: bios.Capabilities, - CapacityBytes: bios.CapacityBytes, - Description: bios.Description, - Metadata: bios.Metadata, - Oem: bios.Oem, - SizeBytes: bios.SizeBytes, - } - - return composeRecords(ctx, exec, &bios.Common, dv.Inband, dv.DeviceID.String(), common.SlugBIOS, attr) -} - -func (dv *DeviceView) getBios(ctx context.Context, exec boil.ContextExecutor) error { - bios := &common.BIOS{} - components, err := componentsFromDatabase(ctx, exec, dv.Inband, dv.DeviceID.String(), common.SlugBIOS) - if err != nil { - return err - } - // We should never have more BIOS component, but a defect could result in multiple records. The - // components slice should come back in order of most recent records first, so first record wins. - for _, comp := range components { - bios.Common = *comp.cmn - bios.Capabilities = comp.attr.Capabilities - bios.CapacityBytes = comp.attr.CapacityBytes - bios.Description = comp.attr.Description - bios.Oem = comp.attr.Oem - bios.SizeBytes = comp.attr.SizeBytes - break - } - dv.Inv.BIOS = bios - return nil -} - -func (dv *DeviceView) writeBMC(ctx context.Context, exec boil.ContextExecutor) error { - bmc := dv.Inv.BMC - - attr := &attributes{ - Capabilities: bmc.Capabilities, - Description: bmc.Description, - Metadata: bmc.Metadata, - Oem: bmc.Oem, - } - - return composeRecords(ctx, exec, &bmc.Common, dv.Inband, dv.DeviceID.String(), common.SlugBMC, attr) -} - -func (dv *DeviceView) getBMC(ctx context.Context, exec boil.ContextExecutor) error { -} - -func (dv *DeviceView) writeMainboard(ctx context.Context, exec boil.ContextExecutor) error { - mb := dv.Inv.Mainboard - - attr := &attributes{ - Capabilities: mb.Capabilities, - Description: mb.Description, - Metadata: mb.Metadata, - Oem: mb.Oem, - PhysicalID: mb.PhysicalID, - } - - return composeRecords(ctx, exec, &mb.Common, dv.Inband, dv.DeviceID.String(), common.SlugMainboard, attr) -} - -func (dv *DeviceView) writeDimms(ctx context.Context, exec boil.ContextExecutor) error { - for idx, dimm := range dv.Inv.Memory { - // skip bogus dimms - if dimm.Vendor == "" && - dimm.ProductName == "" && - dimm.SizeBytes == 0 && - dimm.ClockSpeedHz == 0 { - continue - } - - if strings.TrimSpace(dimm.Serial) == "" { - dimm.Serial = fmt.Sprintf("%d", idx) - } - - attr := &attributes{ - Capabilities: dimm.Capabilities, - ClockSpeedHz: dimm.ClockSpeedHz, - Description: dimm.Description, - FormFactor: dimm.FormFactor, - Metadata: dimm.Metadata, // maybe this should be versioned? - PartNumber: dimm.PartNumber, - SizeBytes: dimm.SizeBytes, - Slot: strings.TrimPrefix(dimm.Slot, "DIMM.Socket."), - } - - if err := composeRecords(ctx, exec, &dimm.Common, dv.Inband, dv.DeviceID.String(), common.SlugPhysicalMem, attr); err != nil { - return err - } - } - return nil -} diff --git a/internal/inventory/device_components_test.go b/internal/inventory/device_components_test.go index ee7b991..d97a7b9 100644 --- a/internal/inventory/device_components_test.go +++ b/internal/inventory/device_components_test.go @@ -4,7 +4,6 @@ package inventory import ( "context" - "database/sql" "testing" "github.com/bmc-toolbox/common" @@ -17,6 +16,7 @@ import ( "github.com/metal-toolbox/fleetdb/internal/dbtools" "github.com/metal-toolbox/fleetdb/internal/models" + rivets "github.com/metal-toolbox/rivets/types" ) func mustCreateServerRecord(t *testing.T, db *sqlx.DB, name string) uuid.UUID { @@ -36,22 +36,77 @@ func mustCreateServerRecord(t *testing.T, db *sqlx.DB, name string) uuid.UUID { func TestComposeComponentRecords(t *testing.T) { db := dbtools.DatabaseTest(t) - t.Run("common case", func(t *testing.T) { - srvUUID := mustCreateServerRecord(t, db, "common-case") + t.Run("nil attr/vattr", func(t *testing.T) { + srvUUID := mustCreateServerRecord(t, db, "nil-attr-case") - // this can be any real slug, but *must* be a real slug, otherwise - // we will panic on the slug -> type-id lookup. + var inband bool slug := common.SlugBIOS + orig := &rivets.Component{ + Name: slug, + Vendor: "the-vendor", + } + + attributeNS := getAttributeNamespace(inband) + fwns := getFirmwareNamespace(inband) + sns := getStatusNamespace(inband) + + tx := db.MustBegin() + err := composeRecords(context.TODO(), tx, orig, inband, srvUUID.String()) + require.NoError(t, err) + _ = tx.Commit() + + scc, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(1), scc) + + scr, err := models.ServerComponents( + models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), + models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), + ).One(context.TODO(), db) + require.NoError(t, err) + require.True(t, scr.Model.IsZero()) + + rc, err := models.Attributes( + models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.AttributeWhere.Namespace.EQ(attributeNS), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(0), rc) + + rc, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(fwns), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(0), rc) + + rc, err = models.VersionedAttributes( + models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), + models.VersionedAttributeWhere.Namespace.EQ(sns), + ).Count(context.TODO(), db) + require.NoError(t, err) + require.Equal(t, int64(0), rc) + }) + t.Run("common case", func(t *testing.T) { + srvUUID := mustCreateServerRecord(t, db, "common-case") + var inband bool attributeNS := getAttributeNamespace(inband) fwns := getFirmwareNamespace(inband) sns := getStatusNamespace(inband) - orig := &common.Common{ - Oem: true, - Vendor: "the-vendor", - ProductName: "super-product", + slug := common.SlugBIOS + + orig := &rivets.Component{ + // this can be any real slug, but *must* be a real slug, otherwise + // we will panic on the slug -> type-id lookup. + Name: slug, + Vendor: "the-vendor", + Serial: "some-serial-number", Firmware: &common.Firmware{ Installed: "old-version", }, @@ -59,21 +114,13 @@ func TestComposeComponentRecords(t *testing.T) { State: "OK", Health: "decent", }, - } - - attr := &attributes{ - WWN: "string payload", - } - - // we will add the product name to the attributes en-passant as part of - // normalizing any missing model information - expAttr := &attributes{ - WWN: "string payload", - ProductName: "super-product", + Attributes: &rivets.ComponentAttributes{ + PhysicalID: "my-id", + }, } tx := db.MustBegin() - err := composeRecords(context.TODO(), tx, orig, inband, srvUUID.String(), slug, attr) + err := composeRecords(context.TODO(), tx, orig, inband, srvUUID.String()) require.NoError(t, err) _ = tx.Commit() @@ -90,10 +137,7 @@ func TestComposeComponentRecords(t *testing.T) { models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), ).One(context.TODO(), db) require.NoError(t, err) - require.False(t, scr.Model.IsZero()) - require.Equal(t, "super-product", scr.Model.String) - require.False(t, scr.Serial.IsZero()) - require.Equal(t, "0", scr.Serial.String) + require.True(t, scr.Model.IsZero()) // validate the record counts of the attributes/versioned-attributes ac, err := models.Attributes( @@ -108,9 +152,6 @@ func TestComposeComponentRecords(t *testing.T) { models.AttributeWhere.Namespace.EQ(attributeNS), ).One(context.TODO(), db) require.NoError(t, err) - recordData := &attributes{} - require.NoError(t, recordData.FromJSON(ar.Data)) - require.Equal(t, expAttr, recordData) vac, err := models.VersionedAttributes( models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), @@ -145,11 +186,10 @@ func TestComposeComponentRecords(t *testing.T) { require.Equal(t, orig.Status, srData) // now do the update - // XXX: If the serial number changes, we will insert a component record instead of updating it - update := &common.Common{ - Oem: true, - Vendor: "the-vendor", - ProductName: "super-new-product", + update := &rivets.Component{ + Name: common.SlugBIOS, + Vendor: "the-vendor", + Serial: "some-serial-number", Firmware: &common.Firmware{ Installed: "new-version", }, @@ -157,12 +197,13 @@ func TestComposeComponentRecords(t *testing.T) { State: "happy", Health: "content", }, + Attributes: &rivets.ComponentAttributes{ + PhysicalID: "my-id", + }, } - expAttr.ProductName = "super-new-product" - tx = db.MustBegin() - err = composeRecords(context.TODO(), tx, update, inband, srvUUID.String(), slug, attr) + err = composeRecords(context.TODO(), tx, update, inband, srvUUID.String()) require.NoError(t, err) _ = tx.Commit() @@ -178,10 +219,6 @@ func TestComposeComponentRecords(t *testing.T) { models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), ).One(context.TODO(), db) require.NoError(t, err) - require.False(t, scr.Model.IsZero()) - require.Equal(t, "super-new-product", scr.Model.String) - require.False(t, scr.Serial.IsZero()) - require.Equal(t, "0", scr.Serial.String) // validate the record counts of the attributes/versioned-attributes ac, err = models.Attributes( @@ -196,9 +233,10 @@ func TestComposeComponentRecords(t *testing.T) { models.AttributeWhere.Namespace.EQ(attributeNS), ).One(context.TODO(), db) require.NoError(t, err) - recordData = &attributes{} - require.NoError(t, recordData.FromJSON(ar.Data)) - require.Equal(t, expAttr, recordData) + require.NotNil(t, ar.Data) + attr, err := componentAttributesFromJSON(ar.Data) + require.NoError(t, err) + require.Equal(t, update.Attributes, attr) vac, err = models.VersionedAttributes( models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), @@ -235,363 +273,8 @@ func TestComposeComponentRecords(t *testing.T) { require.Equal(t, update.Status, srData) // validate that we can get all the component data we expect - comps, err := componentsFromDatabase(context.TODO(), db, inband, srvUUID.String(), slug) + comps, err := componentsFromDatabase(context.TODO(), db, inband, srvUUID.String()) require.NoError(t, err) require.Len(t, comps, 1) - require.NotNil(t, comps[0].cmn) - require.NotNil(t, comps[0].attr) }) } - -// We've tested the Common->component/attribute/versioned-attribute thing already -// so here we only check the component-specific attributes -func TestComponents(t *testing.T) { - db := dbtools.DatabaseTest(t) - t.Run("writeBios", func(t *testing.T) { - srvUUID := mustCreateServerRecord(t, db, "write-bios") - - orig := &common.BIOS{ - Common: common.Common{ - Oem: true, - Vendor: "coolguy", - Model: "xxxx", - ProductName: "the-product", - Serial: "some-serial", - Firmware: &common.Firmware{ - Installed: "some-version", - }, - Status: &common.Status{ - State: "OK", - Health: "decent", - }, - }, - SizeBytes: int64(1111), - CapacityBytes: int64(2222), - } - - update := &common.BIOS{ - Common: common.Common{ - Oem: false, - Vendor: "OpenBios", - ProductName: "super-cool", - Serial: "some-serial", - Firmware: &common.Firmware{ - Installed: "installed-version", - }, - Status: &common.Status{ - State: "contented", - Health: "healthy", - }, - }, - SizeBytes: int64(3333), - CapacityBytes: int64(4444), - } - - device := &DeviceView{ - DeviceID: srvUUID, - Inv: &common.Device{ - BIOS: orig, - }, - } // inband = false - - //attributeNS := getAttributeNamespace(device.Inband) - - tx := db.MustBegin() - err := device.writeBios(context.TODO(), tx) - require.NoError(t, err) - _ = tx.Commit() - - reader := &DeviceView{ - DeviceID: srvUUID, - Inv: &common.Device{}, - } - err = reader.getBios(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, orig.Firmware, reader.Inv.BIOS.Firmware) - require.Equal(t, orig.Status, reader.Inv.BIOS.Status) - require.Equal(t, orig.SizeBytes, reader.Inv.BIOS.SizeBytes) - require.Equal(t, orig.CapacityBytes, reader.Inv.BIOS.CapacityBytes) - - // update the BIOS - device.Inv.BIOS = update - tx2 := db.MustBegin() - err = device.writeBios(context.TODO(), tx2) - require.NoError(t, err) - err = tx2.Commit() - require.NoError(t, err) - - err = reader.getBios(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, update.Firmware, reader.Inv.BIOS.Firmware) - require.Equal(t, update.Status, reader.Inv.BIOS.Status) - require.Equal(t, update.SizeBytes, reader.Inv.BIOS.SizeBytes) - require.Equal(t, update.CapacityBytes, reader.Inv.BIOS.CapacityBytes) - - // failed lookup - nogo := &DeviceView{ - DeviceID: srvUUID, - Inv: &common.Device{}, - Inband: true, - } - - err = nogo.getBios(context.TODO(), db) - require.Error(t, err) - require.ErrorIs(t, err, sql.ErrNoRows) - }) - t.Run("writeBMC", func(t *testing.T) { - // writeBMC is basically a re-run of composeRecords() so skip testing the update - srvUUID := mustCreateServerRecord(t, db, "write-bmc") - - orig := &common.BMC{ - Common: common.Common{ - Oem: true, - Vendor: "coolguy", - Model: "xxxx", - ProductName: "the-product", - Serial: "some-serial", - Firmware: &common.Firmware{ - Installed: "some-version", - }, - Status: &common.Status{ - State: "OK", - Health: "decent", - }, - }, - // BMC contains a NIC and ID string as well, but not populated or stored? - } - - device := &DeviceView{ - DeviceID: srvUUID, - Inv: &common.Device{ - BMC: orig, - }, - } // inband = false - - tx := db.MustBegin() - err := device.writeBMC(context.TODO(), tx) - require.NoError(t, err) - _ = tx.Commit() - - reader := &DeviceView{ - DeviceID: srvUUID, - Inv: &common.Device{}, - } - }) - /*t.Run("writeMainboard", func(t *testing.T) { - srvUUID := mustCreateServerRecord(t, db, "write-mainboard") - - device := &DeviceView{ - DeviceID: srvUUID, - Inband: true, - Inv: &common.Device{ - Mainboard: &common.Mainboard{ - Common: common.Common{ - Oem: true, - Vendor: "theVendor", - Model: "theBest", - ProductName: "super-board", - Description: "a mainboard", - Firmware: &common.Firmware{ - Installed: "old-version", - }, - Status: &common.Status{ - State: "OK", - Health: "meh", - }, - }, - }, - }, - } - - expAttr := { - ProductName: "super-board", - Description: "a mainboard", - } - - tx := db.MustBegin() - err := device.writeMainboard(context.TODO(), tx) - require.NoError(t, err) - _ = tx.Commit() - - scc, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugMainboard)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), scc) - - scr, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugMainboard)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).One(context.TODO(), db) - require.NoError(t, err) - - ac, err := models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(inbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), ac) - - vac, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), vac) - - device.Inv.Mainboard.Firmware = &common.Firmware{ - Installed: "new-version", - } - - tx = db.MustBegin() - err = device.writeMainboard(context.TODO(), tx) - require.NoError(t, err) - _ = tx.Commit() - - vrec, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), - qm.OrderBy("tally DESC"), - ).One(context.TODO(), db) - require.NoError(t, err) - - vattr := versionedAttributes{} - err = json.Unmarshal([]byte(vrec.Data), &vattr) - require.NoError(t, err) - require.Equal(t, "new-version", vattr.Firmware.Installed) - }) - t.Run("writeDimms", func(t *testing.T) { - srvUUID := mustCreateServerRecord(t, db, "write-dimms") - - device := &DeviceView{ - DeviceID: srvUUID, - Inv: &common.Device{ - Memory: []*common.Memory{ - &common.Memory{ - Common: common.Common{ // stutter much? - Vendor: "Elephant", - ProductName: "ivory", - Serial: "my-serial-number", - Firmware: &common.Firmware{ - Installed: "installed-version", - }, - Status: &common.Status{ - State: "carceral", - Health: "meh", - }, - }, - ID: "first", - Slot: "DIMM.Socket.4", - SizeBytes: int64(1024), - ClockSpeedHz: int64(60), - }, - &common.Memory{ - ID: "bogus", - }, - }, - }, - Inband: true, - } - - tx := db.MustBegin() - err := device.writeDimms(context.TODO(), tx) - require.NoError(t, err) - _ = tx.Commit() - - // interrogate the database to validate our transformation - // we expect a server component record, an attributes record, - // and a versioned attributes record - scc, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugPhysicalMem)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), scc) - - scr, err := models.ServerComponents( - models.ServerComponentWhere.Name.EQ(null.StringFrom(common.SlugPhysicalMem)), - models.ServerComponentWhere.ServerID.EQ(srvUUID.String()), - ).One(context.TODO(), db) - require.NoError(t, err) - - ac, err := models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(inbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), ac) - - vac, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), vac) - - // update the DIMM record - device.Inv.Memory = []*common.Memory{ - &common.Memory{ - Common: common.Common{ - Vendor: "Elephant", - ProductName: "ivory", - Serial: "my-serial-number", - Firmware: &common.Firmware{ - Installed: "installed-version", - }, - Status: &common.Status{ - State: "contented", - Health: "healthy", - }, - }, - ID: "first", - Slot: "DIMM.Socket.4", - SizeBytes: int64(4096), - ClockSpeedHz: int64(120), - }, - } - - tx = db.MustBegin() - err = device.writeDimms(context.TODO(), tx) - require.NoError(t, err) - _ = tx.Commit() - - ac, err = models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(inbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(1), ac) - - ar, err := models.Attributes( - models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.AttributeWhere.Namespace.EQ(inbandComponentNamespace), - ).One(context.TODO(), db) - require.NoError(t, err) - // unpack the Data to validate the update - var attr attributes - err = json.Unmarshal([]byte(ar.Data), &attr) - require.NoError(t, err) - require.Equal(t, int64(120), attr.ClockSpeedHz) - require.Equal(t, int64(4096), attr.SizeBytes) - - vac, err = models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), - ).Count(context.TODO(), db) - require.NoError(t, err) - require.Equal(t, int64(2), vac) - - vrec, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerComponentID.EQ(null.StringFrom(scr.ID)), - models.VersionedAttributeWhere.Namespace.EQ(inbandComponentNamespace), - qm.OrderBy("tally DESC"), - ).One(context.TODO(), db) - require.NoError(t, err) - - vattr := versionedAttributes{} - err = json.Unmarshal([]byte(vrec.Data), &vattr) - require.NoError(t, err) - require.Equal(t, "contented", vattr.Status.State) - })*/ -} diff --git a/internal/inventory/device_view.go b/internal/inventory/device_view.go index 0c2fe02..8de5ae8 100644 --- a/internal/inventory/device_view.go +++ b/internal/inventory/device_view.go @@ -3,18 +3,16 @@ package inventory import ( "context" - "database/sql" "encoding/json" - "fmt" - "github.com/bmc-toolbox/common" "github.com/google/uuid" + "github.com/metal-toolbox/fleetdb/internal/dbtools" "github.com/metal-toolbox/fleetdb/internal/models" + rivets "github.com/metal-toolbox/rivets/types" "github.com/pkg/errors" "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/queries/qm" - "github.com/volatiletech/sqlboiler/v4/types" ) /* @@ -33,68 +31,67 @@ import ( var ( // historically these values were determined/set by alloy, even though they are // internal to the data storage layer, hence the names - alloyVendorNamespace = "sh.hollow.alloy.server_vendor_attributes" - alloyMetadataNamespace = "sh.hollow.alloy.server_metadata_attributes" - alloyUefiVarsNamespace = "sh.hollow.alloy.server_uefi_variables" // this is a versioned attribute, we expect it to change + alloyVendorNamespace = "sh.hollow.alloy.server_vendor_attributes" + //alloyMetadataNamespace = "sh.hollow.alloy.server_metadata_attributes" + //alloyUefiVarsNamespace = "sh.hollow.alloy.server_uefi_variables" // this is a versioned attribute, we expect it to change + serverStatusNamespace = "sh.hollow.alloy.server_status" // versioned // metadata keys - modelKey = "model" - vendorKey = "vendor" - serialKey = "serial" - uefiVarsKey = "uefi-variables" + modelKey = "model" + vendorKey = "vendor" + serialKey = "serial" + //uefiVarsKey = "uefi-variables" + + errBadServer = errors.New("data is missing required field") + errBadComponent = errors.New("component data") ) // A reminder for maintenance: this type needs to be able to contain all the // relevant fields from Component-Inventory or Alloy. type DeviceView struct { - Inv *common.Device `json:"inventory"` + Inv *rivets.Server `json:"inventory"` BiosConfig map[string]string `json:"bios_config,omitempty"` Inband bool // the method of inventory collection DeviceID uuid.UUID } -func (dv *DeviceView) vendorAttributes() json.RawMessage { - m := map[string]string{ - modelKey: "unknown", - serialKey: "unknown", - vendorKey: "unknown", - } - - if dv.Inv.Model != "" { - m[modelKey] = dv.Inv.Model - } - - if dv.Inv.Serial != "" { - m[serialKey] = dv.Inv.Serial - } - - if dv.Inv.Vendor != "" { - m[vendorKey] = dv.Inv.Vendor +// ServerSanityCheck handles verifying that all the details in the incoming data +// structure have been set so we maintain database invariants. +func ServerSanityCheck(srv *rivets.Server) error { + srvReq := map[string]string{ + "model": srv.Model, + "vendor": srv.Vendor, + "serial": srv.Serial, + } + for k, v := range srvReq { + if v == "" { + return errors.Wrap(errBadServer, k) + } } - byt, _ := json.Marshal(m) - - return byt -} - -func (dv *DeviceView) metadataAttributes() json.RawMessage { - m := map[string]string{} - - // filter UEFI variables -- they go in a versioned-attribute - for k, v := range dv.Inv.Metadata { - if k != uefiVarsKey { - m[k] = v + for _, cmp := range srv.Components { + if _, err := dbtools.ComponentTypeIDFromName(cmp.Name); err != nil { + return errors.Wrap(errBadComponent, err.Error()) + } + if cmp.Serial == "" { + return errors.Wrap(errBadComponent, cmp.Name+" missing serial") } } + return nil +} - if len(m) == 0 { - return nil +func (dv *DeviceView) vendorAttributes() json.RawMessage { + m := map[string]string{ + modelKey: dv.Inv.Model, + serialKey: dv.Inv.Serial, + vendorKey: dv.Inv.Vendor, } - byt, _ := json.Marshal(m) + return byt } +/* XXX: return this when rivet's Server datatype has a facility to store UEFI vars. func (dv *DeviceView) uefiVariables() (json.RawMessage, error) { var varString string @@ -110,131 +107,48 @@ func (dv *DeviceView) uefiVariables() (json.RawMessage, error) { } return []byte(varString), nil -} - -// the "either server or server-component" facet of attributes makes this function a -// little complicated -func updateAnyAttribute(ctx context.Context, exec boil.ContextExecutor, - isServerAttr bool, id, namespace string, data json.RawMessage) error { - var mods []qm.QueryMod - - idStr := null.StringFrom(id) - attrData := types.JSON(data) - - // create an attribute in the event we need to make an insert - attr := models.Attribute{ - Namespace: namespace, - Data: attrData, - } - - if isServerAttr { - attr.ServerID = idStr - mods = append(mods, models.AttributeWhere.ServerID.EQ(idStr)) - } else { - attr.ServerComponentID = idStr - mods = append(mods, models.AttributeWhere.ServerComponentID.EQ(idStr)) - } - mods = append(mods, models.AttributeWhere.Namespace.EQ(namespace)) - - existing, err := models.Attributes(mods...).One(ctx, exec) - switch err { - case nil: - attr.ID = existing.ID - _, updErr := attr.Update(ctx, exec, boil.Infer()) - return updErr - case sql.ErrNoRows: - return attr.Insert(ctx, exec, boil.Infer()) - default: - return err - } -} +}*/ func (dv *DeviceView) updateVendorAttributes(ctx context.Context, exec boil.ContextExecutor) error { return updateAnyAttribute(ctx, exec, true, dv.DeviceID.String(), alloyVendorNamespace, dv.vendorAttributes()) } -func (dv *DeviceView) updateMetadataAttributes(ctx context.Context, exec boil.ContextExecutor) error { - var err error - if md := dv.metadataAttributes(); md != nil { - err = updateAnyAttribute(ctx, exec, true, dv.DeviceID.String(), alloyMetadataNamespace, md) - } - return err -} - -// insert a new versioned attribute record with the provided data. if this is not the first -// time we've seen a id/namespace tuple, increment the tally -func updateAnyVersionedAttribute(ctx context.Context, exec boil.ContextExecutor, - isServerAttr bool, id, namespace string, data json.RawMessage) error { - var mods []qm.QueryMod - - idStr := null.StringFrom(id) - attrData := types.JSON(data) - - // we will always insert a new versioned attribute, just incrementing the tally - vattr := models.VersionedAttribute{ - Namespace: namespace, - Data: attrData, - } - - if isServerAttr { - vattr.ServerID = idStr - mods = append(mods, models.VersionedAttributeWhere.ServerID.EQ(idStr)) - } else { - vattr.ServerComponentID = idStr - mods = append(mods, models.VersionedAttributeWhere.ServerComponentID.EQ(idStr)) - } - mods = append(mods, models.VersionedAttributeWhere.Namespace.EQ(namespace), qm.OrderBy("tally DESC")) - - lastVA, err := models.VersionedAttributes(mods...).One(ctx, exec) - switch err { - case nil: - vattr.Tally = lastVA.Tally + 1 - case sql.ErrNoRows: - // first time we've seen this vattr - default: - return err - } - return vattr.Insert(ctx, exec, boil.Infer()) -} - -// write a versioned-attribute containing the UEFI variables from this server -func (dv *DeviceView) updateUefiVariables(ctx context.Context, exec boil.ContextExecutor) error { - uefiVarData, err := dv.uefiVariables() - if err != nil { - return err - } +// write all the versioned-attributes from this server +func (dv *DeviceView) updateServerVAs(ctx context.Context, exec boil.ContextExecutor) error { + statusData, _ := json.Marshal(dv.Inv.Status) + // XXX: more VAs here return updateAnyVersionedAttribute(ctx, exec, true, - dv.DeviceID.String(), alloyUefiVarsNamespace, uefiVarData) + dv.DeviceID.String(), serverStatusNamespace, statusData) } func (dv *DeviceView) UpsertInventory(ctx context.Context, exec boil.ContextExecutor) error { // yes, this is a dopey, repetitive style that should be easy for folks to extend or modify if err := dv.updateVendorAttributes(ctx, exec); err != nil { - return errors.Wrap(err, "vendor attributes update") + return errors.Wrap(err, "server vendor attributes update") } - if err := dv.updateMetadataAttributes(ctx, exec); err != nil { - return errors.Wrap(err, "metadata attribute update") + + if err := dv.updateServerVAs(ctx, exec); err != nil { + return errors.Wrap(err, "server versioned attribute update") } - if err := dv.updateUefiVariables(ctx, exec); err != nil { - return errors.Wrap(err, "uefi variables update") + for _, cmp := range dv.Inv.Components { + if err := composeRecords(ctx, exec, cmp, dv.Inband, dv.DeviceID.String()); err != nil { + return err // we already wrapped some contextual data around the error + } } return nil } func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecutor) error { - attrs, err := models.Attributes(qm.Where("server_id=?", dv.DeviceID)).All(ctx, exec) + dv.Inv = &rivets.Server{} + + // we should always have at *least* vendor attributes, so sql.ErrNoRows is a problem here. + attrs, err := models.Attributes( + models.AttributeWhere.ServerID.EQ(null.StringFrom(dv.DeviceID.String())), + ).All(ctx, exec) if err != nil { return err } - if dv.Inv == nil { - dv.Inv = &common.Device{ - Common: common.Common{ - Metadata: map[string]string{}, - }, - } - } - for _, a := range attrs { switch a.Namespace { case alloyVendorNamespace: @@ -245,17 +159,13 @@ func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecut dv.Inv.Vendor = m[vendorKey] dv.Inv.Model = m[modelKey] dv.Inv.Serial = m[serialKey] - case alloyMetadataNamespace: - if err := a.Data.Unmarshal(&dv.Inv.Metadata); err != nil { - return errors.Wrap(err, "unmarshaling metadata attributes") - } default: } } - uefiVarsAttr, err := models.VersionedAttributes( - qm.Where("server_id=?", dv.DeviceID), - qm.And(fmt.Sprintf("namespace='%s'", alloyUefiVarsNamespace)), + statusVAttr, err := models.VersionedAttributes( + models.VersionedAttributeWhere.ServerID.EQ(null.StringFrom(dv.DeviceID.String())), + models.VersionedAttributeWhere.Namespace.EQ(serverStatusNamespace), qm.OrderBy("tally DESC"), ).One(ctx, exec) @@ -263,9 +173,18 @@ func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecut return err } - dv.Inv.Metadata[uefiVarsKey] = uefiVarsAttr.Data.String() + var status string + if err = json.Unmarshal(statusVAttr.Data, &status); err != nil { + return errors.Wrap(err, "unmarshaling status attribute") + } + dv.Inv.Status = status + + comps, err := componentsFromDatabase(ctx, exec, dv.Inband, dv.DeviceID.String()) + if err != nil { + return err + } - // XXX: get components and component attributes and populate the dv.Inv + dv.Inv.Components = comps return nil } diff --git a/internal/inventory/device_view_test.go b/internal/inventory/device_view_test.go index 1e536d5..935a1b1 100644 --- a/internal/inventory/device_view_test.go +++ b/internal/inventory/device_view_test.go @@ -4,32 +4,68 @@ package inventory import ( "context" - "encoding/json" "testing" "github.com/bmc-toolbox/common" "github.com/google/uuid" "github.com/metal-toolbox/fleetdb/internal/dbtools" "github.com/metal-toolbox/fleetdb/internal/models" + rivets "github.com/metal-toolbox/rivets/types" "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" "github.com/stretchr/testify/require" ) +func Test_ServerSanityCheck(t *testing.T) { + srv := &rivets.Server{} + + require.Error(t, ServerSanityCheck(srv), "zero value") + srv.Model = "some model" + require.Error(t, ServerSanityCheck(srv), "model only") + srv.Vendor = "some vendor" + require.Error(t, ServerSanityCheck(srv), "model+vendor") + srv.Serial = "some-serial" + // theoretically a server could have no components, from the FleetDB perspective + require.NoError(t, ServerSanityCheck(srv)) + srv.Components = []*rivets.Component{ + &rivets.Component{}, + } + require.Error(t, ServerSanityCheck(srv), "zero value component") + srv.Components = []*rivets.Component{ + &rivets.Component{Name: common.SlugPhysicalMem}, + } + require.Error(t, ServerSanityCheck(srv), "component name only") + srv.Components = []*rivets.Component{ + &rivets.Component{ + Name: common.SlugPhysicalMem, + Serial: "some-serial-number", + }, + } + require.NoError(t, ServerSanityCheck(srv)) +} + func Test_DeviceViewUpdate(t *testing.T) { t.Run("insert and update device attributes", func(t *testing.T) { db := dbtools.DatabaseTest(t) srvID := uuid.New() dv := DeviceView{ - Inv: &common.Device{ - Common: common.Common{ - Vendor: "CoolVendor", - Model: "BestModel 420", - Serial: "0xdeadbeef", - Metadata: map[string]string{ - "uefi-variables": `{ "msg":"hi there" }`, - "metakey": "value", + Inv: &rivets.Server{ + Vendor: "CoolVendor", + Model: "BestModel 420", + Serial: "0xdeadbeef", + Status: "some status string", + Components: []*rivets.Component{ + &rivets.Component{ + Name: common.SlugBIOS, + Serial: "some-serial", + Firmware: &common.Firmware{ + Installed: "version", + }, + Status: &common.Status{ + State: "great", + Health: "very yes", + }, }, }, }, @@ -49,35 +85,21 @@ func Test_DeviceViewUpdate(t *testing.T) { require.NoError(t, err) // do it again to test the update - dv.Inv.Common.Serial = "roastbeef" - dv.Inv.Metadata["uefi-variables"] = `{ "msg": "hi again" }` + dv.Inv.Status = "different status" err = dv.UpsertInventory(context.TODO(), db) require.NoError(t, err) - // there should be 2 records for the UEFI variables versioned attribute - - count, err := models.VersionedAttributes( - models.VersionedAttributeWhere.ServerID.EQ(null.StringFrom(srvID.String())), - models.VersionedAttributeWhere.Namespace.EQ(alloyUefiVarsNamespace), - ).Count(context.TODO(), db) - - require.NoError(t, err, "counting uefi versioned attributes") - require.Equal(t, int64(2), count) - // validate the contents read := DeviceView{ DeviceID: srvID, } err = read.FromDatastore(context.TODO(), db) require.NoError(t, err) - require.Equal(t, "roastbeef", read.Inv.Serial) - require.Equal(t, 2, len(read.Inv.Metadata)) - - varStr, ok := read.Inv.Metadata["uefi-variables"] - require.True(t, ok) + require.Equal(t, "different status", read.Inv.Status) - uefiVar := map[string]any{} - require.NoError(t, json.Unmarshal([]byte(varStr), &uefiVar)) - require.Equal(t, "hi again", uefiVar["msg"]) + require.Len(t, read.Inv.Components, 1) + bios := read.Inv.Components[0] + require.NotNil(t, bios.Firmware) + require.NotNil(t, bios.Status) }) } From 48652cae9384155ddfc4c3fe0bd662c75f2ed9d0 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Thu, 18 Apr 2024 11:30:51 -0400 Subject: [PATCH 18/21] enforce a test timeout to catch hangs --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2326204..ae261f4 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ test: | unit-test integration-test ## run integration tests integration-test: test-database @echo Running integration tests... - @FLEETDB_CRDB_URI="${TEST_DB}" go test -cover -tags testtools,integration -p 1 ./... + @FLEETDB_CRDB_URI="${TEST_DB}" go test -cover -tags testtools,integration -p 1 -timeout 1m ./... ## run lint and unit tests unit-test: | lint From 6f7d7b01d5bb0c32835c2180be7511945c6cdb52 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Thu, 18 Apr 2024 14:27:50 -0400 Subject: [PATCH 19/21] update linter config and fix linting issues in code --- .golangci.yml | 2 ++ internal/inventory/attribute_utils.go | 3 ++- internal/inventory/component_attributes.go | 1 - internal/inventory/device_components.go | 7 +++---- internal/inventory/device_view.go | 13 +++++++------ 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a020fd9..1ca9442 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,6 +7,8 @@ linters-settings: local-prefixes: github.com/metal-toolbox/fleetdb gofumpt: extra-rules: true + stylecheck: + checks: ["all", "-ST1000"] linters: enable: diff --git a/internal/inventory/attribute_utils.go b/internal/inventory/attribute_utils.go index 90d58de..489942c 100644 --- a/internal/inventory/attribute_utils.go +++ b/internal/inventory/attribute_utils.go @@ -5,11 +5,12 @@ import ( "database/sql" "encoding/json" - "github.com/metal-toolbox/fleetdb/internal/models" "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/queries/qm" "github.com/volatiletech/sqlboiler/v4/types" + + "github.com/metal-toolbox/fleetdb/internal/models" ) // the "either server or server-component" facet of attributes makes this function a diff --git a/internal/inventory/component_attributes.go b/internal/inventory/component_attributes.go index bc017c0..4992d4b 100644 --- a/internal/inventory/component_attributes.go +++ b/internal/inventory/component_attributes.go @@ -1,4 +1,3 @@ -//nolint:all // XXX remove this! package inventory import ( diff --git a/internal/inventory/device_components.go b/internal/inventory/device_components.go index c4fd6a1..4779dca 100644 --- a/internal/inventory/device_components.go +++ b/internal/inventory/device_components.go @@ -1,4 +1,3 @@ -//nolint:all // XXX remove this! package inventory import ( @@ -6,13 +5,14 @@ import ( "database/sql" "github.com/bmc-toolbox/common" - "github.com/metal-toolbox/fleetdb/internal/dbtools" - "github.com/metal-toolbox/fleetdb/internal/models" rivets "github.com/metal-toolbox/rivets/types" "github.com/pkg/errors" "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/queries/qm" + + "github.com/metal-toolbox/fleetdb/internal/dbtools" + "github.com/metal-toolbox/fleetdb/internal/models" ) var ( @@ -198,7 +198,6 @@ func retrieveComponentStatusVA(ctx context.Context, exec boil.ContextExecutor, p func componentsFromDatabase(ctx context.Context, exec boil.ContextExecutor, inband bool, deviceID string) ([]*rivets.Component, error) { records, err := models.ServerComponents( - //models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)), models.ServerComponentWhere.ServerID.EQ(deviceID), qm.OrderBy(models.ServerComponentColumns.CreatedAt+" DESC"), ).All(ctx, exec) diff --git a/internal/inventory/device_view.go b/internal/inventory/device_view.go index 8de5ae8..34af27f 100644 --- a/internal/inventory/device_view.go +++ b/internal/inventory/device_view.go @@ -1,4 +1,3 @@ -//nolint:all // XXX remove this! package inventory import ( @@ -6,13 +5,14 @@ import ( "encoding/json" "github.com/google/uuid" - "github.com/metal-toolbox/fleetdb/internal/dbtools" - "github.com/metal-toolbox/fleetdb/internal/models" rivets "github.com/metal-toolbox/rivets/types" "github.com/pkg/errors" "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/queries/qm" + + "github.com/metal-toolbox/fleetdb/internal/dbtools" + "github.com/metal-toolbox/fleetdb/internal/models" ) /* @@ -32,20 +32,21 @@ var ( // historically these values were determined/set by alloy, even though they are // internal to the data storage layer, hence the names alloyVendorNamespace = "sh.hollow.alloy.server_vendor_attributes" - //alloyMetadataNamespace = "sh.hollow.alloy.server_metadata_attributes" - //alloyUefiVarsNamespace = "sh.hollow.alloy.server_uefi_variables" // this is a versioned attribute, we expect it to change + // XXX: enable this when Server supports UEFI variables + // alloyUefiVarsNamespace = "sh.hollow.alloy.server_uefi_variables" // this is a versioned attribute, we expect it to change serverStatusNamespace = "sh.hollow.alloy.server_status" // versioned // metadata keys modelKey = "model" vendorKey = "vendor" serialKey = "serial" - //uefiVarsKey = "uefi-variables" + // XXX: again, enable after UEFI Variables are a thing. uefiVarsKey = "uefi-variables" errBadServer = errors.New("data is missing required field") errBadComponent = errors.New("component data") ) +// DeviceView encapsulates everything we need to get and set inventory data for servers // A reminder for maintenance: this type needs to be able to contain all the // relevant fields from Component-Inventory or Alloy. type DeviceView struct { From 1432f2527f7ebbe214fc0f749bb34c9c9fc7aa32 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 19 Apr 2024 16:47:40 -0400 Subject: [PATCH 20/21] finish API and tests for inventory --- internal/dbtools/fixtures.go | 45 ++++++++++++++- internal/inventory/device_view.go | 47 +++++++++------ pkg/api/v1/router.go | 7 +-- pkg/api/v1/router_inventory.go | 88 ++++++++++++++++++----------- pkg/api/v1/router_inventory_test.go | 64 +++++++++++++++++++++ pkg/api/v1/server_service.go | 34 +++++++++++ pkg/api/v1/server_service_test.go | 40 +++++++++++++ 7 files changed, 270 insertions(+), 55 deletions(-) create mode 100644 pkg/api/v1/router_inventory_test.go diff --git a/internal/dbtools/fixtures.go b/internal/dbtools/fixtures.go index 413f510..525c5df 100644 --- a/internal/dbtools/fixtures.go +++ b/internal/dbtools/fixtures.go @@ -5,9 +5,11 @@ package dbtools import ( "context" + "encoding/json" "testing" "time" + "github.com/google/uuid" "github.com/jmoiron/sqlx" "github.com/volatiletech/null/v8" "github.com/volatiletech/sqlboiler/v4/boil" @@ -27,6 +29,7 @@ var ( // Server Component Types FixtureFinType *models.ServerComponentType + NemoID uuid.UUID FixtureNemo *models.Server FixtureNemoMetadata *models.Attribute FixtureNemoOtherdata *models.Attribute @@ -83,6 +86,9 @@ var ( FixtureFirmwareUUIDsR640 []string FixtureFirmwareSetR640 *models.ComponentFirmwareSet FixtureFirmwareSetR640Attribute *models.AttributesFirmwareSet + + // Inventory fixtures + FixtureInventoryServer *models.Server ) func addFixtures(t *testing.T) error { @@ -141,6 +147,10 @@ func addFixtures(t *testing.T) error { return err } + if err := setupInventoryFixture(ctx, testDB); err != nil { + return err + } + // excluding Chuckles here since that server is deleted FixtureServers = models.ServerSlice{FixtureNemo, FixtureDory, FixtureMarlin} FixtureDeletedServers = models.ServerSlice{FixtureChuckles} @@ -259,7 +269,11 @@ func setupNemo(ctx context.Context, db *sqlx.DB, t *testing.T) error { return err } - return FixtureNemo.AddVersionedAttributes(ctx, db, true, FixtureNemoVersionedNew) + if err := FixtureNemo.AddVersionedAttributes(ctx, db, true, FixtureNemoVersionedNew); err != nil { + return err + } + NemoID = uuid.MustParse(FixtureNemo.ID) + return nil } func setupDory(ctx context.Context, db *sqlx.DB) error { @@ -606,3 +620,32 @@ func setupFirmwareSetR640(ctx context.Context, db *sqlx.DB) error { return nil } + +func setupInventoryFixture(ctx context.Context, db *sqlx.DB) error { + FixtureInventoryServer = &models.Server{ + Name: null.StringFrom("inventory"), + FacilityCode: null.StringFrom("tf2"), + } + + if err := FixtureInventoryServer.Insert(ctx, db, boil.Infer()); err != nil { + return err + } + + vendorAttrs := map[string]string{ + "model": "myModel", + "vendor": "Awesome Computer, Inc.", + "serial": "1234xyz", + } + vaData, _ := json.Marshal(vendorAttrs) + + vendorAttrRecord := &models.Attribute{ + ServerID: null.StringFrom(FixtureInventoryServer.ID), + Namespace: "sh.hollow.alloy.server_vendor_attributes", // alloyVendorNamespace + Data: types.JSON(vaData), + } + if err := vendorAttrRecord.Insert(ctx, db, boil.Infer()); err != nil { + return err + } + + return nil +} diff --git a/internal/inventory/device_view.go b/internal/inventory/device_view.go index 34af27f..f226304 100644 --- a/internal/inventory/device_view.go +++ b/internal/inventory/device_view.go @@ -2,6 +2,7 @@ package inventory import ( "context" + "database/sql" "encoding/json" "github.com/google/uuid" @@ -44,16 +45,17 @@ var ( errBadServer = errors.New("data is missing required field") errBadComponent = errors.New("component data") + + ErrNoInventory = errors.New("no inventory stored") ) // DeviceView encapsulates everything we need to get and set inventory data for servers // A reminder for maintenance: this type needs to be able to contain all the // relevant fields from Component-Inventory or Alloy. type DeviceView struct { - Inv *rivets.Server `json:"inventory"` - BiosConfig map[string]string `json:"bios_config,omitempty"` - Inband bool // the method of inventory collection - DeviceID uuid.UUID + Inv *rivets.Server + Inband bool // the method of inventory collection + DeviceID uuid.UUID } // ServerSanityCheck handles verifying that all the details in the incoming data @@ -140,14 +142,22 @@ func (dv *DeviceView) UpsertInventory(ctx context.Context, exec boil.ContextExec } func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecutor) error { - dv.Inv = &rivets.Server{} + dv.Inv = &rivets.Server{ + ID: dv.DeviceID.String(), // XXX: remove later? + Name: dv.DeviceID.String(), + } - // we should always have at *least* vendor attributes, so sql.ErrNoRows is a problem here. attrs, err := models.Attributes( models.AttributeWhere.ServerID.EQ(null.StringFrom(dv.DeviceID.String())), ).All(ctx, exec) - if err != nil { - return err + switch err { + case nil: + default: + return errors.Wrap(err, "fetching attributes") + } + + if len(attrs) == 0 { + return ErrNoInventory } for _, a := range attrs { @@ -170,19 +180,22 @@ func (dv *DeviceView) FromDatastore(ctx context.Context, exec boil.ContextExecut qm.OrderBy("tally DESC"), ).One(ctx, exec) - if err != nil { - return err - } - - var status string - if err = json.Unmarshal(statusVAttr.Data, &status); err != nil { - return errors.Wrap(err, "unmarshaling status attribute") + switch err { + case nil: + var status string + if err = json.Unmarshal(statusVAttr.Data, &status); err != nil { + return errors.Wrap(err, "unmarshaling status attribute") + } + dv.Inv.Status = status + case sql.ErrNoRows: + // just skip it, status is optional + default: + return errors.Wrap(err, "fetching versioned attibutes") } - dv.Inv.Status = status comps, err := componentsFromDatabase(ctx, exec, dv.Inband, dv.DeviceID.String()) if err != nil { - return err + return errors.Wrap(err, "fetching components") } dv.Inv.Components = comps diff --git a/pkg/api/v1/router.go b/pkg/api/v1/router.go index 0a091a1..ce03a5e 100644 --- a/pkg/api/v1/router.go +++ b/pkg/api/v1/router.go @@ -144,10 +144,9 @@ func (r *Router) Routes(rg *gin.RouterGroup) { // inventory endpoints srvInventory := rg.Group("/inventory") { - // uuid is the server id, mode is 'inband' or 'outofband' -- the data generated by each method can differ - // significantly, so we keep both sets as separate things to make apples-to-apples comparisons easier. - srvInventory.GET("/:uuid/:mode", amw.AuthRequired(readScopes("server", "inventory")), r.getInventory) - srvInventory.PUT("/:uuid/:mode", amw.AuthRequired(updateScopes("server", "inventory")), r.setInventory) + // uuid is the server id + srvInventory.GET("/:uuid", amw.AuthRequired(readScopes("server")), r.getInventory) + srvInventory.PUT("/:uuid", amw.AuthRequired(updateScopes("server")), r.setInventory) } } diff --git a/pkg/api/v1/router_inventory.go b/pkg/api/v1/router_inventory.go index 30bdda1..49d3583 100644 --- a/pkg/api/v1/router_inventory.go +++ b/pkg/api/v1/router_inventory.go @@ -2,23 +2,49 @@ package fleetdbapi import ( - "net/http" + "fmt" "github.com/gin-gonic/gin" + rivets "github.com/metal-toolbox/rivets/types" "go.uber.org/zap" "github.com/metal-toolbox/fleetdb/internal/inventory" ) -func unimplemented(c *gin.Context) { - m := map[string]string{ - "err": "unimplemented", +func (r *Router) getInventory(c *gin.Context) { + srvID, err := r.parseUUID(c) + if err != nil { + badRequestResponse(c, "invalid server id", err) + return } - c.JSON(http.StatusInternalServerError, m) -} -func (r *Router) getInventory(c *gin.Context) { - unimplemented(c) + var doInband bool + switch c.Query("mode") { + case "inband": + doInband = true + case "outofband": + default: + badRequestResponse(c, "invalid inventory mode", nil) + return + } + + dv := inventory.DeviceView{ + DeviceID: srvID, + Inband: doInband, + } + + err = dv.FromDatastore(c.Request.Context(), r.DB) + switch err { + case nil: + case inventory.ErrNoInventory: + msg := fmt.Sprintf("no inventory for %s", srvID) + notFoundResponse(c, msg) + return + default: + dbErrorResponse(c, err) + return + } + itemResponse(c, dv.Inv) } func (r *Router) setInventory(c *gin.Context) { @@ -29,26 +55,39 @@ func (r *Router) setInventory(c *gin.Context) { } var doInband bool - switch c.Param("mode") { + param := c.Query("mode") + + switch param { case "inband": doInband = true case "outofband": + case "": + badRequestResponse(c, "missing inventory specifier", nil) + return default: - badRequestResponse(c, "invalid inventory mode", nil) + badRequestResponse(c, fmt.Sprintf("invalid inventory mode: %s", param), nil) + return + } + + srv := &rivets.Server{} + if err := c.ShouldBindJSON(srv); err != nil { + badRequestResponse(c, "invalid inventory payload", err) + return } - view := inventory.DeviceView{} - if err := c.ShouldBindJSON(&view); err != nil { + if err := inventory.ServerSanityCheck(srv); err != nil { badRequestResponse(c, "invalid inventory payload", err) return } - view.DeviceID = srvID - view.Inband = doInband + view := &inventory.DeviceView{ + DeviceID: srvID, + Inband: doInband, + Inv: srv, + } txn := r.DB.MustBegin() - // XXX what about BIOS config? if err := view.UpsertInventory(c.Request.Context(), txn); err != nil { if err := txn.Rollback(); err != nil { r.Logger.With( @@ -69,22 +108,5 @@ func (r *Router) setInventory(c *gin.Context) { // increment error metrics dbErrorResponse(c, err) } + updatedResponse(c, "") } - -/*( if err != nil { - tx.Rollback() //nolint errcheck - dbErrorResponse(c, err) - - return - } - // compose the attributes from the inventory - // - server vendor attributes - // - server metadata attributes - - // compose the components - -} - -func (r *Router) setOutofbandInventory(c *gin.Context) { - unimplemented(c) -}*/ diff --git a/pkg/api/v1/router_inventory_test.go b/pkg/api/v1/router_inventory_test.go new file mode 100644 index 0000000..043cc1a --- /dev/null +++ b/pkg/api/v1/router_inventory_test.go @@ -0,0 +1,64 @@ +package fleetdbapi_test + +import ( + "context" + "testing" + + "github.com/bmc-toolbox/common" + "github.com/google/uuid" + rivets "github.com/metal-toolbox/rivets/types" + "github.com/stretchr/testify/require" + + "github.com/metal-toolbox/fleetdb/internal/dbtools" +) + +func TestIntegrationGetInventory(t *testing.T) { + s := serverTest(t) + realClientTests(t, func(ctx context.Context, authToken string, respCode int, expectError bool) error { + s.Client.SetToken(authToken) + + srvID := uuid.MustParse(dbtools.FixtureInventoryServer.ID) + srv, _, err := s.Client.GetServerInventory(ctx, srvID, true) + if !expectError { + // we should get back the fixture data for the inventory server + // we don't care about the name of the server + require.NoError(t, err) + require.NotNil(t, srv) + require.Equal(t, "myModel", srv.Model) + require.Equal(t, "Awesome Computer, Inc.", srv.Vendor) + require.Equal(t, "1234xyz", srv.Serial) + } + return err + }) +} + +func TestIntegrationSetInventory(t *testing.T) { + s := serverTest(t) + realClientTests(t, func(ctx context.Context, authToken string, respCode int, expectError bool) error { + s.Client.SetToken(authToken) + + srv := &rivets.Server{ + Model: "myModel", + Vendor: "AwesomeCo", + Serial: "1234xyz", + Components: []*rivets.Component{ + &rivets.Component{ + Name: common.SlugBIOS, + Serial: "0", + Firmware: &common.Firmware{ + Installed: "best version", + }, + }, + }, + } + srvID := uuid.MustParse(dbtools.FixtureInventoryServer.ID) + r, err := s.Client.SetServerInventory(ctx, srvID, srv, true) + if !expectError { + // we should get back the fixture data for the inventory server + // we don't care about the name of the server + require.NoError(t, err) + require.NotNil(t, r) + } + return err + }) +} diff --git a/pkg/api/v1/server_service.go b/pkg/api/v1/server_service.go index e9facb9..790b390 100644 --- a/pkg/api/v1/server_service.go +++ b/pkg/api/v1/server_service.go @@ -7,6 +7,7 @@ import ( "path" "github.com/google/uuid" + rivets "github.com/metal-toolbox/rivets/types" ) const ( @@ -22,6 +23,7 @@ const ( uploadFileEndpoint = "batch-upload" bomByMacAOCAddressEndpoint = "aoc-mac-address" bomByMacBMCAddressEndpoint = "bmc-mac-address" + inventoryEndpoint = "inventory" ) // ClientInterface provides an interface for the expected calls to interact with a fleetdb api @@ -61,6 +63,8 @@ type ClientInterface interface { BillOfMaterialsBatchUpload(context.Context, []Bom) (*ServerResponse, error) GetBomInfoByAOCMacAddr(context.Context, string) (*Bom, *ServerResponse, error) GetBomInfoByBMCMacAddr(context.Context, string) (*Bom, *ServerResponse, error) + GetServerInventory(context.Context, uuid.UUID, bool) (*rivets.Server, *ServerResponse, error) + SetServerInventory(context.Context, uuid.UUID, *rivets.Server, bool) (*ServerResponse, error) } // Create will attempt to create a server in Hollow and return the new server's UUID @@ -427,3 +431,33 @@ func (c *Client) GetBomInfoByBMCMacAddr(ctx context.Context, bmcMacAddr string) return bom, &r, nil } + +// GetServerInventory returns the last reported server state of the kind specified by the inband parameter +func (c *Client) GetServerInventory(ctx context.Context, srvID uuid.UUID, inband bool) (*rivets.Server, *ServerResponse, error) { + mode := "outofband" + if inband { + mode = "inband" + } + + path := fmt.Sprintf("%s/%s?mode=%s", inventoryEndpoint, srvID.String(), mode) + srv := &rivets.Server{} + r := &ServerResponse{Record: srv} + + if err := c.get(ctx, path, r); err != nil { + return nil, nil, err + } + + return srv, r, nil +} + +// SetServerInventory writes the given server structure back to the database +func (c *Client) SetServerInventory(ctx context.Context, srvID uuid.UUID, + srv *rivets.Server, inband bool) (*ServerResponse, error) { + mode := "outofband" + if inband { + mode = "inband" + } + + path := fmt.Sprintf("%s/%s?mode=%s", inventoryEndpoint, srvID.String(), mode) + return c.put(ctx, path, srv) +} diff --git a/pkg/api/v1/server_service_test.go b/pkg/api/v1/server_service_test.go index da8f4f8..44f7b25 100644 --- a/pkg/api/v1/server_service_test.go +++ b/pkg/api/v1/server_service_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" fleetdbapi "github.com/metal-toolbox/fleetdb/pkg/api/v1" + rivets "github.com/metal-toolbox/rivets/types" ) func TestFleetdbCreate(t *testing.T) { @@ -397,3 +398,42 @@ func TestGetBomInfoByBMCMacAddr(t *testing.T) { return err }) } + +func TestGetInventory(t *testing.T) { + mockClientTests(t, func(ctx context.Context, respCode int, expectError bool) error { + srvID := uuid.New() + srv := &rivets.Server{ + Name: srvID.String(), + } + jsonResponse, err := json.Marshal(fleetdbapi.ServerResponse{Record: srv}) + require.NoError(t, err) + + c := mockClient(string(jsonResponse), respCode) + respInv, _, err := c.GetServerInventory(ctx, srvID, true) + if !expectError { + require.Equal(t, srv, respInv) + } + + return err + }) +} + +func TestSetInventory(t *testing.T) { + mockClientTests(t, func(ctx context.Context, respCode int, expectError bool) error { + srvID := uuid.New() + srv := &rivets.Server{ + Name: srvID.String(), + } + exp := &fleetdbapi.ServerResponse{Message: "resource updated"} + jsonResponse, err := json.Marshal(exp) + require.NoError(t, err) + + c := mockClient(string(jsonResponse), respCode) + got, err := c.SetServerInventory(ctx, srvID, srv, true) + if !expectError { + require.Equal(t, exp, got) + } + + return err + }) +} From 60c421433f0aebcccddec4a680c8db6cbdd2dc22 Mon Sep 17 00:00:00 2001 From: Doctor Vince Date: Fri, 19 Apr 2024 16:48:35 -0400 Subject: [PATCH 21/21] fix up some overly brittle tests --- pkg/api/v1/router_server_components_test.go | 18 +++++++++++----- pkg/api/v1/router_server_test.go | 23 +++++++++------------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pkg/api/v1/router_server_components_test.go b/pkg/api/v1/router_server_components_test.go index b2f0004..b5c74b6 100644 --- a/pkg/api/v1/router_server_components_test.go +++ b/pkg/api/v1/router_server_components_test.go @@ -204,7 +204,7 @@ func TestIntegrationServerGetComponents(t *testing.T) { // fixture to create a server components csFixtureCreate := fleetdbapi.ServerComponentSlice{ { - ServerUUID: servers[0].UUID, + ServerUUID: servers[1].UUID, Name: "My Lucky Fin", Vendor: "barracuda", Model: "a lucky fin", @@ -226,7 +226,7 @@ func TestIntegrationServerGetComponents(t *testing.T) { } // create server component - _, err = s.Client.CreateComponents(context.TODO(), servers[0].UUID, csFixtureCreate) + _, err = s.Client.CreateComponents(context.TODO(), servers[1].UUID, csFixtureCreate) if err != nil { t.Fatal(err) } @@ -247,10 +247,10 @@ func TestIntegrationServerGetComponents(t *testing.T) { }, { "component Versioned Attributes is returned as expected", - servers[0].UUID, + servers[1].UUID, 3, fleetdbapi.ServerComponent{ - ServerUUID: servers[0].UUID, + ServerUUID: servers[1].UUID, Name: "My Lucky Fin", Vendor: "barracuda", Model: "a lucky fin", @@ -504,8 +504,16 @@ func TestIntegrationServerUpdateComponents(t *testing.T) { t.Fatal(err) } + var nemo *fleetdbapi.Server + for _, s := range servers { + if s.Name == dbtools.FixtureNemo.Name.String { + nemo = &s + } + } + require.NotNil(t, nemo, "couldn't find nemo") + // update serial attribute for update to work - sc = fleetdbapi.ServerComponentSlice{servers[0].Components[0]} + sc = fleetdbapi.ServerComponentSlice{nemo.Components[0]} sc[0].Serial = "lala" } diff --git a/pkg/api/v1/router_server_test.go b/pkg/api/v1/router_server_test.go index 94793f1..78dcfed 100644 --- a/pkg/api/v1/router_server_test.go +++ b/pkg/api/v1/router_server_test.go @@ -24,11 +24,11 @@ func TestIntegrationServerList(t *testing.T) { r, resp, err := s.Client.List(ctx, nil) if !expectError { require.NoError(t, err) - assert.Len(t, r, 3) + assert.Len(t, r, 4) - assert.EqualValues(t, 3, resp.PageCount) + assert.EqualValues(t, 4, resp.PageCount) assert.EqualValues(t, 1, resp.TotalPages) - assert.EqualValues(t, 3, resp.TotalRecordCount) + assert.EqualValues(t, 4, resp.TotalRecordCount) // We returned everything, so we shouldnt have a next page info assert.Nil(t, resp.Links.Next) assert.Nil(t, resp.Links.Previous) @@ -144,7 +144,7 @@ func TestIntegrationServerList(t *testing.T) { { "empty search filter", nil, - []string{dbtools.FixtureNemo.ID, dbtools.FixtureDory.ID, dbtools.FixtureMarlin.ID}, + []string{dbtools.FixtureInventoryServer.ID, dbtools.FixtureNemo.ID, dbtools.FixtureDory.ID, dbtools.FixtureMarlin.ID}, false, "", }, @@ -436,14 +436,14 @@ func TestIntegrationServerList(t *testing.T) { { "search for server without IncludeDeleted defined", &fleetdbapi.ServerListParams{}, - []string{dbtools.FixtureNemo.ID, dbtools.FixtureDory.ID, dbtools.FixtureMarlin.ID}, + []string{dbtools.FixtureInventoryServer.ID, dbtools.FixtureNemo.ID, dbtools.FixtureDory.ID, dbtools.FixtureMarlin.ID}, false, "", }, { "search for server with IncludeDeleted defined", &fleetdbapi.ServerListParams{IncludeDeleted: true}, - []string{dbtools.FixtureNemo.ID, dbtools.FixtureDory.ID, dbtools.FixtureMarlin.ID, dbtools.FixtureChuckles.ID}, + []string{dbtools.FixtureInventoryServer.ID, dbtools.FixtureNemo.ID, dbtools.FixtureDory.ID, dbtools.FixtureMarlin.ID, dbtools.FixtureChuckles.ID}, false, "", }, @@ -511,12 +511,10 @@ func TestIntegrationServerListPagination(t *testing.T) { assert.NoError(t, err) assert.Len(t, r, 2) - assert.Equal(t, dbtools.FixtureServers[2].ID, r[0].UUID.String()) - assert.Equal(t, dbtools.FixtureServers[1].ID, r[1].UUID.String()) assert.EqualValues(t, 2, resp.PageCount) assert.EqualValues(t, 2, resp.TotalPages) - assert.EqualValues(t, 3, resp.TotalRecordCount) + assert.EqualValues(t, 4, resp.TotalRecordCount) // Since we have a next page let's make sure all the links are set assert.NotNil(t, resp.Links.Next) assert.Nil(t, resp.Links.Previous) @@ -528,16 +526,15 @@ func TestIntegrationServerListPagination(t *testing.T) { resp, err = s.Client.NextPage(context.TODO(), *resp, &r) assert.NoError(t, err) - assert.Len(t, r, 1) - assert.Equal(t, dbtools.FixtureServers[0].ID, r[0].UUID.String()) + assert.Len(t, r, 2) - assert.EqualValues(t, 1, resp.PageCount) + assert.EqualValues(t, 2, resp.PageCount) // we should have followed the cursor so first/previous/next/last links shouldn't be set // but there is another page so we should have a next cursor link. Total counts are not includes // cursor pages assert.EqualValues(t, 2, resp.TotalPages) - assert.EqualValues(t, 3, resp.TotalRecordCount) + assert.EqualValues(t, 4, resp.TotalRecordCount) assert.NotNil(t, resp.Links.First) assert.NotNil(t, resp.Links.Previous) assert.Nil(t, resp.Links.Next)