Merge branch 'main' into irenarindos-env-file-controllerconfig

This commit is contained in:
Irena Rindos authored and GitHub committed 2021-12-08 04:58:46 -08:00
commit 1f43ec174f
12 files changed
+505 -173

No files matched your search

+2
View File
@@ -8,6 +8,8 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
* config: The `description` field for controllers now supports being set
from environment variables or a file on disk
([PR](https://github.com/hashicorp/boundary/pull/1766))
* 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
+53 -2
View File
@@ -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
@@ -344,12 +346,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)
}
@@ -381,6 +431,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)
+286
View File
@@ -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()
@@ -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
}
@@ -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))
}
+19 -19
View File
@@ -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)
}
}
@@ -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{}{
@@ -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
@@ -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
@@ -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
+31 -65
View File
@@ -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=="
}
}
},
+1 -1
View File
@@ -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",