Skip to content

Commit cc346af

Browse files
authored
Use licenses to populate the Entitlements API (#3715)
* Use licenses for entitlements API Signed-off-by: Spike Curtis <spike@coder.com> * Tests for entitlements API Signed-off-by: Spike Curtis <spike@coder.com> * Add commentary about FeatureService Signed-off-by: Spike Curtis <spike@coder.com> * Lint Signed-off-by: Spike Curtis <spike@coder.com> * Quiet down the logs Signed-off-by: Spike Curtis <spike@coder.com> * Tell revive it's ok Signed-off-by: Spike Curtis <spike@coder.com> Signed-off-by: Spike Curtis <spike@coder.com>
1 parent 05f932b commit cc346af

File tree

14 files changed

+773
-10
lines changed

14 files changed

+773
-10
lines changed

cli/features.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
67
"strings"
@@ -53,12 +54,14 @@ func featuresList() *cobra.Command {
5354
return xerrors.Errorf("render table: %w", err)
5455
}
5556
case "json":
56-
outBytes, err := json.Marshal(entitlements)
57+
buf := new(bytes.Buffer)
58+
enc := json.NewEncoder(buf)
59+
enc.SetIndent("", " ")
60+
err = enc.Encode(entitlements)
5761
if err != nil {
5862
return xerrors.Errorf("marshal features to JSON: %w", err)
5963
}
60-
61-
out = string(outBytes)
64+
out = buf.String()
6265
default:
6366
return xerrors.Errorf(`unknown output format %q, only "table" and "json" are supported`, outputFormat)
6467
}

coderd/coderd.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type Options struct {
6666
TracerProvider *sdktrace.TracerProvider
6767
AutoImportTemplates []AutoImportTemplate
6868
LicenseHandler http.Handler
69+
FeaturesService FeaturesService
6970
}
7071

7172
// New constructs a Coder API handler.
@@ -95,6 +96,9 @@ func New(options *Options) *API {
9596
if options.LicenseHandler == nil {
9697
options.LicenseHandler = licenses()
9798
}
99+
if options.FeaturesService == nil {
100+
options.FeaturesService = featuresService{}
101+
}
98102

99103
siteCacheDir := options.CacheDir
100104
if siteCacheDir != "" {
@@ -400,7 +404,7 @@ func New(options *Options) *API {
400404
})
401405
r.Route("/entitlements", func(r chi.Router) {
402406
r.Use(apiKeyMiddleware)
403-
r.Get("/", entitlements)
407+
r.Get("/", api.FeaturesService.EntitlementsAPI)
404408
})
405409
r.Route("/licenses", func(r chi.Router) {
406410
r.Use(apiKeyMiddleware)

coderd/database/databasefake/databasefake.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,19 @@ func (q *fakeQuerier) GetUserCount(_ context.Context) (int64, error) {
246246
return int64(len(q.users)), nil
247247
}
248248

249+
func (q *fakeQuerier) GetActiveUserCount(_ context.Context) (int64, error) {
250+
q.mutex.RLock()
251+
defer q.mutex.RUnlock()
252+
253+
active := int64(0)
254+
for _, u := range q.users {
255+
if u.Status == database.UserStatusActive {
256+
active++
257+
}
258+
}
259+
return active, nil
260+
}
261+
249262
func (q *fakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams) ([]database.User, error) {
250263
q.mutex.RLock()
251264
defer q.mutex.RUnlock()
@@ -2322,6 +2335,21 @@ func (q *fakeQuerier) GetLicenses(_ context.Context) ([]database.License, error)
23222335
return results, nil
23232336
}
23242337

2338+
func (q *fakeQuerier) GetUnexpiredLicenses(_ context.Context) ([]database.License, error) {
2339+
q.mutex.RLock()
2340+
defer q.mutex.RUnlock()
2341+
2342+
now := time.Now()
2343+
var results []database.License
2344+
for _, l := range q.licenses {
2345+
if l.Exp.After(now) {
2346+
results = append(results, l)
2347+
}
2348+
}
2349+
sort.Slice(results, func(i, j int) bool { return results[i].ID < results[j].ID })
2350+
return results, nil
2351+
}
2352+
23252353
func (q *fakeQuerier) DeleteLicense(_ context.Context, id int32) (int32, error) {
23262354
q.mutex.Lock()
23272355
defer q.mutex.Unlock()

coderd/database/querier.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/licenses.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ SELECT *
1313
FROM licenses
1414
ORDER BY (id);
1515

16+
-- name: GetUnexpiredLicenses :many
17+
SELECT *
18+
FROM licenses
19+
WHERE exp > NOW()
20+
ORDER BY (id);
21+
1622
-- name: DeleteLicense :one
1723
DELETE
1824
FROM licenses

coderd/database/queries/users.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ SELECT
2828
FROM
2929
users;
3030

31+
-- name: GetActiveUserCount :one
32+
SELECT
33+
COUNT(*)
34+
FROM
35+
users
36+
WHERE
37+
status = 'active'::public.user_status;
38+
3139
-- name: InsertUser :one
3240
INSERT INTO
3341
users (

coderd/features.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,20 @@ import (
77
"github.com/coder/coder/codersdk"
88
)
99

10-
func entitlements(rw http.ResponseWriter, _ *http.Request) {
10+
// FeaturesService is the interface for interacting with enterprise features.
11+
type FeaturesService interface {
12+
EntitlementsAPI(w http.ResponseWriter, r *http.Request)
13+
14+
// TODO
15+
// Get returns the implementations for feature interfaces. Parameter `s `must be a pointer to a
16+
// struct type containing feature interfaces as fields. The FeatureService sets all fields to
17+
// the correct implementations depending on whether the features are turned on.
18+
// Get(s any) error
19+
}
20+
21+
type featuresService struct{}
22+
23+
func (featuresService) EntitlementsAPI(rw http.ResponseWriter, _ *http.Request) {
1124
features := make(map[string]codersdk.Feature)
1225
for _, f := range codersdk.FeatureNames {
1326
features[f] = codersdk.Feature{

coderd/features_internal_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestEntitlements(t *testing.T) {
1818
t.Parallel()
1919
r := httptest.NewRequest("GET", "https://example.com/api/v2/entitlements", nil)
2020
rw := httptest.NewRecorder()
21-
entitlements(rw, r)
21+
featuresService{}.EntitlementsAPI(rw, r)
2222
resp := rw.Result()
2323
defer resp.Body.Close()
2424
assert.Equal(t, http.StatusOK, resp.StatusCode)

codersdk/features.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ var FeatureNames = []string{FeatureUserLimit, FeatureAuditLog}
2424
type Feature struct {
2525
Entitlement Entitlement `json:"entitlement"`
2626
Enabled bool `json:"enabled"`
27-
Limit *int64 `json:"limit"`
28-
Actual *int64 `json:"actual"`
27+
Limit *int64 `json:"limit,omitempty"`
28+
Actual *int64 `json:"actual,omitempty"`
2929
}
3030

3131
type Entitlements struct {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy