Skip to content

Commit

Permalink
Merge pull request #12138 from jorgemarey/f-ns-meta
Browse files Browse the repository at this point in the history
Add metadata to namespaces
  • Loading branch information
schmichael committed Mar 7, 2022
2 parents bc40222 + 1004050 commit 7c21709
Show file tree
Hide file tree
Showing 16 changed files with 122 additions and 46 deletions.
3 changes: 3 additions & 0 deletions .changelog/12138.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
namespaces: Allow adding custom metadata to namespaces.
```
1 change: 1 addition & 0 deletions api/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type Namespace struct {
Description string
Quota string
Capabilities *NamespaceCapabilities `hcl:"capabilities,block"`
Meta map[string]string
CreateIndex uint64
ModifyIndex uint64
}
Expand Down
3 changes: 0 additions & 3 deletions command/agent/namespace_endpoint_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build ent
// +build ent

package agent

import (
Expand Down
19 changes: 18 additions & 1 deletion command/namespace_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ func (c *NamespaceApplyCommand) AutocompleteFlags() complete.Flags {
}

func (c *NamespaceApplyCommand) AutocompleteArgs() complete.Predictor {
return NamespacePredictor(c.Meta.Client, nil)
return complete.PredictOr(
NamespacePredictor(c.Meta.Client, nil),
complete.PredictFiles("*.hcl"),
complete.PredictFiles("*.json"),
)
}

func (c *NamespaceApplyCommand) Synopsis() string {
Expand Down Expand Up @@ -216,6 +220,7 @@ func parseNamespaceSpecImpl(result *api.Namespace, list *ast.ObjectList) error {
}

delete(m, "capabilities")
delete(m, "meta")

// Decode the rest
if err := mapstructure.WeakDecode(m, result); err != nil {
Expand All @@ -238,5 +243,17 @@ func parseNamespaceSpecImpl(result *api.Namespace, list *ast.ObjectList) error {
}
}

if metaO := list.Filter("meta"); len(metaO.Items) > 0 {
for _, o := range metaO.Elem().Items {
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
if err := mapstructure.WeakDecode(m, &result.Meta); err != nil {
return err
}
}
}

return nil
}
3 changes: 0 additions & 3 deletions command/namespace_apply_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build ent
// +build ent

package command

import (
Expand Down
3 changes: 0 additions & 3 deletions command/namespace_delete_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build ent
// +build ent

package command

import (
Expand Down
3 changes: 0 additions & 3 deletions command/namespace_inspect_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build ent
// +build ent

package command

import (
Expand Down
8 changes: 1 addition & 7 deletions command/namespace_list_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build ent
// +build ent

package command

import (
Expand All @@ -10,10 +7,7 @@ import (
"github.com/mitchellh/cli"
)

func TestNamespaceListCommand_Implements(t *testing.T) {
t.Parallel()
var _ cli.Command = &NamespaceListCommand{}
}
var _ cli.Command = (*NamespaceListCommand)(nil)

func TestNamespaceListCommand_Fails(t *testing.T) {
t.Parallel()
Expand Down
11 changes: 11 additions & 0 deletions command/namespace_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package command

import (
"fmt"
"sort"
"strings"

"github.com/hashicorp/nomad/api"
Expand Down Expand Up @@ -81,6 +82,16 @@ func (c *NamespaceStatusCommand) Run(args []string) int {

c.Ui.Output(formatNamespaceBasics(ns))

if len(ns.Meta) > 0 {
c.Ui.Output(c.Colorize().Color("\n[bold]Metadata[reset]"))
var meta []string
for k := range ns.Meta {
meta = append(meta, fmt.Sprintf("%s|%s", k, ns.Meta[k]))
}
sort.Strings(meta)
c.Ui.Output(formatKV(meta))
}

if ns.Quota != "" {
quotas := client.Quotas()
spec, _, err := quotas.Info(ns.Quota, nil)
Expand Down
10 changes: 10 additions & 0 deletions command/namespace_status_oss_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build !ent
// +build !ent

package command

import "github.com/hashicorp/nomad/api"

func testQuotaSpec() *api.QuotaSpec {
panic("not implemented - enterprise only")
}
7 changes: 4 additions & 3 deletions command/namespace_status_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build ent
// +build ent

package command

import (
Expand Down Expand Up @@ -77,6 +74,10 @@ func TestNamespaceStatusCommand_Good_Quota(t *testing.T) {
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()

if !srv.Enterprise {
t.Skip("Skipping enterprise-only quota test")
}

ui := cli.NewMockUi()
cmd := &NamespaceStatusCommand{Meta: Meta{Ui: ui}}

Expand Down
4 changes: 3 additions & 1 deletion nomad/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -2246,8 +2246,10 @@ func AllocNetworkStatus() *structs.AllocNetworkStatus {
}

func Namespace() *structs.Namespace {
uuid := uuid.Generate()
ns := &structs.Namespace{
Name: fmt.Sprintf("team-%s", uuid.Generate()),
Name: fmt.Sprintf("team-%s", uuid),
Meta: map[string]string{"team": uuid},
Description: "test namespace",
CreateIndex: 100,
ModifyIndex: 200,
Expand Down
21 changes: 21 additions & 0 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4963,6 +4963,9 @@ type Namespace struct {
// Capabilities is the set of capabilities allowed for this namespace
Capabilities *NamespaceCapabilities

// Meta is the set of metadata key/value pairs that attached to the namespace
Meta map[string]string

// Hash is the hash of the namespace which is used to efficiently replicate
// cross-regions.
Hash []byte
Expand Down Expand Up @@ -5016,6 +5019,18 @@ func (n *Namespace) SetHash() []byte {
}
}

// sort keys to ensure hash stability when meta is stored later
var keys []string
for k := range n.Meta {
keys = append(keys, k)
}
sort.Strings(keys)

for _, k := range keys {
_, _ = hash.Write([]byte(k))
_, _ = hash.Write([]byte(n.Meta[k]))
}

// Finalize the hash
hashVal := hash.Sum(nil)

Expand All @@ -5035,6 +5050,12 @@ func (n *Namespace) Copy() *Namespace {
c.DisabledTaskDrivers = helper.CopySliceString(n.Capabilities.DisabledTaskDrivers)
nc.Capabilities = c
}
if n.Meta != nil {
nc.Meta = make(map[string]string, len(n.Meta))
for k, v := range n.Meta {
nc.Meta[k] = v
}
}
copy(nc.Hash, n.Hash)
return nc
}
Expand Down
50 changes: 35 additions & 15 deletions website/content/api-docs/namespaces.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,24 @@ $ curl \
```json
[
{
"CreateIndex": 31,
"Description": "Production API Servers",
"ModifyIndex": 31,
"Name": "api-prod",
"Capabilities": null,
"CreateIndex": 1,
"Description": "Default shared namespace",
"Meta": null,
"ModifyIndex": 1,
"Name": "default",
"Quota": ""
},
{
"CreateIndex": 5,
"Description": "Default shared namespace",
"ModifyIndex": 5,
"Name": "default",
"Capabilities": null,
"CreateIndex": 17,
"Description": "Development Staging Namespace",
"Meta": {
"type": "dev",
"contact": "helpdesk@example.com"
},
"ModifyIndex": 17,
"Name": "staging",
"Quota": ""
}
]
Expand Down Expand Up @@ -88,19 +95,23 @@ The table below shows this endpoint's support for

```shell-session
$ curl \
https://localhost:4646/v1/namespace/api-prod
https://localhost:4646/v1/namespace/staging
```

### Sample Response

```json
{
"CreateIndex": 31,
"Description": "Production API Servers",
"Quota": "",
"Hash": "N8WvePwqkp6J354eLJMKyhvsFdPELAos0VuBfMoVKoU=",
"ModifyIndex": 31,
"Name": "api-prod"
"Capabilities": null,
"CreateIndex": 17,
"Description": "Development Staging Namespace",
"Meta": {
"type": "dev",
"contact": "helpdesk@example.com"
},
"ModifyIndex": 17,
"Name": "staging",
"Quota": ""
}
```

Expand Down Expand Up @@ -128,6 +139,10 @@ The table below shows this endpoint's support for
- `Description` `(string: "")` - Specifies an optional human-readable
description of the namespace.

- `Meta` `(object: null)` - Optional object with string keys and values of
metadata to attach to the namespace. Namespace metadata is not used by Nomad
and is intended for use by operators and third party tools.

- `Quota` `(string: "")` - Specifies an quota to attach to the namespace.

### Sample Payload
Expand All @@ -136,10 +151,15 @@ The table below shows this endpoint's support for
{
"Name": "api-prod",
"Description": "Production API Servers",
"Meta": {
"contact": "platform-eng@example.com"
},
"Quota": "prod-quota"
}
```

Note that the `Quota` key is Enterprise-only.

### Sample Request

```shell-session
Expand Down
13 changes: 9 additions & 4 deletions website/content/docs/commands/namespace/apply.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ when introduced in Nomad 0.7.
nomad namespace apply [options] <input>
```

Apply is used to create or update a namespace. The specification file
Apply is used to create or update a namespace. The HCL specification file
will be read from stdin by specifying "-", otherwise a path to the file is
expected.

Expand All @@ -37,7 +37,7 @@ If ACLs are enabled, this command requires a management ACL token.

- `-description` : An optional human readable description for the namespace.

- `json` : Parse the input as a JSON namespace specification.
- `-json` : Parse the input as a JSON namespace specification.

## Examples

Expand All @@ -56,13 +56,18 @@ $ nomad namespace apply -quota= api-prod

Create a namespace from a file:
```shell-session
$ cat namespace.json
$ cat namespace.hcl
name = "dev"
description = "Namespace for developers"
capabilities {
enabled_task_drivers = ["docker", "exec"]
disabled_task_drivers = ["raw_exec"]
}
$ nomad namespace apply namespace.json
meta {
owner = "John Doe"
contact_mail = "john@mycompany.com
}
$ nomad namespace apply namespace.hcl
```
9 changes: 6 additions & 3 deletions website/content/docs/commands/namespace/status.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ View the status of a namespace:

```shell-session
$ nomad namespace status default
Name = default
Description = Default shared namespace
Quota = shared-default-quota
Name = api-prod
Description = Prod API servers
Quota = prod
EnabledDrivers = docker,exec
DisabledDrivers = raw_exec
Metadata
contact = platform-eng@example.com
Quota Limits
Region CPU Usage Memory Usage
Expand Down

0 comments on commit 7c21709

Please sign in to comment.