From 1c536efd053dab90d6436e9b7e47a28672b1a919 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Mon, 15 Aug 2022 14:57:10 -0700 Subject: [PATCH 1/3] AGPL Entitlements API Signed-off-by: Spike Curtis --- coderd/coderd.go | 4 ++++ coderd/coderd_test.go | 1 + coderd/features.go | 23 ++++++++++++++++++++ coderd/features_internal_test.go | 36 ++++++++++++++++++++++++++++++++ codersdk/features.go | 29 +++++++++++++++++++++++++ 5 files changed, 93 insertions(+) create mode 100644 coderd/features.go create mode 100644 coderd/features_internal_test.go create mode 100644 codersdk/features.go diff --git a/coderd/coderd.go b/coderd/coderd.go index 9c5633cb03033..cf29aa986fc22 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -391,6 +391,10 @@ func New(options *Options) *API { r.Get("/resources", api.workspaceBuildResources) r.Get("/state", api.workspaceBuildState) }) + r.Route("/entitlements", func(r chi.Router) { + r.Use(apiKeyMiddleware) + r.Get("/", entitlements) + }) }) r.NotFound(compressHandler(http.HandlerFunc(api.siteHandler.ServeHTTP)).ServeHTTP) diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index 89fa81b275db7..c042855687cd3 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -242,6 +242,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) { "POST:/api/v2/users/login": {NoAuthorize: true}, "GET:/api/v2/users/authmethods": {NoAuthorize: true}, "POST:/api/v2/csp/reports": {NoAuthorize: true}, + "GET:/api/v2/entitlements": {NoAuthorize: true}, "GET:/%40{user}/{workspacename}/apps/{application}/*": { AssertAction: rbac.ActionRead, diff --git a/coderd/features.go b/coderd/features.go new file mode 100644 index 0000000000000..33491a9583aa7 --- /dev/null +++ b/coderd/features.go @@ -0,0 +1,23 @@ +package coderd + +import ( + "net/http" + + "github.com/coder/coder/coderd/httpapi" + "github.com/coder/coder/codersdk" +) + +func entitlements(rw http.ResponseWriter, _ *http.Request) { + features := make(map[string]codersdk.Feature) + for _, f := range codersdk.AllFeatures { + features[f] = codersdk.Feature{ + Entitlement: codersdk.EntitlementNotEntitled, + Enabled: false, + } + } + httpapi.Write(rw, http.StatusOK, codersdk.Entitlements{ + Features: features, + Warnings: nil, + HasLicense: false, + }) +} diff --git a/coderd/features_internal_test.go b/coderd/features_internal_test.go new file mode 100644 index 0000000000000..79969516d398f --- /dev/null +++ b/coderd/features_internal_test.go @@ -0,0 +1,36 @@ +package coderd + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/coder/coder/codersdk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEntitlements(t *testing.T) { + t.Parallel() + t.Run("GET", func(t *testing.T) { + t.Parallel() + r := httptest.NewRequest("GET", "https://example.com/api/v2/entitlements", nil) + rw := httptest.NewRecorder() + entitlements(rw, r) + resp := rw.Result() + assert.Equal(t, http.StatusOK, resp.StatusCode) + dec := json.NewDecoder(resp.Body) + var result codersdk.Entitlements + err := dec.Decode(&result) + require.NoError(t, err) + assert.False(t, result.HasLicense) + assert.Empty(t, result.Warnings) + for _, f := range codersdk.AllFeatures { + require.Contains(t, result.Features, f) + fe := result.Features[f] + assert.False(t, fe.Enabled) + assert.Equal(t, codersdk.EntitlementNotEntitled, fe.Entitlement) + } + }) +} diff --git a/codersdk/features.go b/codersdk/features.go new file mode 100644 index 0000000000000..2d2424d00479a --- /dev/null +++ b/codersdk/features.go @@ -0,0 +1,29 @@ +package codersdk + +type Entitlement string + +const ( + EntitlementEntitled Entitlement = "entitled" + EntitlementGracePeriod Entitlement = "grace_period" + EntitlementNotEntitled Entitlement = "not_entitled" +) + +const ( + FeatureUserLimit = "user_limit" + FeatureAuditLog = "audit_log" +) + +var AllFeatures = []string{FeatureUserLimit, FeatureAuditLog} + +type Feature struct { + Entitlement Entitlement `json:"entitlement"` + Enabled bool `json:"enabled"` + Limit *int64 `json:"limit"` + Actual *int64 `json:"actual"` +} + +type Entitlements struct { + Features map[string]Feature `json:"features"` + Warnings []string `json:"warnings"` + HasLicense bool `json:"has_license"` +} From 978590761f8fff2b695f21e01bea8a4f2a518659 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Mon, 15 Aug 2022 16:49:31 -0700 Subject: [PATCH 2/3] Generate typesGenerated.ts Signed-off-by: Spike Curtis --- site/src/api/typesGenerated.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index def9cd07e894a..256a41751ba96 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -129,6 +129,21 @@ export interface CreateWorkspaceRequest { readonly parameter_values?: CreateParameterRequest[] } +// From codersdk/features.go +export interface Entitlements { + readonly features: Record + readonly warnings: string[] + readonly has_license: boolean +} + +// From codersdk/features.go +export interface Feature { + readonly entitlement: Entitlement + readonly enabled: boolean + readonly limit?: number + readonly actual?: number +} + // From codersdk/users.go export interface GenerateAPIKeyResponse { readonly key: string @@ -530,6 +545,9 @@ export interface WorkspaceResourceMetadata { // From codersdk/workspacebuilds.go export type BuildReason = "autostart" | "autostop" | "initiator" +// From codersdk/features.go +export type Entitlement = "entitled" | "grace_period" | "not_entitled" + // From codersdk/provisionerdaemons.go export type LogLevel = "debug" | "error" | "info" | "trace" | "warn" From 1f07ceb8b1b5e11adf29d1e5731c5a2cba1fd404 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Tue, 16 Aug 2022 15:18:55 -0700 Subject: [PATCH 3/3] AllFeatures -> FeatureNames Signed-off-by: Spike Curtis --- coderd/features.go | 2 +- coderd/features_internal_test.go | 2 +- codersdk/features.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/features.go b/coderd/features.go index 33491a9583aa7..e8fbc67612e4f 100644 --- a/coderd/features.go +++ b/coderd/features.go @@ -9,7 +9,7 @@ import ( func entitlements(rw http.ResponseWriter, _ *http.Request) { features := make(map[string]codersdk.Feature) - for _, f := range codersdk.AllFeatures { + for _, f := range codersdk.FeatureNames { features[f] = codersdk.Feature{ Entitlement: codersdk.EntitlementNotEntitled, Enabled: false, diff --git a/coderd/features_internal_test.go b/coderd/features_internal_test.go index 79969516d398f..b86eb30dc8d8c 100644 --- a/coderd/features_internal_test.go +++ b/coderd/features_internal_test.go @@ -26,7 +26,7 @@ func TestEntitlements(t *testing.T) { require.NoError(t, err) assert.False(t, result.HasLicense) assert.Empty(t, result.Warnings) - for _, f := range codersdk.AllFeatures { + for _, f := range codersdk.FeatureNames { require.Contains(t, result.Features, f) fe := result.Features[f] assert.False(t, fe.Enabled) diff --git a/codersdk/features.go b/codersdk/features.go index 2d2424d00479a..124a351dc69ed 100644 --- a/codersdk/features.go +++ b/codersdk/features.go @@ -13,7 +13,7 @@ const ( FeatureAuditLog = "audit_log" ) -var AllFeatures = []string{FeatureUserLimit, FeatureAuditLog} +var FeatureNames = []string{FeatureUserLimit, FeatureAuditLog} type Feature struct { Entitlement Entitlement `json:"entitlement"`