Skip to content

Commit 90db668

Browse files
authored
fix: refresh entitlements after creating first user (#12285)
1 parent 2cb9bfd commit 90db668

File tree

7 files changed

+64
-15
lines changed

7 files changed

+64
-15
lines changed

coderd/coderd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ type Options struct {
125125
ExternalAuthConfigs []*externalauth.Config
126126
RealIPConfig *httpmw.RealIPConfig
127127
TrialGenerator func(ctx context.Context, body codersdk.LicensorTrialRequest) error
128+
// RefreshEntitlements is used to set correct entitlements after creating first user and generating trial license.
129+
RefreshEntitlements func(ctx context.Context) error
128130
// TLSCertificates is used to mesh DERP servers securely.
129131
TLSCertificates []tls.Certificate
130132
TailnetCoordinator tailnet.Coordinator

coderd/coderdtest/coderdtest.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ type Options struct {
107107
TLSCertificates []tls.Certificate
108108
ExternalAuthConfigs []*externalauth.Config
109109
TrialGenerator func(ctx context.Context, body codersdk.LicensorTrialRequest) error
110+
RefreshEntitlements func(ctx context.Context) error
110111
TemplateScheduleStore schedule.TemplateScheduleStore
111112
Coordinator tailnet.Coordinator
112113

@@ -434,6 +435,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
434435
AccessControlStore: accessControlStore,
435436
TLSCertificates: options.TLSCertificates,
436437
TrialGenerator: options.TrialGenerator,
438+
RefreshEntitlements: options.RefreshEntitlements,
437439
TailnetCoordinator: options.Coordinator,
438440
BaseDERPMap: derpMap,
439441
DERPMapUpdateFrequency: 150 * time.Millisecond,

coderd/users.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,16 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
191191
return
192192
}
193193

194+
if api.RefreshEntitlements != nil {
195+
err = api.RefreshEntitlements(ctx)
196+
if err != nil {
197+
api.Logger.Error(ctx, "failed to refresh entitlements after generating trial license")
198+
return
199+
}
200+
} else {
201+
api.Logger.Debug(ctx, "entitlements will not be refreshed")
202+
}
203+
194204
telemetryUser := telemetry.ConvertUser(user)
195205
// Send the initial users email address!
196206
telemetryUser.Email = &user.Email

coderd/users_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,16 @@ func TestFirstUser(t *testing.T) {
7575

7676
t.Run("Trial", func(t *testing.T) {
7777
t.Parallel()
78-
called := make(chan struct{})
78+
trialGenerated := make(chan struct{})
79+
entitlementsRefreshed := make(chan struct{})
80+
7981
client := coderdtest.New(t, &coderdtest.Options{
8082
TrialGenerator: func(context.Context, codersdk.LicensorTrialRequest) error {
81-
close(called)
83+
close(trialGenerated)
84+
return nil
85+
},
86+
RefreshEntitlements: func(context.Context) error {
87+
close(entitlementsRefreshed)
8288
return nil
8389
},
8490
})
@@ -94,7 +100,9 @@ func TestFirstUser(t *testing.T) {
94100
}
95101
_, err := client.CreateFirstUser(ctx, req)
96102
require.NoError(t, err)
97-
<-called
103+
104+
_ = testutil.RequireRecvCtx(ctx, t, trialGenerated)
105+
_ = testutil.RequireRecvCtx(ctx, t, entitlementsRefreshed)
98106
})
99107
}
100108

enterprise/coderd/coderd.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
203203
})
204204
})
205205
})
206+
api.AGPL.RefreshEntitlements = func(ctx context.Context) error {
207+
return api.refreshEntitlements(ctx)
208+
}
206209

207210
api.AGPL.APIHandler.Group(func(r chi.Router) {
208211
r.Get("/entitlements", api.serveEntitlements)

enterprise/coderd/licenses.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,10 @@ func (api *API) postRefreshEntitlements(rw http.ResponseWriter, r *http.Request)
197197
return
198198
}
199199

200-
err = api.updateEntitlements(ctx)
201-
if err != nil {
202-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
203-
Message: "Failed to update entitlements",
204-
Detail: err.Error(),
205-
})
206-
return
207-
}
208-
209-
err = api.Pubsub.Publish(PubsubEventLicenses, []byte("refresh"))
200+
err = api.refreshEntitlements(ctx)
210201
if err != nil {
211-
api.Logger.Error(context.Background(), "failed to publish forced entitlement update", slog.Error(err))
212202
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
213-
Message: "Failed to publish forced entitlement update. Other replicas might not be updated.",
203+
Message: "Failed to refresh entitlements",
214204
Detail: err.Error(),
215205
})
216206
return
@@ -221,6 +211,21 @@ func (api *API) postRefreshEntitlements(rw http.ResponseWriter, r *http.Request)
221211
})
222212
}
223213

214+
func (api *API) refreshEntitlements(ctx context.Context) error {
215+
api.Logger.Info(ctx, "refresh entitlements now")
216+
217+
err := api.updateEntitlements(ctx)
218+
if err != nil {
219+
return xerrors.Errorf("failed to update entitlements: %w", err)
220+
}
221+
err = api.Pubsub.Publish(PubsubEventLicenses, []byte("refresh"))
222+
if err != nil {
223+
api.Logger.Error(ctx, "failed to publish forced entitlement update", slog.Error(err))
224+
return xerrors.Errorf("failed to publish forced entitlement update, other replicas might not be updated: %w", err)
225+
}
226+
return nil
227+
}
228+
224229
// @Summary Get licenses
225230
// @ID get-licenses
226231
// @Security CoderSessionToken

enterprise/coderd/users_test.go

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

33
import (
4+
"context"
45
"net/http"
56
"testing"
67
"time"
@@ -218,3 +219,21 @@ func TestUserQuietHours(t *testing.T) {
218219
require.Contains(t, sdkErr.Message, "cannot set custom quiet hours schedule")
219220
})
220221
}
222+
223+
func TestCreateFirstUser_Entitlements_Trial(t *testing.T) {
224+
t.Parallel()
225+
226+
adminClient, _ := coderdenttest.New(t, &coderdenttest.Options{
227+
LicenseOptions: &coderdenttest.LicenseOptions{
228+
Trial: true,
229+
},
230+
})
231+
232+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
233+
defer cancel()
234+
235+
//nolint:gocritic // we need the first user so admin
236+
entitlements, err := adminClient.Entitlements(ctx)
237+
require.NoError(t, err)
238+
require.True(t, entitlements.Trial, "Trial license should be immediately active.")
239+
}

0 commit comments

Comments
 (0)