@@ -15,6 +15,7 @@ import (
15
15
"github.com/coder/coder/v2/buildinfo"
16
16
"github.com/coder/coder/v2/coderd/appearance"
17
17
"github.com/coder/coder/v2/coderd/database"
18
+ "github.com/coder/coder/v2/coderd/entitlements"
18
19
agplportsharing "github.com/coder/coder/v2/coderd/portsharing"
19
20
"github.com/coder/coder/v2/coderd/rbac/policy"
20
21
"github.com/coder/coder/v2/enterprise/coderd/portsharing"
@@ -103,19 +104,26 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
103
104
}
104
105
return nil , xerrors .Errorf ("init database encryption: %w" , err )
105
106
}
107
+
108
+ entitlementsSet := entitlements .New ()
106
109
options .Database = cryptDB
107
110
api := & API {
108
- ctx : ctx ,
109
- cancel : cancelFunc ,
110
- Options : options ,
111
+ ctx : ctx ,
112
+ cancel : cancelFunc ,
113
+ Options : options ,
114
+ entitlements : entitlementsSet ,
111
115
provisionerDaemonAuth : & provisionerDaemonAuth {
112
116
psk : options .ProvisionerDaemonPSK ,
113
117
authorizer : options .Authorizer ,
114
118
db : options .Database ,
115
119
},
120
+ licenseMetricsCollector : & license.MetricsCollector {
121
+ Entitlements : entitlementsSet ,
122
+ },
116
123
}
117
124
// This must happen before coderd initialization!
118
125
options .PostAuthAdditionalHeadersFunc = api .writeEntitlementWarningsHeader
126
+ options .Options .Entitlements = api .entitlements
119
127
api .AGPL = coderd .New (options .Options )
120
128
defer func () {
121
129
if err != nil {
@@ -493,7 +501,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
493
501
}
494
502
api .AGPL .WorkspaceProxiesFetchUpdater .Store (& fetchUpdater )
495
503
496
- err = api .PrometheusRegistry .Register (& api .licenseMetricsCollector )
504
+ err = api .PrometheusRegistry .Register (api .licenseMetricsCollector )
497
505
if err != nil {
498
506
return nil , xerrors .Errorf ("unable to register license metrics collector" )
499
507
}
@@ -553,13 +561,11 @@ type API struct {
553
561
// ProxyHealth checks the reachability of all workspace proxies.
554
562
ProxyHealth * proxyhealth.ProxyHealth
555
563
556
- entitlementsUpdateMu sync.Mutex
557
- entitlementsMu sync.RWMutex
558
- entitlements codersdk.Entitlements
564
+ entitlements * entitlements.Set
559
565
560
566
provisionerDaemonAuth * provisionerDaemonAuth
561
567
562
- licenseMetricsCollector license.MetricsCollector
568
+ licenseMetricsCollector * license.MetricsCollector
563
569
tailnetService * tailnet.ClientService
564
570
}
565
571
@@ -588,11 +594,8 @@ func (api *API) writeEntitlementWarningsHeader(a rbac.Subject, header http.Heade
588
594
// has no roles. This is a normal user!
589
595
return
590
596
}
591
- api .entitlementsMu .RLock ()
592
- defer api .entitlementsMu .RUnlock ()
593
- for _ , warning := range api .entitlements .Warnings {
594
- header .Add (codersdk .EntitlementsWarningHeader , warning )
595
- }
597
+
598
+ api .entitlements .WriteEntitlementWarningHeaders (header )
596
599
}
597
600
598
601
func (api * API ) Close () error {
@@ -614,9 +617,6 @@ func (api *API) Close() error {
614
617
}
615
618
616
619
func (api * API ) updateEntitlements (ctx context.Context ) error {
617
- api .entitlementsUpdateMu .Lock ()
618
- defer api .entitlementsUpdateMu .Unlock ()
619
-
620
620
replicas := api .replicaManager .AllPrimary ()
621
621
agedReplicas := make ([]database.Replica , 0 , len (replicas ))
622
622
for _ , replica := range replicas {
@@ -632,7 +632,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
632
632
agedReplicas = append (agedReplicas , replica )
633
633
}
634
634
635
- entitlements , err := license .Entitlements (
635
+ reloadedEntitlements , err := license .Entitlements (
636
636
ctx , api .Database ,
637
637
len (agedReplicas ), len (api .ExternalAuthConfigs ), api .LicenseKeys , map [codersdk.FeatureName ]bool {
638
638
codersdk .FeatureAuditLog : api .AuditLogging ,
@@ -652,29 +652,24 @@ func (api *API) updateEntitlements(ctx context.Context) error {
652
652
return err
653
653
}
654
654
655
- if entitlements .RequireTelemetry && ! api .DeploymentValues .Telemetry .Enable .Value () {
655
+ if reloadedEntitlements .RequireTelemetry && ! api .DeploymentValues .Telemetry .Enable .Value () {
656
656
// We can't fail because then the user couldn't remove the offending
657
657
// license w/o a restart.
658
658
//
659
659
// We don't simply append to entitlement.Errors since we don't want any
660
660
// enterprise features enabled.
661
- api .entitlements .Errors = []string {
662
- "License requires telemetry but telemetry is disabled" ,
663
- }
661
+ api .entitlements .Update (func (entitlements * codersdk.Entitlements ) {
662
+ entitlements .Errors = []string {
663
+ "License requires telemetry but telemetry is disabled" ,
664
+ }
665
+ })
666
+
664
667
api .Logger .Error (ctx , "license requires telemetry enabled" )
665
668
return nil
666
669
}
667
670
668
671
featureChanged := func (featureName codersdk.FeatureName ) (initial , changed , enabled bool ) {
669
- if api .entitlements .Features == nil {
670
- return true , false , entitlements .Features [featureName ].Enabled
671
- }
672
- oldFeature := api .entitlements .Features [featureName ]
673
- newFeature := entitlements .Features [featureName ]
674
- if oldFeature .Enabled != newFeature .Enabled {
675
- return false , true , newFeature .Enabled
676
- }
677
- return false , false , newFeature .Enabled
672
+ return api .entitlements .FeatureChanged (featureName , reloadedEntitlements .Features [featureName ])
678
673
}
679
674
680
675
shouldUpdate := func (initial , changed , enabled bool ) bool {
@@ -831,20 +826,16 @@ func (api *API) updateEntitlements(ctx context.Context) error {
831
826
}
832
827
833
828
// External token encryption is soft-enforced
834
- featureExternalTokenEncryption := entitlements .Features [codersdk .FeatureExternalTokenEncryption ]
829
+ featureExternalTokenEncryption := reloadedEntitlements .Features [codersdk .FeatureExternalTokenEncryption ]
835
830
featureExternalTokenEncryption .Enabled = len (api .ExternalTokenEncryption ) > 0
836
831
if featureExternalTokenEncryption .Enabled && featureExternalTokenEncryption .Entitlement != codersdk .EntitlementEntitled {
837
832
msg := fmt .Sprintf ("%s is enabled (due to setting external token encryption keys) but your license is not entitled to this feature." , codersdk .FeatureExternalTokenEncryption .Humanize ())
838
833
api .Logger .Warn (ctx , msg )
839
- entitlements .Warnings = append (entitlements .Warnings , msg )
834
+ reloadedEntitlements .Warnings = append (reloadedEntitlements .Warnings , msg )
840
835
}
841
- entitlements .Features [codersdk .FeatureExternalTokenEncryption ] = featureExternalTokenEncryption
836
+ reloadedEntitlements .Features [codersdk .FeatureExternalTokenEncryption ] = featureExternalTokenEncryption
842
837
843
- api .entitlementsMu .Lock ()
844
- defer api .entitlementsMu .Unlock ()
845
- api .entitlements = entitlements
846
- api .licenseMetricsCollector .Entitlements .Store (& entitlements )
847
- api .AGPL .SiteHandler .Entitlements .Store (& entitlements )
838
+ api .entitlements .Replace (reloadedEntitlements )
848
839
return nil
849
840
}
850
841
@@ -1024,10 +1015,7 @@ func derpMapper(logger slog.Logger, proxyHealth *proxyhealth.ProxyHealth) func(*
1024
1015
// @Router /entitlements [get]
1025
1016
func (api * API ) serveEntitlements (rw http.ResponseWriter , r * http.Request ) {
1026
1017
ctx := r .Context ()
1027
- api .entitlementsMu .RLock ()
1028
- entitlements := api .entitlements
1029
- api .entitlementsMu .RUnlock ()
1030
- httpapi .Write (ctx , rw , http .StatusOK , entitlements )
1018
+ httpapi .Write (ctx , rw , http .StatusOK , api .entitlements .AsJSON ())
1031
1019
}
1032
1020
1033
1021
func (api * API ) runEntitlementsLoop (ctx context.Context ) {
0 commit comments