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..e8fbc67612e4f --- /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.FeatureNames { + 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..b86eb30dc8d8c --- /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.FeatureNames { + 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..124a351dc69ed --- /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 FeatureNames = []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"` +} 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"