Skip to content

Commit ba450b3

Browse files
committed
Backend WIP
1 parent 0a7a030 commit ba450b3

File tree

16 files changed

+206
-22
lines changed

16 files changed

+206
-22
lines changed

coderd/database/databasefake/databasefake.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ type data struct {
122122
deploymentID string
123123
derpMeshKey string
124124
lastUpdateCheck []byte
125+
serviceBanner []byte
125126
lastLicenseID int32
126127
}
127128

@@ -3291,6 +3292,25 @@ func (q *fakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) {
32913292
return string(q.lastUpdateCheck), nil
32923293
}
32933294

3295+
func (q *fakeQuerier) InsertOrUpdateServiceBanner(_ context.Context, data string) error {
3296+
q.mutex.RLock()
3297+
defer q.mutex.RUnlock()
3298+
3299+
q.serviceBanner = []byte(data)
3300+
return nil
3301+
}
3302+
3303+
func (q *fakeQuerier) GetServiceBanner(_ context.Context) (string, error) {
3304+
q.mutex.RLock()
3305+
defer q.mutex.RUnlock()
3306+
3307+
if q.serviceBanner == nil {
3308+
return "", sql.ErrNoRows
3309+
}
3310+
3311+
return string(q.lastUpdateCheck), nil
3312+
}
3313+
32943314
func (q *fakeQuerier) InsertLicense(
32953315
_ context.Context, arg database.InsertLicenseParams,
32963316
) (database.License, error) {

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: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/siteconfig.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,10 @@ ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update
1616

1717
-- name: GetLastUpdateCheck :one
1818
SELECT value FROM site_configs WHERE key = 'last_update_check';
19+
20+
-- name: InsertOrUpdateServiceBanner :exec
21+
INSERT INTO site_configs (key, value) VALUES ('service_banner', $1)
22+
ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner';
23+
24+
-- name: GetServiceBanner :one
25+
SELECT value FROM site_configs WHERE key = 'service_banner';

codersdk/features.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const (
2323
FeatureHighAvailability = "high_availability"
2424
FeatureMultipleGitAuth = "multiple_git_auth"
2525
FeatureExternalProvisionerDaemons = "external_provisioner_daemons"
26+
FeatureServiceBanners = "service_banners"
2627
)
2728

2829
var FeatureNames = []string{
@@ -34,6 +35,7 @@ var FeatureNames = []string{
3435
FeatureHighAvailability,
3536
FeatureMultipleGitAuth,
3637
FeatureExternalProvisionerDaemons,
38+
FeatureServiceBanners,
3739
}
3840

3941
type Feature struct {

enterprise/coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ func New(ctx context.Context, options *Options) (*API, error) {
132132
apiKeyMiddleware,
133133
)
134134
r.Get("/", api.serviceBanner)
135+
r.Put("/", api.putServiceBanner)
135136
})
136137
})
137138

enterprise/coderd/coderdenttest/coderdenttest.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ type LicenseOptions struct {
113113
HighAvailability bool
114114
MultipleGitAuth bool
115115
ExternalProvisionerDaemons bool
116+
ServiceBanners bool
116117
}
117118

118119
// AddLicense generates a new license with the options provided and inserts it.
@@ -164,6 +165,11 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
164165
externalProvisionerDaemons = 1
165166
}
166167

168+
serviceBanners := int64(0)
169+
if options.ServiceBanners {
170+
serviceBanners = 1
171+
}
172+
167173
c := &license.Claims{
168174
RegisteredClaims: jwt.RegisteredClaims{
169175
Issuer: "test@testing.test",
@@ -186,6 +192,7 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
186192
TemplateRBAC: rbacEnabled,
187193
MultipleGitAuth: multipleGitAuth,
188194
ExternalProvisionerDaemons: externalProvisionerDaemons,
195+
ServiceBanners: serviceBanners,
189196
},
190197
}
191198
tok := jwt.NewWithClaims(jwt.SigningMethodEdDSA, c)

enterprise/coderd/license/license.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ func Entitlements(
123123
Enabled: true,
124124
}
125125
}
126+
if claims.Features.ServiceBanners > 0 {
127+
entitlements.Features[codersdk.FeatureServiceBanners] = codersdk.Feature{
128+
Entitlement: entitlement,
129+
Enabled: true,
130+
}
131+
}
126132
if claims.AllFeatures {
127133
allFeatures = true
128134
}
@@ -252,6 +258,7 @@ type Features struct {
252258
HighAvailability int64 `json:"high_availability"`
253259
MultipleGitAuth int64 `json:"multiple_git_auth"`
254260
ExternalProvisionerDaemons int64 `json:"external_provisioner_daemons"`
261+
ServiceBanners int64 `json:"service_banners"`
255262
}
256263

257264
type Claims struct {

enterprise/coderd/servicebanner.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,80 @@
11
package coderd
22

33
import (
4+
"database/sql"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
48
"net/http"
59

610
"github.com/coder/coder/coderd/httpapi"
711
"github.com/coder/coder/codersdk"
812
)
913

1014
func (api *API) serviceBanner(rw http.ResponseWriter, r *http.Request) {
11-
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.ServiceBanner{
12-
Enabled: true,
13-
Message: "Testing!",
14-
BackgroundColor: "#FF00FF",
15+
api.entitlementsMu.RLock()
16+
isEntitled := api.entitlements.Features[codersdk.FeatureServiceBanners].Entitlement == codersdk.EntitlementEntitled
17+
api.entitlementsMu.RUnlock()
18+
19+
ctx := r.Context()
20+
21+
if !isEntitled {
22+
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ServiceBanner{
23+
Enabled: false,
24+
})
25+
return
26+
}
27+
28+
serviceBannerJSON, err := api.Database.GetServiceBanner(r.Context())
29+
if errors.Is(err, sql.ErrNoRows) {
30+
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ServiceBanner{
31+
Enabled: false,
32+
})
33+
return
34+
} else if err != nil {
35+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
36+
Message: fmt.Sprintf("database error: %+v", err),
37+
})
38+
return
39+
}
40+
41+
var serviceBanner codersdk.ServiceBanner
42+
err = json.Unmarshal([]byte(serviceBannerJSON), &serviceBanner)
43+
if err != nil {
44+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
45+
Message: fmt.Sprintf("unmarshal json: %+v", err),
46+
})
47+
return
48+
}
49+
50+
httpapi.Write(r.Context(), rw, http.StatusOK, serviceBanner)
51+
}
52+
53+
func (api *API) putServiceBanner(rw http.ResponseWriter, r *http.Request) {
54+
ctx := r.Context()
55+
56+
var serviceBanner codersdk.ServiceBanner
57+
if !httpapi.Read(ctx, rw, r, &serviceBanner) {
58+
return
59+
}
60+
61+
serviceBannerJSON, err := json.Marshal(serviceBanner)
62+
if err != nil {
63+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
64+
Message: fmt.Sprintf("marshal banner: %+v", err),
65+
})
66+
return
67+
}
68+
69+
err = api.Database.InsertOrUpdateServiceBanner(ctx, string(serviceBannerJSON))
70+
if err != nil {
71+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
72+
Message: fmt.Sprintf("database error: %+v", err),
73+
})
74+
return
75+
}
76+
77+
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.Response{
78+
Message: "ok",
1579
})
1680
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package coderd_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/coder/coder/coderd/coderdtest"
8+
"github.com/coder/coder/enterprise/coderd/coderdenttest"
9+
"github.com/coder/coder/testutil"
10+
)
11+
12+
func TestServiceBanners(t *testing.T) {
13+
t.Parallel()
14+
15+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
16+
defer cancel()
17+
18+
client := coderdenttest.New(t, &coderdenttest.Options{})
19+
20+
user := coderdtest.CreateFirstUser(t, client)
21+
coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
22+
ServiceBanners: true,
23+
})
24+
}

site/src/api/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export enum FeatureNames {
2323
SCIM = "scim",
2424
TemplateRBAC = "template_rbac",
2525
HighAvailability = "high_availability",
26+
ServiceBanners = "service_banners",
2627
}

site/src/components/DeploySettingsLayout/Badges.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ export const EnabledBadge: React.FC = () => {
1212
)
1313
}
1414

15+
export const EntitledBadge: React.FC = () => {
16+
const styles = useStyles()
17+
return (
18+
<span className={combineClasses([styles.badge, styles.enabledBadge])}>
19+
Entitled
20+
</span>
21+
)
22+
}
23+
1524
export const DisabledBadge: React.FC = () => {
1625
const styles = useStyles()
1726
return (

site/src/components/ServiceBanner/ServiceBanner.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ export const ServiceBanner: React.FC = () => {
2121
return null
2222
}
2323

24-
if (enabled && message !== undefined && background_color !== undefined) {
24+
if (message !== undefined && background_color !== undefined) {
2525
return (
26-
<ServiceBannerView message={message} backgroundColor={background_color} />
26+
<ServiceBannerView
27+
message={message}
28+
backgroundColor={background_color}
29+
preview={serviceBannerState.context.preview}
30+
/>
2731
)
2832
} else {
2933
return null

site/src/components/ServiceBanner/ServiceBannerView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { makeStyles } from "@material-ui/core/styles"
2+
import { Pill } from "components/Pill/Pill"
23
import ReactMarkdown from "react-markdown"
34
import { colors } from "theme/colors"
45

56
export interface ServiceBannerViewProps {
67
message: string
78
backgroundColor: string
9+
preview: boolean
810
}
911

1012
export const ServiceBannerView: React.FC<ServiceBannerViewProps> = ({
1113
message,
1214
backgroundColor,
15+
preview,
1316
}) => {
1417
const styles = useStyles()
1518
const markdownElementsAllowed = [
@@ -25,6 +28,7 @@ export const ServiceBannerView: React.FC<ServiceBannerViewProps> = ({
2528
className={`${styles.container}`}
2629
style={{ backgroundColor: backgroundColor }}
2730
>
31+
{preview && <Pill text="Preview" type="primary" lightBorder />}
2832
<div className={styles.centerContent}>
2933
<ReactMarkdown
3034
allowedElements={markdownElementsAllowed}

0 commit comments

Comments
 (0)