feat: expose ai_providers_env_drift_detected on appearance config

Surface the deprecated-env-drift flag set at startup through a new
read-only AppearanceConfig.ai_providers_env_drift_detected field. The
AGPL and enterprise appearance fetchers read a process-local atomic
(API.AIProvidersEnvDrift) so the dashboard can warn admins that their
env changes are ineffective. Regenerated SDK types and swagger.
This commit is contained in:
Danny Kopping
2026-06-01 09:29:54 +00:00
parent 2716632a51
commit 56c9d0dcbb
13 changed files with 67 additions and 30 deletions
+4
View File
@@ -15840,6 +15840,10 @@ const docTemplate = `{
"codersdk.AppearanceConfig": {
"type": "object",
"properties": {
"ai_providers_env_drift_detected": {
"description": "AIProvidersEnvDriftDetected is true when deprecated CODER_AIBRIDGE_*\nenv configuration differs from the AI provider rows already stored in\nthe database, meaning those env changes are ineffective. It is\noutput-only and is not part of UpdateAppearanceConfig.",
"type": "boolean"
},
"announcement_banners": {
"type": "array",
"items": {
+4
View File
@@ -14211,6 +14211,10 @@
"codersdk.AppearanceConfig": {
"type": "object",
"properties": {
"ai_providers_env_drift_detected": {
"description": "AIProvidersEnvDriftDetected is true when deprecated CODER_AIBRIDGE_*\nenv configuration differs from the AI provider rows already stored in\nthe database, meaning those env changes are ineffective. It is\noutput-only and is not part of UpdateAppearanceConfig.",
"type": "boolean"
},
"announcement_banners": {
"type": "array",
"items": {
+12 -5
View File
@@ -2,6 +2,7 @@ package appearance
import (
"context"
"sync/atomic"
"github.com/coder/coder/v2/codersdk"
)
@@ -12,21 +13,27 @@ type Fetcher interface {
type AGPLFetcher struct {
docsURL string
// aiProvidersEnvDrift reports whether deprecated CODER_AIBRIDGE_* env
// configuration is ineffective because it differs from the database.
// It may be nil when no source is wired in.
aiProvidersEnvDrift *atomic.Bool
}
func (f AGPLFetcher) Fetch(context.Context) (codersdk.AppearanceConfig, error) {
return codersdk.AppearanceConfig{
AnnouncementBanners: []codersdk.BannerConfig{},
SupportLinks: codersdk.DefaultSupportLinks(f.docsURL),
DocsURL: f.docsURL,
AnnouncementBanners: []codersdk.BannerConfig{},
SupportLinks: codersdk.DefaultSupportLinks(f.docsURL),
DocsURL: f.docsURL,
AIProvidersEnvDriftDetected: f.aiProvidersEnvDrift != nil && f.aiProvidersEnvDrift.Load(),
}, nil
}
func NewDefaultFetcher(docsURL string) Fetcher {
func NewDefaultFetcher(docsURL string, aiProvidersEnvDrift *atomic.Bool) Fetcher {
if docsURL == "" {
docsURL = codersdk.DefaultDocsURL()
}
return &AGPLFetcher{
docsURL: docsURL,
docsURL: docsURL,
aiProvidersEnvDrift: aiProvidersEnvDrift,
}
}
+1 -1
View File
@@ -666,7 +666,7 @@ func New(options *Options) *API {
options.AppSigningKeyCache,
)
f := appearance.NewDefaultFetcher(api.DeploymentValues.DocsURL.String())
f := appearance.NewDefaultFetcher(api.DeploymentValues.DocsURL.String(), &api.AIProvidersEnvDrift)
api.AppearanceFetcher.Store(&f)
api.PortSharer.Store(&portsharing.DefaultPortSharer)
api.PrebuildsClaimer.Store(&prebuilds.DefaultClaimer)
+5
View File
@@ -4882,6 +4882,11 @@ type AppearanceConfig struct {
ServiceBanner BannerConfig `json:"service_banner"`
AnnouncementBanners []BannerConfig `json:"announcement_banners"`
SupportLinks []LinkConfig `json:"support_links,omitempty"`
// AIProvidersEnvDriftDetected is true when deprecated CODER_AIBRIDGE_*
// env configuration differs from the AI provider rows already stored in
// the database, meaning those env changes are ineffective. It is
// output-only and is not part of UpdateAppearanceConfig.
AIProvidersEnvDriftDetected bool `json:"ai_providers_env_drift_detected"`
}
type UpdateAppearanceConfig struct {
+1
View File
@@ -103,6 +103,7 @@ curl -X GET http://coder-server:8080/api/v2/appearance \
```json
{
"ai_providers_env_drift_detected": true,
"announcement_banners": [
{
"background_color": "string",
+10 -8
View File
@@ -1572,6 +1572,7 @@ None
```json
{
"ai_providers_env_drift_detected": true,
"announcement_banners": [
{
"background_color": "string",
@@ -1600,14 +1601,15 @@ None
### Properties
| Name | Type | Required | Restrictions | Description |
|------------------------|---------------------------------------------------------|----------|--------------|---------------------------------------------------------------------|
| `announcement_banners` | array of [codersdk.BannerConfig](#codersdkbannerconfig) | false | | |
| `application_name` | string | false | | |
| `docs_url` | string | false | | |
| `logo_url` | string | false | | |
| `service_banner` | [codersdk.BannerConfig](#codersdkbannerconfig) | false | | Deprecated: ServiceBanner has been replaced by AnnouncementBanners. |
| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
| Name | Type | Required | Restrictions | Description |
|-----------------------------------|---------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ai_providers_env_drift_detected` | boolean | false | | Ai providers env drift detected is true when deprecated CODER_AIBRIDGE_* env configuration differs from the AI provider rows already stored in the database, meaning those env changes are ineffective. It is output-only and is not part of UpdateAppearanceConfig. |
| `announcement_banners` | array of [codersdk.BannerConfig](#codersdkbannerconfig) | false | | |
| `application_name` | string | false | | |
| `docs_url` | string | false | | |
| `logo_url` | string | false | | |
| `service_banner` | [codersdk.BannerConfig](#codersdkbannerconfig) | false | | Deprecated: ServiceBanner has been replaced by AnnouncementBanners. |
| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
## codersdk.ArchiveTemplateVersionsRequest
+18 -14
View File
@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"net/http"
"sync/atomic"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"
@@ -42,21 +43,23 @@ func (api *API) appearance(rw http.ResponseWriter, r *http.Request) {
}
type appearanceFetcher struct {
database database.Store
supportLinks []codersdk.LinkConfig
docsURL string
coderVersion string
database database.Store
supportLinks []codersdk.LinkConfig
docsURL string
coderVersion string
aiProvidersEnvDrift *atomic.Bool
}
func newAppearanceFetcher(store database.Store, links []codersdk.LinkConfig, docsURL, coderVersion string) agpl.Fetcher {
func newAppearanceFetcher(store database.Store, links []codersdk.LinkConfig, docsURL, coderVersion string, aiProvidersEnvDrift *atomic.Bool) agpl.Fetcher {
if docsURL == "" {
docsURL = codersdk.DefaultDocsURL()
}
return &appearanceFetcher{
database: store,
supportLinks: links,
docsURL: docsURL,
coderVersion: coderVersion,
database: store,
supportLinks: links,
docsURL: docsURL,
coderVersion: coderVersion,
aiProvidersEnvDrift: aiProvidersEnvDrift,
}
}
@@ -94,11 +97,12 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
}
cfg := codersdk.AppearanceConfig{
ApplicationName: applicationName,
LogoURL: logoURL,
AnnouncementBanners: []codersdk.BannerConfig{},
SupportLinks: codersdk.DefaultSupportLinks(f.docsURL),
DocsURL: f.docsURL,
ApplicationName: applicationName,
LogoURL: logoURL,
AnnouncementBanners: []codersdk.BannerConfig{},
SupportLinks: codersdk.DefaultSupportLinks(f.docsURL),
DocsURL: f.docsURL,
AIProvidersEnvDriftDetected: f.aiProvidersEnvDrift != nil && f.aiProvidersEnvDrift.Load(),
}
if announcementBannersJSON != "" {
+2 -1
View File
@@ -1014,10 +1014,11 @@ func (api *API) updateEntitlements(ctx context.Context) error {
api.DeploymentValues.Support.Links.Value,
api.DeploymentValues.DocsURL.String(),
buildinfo.Version(),
&api.AGPL.AIProvidersEnvDrift,
)
api.AGPL.AppearanceFetcher.Store(&f)
} else {
f := appearance.NewDefaultFetcher(api.DeploymentValues.DocsURL.String())
f := appearance.NewDefaultFetcher(api.DeploymentValues.DocsURL.String(), &api.AGPL.AIProvidersEnvDrift)
api.AGPL.AppearanceFetcher.Store(&f)
}
}
+1 -1
View File
@@ -88,7 +88,7 @@ type Options struct {
func New(opts *Options) (*Handler, error) {
if opts.AppearanceFetcher == nil {
daf := atomic.Pointer[appearance.Fetcher]{}
f := appearance.NewDefaultFetcher(opts.DocsURL)
f := appearance.NewDefaultFetcher(opts.DocsURL, nil)
daf.Store(&f)
opts.AppearanceFetcher = &daf
}
+1
View File
@@ -2393,6 +2393,7 @@ class ApiMethods {
service_banner: {
enabled: false,
},
ai_providers_env_drift_detected: false,
};
}
+7
View File
@@ -1132,6 +1132,13 @@ export interface AppearanceConfig {
readonly service_banner: BannerConfig;
readonly announcement_banners: readonly BannerConfig[];
readonly support_links?: readonly LinkConfig[];
/**
* AIProvidersEnvDriftDetected is true when deprecated CODER_AIBRIDGE_*
* env configuration differs from the AI provider rows already stored in
* the database, meaning those env changes are ineffective. It is
* output-only and is not part of UpdateAppearanceConfig.
*/
readonly ai_providers_env_drift_detected: boolean;
}
// From codersdk/templates.go
+1
View File
@@ -3392,6 +3392,7 @@ export const MockAppearanceConfig: TypesGen.AppearanceConfig = {
},
announcement_banners: [],
docs_url: "https://coder.com/docs/@main/",
ai_providers_env_drift_detected: false,
};
export const MockWorkspaceBuildParameter1: TypesGen.WorkspaceBuildParameter = {