From 1576af70cc13e1065709f4ab9db06274221e6790 Mon Sep 17 00:00:00 2001 From: Hugo Vieira Date: Fri, 3 Dec 2021 11:47:00 +0000 Subject: [PATCH 1/3] feat(config): Add env and file support to Worker Tags This feature allows the user to set the `tags` field to a string that points to either an environment variable or file. We then dynamically pull out the value and parse it into the config. This can also be done at the keys level, where you set your keys directly in Boundary's config file, but point the values to env/file. --- CHANGELOG.md | 2 + internal/cmd/config/config.go | 55 +++- internal/cmd/config/config_test.go | 286 ++++++++++++++++++ .../docs/concepts/filtering/worker-tags.mdx | 47 +++ website/content/docs/configuration/worker.mdx | 3 +- 5 files changed, 390 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d345231f5..122b4e591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. ### New and Improved +* config: Add support for reading worker tags off of environment variables +as well as files. ([PR](https://github.com/hashicorp/boundary/pull/1758)) * config: Add support for go-sockaddr templates to Worker and Controller addresses. ([PR](https://github.com/hashicorp/boundary/pull/1731)) * host: Plugin-based host catalogs will now schedule updates for all diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index 824ce0523..27f267307 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/rand" "encoding/base64" + "encoding/json" "errors" "fmt" "io" @@ -157,9 +158,10 @@ type Worker struct { // We use a raw interface for parsing so that people can use JSON-like // syntax that maps directly to the filter input or possibly more familiar - // key=value syntax. This is trued up in the Parse function below. - TagsRaw interface{} `hcl:"tags"` + // key=value syntax, as well as accepting a string denoting an env or file + // pointer. This is trued up in the Parse function below. Tags map[string][]string `hcl:"-"` + TagsRaw interface{} `hcl:"tags"` // StatusGracePeriod represents the period of time (as a duration) that the // worker will wait before disconnecting connections if it cannot make a @@ -337,12 +339,60 @@ func Parse(d string) (*Config, error) { if !strutil.Printable(result.Worker.Name) { return nil, errors.New("Worker name contains non-printable characters") } + if result.Worker.TagsRaw != nil { switch t := result.Worker.TagsRaw.(type) { + // We allow `tags` to be a simple string containing a URL with schema. + // See: https://github.com/hashicorp/go-secure-stdlib/blob/main/parseutil/parsepath.go + case string: + rawTags, err := parseutil.ParsePath(t) + if err != nil { + return nil, fmt.Errorf("Error parsing worker tags: %w", err) + } + + var temp []map[string]interface{} + err = hcl.Decode(&temp, rawTags) + if err != nil { + return nil, fmt.Errorf("Error decoding raw worker tags: %w", err) + } + + if err := mapstructure.WeakDecode(temp, &result.Worker.Tags); err != nil { + return nil, fmt.Errorf("Error decoding the worker's tags: %w", err) + } + // HCL allows multiple labeled blocks with the same name, turning it // into a slice of maps, hence the slice here. This format is the // one that ends up matching the JSON that we use in the expression. case []map[string]interface{}: + for _, m := range t { + for k, v := range m { + // We allow the user to pass in only the keys in HCL, and + // then set the values to point to a URL with schema. + valStr, ok := v.(string) + if !ok { + continue + } + + parsed, err := parseutil.ParsePath(valStr) + if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) { + return nil, fmt.Errorf("Error parsing worker tag values: %w", err) + } + if valStr == parsed { + // Nothing was found, ignore. + // WeakDecode will still parse it though as we + // don't know if this could be a valid tag. + continue + } + + var tags []string + err = json.Unmarshal([]byte(parsed), &tags) + if err != nil { + return nil, fmt.Errorf("Error unmarshalling env var/file contents: %w", err) + } + m[k] = tags + } + } + if err := mapstructure.WeakDecode(t, &result.Worker.Tags); err != nil { return nil, fmt.Errorf("Error decoding the worker's %q section: %w", "tags", err) } @@ -374,6 +424,7 @@ func Parse(d string) (*Config, error) { } } } + for k, v := range result.Worker.Tags { if k != strings.ToLower(k) { return nil, fmt.Errorf("Tag key %q is not all lower-case letters", k) diff --git a/internal/cmd/config/config_test.go b/internal/cmd/config/config_test.go index 3f93c08c9..70134e18b 100644 --- a/internal/cmd/config/config_test.go +++ b/internal/cmd/config/config_test.go @@ -337,6 +337,292 @@ func TestParsingName(t *testing.T) { } } +func TestWorkerTags(t *testing.T) { + defaultStateFn := func(t *testing.T, tags string) { + t.Setenv("BOUNDARY_WORKER_TAGS", tags) + } + tests := []struct { + name string + in string + stateFn func(t *testing.T, tags string) + actualTags string + expWorkerTags map[string][]string + expErr bool + expErrStr string + }{ + { + name: "tags in HCL", + in: ` + worker { + tags { + type = ["dev", "local"] + typetwo = "devtwo" + } + }`, + expWorkerTags: map[string][]string{ + "type": {"dev", "local"}, + "typetwo": {"devtwo"}, + }, + expErr: false, + }, + { + name: "tags in HCL key=value", + in: ` + worker { + tags = ["type=dev", "type=local", "typetwo=devtwo"] + } + `, + expWorkerTags: map[string][]string{ + "type": {"dev", "local"}, + "typetwo": {"devtwo"}, + }, + expErr: false, + }, + { + name: "no tags", + in: ` + worker { + name = "testworker" + } + `, + expWorkerTags: nil, + expErr: false, + }, + { + name: "empty tags", + in: ` + worker { + name = "testworker" + tags = {} + } + `, + expWorkerTags: map[string][]string{}, + expErr: false, + }, + { + name: "empty tags 2", + in: ` + worker { + name = "testworker" + tags = [] + } + `, + expWorkerTags: map[string][]string{}, + expErr: false, + }, + { + name: "empty str", + in: ` + worker { + tags = "" + }`, + expWorkerTags: map[string][]string{}, + expErr: false, + }, + { + name: "empty env var", + in: ` + worker { + tags = "env://BOUNDARY_WORKER_TAGS" + }`, + expWorkerTags: map[string][]string{}, + expErr: false, + }, + { + name: "not a url - entire tags block", + in: ` + worker { + tags = "\x00" + }`, + expWorkerTags: map[string][]string{}, + expErr: true, + expErrStr: `Error parsing worker tags: error parsing url ("parse \"\\x00\": net/url: invalid control character in URL"): not a url`, + }, + { + name: "not a url - key's value set to string", + in: ` + worker { + tags { + type = "\x00" + } + } + `, + expWorkerTags: map[string][]string{ + "type": {"\x00"}, + }, + expErr: false, + }, + { + name: "one tag key", + in: ` + worker { + tags = "env://BOUNDARY_WORKER_TAGS" + }`, + stateFn: defaultStateFn, + actualTags: `type = ["dev", "local"]`, + expWorkerTags: map[string][]string{ + "type": {"dev", "local"}, + }, + expErr: false, + }, + { + name: "multiple tag keys", + in: ` + worker { + tags = "env://BOUNDARY_WORKER_TAGS" + }`, + stateFn: defaultStateFn, + actualTags: ` + type = ["dev", "local"] + typetwo = ["devtwo", "localtwo"] + `, + expWorkerTags: map[string][]string{ + "type": {"dev", "local"}, + "typetwo": {"devtwo", "localtwo"}, + }, + expErr: false, + }, + { + name: "json tags - entire tags block", + in: ` + worker { + tags = "env://BOUNDARY_WORKER_TAGS" + }`, + stateFn: defaultStateFn, + actualTags: ` + { + "type": ["dev", "local"], + "typetwo": ["devtwo", "localtwo"] + } + `, + expWorkerTags: map[string][]string{ + "type": {"dev", "local"}, + "typetwo": {"devtwo", "localtwo"}, + }, + expErr: false, + }, + { + name: "json tags - keys specified in the HCL file, values point to env/file", + in: ` + worker { + tags = { + type = "env://BOUNDARY_WORKER_TAGS" + typetwo = "env://BOUNDARY_WORKER_TAGS_TWO" + } + }`, + stateFn: func(t *testing.T, tags string) { + defaultStateFn(t, tags) + t.Setenv("BOUNDARY_WORKER_TAGS_TWO", `["devtwo", "localtwo"]`) + }, + actualTags: `["dev","local"]`, + expWorkerTags: map[string][]string{ + "type": {"dev", "local"}, + "typetwo": {"devtwo", "localtwo"}, + }, + expErr: false, + }, + { + name: "json tags - mix n' match", + in: ` + worker { + name = "web-prod-us-east-1" + tags { + type = "env://BOUNDARY_WORKER_TYPE_TAGS" + typetwo = "file://type_two_tags.json" + typethree = ["devthree", "localthree"] + } + } + `, + stateFn: func(t *testing.T, tags string) { + workerTypeTags := `["dev", "local"]` + t.Setenv("BOUNDARY_WORKER_TYPE_TAGS", workerTypeTags) + + filepath := "./type_two_tags.json" + err := os.WriteFile(filepath, []byte(`["devtwo", "localtwo"]`), 0o666) + require.NoError(t, err) + + t.Cleanup(func() { + err := os.Remove(filepath) + require.NoError(t, err) + }) + }, + expWorkerTags: map[string][]string{ + "type": {"dev", "local"}, + "typetwo": {"devtwo", "localtwo"}, + "typethree": {"devthree", "localthree"}, + }, + expErr: false, + }, + { + name: "bad json tags", + in: ` + worker { + tags = { + type = "env://BOUNDARY_WORKER_TAGS" + typetwo = "env://BOUNDARY_WORKER_TAGS" + } + }`, + stateFn: defaultStateFn, + actualTags: ` + { + "type": ["dev", "local"], + "typetwo": ["devtwo", "localtwo"] + } + `, + expWorkerTags: nil, + expErr: true, + expErrStr: "Error unmarshalling env var/file contents: json: cannot unmarshal object into Go value of type []string", + }, + { + name: "no clean mapping to internal structures", + in: ` + worker { + tags = "env://BOUNDARY_WORKER_TAGS" + }`, + stateFn: defaultStateFn, + actualTags: ` + worker { + tags { + type = "indeed" + } + } + `, + expErr: true, + expErrStr: "Error decoding the worker's tags: 1 error(s) decoding:\n\n* '[0][worker][0]' expected type 'string', got unconvertible type 'map[string]interface {}', value: 'map[tags:[map[type:indeed]]]'", + }, + { + name: "not HCL", + in: `worker { + tags = "env://BOUNDARY_WORKER_TAGS" + }`, + stateFn: defaultStateFn, + actualTags: `not_hcl`, + expErr: true, + expErrStr: "Error decoding raw worker tags: At 1:9: key 'not_hcl' expected start of object ('{') or assignment ('=')", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.stateFn != nil { + tt.stateFn(t, tt.actualTags) + } + + c, err := Parse(tt.in) + if tt.expErr { + require.EqualError(t, err, tt.expErrStr) + require.Nil(t, c) + return + } + + require.NoError(t, err) + require.NotNil(t, c) + require.NotNil(t, c.Worker) + require.Equal(t, tt.expWorkerTags, c.Worker.Tags) + }) + } +} + func TestController_EventingConfig(t *testing.T) { t.Parallel() diff --git a/website/content/docs/concepts/filtering/worker-tags.mdx b/website/content/docs/concepts/filtering/worker-tags.mdx index dcc325508..d0d626726 100644 --- a/website/content/docs/concepts/filtering/worker-tags.mdx +++ b/website/content/docs/concepts/filtering/worker-tags.mdx @@ -53,6 +53,53 @@ worker { In this format, it is not possible to have an equal sign be a part of the key. +It is also possible to set the entire `tags` block or the keys' values within +to point to an environment variable or filepath in the system, through the +`env://` and `file://` URLs: + +```hcl +worker { + name = "web-prod-us-east-1" + tags = "env://BOUNDARY_ALL_WORKER_TAGS" +} +``` + +```hcl +worker { + name = "web-prod-us-east-1" + tags { + type = "env://BOUNDARY_WORKER_TYPE_TAGS" + region = "file://config/worker/region_tags" + usage = ["admin"] + } +} +``` + +Note that the syntax within the environment variable / file changes +slightly depending on how the configuration file is set: + +For setting the entire `tags` block, both the keys and values need +to be specified, in JSON or HCL format: + +```json +{ + "region": ["us-east-1"], + "type": ["prod", "webservers"] +} +``` + +```hcl +region = ["us-east-1"] +type = ["prod", "webservers"] +``` + +For setting the keys' values within the `tags` block, only a JSON +array with the tags intended for the particular key is required: + +```json +["prod", "webservers"] +``` + # Target Worker Filtering Once workers have tags, it is possible to use these tags to control which diff --git a/website/content/docs/configuration/worker.mdx b/website/content/docs/configuration/worker.mdx index 7a371faf0..93e1e6d17 100644 --- a/website/content/docs/configuration/worker.mdx +++ b/website/content/docs/configuration/worker.mdx @@ -39,7 +39,8 @@ worker { - `tags` - A map of key-value pairs where values are an array of strings. Most commonly used for [filtering](/docs/concepts/filtering) targets a worker can proxy via [worker tags](/docs/concepts/filtering/worker-tags). On `SIGHUP`, the - tags set here will be re-parsed and new values used.. + tags set here will be re-parsed and new values used. It can also be a string + referring to a file on disk (file://) or an env var (env://). ## KMS Configuration From bf18555d5ef62e6fe559aeb71876b98df635db2c Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Tue, 7 Dec 2021 12:38:34 -0800 Subject: [PATCH 2/3] internal/host/plugin: remove custom queries for secrets (#1676) * internal/host/plugin: remove custom queries for secrets This removes the custom queries for host catalog secrets, now that #1658 has been merged. It also does a lot of cleanup surrounding the related oplog entries, using db.WithOplog natively. * Simplify conflict actions by replacing SetColumnValues with SetColumns * Remove unused oplog method --- internal/host/plugin/host_catalog_secret.go | 42 -------- .../host/plugin/host_catalog_secret_test.go | 99 ++++++++++++------- .../host/plugin/repository_host_catalog.go | 38 +++---- .../plugin/repository_host_catalog_test.go | 4 +- .../host/plugin/repository_host_set_test.go | 4 +- 5 files changed, 83 insertions(+), 104 deletions(-) diff --git a/internal/host/plugin/host_catalog_secret.go b/internal/host/plugin/host_catalog_secret.go index 84eda4867..d618b2bbf 100644 --- a/internal/host/plugin/host_catalog_secret.go +++ b/internal/host/plugin/host_catalog_secret.go @@ -2,12 +2,9 @@ package plugin import ( "context" - "database/sql" - "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/host/plugin/store" - "github.com/hashicorp/boundary/internal/oplog" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-kms-wrapping/structwrapping" "google.golang.org/protobuf/proto" @@ -89,42 +86,3 @@ func (c *HostCatalogSecret) decrypt(ctx context.Context, cipher wrapping.Wrapper c.CtSecret = nil return nil } - -// TODO(ICU-2835): this is necessary as update logic requires a static -// public_id or private_id field. Once post-lookup updates can -// support alternate primary key fields, we can port this to native -// Update and upserts w/OnConflict. -func (c *HostCatalogSecret) upsertQuery() (query string, queryValues []interface{}) { - query = upsertHostCatalogSecretQuery - queryValues = []interface{}{ - sql.Named("catalog_id", c.CatalogId), - sql.Named("secret", c.CtSecret), - sql.Named("key_id", c.KeyId), - } - return -} - -func (c *HostCatalogSecret) deleteQuery() (query string, queryValues []interface{}) { - query = deleteHostCatalogSecretQuery - queryValues = []interface{}{ - sql.Named("catalog_id", c.CatalogId), - } - return -} - -func (c *HostCatalogSecret) oplogMessage(opType db.OpType) *oplog.Message { - msg := oplog.Message{ - Message: c.clone(), - TypeName: c.TableName(), - } - switch opType { - case db.CreateOp: - msg.OpType = oplog.OpType_OP_TYPE_CREATE - case db.UpdateOp: - msg.OpType = oplog.OpType_OP_TYPE_UPDATE - msg.FieldMaskPaths = []string{"secret", "key_id"} - case db.DeleteOp: - msg.OpType = oplog.OpType_OP_TYPE_DELETE - } - return &msg -} diff --git a/internal/host/plugin/host_catalog_secret_test.go b/internal/host/plugin/host_catalog_secret_test.go index 09bb7af02..146af909b 100644 --- a/internal/host/plugin/host_catalog_secret_test.go +++ b/internal/host/plugin/host_catalog_secret_test.go @@ -118,57 +118,82 @@ func TestHostCatalogSecret_New(t *testing.T) { } } -func TestHostCatalogSecret_Custom_Queries(t *testing.T) { - ctx := context.Background() +func TestHostCatalogSecret_Create_Upsert_Update_Delete(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) wrapper := db.TestWrapper(t) kkms := kms.TestKms(t, conn, wrapper) _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) plg := host.TestPlugin(t, conn, "test") cat := TestCatalog(t, conn, prj.GetPublicId(), plg.GetPublicId()) + ctx := context.Background() + + secret, err := newHostCatalogSecret(ctx, cat.GetPublicId(), mustStruct(map[string]interface{}{ + "foo": "bar", + })) + require.NoError(t, err) + require.NotNil(t, secret) + require.Empty(t, secret.CtSecret) + databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase) require.NoError(t, err) + require.NotNil(t, databaseWrapper) + require.NoError(t, secret.encrypt(ctx, databaseWrapper)) - hcs, err := newHostCatalogSecret(ctx, cat.GetPublicId(), - &structpb.Struct{Fields: map[string]*structpb.Value{"foo": structpb.NewStringValue("bar")}}) - require.NoError(t, err) - assert.NoError(t, hcs.encrypt(ctx, databaseWrapper)) - q, v := hcs.upsertQuery() - _, err = rw.Exec(ctx, q, v) - assert.NoError(t, err) + // Create + w := db.New(conn) + require.NoError(t, w.Create(ctx, secret)) - found, err := newHostCatalogSecret(ctx, cat.GetPublicId(), nil) - require.NoError(t, err) - assert.NoError(t, rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId())) + // Upsert + newStructUpsert := mustMarshal(map[string]interface{}{ + "baz": "qux", + }) + newSecretUpsert := secret.clone() + newSecretUpsert.Secret = newStructUpsert + require.NoError(t, newSecretUpsert.encrypt(ctx, databaseWrapper)) + require.NoError(t, w.Create(ctx, newSecretUpsert, db.WithOnConflict(&db.OnConflict{ + Target: db.Columns{"catalog_id"}, + Action: db.SetColumns([]string{"secret", "key_id"}), + }))) + found := &HostCatalogSecret{ + HostCatalogSecret: &store.HostCatalogSecret{ + CatalogId: cat.GetPublicId(), + }, + } + require.NoError(t, w.LookupById(ctx, found)) + assert.Empty(t, cmp.Diff(newSecretUpsert.HostCatalogSecret, found.HostCatalogSecret, protocmp.Transform())) require.NoError(t, found.decrypt(ctx, databaseWrapper)) - require.NoError(t, hcs.decrypt(ctx, databaseWrapper)) - // update the created/updated time from the original - hcs.CreateTime, hcs.UpdateTime = found.CreateTime, found.UpdateTime - assert.Empty(t, cmp.Diff(hcs, found, protocmp.Transform())) + assert.Empty(t, cmp.Diff(newStructUpsert, found.Secret, protocmp.Transform())) - // Update the secret and see the value updated. - updated, err := newHostCatalogSecret(ctx, cat.GetPublicId(), - &structpb.Struct{Fields: map[string]*structpb.Value{"updated": structpb.NewStringValue("value")}}) + // Update + newStructUpdate := mustMarshal(map[string]interface{}{ + "one": "two", + }) + newSecretUpdate := newSecretUpsert.clone() + newSecretUpdate.Secret = newStructUpdate + require.NoError(t, newSecretUpdate.encrypt(ctx, databaseWrapper)) + rowsUpdated, err := w.Update(ctx, newSecretUpdate, []string{"CtSecret"}, []string{}) require.NoError(t, err) - assert.NoError(t, updated.encrypt(ctx, databaseWrapper)) - q, v = updated.upsertQuery() - _, err = rw.Exec(ctx, q, v) - assert.NoError(t, err) - - assert.NoError(t, rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId())) + require.Equal(t, 1, rowsUpdated) + found = &HostCatalogSecret{ + HostCatalogSecret: &store.HostCatalogSecret{ + CatalogId: cat.GetPublicId(), + }, + } + require.NoError(t, w.LookupById(ctx, found)) + assert.Empty(t, cmp.Diff(newSecretUpdate.HostCatalogSecret, found.HostCatalogSecret, protocmp.Transform())) require.NoError(t, found.decrypt(ctx, databaseWrapper)) - require.NoError(t, updated.decrypt(ctx, databaseWrapper)) - // set the created time to the first and update time from the newly found - updated.CreateTime, updated.UpdateTime = hcs.CreateTime, found.UpdateTime - assert.Empty(t, cmp.Diff(updated, found, protocmp.Transform())) + assert.Empty(t, cmp.Diff(newStructUpdate, found.Secret, protocmp.Transform())) - // Try to delete this secret. - q, v = updated.deleteQuery() - _, err = rw.Exec(ctx, q, v) - assert.NoError(t, err) - err = rw.LookupWhere(ctx, found, "catalog_id=?", found.GetCatalogId()) - assert.Error(t, err) - assert.True(t, errors.IsNotFoundError(err)) + // Delete + rowsDeleted, err := w.Delete(ctx, found) + require.NoError(t, err) + require.Equal(t, 1, rowsDeleted) + err = w.LookupById(ctx, &HostCatalogSecret{ + HostCatalogSecret: &store.HostCatalogSecret{ + CatalogId: cat.GetPublicId(), + }, + }) + require.Error(t, err) + require.True(t, errors.IsNotFoundError(err)) } diff --git a/internal/host/plugin/repository_host_catalog.go b/internal/host/plugin/repository_host_catalog.go index 2bf89a2c3..3b6f6469b 100644 --- a/internal/host/plugin/repository_host_catalog.go +++ b/internal/host/plugin/repository_host_catalog.go @@ -142,15 +142,11 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, _ ...Opt } if hcSecret != nil { newSecret := hcSecret.clone() - q, v := newSecret.upsertQuery() - rows, err := w.Exec(ctx, q, v) - if err != nil { + var sOplogMsg oplog.Message + if err := w.Create(ctx, newSecret, db.NewOplogMsg(&sOplogMsg)); err != nil { return errors.Wrap(ctx, err, op) } - if rows > 1 { - return errors.New(ctx, errors.MultipleRecords, op, "more than 1 catalog secret would have been created") - } - msgs = append(msgs, newSecret.oplogMessage(db.CreateOp)) + msgs = append(msgs, &sOplogMsg) } } @@ -400,16 +396,16 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version // We didn't set/encrypt the persisted data because there was // none returned. Just delete the entry. deletedSecret := hcSecret.clone() - q, v := deletedSecret.deleteQuery() - secretsUpdated, err := w.Exec(ctx, q, v) + var sOplogMsg oplog.Message + secretsDeleted, err := w.Delete(ctx, deletedSecret, db.NewOplogMsg(&sOplogMsg)) if err != nil { return errors.Wrap(ctx, err, op) } - if secretsUpdated != 1 { - return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog secret to be deleted, got %d", secretsUpdated)) + if secretsDeleted != 1 { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog secret to be deleted, got %d", secretsDeleted)) } updatedPersisted = true - msgs = append(msgs, deletedSecret.oplogMessage(db.DeleteOp)) + msgs = append(msgs, &sOplogMsg) } else { hcSecret, err := newHostCatalogSecret(ctx, currentCatalog.GetPublicId(), plgResp.GetPersisted().GetSecrets()) if err != nil { @@ -421,16 +417,20 @@ func (r *Repository) UpdateCatalog(ctx context.Context, c *HostCatalog, version // Update the secrets. updatedSecret := hcSecret.clone() - q, v := updatedSecret.upsertQuery() - secretsUpdated, err := w.Exec(ctx, q, v) - if err != nil { + var sOplogMsg oplog.Message + if err := w.Create( + ctx, + updatedSecret, + db.WithOnConflict(&db.OnConflict{ + Target: db.Columns{"catalog_id"}, + Action: db.SetColumns([]string{"secret", "key_id"}), + }), + db.NewOplogMsg(&sOplogMsg), + ); err != nil { return errors.Wrap(ctx, err, op) } - if secretsUpdated != 1 { - return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 catalog secret to be updated, got %d", secretsUpdated)) - } updatedPersisted = true - msgs = append(msgs, updatedSecret.oplogMessage(db.UpdateOp)) + msgs = append(msgs, &sOplogMsg) } } diff --git a/internal/host/plugin/repository_host_catalog_test.go b/internal/host/plugin/repository_host_catalog_test.go index 3125a81ac..32e371639 100644 --- a/internal/host/plugin/repository_host_catalog_test.go +++ b/internal/host/plugin/repository_host_catalog_test.go @@ -1119,10 +1119,8 @@ func TestRepository_UpdateCatalog(t *testing.T) { cSecret, err := newHostCatalogSecret(ctx, cat.GetPublicId(), cat.Secrets) require.NoError(err) require.NoError(cSecret.encrypt(ctx, scopeWrapper)) - cSecretQ, cSecretV := cSecret.upsertQuery() - secretsUpdated, err := dbRW.Exec(ctx, cSecretQ, cSecretV) + err = dbRW.Create(ctx, cSecret) require.NoError(err) - require.Equal(1, secretsUpdated) // Set some (default) attributes on our test catalog and update SecretsHmac at the same time cat.Attributes = mustMarshal(map[string]interface{}{ diff --git a/internal/host/plugin/repository_host_set_test.go b/internal/host/plugin/repository_host_set_test.go index 581fb0757..31fa3c55e 100644 --- a/internal/host/plugin/repository_host_set_test.go +++ b/internal/host/plugin/repository_host_set_test.go @@ -424,10 +424,8 @@ func TestRepository_UpdateSet(t *testing.T) { scopeWrapper, err := dbKmsCache.GetWrapper(ctx, testCatalog.GetScopeId(), kms.KeyPurposeDatabase) require.NoError(t, err) require.NoError(t, testCatalogSecret.encrypt(ctx, scopeWrapper)) - testCatalogSecretQ, testCatalogSecretV := testCatalogSecret.upsertQuery() - secretsUpdated, err := dbRW.Exec(ctx, testCatalogSecretQ, testCatalogSecretV) + err = dbRW.Create(ctx, testCatalogSecret) require.NoError(t, err) - require.Equal(t, 1, secretsUpdated) // Create a test duplicate set. We don't use this set, it just // exists to ensure that we can test for conflicts when setting From d3687153aab6d349f8b3e28f162b772ee5883db2 Mon Sep 17 00:00:00 2001 From: Noel Quiles <3746694+EnMod@users.noreply.github.com> Date: Tue, 7 Dec 2021 17:02:16 -0500 Subject: [PATCH 3/3] website: Upgrade to latest (#1762) --- website/package-lock.json | 96 +++++++++++++-------------------------- website/package.json | 2 +- 2 files changed, 32 insertions(+), 66 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 7d7008842..af33d1e34 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -21,7 +21,7 @@ "@hashicorp/react-content": "^8.0.2", "@hashicorp/react-docs-page": "^14.4.2", "@hashicorp/react-enterprise-alert": "^6.0.1", - "@hashicorp/react-hashi-stack-menu": "^2.0.7", + "@hashicorp/react-hashi-stack-menu": "^2.1.2", "@hashicorp/react-head": "^3.1.2", "@hashicorp/react-hero": "^8.0.2", "@hashicorp/react-image": "^4.0.3", @@ -1342,12 +1342,13 @@ "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" }, "node_modules/@hashicorp/react-hashi-stack-menu": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@hashicorp/react-hashi-stack-menu/-/react-hashi-stack-menu-2.0.7.tgz", - "integrity": "sha512-3b9VxAzSPbb5YeiKbMiygrointTb56VsMBp27mXPXS2dqrZ4pj1o5AJCYwescv7g7KbwwL+YC9hlLlhRxjErUw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@hashicorp/react-hashi-stack-menu/-/react-hashi-stack-menu-2.1.2.tgz", + "integrity": "sha512-3K6Y2FNSLq6TsMceX3a1pdmxaVx8lRvxCgjxcKL1P6DpmOk0AU5/eL0CvKP8GzXSYstL36xXxIhWFfiaAaF/yQ==", "dependencies": { - "@hashicorp/react-inline-svg": "^1.0.2", - "slugify": "1.3.4" + "@hashicorp/react-inline-svg": "^6.0.1", + "@hashicorp/react-link-wrap": "^3.0.3", + "slugify": "1.6.0" }, "peerDependencies": { "@hashicorp/mktg-global-styles": ">=3.x", @@ -1355,20 +1356,12 @@ "react": ">=16.x" } }, - "node_modules/@hashicorp/react-hashi-stack-menu/node_modules/@hashicorp/react-inline-svg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@hashicorp/react-inline-svg/-/react-inline-svg-1.0.2.tgz", - "integrity": "sha512-AAFnBslSTgnEr++dTbMn3sybAqvn7myIj88ijGigF6u11eSRiV64zqEcyYLQKWTV6dF4AvYoxiYC6GSOgiM0Yw==", - "peerDependencies": { - "react": "^16.9.0" - } - }, "node_modules/@hashicorp/react-hashi-stack-menu/node_modules/slugify": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.4.tgz", - "integrity": "sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.0.tgz", + "integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang==", "engines": { - "node": ">=4.0.0" + "node": ">=8.0.0" } }, "node_modules/@hashicorp/react-head": { @@ -2347,13 +2340,9 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.2.tgz", "integrity": "sha512-hZuwOlGOwBZADA8EyDYyjx3+4JGIGjSHDHWrmpI7g5rFmQNltjlbaefAbiU5Kk7j3BUSDwt30quJRFv3nyJQ0w==", - "cpu": [ - "arm64" - ], + "cpu": ["arm64"], "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": ">= 10" } @@ -2362,13 +2351,9 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.2.tgz", "integrity": "sha512-PGOp0E1GisU+EJJlsmJVGE+aPYD0Uh7zqgsrpD3F/Y3766Ptfbe1lEPPWnRDl+OzSSrSrX1lkyM/Jlmh5OwNvA==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": ">= 10" } @@ -2377,13 +2362,9 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.2.tgz", "integrity": "sha512-YcDHTJjn/8RqvyJVB6pvEKXihDcdrOwga3GfMv/QtVeLphTouY4BIcEUfrG5+26Nf37MP1ywN3RRl1TxpurAsQ==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "optional": true, - "os": [ - "linux" - ], + "os": ["linux"], "engines": { "node": ">= 10" } @@ -2392,13 +2373,9 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.2.tgz", "integrity": "sha512-e/pIKVdB+tGQYa1cW3sAeHm8gzEri/HYLZHT4WZojrUxgWXqx8pk7S7Xs47uBcFTqBDRvK3EcQpPLf3XdVsDdg==", - "cpu": [ - "x64" - ], + "cpu": ["x64"], "optional": true, - "os": [ - "win32" - ], + "os": ["win32"], "engines": { "node": ">= 10" } @@ -5711,9 +5688,7 @@ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "devOptional": true, - "engines": [ - "node >= 0.8" - ], + "engines": ["node >= 0.8"], "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -8821,9 +8796,7 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -19529,9 +19502,7 @@ "dev": true, "hasInstallScript": true, "optional": true, - "os": [ - "darwin" - ], + "os": ["darwin"], "peer": true, "dependencies": { "bindings": "^1.5.0", @@ -21413,24 +21384,19 @@ } }, "@hashicorp/react-hashi-stack-menu": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@hashicorp/react-hashi-stack-menu/-/react-hashi-stack-menu-2.0.7.tgz", - "integrity": "sha512-3b9VxAzSPbb5YeiKbMiygrointTb56VsMBp27mXPXS2dqrZ4pj1o5AJCYwescv7g7KbwwL+YC9hlLlhRxjErUw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@hashicorp/react-hashi-stack-menu/-/react-hashi-stack-menu-2.1.2.tgz", + "integrity": "sha512-3K6Y2FNSLq6TsMceX3a1pdmxaVx8lRvxCgjxcKL1P6DpmOk0AU5/eL0CvKP8GzXSYstL36xXxIhWFfiaAaF/yQ==", "requires": { - "@hashicorp/react-inline-svg": "^1.0.2", - "slugify": "1.3.4" + "@hashicorp/react-inline-svg": "^6.0.1", + "@hashicorp/react-link-wrap": "^3.0.3", + "slugify": "1.6.0" }, "dependencies": { - "@hashicorp/react-inline-svg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@hashicorp/react-inline-svg/-/react-inline-svg-1.0.2.tgz", - "integrity": "sha512-AAFnBslSTgnEr++dTbMn3sybAqvn7myIj88ijGigF6u11eSRiV64zqEcyYLQKWTV6dF4AvYoxiYC6GSOgiM0Yw==", - "requires": {} - }, "slugify": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.4.tgz", - "integrity": "sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.0.tgz", + "integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang==" } } }, diff --git a/website/package.json b/website/package.json index ee9fcb45b..c8e9e3324 100644 --- a/website/package.json +++ b/website/package.json @@ -17,7 +17,7 @@ "@hashicorp/react-content": "^8.0.2", "@hashicorp/react-docs-page": "^14.4.2", "@hashicorp/react-enterprise-alert": "^6.0.1", - "@hashicorp/react-hashi-stack-menu": "^2.0.7", + "@hashicorp/react-hashi-stack-menu": "^2.1.2", "@hashicorp/react-head": "^3.1.2", "@hashicorp/react-hero": "^8.0.2", "@hashicorp/react-image": "^4.0.3",