Skip to content

merge main into groups #4439

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a6bb3b2
docs: add quotas (#4366)
ammario Oct 4, 2022
f295200
fix: Ensure WebSockets routinely transfer data (#4367)
kylecarbs Oct 4, 2022
06d7e36
fix: Ignore hidden folders when archiving (#4370)
kylecarbs Oct 4, 2022
5870176
feat: Also log out of apps if they are hosted on the same domain (#4334)
Emyrk Oct 4, 2022
8940ea1
fix: Always set `DisconnectedAt` if the agent isn't connected (#4328)
kylecarbs Oct 5, 2022
504cd46
fix: Check for a response body when dialing the Tailnet WebSocket (#4…
kylecarbs Oct 5, 2022
3759bb2
docs: fixed a typo (#4374)
matifali Oct 5, 2022
8d14076
fix: move quotas above inputs (#4376)
f0ssel Oct 5, 2022
b65c555
fix: warn user if not entitled feature is enabled (#4377)
f0ssel Oct 5, 2022
4f3958c
docs: link all enterprise features (#4368)
ammario Oct 5, 2022
2a66395
feat: use app wildcards for apps if configured (#4263)
deansheather Oct 5, 2022
9a670b9
chore: refactor frontend to use workspace status directly (#4361)
presleyp Oct 5, 2022
3ad5e11
feat: add warning if workspace page becomes stale (#4375)
Kira-Pilot Oct 5, 2022
bbe2baf
fix: Ignore all hidden files and folders in archive (#4382)
kylecarbs Oct 6, 2022
1386465
feat: add endpoint to get listening ports in agent (#4260)
deansheather Oct 6, 2022
93b8121
fix: Change use of 1337 to 13337 in example templates (#4386)
mafredri Oct 6, 2022
29a2fe4
fix: fix builds on windows_arm64 (#4388)
deansheather Oct 6, 2022
d275331
fix: Remove audit warning if unlicensed (#4387)
kylecarbs Oct 6, 2022
0ebcb7d
fix: Remove reliance of `relative_path` on subdomains (#4390)
kylecarbs Oct 6, 2022
ea42212
chore: add icons to quickstarts (#4379)
bpmct Oct 6, 2022
9b1ff43
fix: Don't run CI for releases (#4393)
kylecarbs Oct 6, 2022
3b15f13
fix: fix apps being unavailable until rebuild (#4395)
deansheather Oct 6, 2022
9cf3e10
chore: Pin typos to fix CI (#4396)
kylecarbs Oct 6, 2022
fe7c9f8
chore: Stop building images on tag pushes (#4397)
kylecarbs Oct 6, 2022
f5df548
feat: tokens (#4380)
f0ssel Oct 6, 2022
a89d690
fix: show help on wraper commands (#4402)
f0ssel Oct 6, 2022
32bb1e7
fix: add back missing postAPIKey route (#4406)
f0ssel Oct 6, 2022
05670d1
fix: Spelling in audit log docs (#4384)
ntimo Oct 6, 2022
915bb41
feat: Add trial property to licenses (#4372)
kylecarbs Oct 7, 2022
3049a56
fix: Use the maximum number of users for a license warning (#4410)
kylecarbs Oct 7, 2022
3cc77d9
feat: Add tunnel by default (#4399)
kylecarbs Oct 7, 2022
e8e095e
feat: redesign error alert (#4403)
Kira-Pilot Oct 7, 2022
adcf883
fix: Ensure GitHub OAuth2 users are active in organization (#4416)
mafredri Oct 7, 2022
2b6586d
chore: add ignoreRestSiblings to no-unused-vars ESLint rule (#4404)
jsjoeio Oct 7, 2022
257df81
chore: replace old ErrorSummary component (#4417)
Kira-Pilot Oct 7, 2022
34f7992
refactor: Remove user roles from dropdown (#4419)
BrunoQuaresma Oct 7, 2022
50966c4
fix: Fix keyboard focus styles on buttons (#4418)
BrunoQuaresma Oct 7, 2022
3ad27b5
chore: Rename context in `cli/agent` (#4422)
mafredri Oct 7, 2022
0ad8e77
fix(ci): use correct `retention-days` in e2e (#4424)
coadler Oct 7, 2022
c01910f
docs: use enterprise badges (#4428)
ammario Oct 8, 2022
dd5173b
fix: apply loadBalancerIP and externalTrafficPolicy values in helm ch…
andrei-nefeli Oct 8, 2022
eefc26c
Hide build logs older than 30 days (#4436)
ammario Oct 9, 2022
6266895
Merge branch 'main' into mergemain
sreya Oct 9, 2022
99868f2
remove migration conflict
sreya Oct 9, 2022
c46eba4
fix license woes
sreya Oct 9, 2022
33a5599
fix coderd/license
sreya Oct 9, 2022
20a89eb
Update error banner
BrunoQuaresma Oct 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: Add trial property to licenses (#4372)
* feat: Add trial property to licenses

This allows the frontend to display whether the user is on
a trial license of Coder. This is useful for advertising
Enterprise functionality.

* Improve tests for license enablement code

* Add all features property
  • Loading branch information
kylecarbs authored Oct 7, 2022
commit 915bb41ea22687bebcb159e29ab0ba995c148a90
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"cliflag",
"cliui",
"codecov",
"Codespaces",
"coderd",
"coderdenttest",
"coderdtest",
"codersdk",
"cronstrue",
Expand All @@ -24,6 +24,7 @@
"drpcmux",
"drpcserver",
"Dsts",
"enablements",
"fatih",
"Formik",
"gitsshkey",
Expand Down
1 change: 1 addition & 0 deletions codersdk/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Entitlements struct {
Warnings []string `json:"warnings"`
HasLicense bool `json:"has_license"`
Experimental bool `json:"experimental"`
Trial bool `json:"trial"`
}

func (c *Client) Entitlements(ctx context.Context) (Entitlements, error) {
Expand Down
4 changes: 3 additions & 1 deletion enterprise/cli/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestFeaturesList(t *testing.T) {
var entitlements codersdk.Entitlements
err := json.Unmarshal(buf.Bytes(), &entitlements)
require.NoError(t, err, "unmarshal JSON output")
assert.Len(t, entitlements.Features, 4)
assert.Len(t, entitlements.Features, 5)
assert.Empty(t, entitlements.Warnings)
assert.Equal(t, codersdk.EntitlementNotEntitled,
entitlements.Features[codersdk.FeatureUserLimit].Entitlement)
Expand All @@ -67,6 +67,8 @@ func TestFeaturesList(t *testing.T) {
entitlements.Features[codersdk.FeatureBrowserOnly].Entitlement)
assert.Equal(t, codersdk.EntitlementNotEntitled,
entitlements.Features[codersdk.FeatureWorkspaceQuota].Entitlement)
assert.Equal(t, codersdk.EntitlementNotEntitled,
entitlements.Features[codersdk.FeatureSCIM].Entitlement)
assert.False(t, entitlements.HasLicense)
assert.False(t, entitlements.Experimental)
})
Expand Down
207 changes: 35 additions & 172 deletions enterprise/coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package coderd
import (
"context"
"crypto/ed25519"
"fmt"
"net/http"
"sync"
"time"
Expand All @@ -15,11 +14,14 @@ import (

"cdr.dev/slog"
"github.com/coder/coder/coderd"
agplaudit "github.com/coder/coder/coderd/audit"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/coderd/workspacequota"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/enterprise/audit"
"github.com/coder/coder/enterprise/audit/backends"
"github.com/coder/coder/enterprise/coderd/license"
)

// New constructs an Enterprise coderd API instance.
Expand All @@ -34,19 +36,8 @@ func New(ctx context.Context, options *Options) (*API, error) {
}
ctx, cancelFunc := context.WithCancel(ctx)
api := &API{
AGPL: coderd.New(options.Options),
Options: options,

entitlements: entitlements{
activeUsers: codersdk.Feature{
Entitlement: codersdk.EntitlementNotEntitled,
Enabled: false,
},
auditLogs: codersdk.EntitlementNotEntitled,
browserOnly: codersdk.EntitlementNotEntitled,
scim: codersdk.EntitlementNotEntitled,
workspaceQuota: codersdk.EntitlementNotEntitled,
},
AGPL: coderd.New(options.Options),
Options: options,
cancelEntitlementsLoop: cancelFunc,
}
oauthConfigs := &httpmw.OAuth2Configs{
Expand Down Expand Up @@ -117,16 +108,7 @@ type API struct {

cancelEntitlementsLoop func()
entitlementsMu sync.RWMutex
entitlements entitlements
}

type entitlements struct {
hasLicense bool
activeUsers codersdk.Feature
auditLogs codersdk.Entitlement
browserOnly codersdk.Entitlement
scim codersdk.Entitlement
workspaceQuota codersdk.Entitlement
entitlements codersdk.Entitlements
}

func (api *API) Close() error {
Expand All @@ -135,94 +117,57 @@ func (api *API) Close() error {
}

func (api *API) updateEntitlements(ctx context.Context) error {
licenses, err := api.Database.GetUnexpiredLicenses(ctx)
if err != nil {
return err
}
api.entitlementsMu.Lock()
defer api.entitlementsMu.Unlock()
now := time.Now()

// Default all entitlements to be disabled.
entitlements := entitlements{
hasLicense: false,
activeUsers: codersdk.Feature{
Enabled: false,
Entitlement: codersdk.EntitlementNotEntitled,
},
auditLogs: codersdk.EntitlementNotEntitled,
scim: codersdk.EntitlementNotEntitled,
browserOnly: codersdk.EntitlementNotEntitled,
workspaceQuota: codersdk.EntitlementNotEntitled,
entitlements, err := license.Entitlements(ctx, api.Database, api.Logger, api.Keys, map[string]bool{
codersdk.FeatureAuditLog: api.AuditLogging,
codersdk.FeatureBrowserOnly: api.BrowserOnly,
codersdk.FeatureSCIM: len(api.SCIMAPIKey) != 0,
codersdk.FeatureWorkspaceQuota: api.UserWorkspaceQuota != 0,
})
if err != nil {
return err
}

// Here we loop through licenses to detect enabled features.
for _, l := range licenses {
claims, err := validateDBLicense(l, api.Keys)
if err != nil {
api.Logger.Debug(ctx, "skipping invalid license",
slog.F("id", l.ID), slog.Error(err))
continue
featureChanged := func(featureName string) (changed bool, enabled bool) {
if api.entitlements.Features == nil {
return true, entitlements.Features[featureName].Enabled
}
entitlements.hasLicense = true
entitlement := codersdk.EntitlementEntitled
if now.After(claims.LicenseExpires.Time) {
// if the grace period were over, the validation fails, so if we are after
// LicenseExpires we must be in grace period.
entitlement = codersdk.EntitlementGracePeriod
}
if claims.Features.UserLimit > 0 {
entitlements.activeUsers = codersdk.Feature{
Enabled: true,
Entitlement: entitlement,
}
currentLimit := int64(0)
if entitlements.activeUsers.Limit != nil {
currentLimit = *entitlements.activeUsers.Limit
}
limit := max(currentLimit, claims.Features.UserLimit)
entitlements.activeUsers.Limit = &limit
}
if claims.Features.AuditLog > 0 {
entitlements.auditLogs = entitlement
}
if claims.Features.BrowserOnly > 0 {
entitlements.browserOnly = entitlement
}
if claims.Features.SCIM > 0 {
entitlements.scim = entitlement
}
if claims.Features.WorkspaceQuota > 0 {
entitlements.workspaceQuota = entitlement
oldFeature := api.entitlements.Features[featureName]
newFeature := entitlements.Features[featureName]
if oldFeature.Enabled != newFeature.Enabled {
return true, newFeature.Enabled
}
return false, newFeature.Enabled
}

if entitlements.auditLogs != api.entitlements.auditLogs {
// A flag could be added to the options that would allow disabling
// enhanced audit logging here!
if entitlements.auditLogs != codersdk.EntitlementNotEntitled && api.AuditLogging {
auditor := audit.NewAuditor(
if changed, enabled := featureChanged(codersdk.FeatureAuditLog); changed {
auditor := agplaudit.NewNop()
if enabled {
auditor = audit.NewAuditor(
audit.DefaultFilter,
backends.NewPostgres(api.Database, true),
backends.NewSlog(api.Logger),
)
api.AGPL.Auditor.Store(&auditor)
}
api.AGPL.Auditor.Store(&auditor)
}

if entitlements.browserOnly != api.entitlements.browserOnly {
if changed, enabled := featureChanged(codersdk.FeatureBrowserOnly); changed {
var handler func(rw http.ResponseWriter) bool
if entitlements.browserOnly != codersdk.EntitlementNotEntitled && api.BrowserOnly {
if enabled {
handler = api.shouldBlockNonBrowserConnections
}
api.AGPL.WorkspaceClientCoordinateOverride.Store(&handler)
}

if entitlements.workspaceQuota != api.entitlements.workspaceQuota {
if entitlements.workspaceQuota != codersdk.EntitlementNotEntitled && api.UserWorkspaceQuota > 0 {
enforcer := NewEnforcer(api.Options.UserWorkspaceQuota)
api.AGPL.WorkspaceQuotaEnforcer.Store(&enforcer)
if changed, enabled := featureChanged(codersdk.FeatureWorkspaceQuota); changed {
enforcer := workspacequota.NewNop()
if enabled {
enforcer = NewEnforcer(api.Options.UserWorkspaceQuota)
}
api.AGPL.WorkspaceQuotaEnforcer.Store(&enforcer)
}

api.entitlements = entitlements
Expand All @@ -235,82 +180,7 @@ func (api *API) serveEntitlements(rw http.ResponseWriter, r *http.Request) {
api.entitlementsMu.RLock()
entitlements := api.entitlements
api.entitlementsMu.RUnlock()

resp := codersdk.Entitlements{
Features: make(map[string]codersdk.Feature),
Warnings: make([]string, 0),
HasLicense: entitlements.hasLicense,
Experimental: api.Experimental,
}

if entitlements.activeUsers.Limit != nil {
activeUserCount, err := api.Database.GetActiveUserCount(ctx)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Unable to query database",
Detail: err.Error(),
})
return
}
entitlements.activeUsers.Actual = &activeUserCount
if activeUserCount > *entitlements.activeUsers.Limit {
resp.Warnings = append(resp.Warnings,
fmt.Sprintf(
"Your deployment has %d active users but is only licensed for %d.",
activeUserCount, *entitlements.activeUsers.Limit))
}
}
resp.Features[codersdk.FeatureUserLimit] = entitlements.activeUsers

// Audit logs
resp.Features[codersdk.FeatureAuditLog] = codersdk.Feature{
Entitlement: entitlements.auditLogs,
Enabled: api.AuditLogging,
}
// Audit logging is enabled by default. We don't want to display
// a warning if they don't have a license.
if entitlements.hasLicense && api.AuditLogging {
if entitlements.auditLogs == codersdk.EntitlementNotEntitled {
resp.Warnings = append(resp.Warnings,
"Audit logging is enabled but your license is not entitled to this feature.")
}
if entitlements.auditLogs == codersdk.EntitlementGracePeriod {
resp.Warnings = append(resp.Warnings,
"Audit logging is enabled but your license for this feature is expired.")
}
}

resp.Features[codersdk.FeatureBrowserOnly] = codersdk.Feature{
Entitlement: entitlements.browserOnly,
Enabled: api.BrowserOnly,
}
if api.BrowserOnly {
if entitlements.browserOnly == codersdk.EntitlementNotEntitled {
resp.Warnings = append(resp.Warnings,
"Browser only connections are enabled but your license is not entitled to this feature.")
}
if entitlements.browserOnly == codersdk.EntitlementGracePeriod {
resp.Warnings = append(resp.Warnings,
"Browser only connections are enabled but your license for this feature is expired.")
}
}

resp.Features[codersdk.FeatureWorkspaceQuota] = codersdk.Feature{
Entitlement: entitlements.workspaceQuota,
Enabled: api.UserWorkspaceQuota > 0,
}
if api.UserWorkspaceQuota > 0 {
if entitlements.workspaceQuota == codersdk.EntitlementNotEntitled {
resp.Warnings = append(resp.Warnings,
"Workspace quotas are enabled but your license is not entitled to this feature.")
}
if entitlements.workspaceQuota == codersdk.EntitlementGracePeriod {
resp.Warnings = append(resp.Warnings,
"Workspace quotas are enabled but your license for this feature is expired.")
}
}

httpapi.Write(ctx, rw, http.StatusOK, resp)
httpapi.Write(ctx, rw, http.StatusOK, entitlements)
}

func (api *API) runEntitlementsLoop(ctx context.Context) {
Expand Down Expand Up @@ -374,10 +244,3 @@ func (api *API) runEntitlementsLoop(ctx context.Context) {
}
}
}

func max(a, b int64) int64 {
if a > b {
return a
}
return b
}
42 changes: 0 additions & 42 deletions enterprise/coderd/coderd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,48 +86,6 @@ func TestEntitlements(t *testing.T) {
assert.Equal(t, codersdk.EntitlementNotEntitled, al.Entitlement)
assert.True(t, al.Enabled)
})
t.Run("Warnings", func(t *testing.T) {
t.Parallel()
client := coderdenttest.New(t, &coderdenttest.Options{
AuditLogging: true,
BrowserOnly: true,
})
first := coderdtest.CreateFirstUser(t, client)
for i := 0; i < 4; i++ {
coderdtest.CreateAnotherUser(t, client, first.OrganizationID)
}
coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
UserLimit: 4,
AuditLog: true,
BrowserOnly: true,
GraceAt: time.Now().Add(-time.Second),
})
res, err := client.Entitlements(context.Background())
require.NoError(t, err)
assert.True(t, res.HasLicense)
ul := res.Features[codersdk.FeatureUserLimit]
assert.Equal(t, codersdk.EntitlementGracePeriod, ul.Entitlement)
assert.Equal(t, int64(4), *ul.Limit)
assert.Equal(t, int64(5), *ul.Actual)
assert.True(t, ul.Enabled)
al := res.Features[codersdk.FeatureAuditLog]
assert.Equal(t, codersdk.EntitlementGracePeriod, al.Entitlement)
assert.True(t, al.Enabled)
assert.Nil(t, al.Limit)
assert.Nil(t, al.Actual)
bo := res.Features[codersdk.FeatureBrowserOnly]
assert.Equal(t, codersdk.EntitlementGracePeriod, bo.Entitlement)
assert.True(t, bo.Enabled)
assert.Nil(t, bo.Limit)
assert.Nil(t, bo.Actual)
assert.Len(t, res.Warnings, 3)
assert.Contains(t, res.Warnings,
"Your deployment has 5 active users but is only licensed for 4.")
assert.Contains(t, res.Warnings,
"Audit logging is enabled but your license for this feature is expired.")
assert.Contains(t, res.Warnings,
"Browser only connections are enabled but your license for this feature is expired.")
})
t.Run("Pubsub", func(t *testing.T) {
t.Parallel()
client, _, api := coderdenttest.NewWithAPI(t, nil)
Expand Down
Loading