Repository: nats-io/nsc
Pull request #473 (2022-03-29 00:16:04)

Print tiers and other JS limits and removed support for ha resources
Signed-off-by: Matthias Hanel <mh@synadia.com>

```
+--------------------------------------------------------------------------------------+
|                                   Account Details                                    |
+---------------------------+----------------------------------------------------------+
| Name                      | A                                                        |
| Account ID                | ACQX22ON4KVPWQJ6HXZSVP4R2MB3LP5QWJLNBTQIGECPNSNJDBQERPT2 |
| Issuer ID                 | OBX55INRMCJXRYO423CMSCW3RREA55GULZGOUM3DTSHUZXSQ6Z6PPTPW |
| Issued                    | 2022-03-29 00:12:32 UTC                                  |
| Expires                   |                                                          |
+---------------------------+----------------------------------------------------------+
| Max Connections           | Unlimited                                                |
| Max Leaf Node Connections | Unlimited                                                |
| Max Data                  | Unlimited                                                |
| Max Exports               | Unlimited                                                |
| Max Imports               | Unlimited                                                |
| Max Msg Payload           | Unlimited                                                |
| Max Subscriptions         | Unlimited                                                |
| Exports Allows Wildcards  | True                                                     |
| Response Permissions      | Not Set                                                  |
+---------------------------+----------------------------------------------------------+
| Jetstream Tier            | R1                                                       |
| Max Disk Storage          | 1.0 kB                                                   |
| Max Mem Storage           | Disabled                                                 |
| Max Streams               | 10                                                       |
| Max Consumer              | 0                                                        |
| Max Bytes                 | required (Stream setting)                                |
| Max Memory Stream         | 0 B (0 bytes)                                            |
| Max Disk Stream           | 512 B (512 bytes)                                        |
+---------------------------+----------------------------------------------------------+
| Jetstream Tier            | R3                                                       |
| Max Disk Storage          | Disabled                                                 |
| Max Mem Storage           | 1.0 kB                                                   |
| Max Streams               | 10                                                       |
| Max Consumer              | 0                                                        |
| Max Bytes                 | optional (Stream setting)                                |
| Max Memory Stream         | 512 B (512 bytes)                                        |
| Max Disk Stream           | 0 B (0 bytes)                                            |
+---------------------------+----------------------------------------------------------+
| Imports                   | None                                                     |
+---------------------------+----------------------------------------------------------+
```


Patch:
diff --git a/cmd/describer.go b/cmd/describer.go
index e0106ac..baf52c6 100644
--- a/cmd/describer.go
+++ b/cmd/describer.go
@@ -100,11 +100,7 @@ func (a *AccountDescriber) Describe() string {
 
 	AddPermissions(table, a.DefaultPermissions)
 
-	table.AddSeparator()
-	if a.Limits.DiskStorage == 0 && a.Limits.MemoryStorage == 0 {
-		table.AddRow("Jetstream", "Disabled")
-	} else {
-		table.AddRow("Jetstream", "Enabled")
+	printJsLimit := func(lim jwt.JetStreamLimits) {
 		switch {
 		case lim.DiskStorage > 0:
 			table.AddRow("Max Disk Storage", humanize.Bytes(uint64(lim.DiskStorage)))
@@ -123,7 +119,32 @@ func (a *AccountDescriber) Describe() string {
 		}
 		addLimitRow(table, "Max Streams", lim.Streams, false)
 		addLimitRow(table, "Max Consumer", lim.Consumer, false)
-		addLimitRow(table, "Max High Availability Resources", lim.HaResources, false)
+		maxBytes := "optional (Stream setting)"
+		if lim.MaxBytesRequired {
+			maxBytes = "required (Stream setting)"
+		}
+		table.AddRow("Max Bytes", maxBytes)
+
+		addLimitRow(table, "Max Memory Stream", lim.MemoryMaxStreamBytes, true)
+		addLimitRow(table, "Max Disk Stream", lim.DiskMaxStreamBytes, true)
+	}
+
+	table.AddSeparator()
+	if !a.Limits.IsJSEnabled() {
+		table.AddRow("Jetstream", "Disabled")
+	} else if len(a.Limits.JetStreamTieredLimits) == 0 {
+		table.AddRow("Jetstream", "Enabled")
+		printJsLimit(a.Limits.JetStreamLimits)
+	} else {
+		remaining := len(a.Limits.JetStreamTieredLimits)
+		for tier, lim := range a.Limits.JetStreamTieredLimits {
+			table.AddRow("Jetstream Tier", tier)
+			printJsLimit(lim)
+			remaining--
+			if remaining != 0 {
+				table.AddSeparator()
+			}
+		}
 	}
 
 	table.AddSeparator()
diff --git a/cmd/editaccount.go b/cmd/editaccount.go
index defe686..0d3247f 100644
--- a/cmd/editaccount.go
+++ b/cmd/editaccount.go
@@ -220,10 +220,6 @@ func (p *EditAccountParams) Load(ctx ActionCtx) error {
 		p.consumer = NumberParams(p.claim.Limits.Consumer)
 	}
 
-	if !ctx.CurrentCmd().Flags().Changed("js-ha-resources") {
-		p.haResources = NumberParams(p.claim.Limits.HaResources)
-	}
-
 	if !ctx.CurrentCmd().Flags().Changed("description") {
 		p.description = p.claim.Description
 	}
@@ -427,11 +423,6 @@ func (p *EditAccountParams) Run(ctx ActionCtx) (store.Status, error) {
 		r.AddOK("changed max consumer to %d", p.claim.Limits.Consumer)
 	}
 
-	p.claim.Limits.HaResources = p.haResources.Int64()
-	if flags.Changed("js-ha-resources") {
-		r.AddOK("changed high availability resources to %d", p.claim.Limits.HaResources)
-	}
-
 	p.claim.Description = p.description
 	if flags.Changed("description") {
 		r.AddOK(`changed description to %q`, p.claim.Description)
diff --git a/go.mod b/go.mod
index 7e805a1..0c0528c 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/nats-io/cliprompts/v2 v2.0.0-20191226174129-372d79b36768
 	github.com/nats-io/jwt v1.2.2
-	github.com/nats-io/jwt/v2 v2.2.1-0.20220208174425-4b4a1c85619d
+	github.com/nats-io/jwt/v2 v2.2.1-0.20220328222144-5efd4536dd5c
 	github.com/nats-io/nats-server/v2 v2.6.4
 	github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483
 	github.com/nats-io/nkeys v0.3.0
diff --git a/go.sum b/go.sum
index 32f3aa1..c5291d9 100644
--- a/go.sum
+++ b/go.sum
@@ -72,8 +72,8 @@ github.com/nats-io/cliprompts/v2 v2.0.0-20191226174129-372d79b36768/go.mod h1:ow
 github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU=
 github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
 github.com/nats-io/jwt/v2 v2.1.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
-github.com/nats-io/jwt/v2 v2.2.1-0.20220208174425-4b4a1c85619d h1:gZ/4UqeamciBaly0A+Doxg+xr5z+pJXLQ0euGK2nkDI=
-github.com/nats-io/jwt/v2 v2.2.1-0.20220208174425-4b4a1c85619d/go.mod h1:3dd7YxgBieCh8qT6R3lOGvKezVaDVtZ/YB6OTcLxu+Y=
+github.com/nats-io/jwt/v2 v2.2.1-0.20220328222144-5efd4536dd5c h1:UvynQFR0nFftljtWBBVXg0CO2PFZUgr8ph/3nbzMidc=
+github.com/nats-io/jwt/v2 v2.2.1-0.20220328222144-5efd4536dd5c/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
 github.com/nats-io/nats-server/v2 v2.6.4 h1:WjR1ylV/5Urth88K8U78wEEnWFYEJ9DNM0Q5DTlTx0g=
 github.com/nats-io/nats-server/v2 v2.6.4/go.mod h1:LlMieumxNUnCloOTVFv7Wog0YnasScxARUMXVXv9/+M=
 github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483 h1:GMx3ZOcMEVM5qnUItQ4eJyQ6ycwmIEB/VC/UxvdevE0=
diff --git a/vendor/github.com/nats-io/jwt/v2/account_claims.go b/vendor/github.com/nats-io/jwt/v2/account_claims.go
index fc4b825..446cf8e 100644
--- a/vendor/github.com/nats-io/jwt/v2/account_claims.go
+++ b/vendor/github.com/nats-io/jwt/v2/account_claims.go
@@ -51,47 +51,77 @@ func (n *NatsLimits) IsUnlimited() bool {
 }
 
 type JetStreamLimits struct {
-	HaResources      int64 `json:"ha_resources"`                 // Max number of bytes high availability resources (streams & consumer). (0 means disabled). no omitempty on purpose
-	MemoryStorage    int64 `json:"mem_storage,omitempty"`        // Max number of bytes stored in memory across all streams. (0 means disabled)
-	DiskStorage      int64 `json:"disk_storage,omitempty"`       // Max number of bytes stored on disk across all streams. (0 means disabled)
-	Streams          int64 `json:"streams,omitempty"`            // Max number of streams
-	Consumer         int64 `json:"consumer,omitempty"`           // Max number of consumers
-	MaxBytesRequired bool  `json:"max_bytes_required,omitempty"` // Max bytes required by all Streams
+	MemoryStorage        int64 `json:"mem_storage,omitempty"`           // Max number of bytes stored in memory across all streams. (0 means disabled)
+	DiskStorage          int64 `json:"disk_storage,omitempty"`          // Max number of bytes stored on disk across all streams. (0 means disabled)
+	Streams              int64 `json:"streams,omitempty"`               // Max number of streams
+	Consumer             int64 `json:"consumer,omitempty"`              // Max number of consumers
+	MemoryMaxStreamBytes int64 `json:"mem_max_stream_bytes,omitempty"`  // Max bytes a memory backed stream can have. (0 means disabled/unlimited)
+	DiskMaxStreamBytes   int64 `json:"disk_max_stream_bytes,omitempty"` // Max bytes a disk backed stream can have. (0 means disabled/unlimited)
+	MaxBytesRequired     bool  `json:"max_bytes_required,omitempty"`    // Max bytes required by all Streams
 }
 
 // IsUnlimited returns true if all limits are unlimited
 func (j *JetStreamLimits) IsUnlimited() bool {
-	return *j == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, false}
-}
-
-// IsJSEnabled returns true if JS is enabled by either disk or memory storage being enabled
-func (j *JetStreamLimits) IsJSEnabled() bool {
-	if j == nil {
-		return false
+	lim := *j
+	// workaround in case NoLimit was used instead of 0
+	if lim.MemoryMaxStreamBytes < 0 {
+		lim.MemoryMaxStreamBytes = 0
+	}
+	if lim.DiskMaxStreamBytes < 0 {
+		lim.DiskMaxStreamBytes = 0
 	}
-	return j.MemoryStorage != 0 || j.DiskStorage != 0
+	return lim == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit, 0, 0, false}
 }
 
+type JetStreamTieredLimits map[string]JetStreamLimits
+
 // OperatorLimits are used to limit access by an account
 type OperatorLimits struct {
 	NatsLimits
 	AccountLimits
 	JetStreamLimits
+	JetStreamTieredLimits `json:"tiered_limits,omitempty"`
 }
 
-// IsEmpty returns true if all of the limits are 0/false.
+// IsJSEnabled returns if this account claim has JS enabled either through a tier or the non tiered limits.
+func (o *OperatorLimits) IsJSEnabled() bool {
+	if len(o.JetStreamTieredLimits) > 0 {
+		for _, l := range o.JetStreamTieredLimits {
+			if l.MemoryStorage != 0 || l.DiskStorage != 0 {
+				return true
+			}
+		}
+		return false
+	}
+	l := o.JetStreamLimits
+	return l.MemoryStorage != 0 || l.DiskStorage != 0
+}
+
+// IsEmpty returns true if all limits are 0/false/empty.
 func (o *OperatorLimits) IsEmpty() bool {
-	return *o == OperatorLimits{}
+	return o.NatsLimits == NatsLimits{} &&
+		o.AccountLimits == AccountLimits{} &&
+		o.JetStreamLimits == JetStreamLimits{} &&
+		len(o.JetStreamTieredLimits) == 0
 }
 
 // IsUnlimited returns true if all limits are unlimited
 func (o *OperatorLimits) IsUnlimited() bool {
-	return o.AccountLimits.IsUnlimited() && o.NatsLimits.IsUnlimited() && o.JetStreamLimits.IsUnlimited()
+	return o.AccountLimits.IsUnlimited() && o.NatsLimits.IsUnlimited() &&
+		o.JetStreamLimits.IsUnlimited() && len(o.JetStreamTieredLimits) == 0
 }
 
 // Validate checks that the operator limits contain valid values
-func (o *OperatorLimits) Validate(_ *ValidationResults) {
+func (o *OperatorLimits) Validate(vr *ValidationResults) {
 	// negative values mean unlimited, so all numbers are valid
+	if len(o.JetStreamTieredLimits) > 0 {
+		if (o.JetStreamLimits != JetStreamLimits{}) {
+			vr.AddError("JetStream Limits and tiered JetStream Limits are mutually exclusive")
+		}
+		if _, ok := o.JetStreamTieredLimits[""]; ok {
+			vr.AddError(`Tiered JetStream Limits can not contain a blank "" tier name`)
+		}
+	}
 }
 
 // Mapping for publishes
@@ -198,7 +228,9 @@ func NewAccountClaims(subject string) *AccountClaims {
 	c.Limits = OperatorLimits{
 		NatsLimits{NoLimit, NoLimit, NoLimit},
 		AccountLimits{NoLimit, NoLimit, true, NoLimit, NoLimit},
-		JetStreamLimits{0, 0, 0, 0, 0, false}}
+		JetStreamLimits{0, 0, 0, 0, 0, 0, false},
+		JetStreamTieredLimits{},
+	}
 	c.Subject = subject
 	c.Mappings = Mapping{}
 	return c
diff --git a/vendor/github.com/nats-io/jwt/v2/decoder_account.go b/vendor/github.com/nats-io/jwt/v2/decoder_account.go
index f1f4e34..025aa0e 100644
--- a/vendor/github.com/nats-io/jwt/v2/decoder_account.go
+++ b/vendor/github.com/nats-io/jwt/v2/decoder_account.go
@@ -41,13 +41,12 @@ func loadAccount(data []byte, version int) (*AccountClaims, error) {
 		return v1a.Migrate()
 	case 2:
 		var v2a AccountClaims
-		v2a.Limits.HaResources = NoLimit
 		v2a.SigningKeys = make(SigningKeys)
 		if err := json.Unmarshal(data, &v2a); err != nil {
 			return nil, err
 		}
-		if !v2a.Limits.IsJSEnabled() {
-			v2a.Limits.HaResources = 0
+		if len(v2a.Limits.JetStreamTieredLimits) > 0 {
+			v2a.Limits.JetStreamLimits = JetStreamLimits{}
 		}
 		return &v2a, nil
 	default:
@@ -77,7 +76,7 @@ func (oa v1AccountClaims) migrateV1() (*AccountClaims, error) {
 	a.Account.Exports = oa.v1NatsAccount.Exports
 	a.Account.Limits.AccountLimits = oa.v1NatsAccount.Limits.AccountLimits
 	a.Account.Limits.NatsLimits = oa.v1NatsAccount.Limits.NatsLimits
-	a.Account.Limits.JetStreamLimits = JetStreamLimits{0, 0, 0, 0, 0, false}
+	a.Account.Limits.JetStreamLimits = JetStreamLimits{}
 	a.Account.SigningKeys = make(SigningKeys)
 	for _, v := range oa.SigningKeys {
 		a.Account.SigningKeys.Add(v)
diff --git a/vendor/github.com/nats-io/jwt/v2/go.mod b/vendor/github.com/nats-io/jwt/v2/go.mod
index 7987bc0..56a8ddb 100644
--- a/vendor/github.com/nats-io/jwt/v2/go.mod
+++ b/vendor/github.com/nats-io/jwt/v2/go.mod
@@ -2,7 +2,4 @@ module github.com/nats-io/jwt/v2
 
 go 1.16
 
-require (
-	github.com/nats-io/jwt v1.2.2
-	github.com/nats-io/nkeys v0.3.0
-)
+require github.com/nats-io/nkeys v0.3.0
diff --git a/vendor/github.com/nats-io/jwt/v2/go.sum b/vendor/github.com/nats-io/jwt/v2/go.sum
index 12924ac..e2eb252 100644
--- a/vendor/github.com/nats-io/jwt/v2/go.sum
+++ b/vendor/github.com/nats-io/jwt/v2/go.sum
@@ -1,18 +1,9 @@
-github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU=
-github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
-github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
 github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
 github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as=
 golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/vendor/modules.txt b/vendor/modules.txt
index df6370e..f6d45cd 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -55,7 +55,7 @@ github.com/nats-io/cliprompts/v2
 # github.com/nats-io/jwt v1.2.2
 ## explicit
 github.com/nats-io/jwt
-# github.com/nats-io/jwt/v2 v2.2.1-0.20220208174425-4b4a1c85619d
+# github.com/nats-io/jwt/v2 v2.2.1-0.20220328222144-5efd4536dd5c
 ## explicit
 github.com/nats-io/jwt/v2
 # github.com/nats-io/nats-server/v2 v2.6.4


Test patch:
diff --git a/cmd/describeaccount_test.go b/cmd/describeaccount_test.go
index 7671f72..e3ba734 100644
--- a/cmd/describeaccount_test.go
+++ b/cmd/describeaccount_test.go
@@ -191,3 +191,29 @@ func TestDescribeAccount_JsonPath(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, fmt.Sprintf("\"%s\"\n", ac.Subject), out)
 }
+
+func TestDescribeAccount_JSTiers(t *testing.T) {
+	ts := NewTestStore(t, "O")
+	defer ts.Done(t)
+
+	ts.AddAccount(t, "A")
+	ts.AddExport(t, "A", jwt.Service, "q", true)
+	ac, err := ts.Store.ReadAccountClaim("A")
+	require.NoError(t, err)
+	ac.Limits.JetStreamTieredLimits = jwt.JetStreamTieredLimits{}
+	ac.Limits.JetStreamTieredLimits["R1"] = jwt.JetStreamLimits{
+		DiskStorage: 1024, Streams: 10, MaxBytesRequired: true, DiskMaxStreamBytes: 512}
+	ac.Limits.JetStreamTieredLimits["R3"] = jwt.JetStreamLimits{
+		MemoryStorage: 1024, Streams: 10, MaxBytesRequired: false, MemoryMaxStreamBytes: 512}
+	token, err := ac.Encode(ts.OperatorKey)
+	require.NoError(t, err)
+	_, err = ts.Store.StoreClaim([]byte(token))
+	require.NoError(t, err)
+
+	out, _, err := ExecuteInteractiveCmd(createDescribeAccountCmd(), []interface{}{0})
+	require.NoError(t, err)
+	require.Contains(t, out, " | R1")
+	require.Contains(t, out, " | R3")
+	require.Contains(t, out, " | required")
+	require.Contains(t, out, " | optional")
+}
diff --git a/cmd/editaccount_test.go b/cmd/editaccount_test.go
index 3bb72ce..7eefa04 100644
--- a/cmd/editaccount_test.go
+++ b/cmd/editaccount_test.go
@@ -132,7 +132,6 @@ func Test_EditAccountLimits(t *testing.T) {
 	require.Equal(t, int64(6), ac.Limits.Consumer)
 	require.Equal(t, int64(7), ac.Limits.DiskStorage)
 	require.Equal(t, int64(8), ac.Limits.MemoryStorage)
-	require.Equal(t, int64(90), ac.Limits.HaResources)
 }
 
 func Test_EditAccountSigningKeys(t *testing.T) {