Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43e4897d23 | |||
| fa0ce565b6 | |||
| 379ced672e | |||
| 971b1a87bd | |||
| 5133315792 | |||
| 683a7209b5 |
@@ -1674,7 +1674,7 @@ func (a *agent) manageProcessPriority(ctx context.Context, debouncer *logDebounc
|
||||
}
|
||||
|
||||
score, niceErr := proc.Niceness(a.syscaller)
|
||||
if !isBenignProcessErr(niceErr) {
|
||||
if niceErr != nil && !isBenignProcessErr(niceErr) {
|
||||
debouncer.Warn(ctx, "unable to get proc niceness",
|
||||
slog.F("cmd", proc.Cmd()),
|
||||
slog.F("pid", proc.PID),
|
||||
@@ -1693,7 +1693,7 @@ func (a *agent) manageProcessPriority(ctx context.Context, debouncer *logDebounc
|
||||
|
||||
if niceErr == nil {
|
||||
err := proc.SetNiceness(a.syscaller, niceness)
|
||||
if !isBenignProcessErr(err) {
|
||||
if err != nil && !isBenignProcessErr(err) {
|
||||
debouncer.Warn(ctx, "unable to set proc niceness",
|
||||
slog.F("cmd", proc.Cmd()),
|
||||
slog.F("pid", proc.PID),
|
||||
@@ -1707,7 +1707,7 @@ func (a *agent) manageProcessPriority(ctx context.Context, debouncer *logDebounc
|
||||
if oomScore != unsetOOMScore && oomScore != proc.OOMScoreAdj && !isCustomOOMScore(agentScore, proc) {
|
||||
oomScoreStr := strconv.Itoa(oomScore)
|
||||
err := afero.WriteFile(a.filesystem, fmt.Sprintf("/proc/%d/oom_score_adj", proc.PID), []byte(oomScoreStr), 0o644)
|
||||
if !isBenignProcessErr(err) {
|
||||
if err != nil && !isBenignProcessErr(err) {
|
||||
debouncer.Warn(ctx, "unable to set oom_score_adj",
|
||||
slog.F("cmd", proc.Cmd()),
|
||||
slog.F("pid", proc.PID),
|
||||
|
||||
@@ -20,7 +20,6 @@ func createOpts(t *testing.T) *coderdtest.Options {
|
||||
t.Helper()
|
||||
|
||||
dt := coderdtest.DeploymentValues(t)
|
||||
dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
|
||||
return &coderdtest.Options{
|
||||
DeploymentValues: dt,
|
||||
}
|
||||
|
||||
@@ -56,15 +56,16 @@ import (
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/sloghuman"
|
||||
"github.com/coder/coder/v2/coderd/entitlements"
|
||||
"github.com/coder/coder/v2/coderd/notifications/reports"
|
||||
"github.com/coder/coder/v2/coderd/runtimeconfig"
|
||||
"github.com/coder/pretty"
|
||||
"github.com/coder/quartz"
|
||||
"github.com/coder/retry"
|
||||
"github.com/coder/serpent"
|
||||
"github.com/coder/wgtunnel/tunnelsdk"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/entitlements"
|
||||
"github.com/coder/coder/v2/coderd/notifications/reports"
|
||||
"github.com/coder/coder/v2/coderd/runtimeconfig"
|
||||
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/cli/clilog"
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
@@ -679,10 +680,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
options.OIDCConfig = oc
|
||||
}
|
||||
|
||||
experiments := coderd.ReadExperiments(
|
||||
options.Logger, options.DeploymentValues.Experiments.Value(),
|
||||
)
|
||||
|
||||
// We'll read from this channel in the select below that tracks shutdown. If it remains
|
||||
// nil, that case of the select will just never fire, but it's important not to have a
|
||||
// "bare" read on this channel.
|
||||
@@ -946,6 +943,33 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
return xerrors.Errorf("write config url: %w", err)
|
||||
}
|
||||
|
||||
// Manage notifications.
|
||||
cfg := options.DeploymentValues.Notifications
|
||||
metrics := notifications.NewMetrics(options.PrometheusRegistry)
|
||||
helpers := templateHelpers(options)
|
||||
|
||||
// The enqueuer is responsible for enqueueing notifications to the given store.
|
||||
enqueuer, err := notifications.NewStoreEnqueuer(cfg, options.Database, helpers, logger.Named("notifications.enqueuer"), quartz.NewReal())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to instantiate notification store enqueuer: %w", err)
|
||||
}
|
||||
options.NotificationsEnqueuer = enqueuer
|
||||
|
||||
// The notification manager is responsible for:
|
||||
// - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
|
||||
// - keeping the store updated with status updates
|
||||
notificationsManager, err := notifications.NewManager(cfg, options.Database, helpers, metrics, logger.Named("notifications.manager"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to instantiate notification manager: %w", err)
|
||||
}
|
||||
|
||||
// nolint:gocritic // TODO: create own role.
|
||||
notificationsManager.Run(dbauthz.AsSystemRestricted(ctx))
|
||||
|
||||
// Run report generator to distribute periodic reports.
|
||||
notificationReportGenerator := reports.NewReportGenerator(ctx, logger.Named("notifications.report_generator"), options.Database, options.NotificationsEnqueuer, quartz.NewReal())
|
||||
defer notificationReportGenerator.Close()
|
||||
|
||||
// Since errCh only has one buffered slot, all routines
|
||||
// sending on it must be wrapped in a select/default to
|
||||
// avoid leaving dangling goroutines waiting for the
|
||||
@@ -1002,38 +1026,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
options.WorkspaceUsageTracker = tracker
|
||||
defer tracker.Close()
|
||||
|
||||
// Manage notifications.
|
||||
var (
|
||||
notificationsManager *notifications.Manager
|
||||
)
|
||||
if experiments.Enabled(codersdk.ExperimentNotifications) {
|
||||
cfg := options.DeploymentValues.Notifications
|
||||
metrics := notifications.NewMetrics(options.PrometheusRegistry)
|
||||
helpers := templateHelpers(options)
|
||||
|
||||
// The enqueuer is responsible for enqueueing notifications to the given store.
|
||||
enqueuer, err := notifications.NewStoreEnqueuer(cfg, options.Database, helpers, logger.Named("notifications.enqueuer"), quartz.NewReal())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to instantiate notification store enqueuer: %w", err)
|
||||
}
|
||||
options.NotificationsEnqueuer = enqueuer
|
||||
|
||||
// The notification manager is responsible for:
|
||||
// - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
|
||||
// - keeping the store updated with status updates
|
||||
notificationsManager, err = notifications.NewManager(cfg, options.Database, helpers, metrics, logger.Named("notifications.manager"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to instantiate notification manager: %w", err)
|
||||
}
|
||||
|
||||
// nolint:gocritic // TODO: create own role.
|
||||
notificationsManager.Run(dbauthz.AsSystemRestricted(ctx))
|
||||
|
||||
// Run report generator to distribute periodic reports.
|
||||
notificationReportGenerator := reports.NewReportGenerator(ctx, logger.Named("notifications.report_generator"), options.Database, options.NotificationsEnqueuer, quartz.NewReal())
|
||||
defer notificationReportGenerator.Close()
|
||||
}
|
||||
|
||||
// Wrap the server in middleware that redirects to the access URL if
|
||||
// the request is not to a local IP.
|
||||
var handler http.Handler = coderAPI.RootHandler
|
||||
@@ -1153,19 +1145,17 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
// Cancel any remaining in-flight requests.
|
||||
shutdownConns()
|
||||
|
||||
if notificationsManager != nil {
|
||||
// Stop the notification manager, which will cause any buffered updates to the store to be flushed.
|
||||
// If the Stop() call times out, messages that were sent but not reflected as such in the store will have
|
||||
// their leases expire after a period of time and will be re-queued for sending.
|
||||
// See CODER_NOTIFICATIONS_LEASE_PERIOD.
|
||||
cliui.Info(inv.Stdout, "Shutting down notifications manager..."+"\n")
|
||||
err = shutdownWithTimeout(notificationsManager.Stop, 5*time.Second)
|
||||
if err != nil {
|
||||
cliui.Warnf(inv.Stderr, "Notifications manager shutdown took longer than 5s, "+
|
||||
"this may result in duplicate notifications being sent: %s\n", err)
|
||||
} else {
|
||||
cliui.Info(inv.Stdout, "Gracefully shut down notifications manager\n")
|
||||
}
|
||||
// Stop the notification manager, which will cause any buffered updates to the store to be flushed.
|
||||
// If the Stop() call times out, messages that were sent but not reflected as such in the store will have
|
||||
// their leases expire after a period of time and will be re-queued for sending.
|
||||
// See CODER_NOTIFICATIONS_LEASE_PERIOD.
|
||||
cliui.Info(inv.Stdout, "Shutting down notifications manager..."+"\n")
|
||||
err = shutdownWithTimeout(notificationsManager.Stop, 5*time.Second)
|
||||
if err != nil {
|
||||
cliui.Warnf(inv.Stderr, "Notifications manager shutdown took longer than 5s, "+
|
||||
"this may result in duplicate notifications being sent: %s\n", err)
|
||||
} else {
|
||||
cliui.Info(inv.Stdout, "Gracefully shut down notifications manager\n")
|
||||
}
|
||||
|
||||
// Shut down provisioners before waiting for WebSockets
|
||||
|
||||
@@ -37,11 +37,12 @@ import (
|
||||
"tailscale.com/util/singleflight"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/quartz"
|
||||
"github.com/coder/serpent"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/entitlements"
|
||||
"github.com/coder/coder/v2/coderd/idpsync"
|
||||
"github.com/coder/coder/v2/coderd/runtimeconfig"
|
||||
"github.com/coder/quartz"
|
||||
"github.com/coder/serpent"
|
||||
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
@@ -1257,10 +1258,7 @@ func New(options *Options) *API {
|
||||
})
|
||||
})
|
||||
r.Route("/notifications", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentNotifications),
|
||||
)
|
||||
r.Use(apiKeyMiddleware)
|
||||
r.Get("/settings", api.notificationsSettings)
|
||||
r.Put("/settings", api.putNotificationsSettings)
|
||||
r.Route("/templates", func(r chi.Router) {
|
||||
|
||||
@@ -54,6 +54,7 @@ type Manager struct {
|
||||
|
||||
runOnce sync.Once
|
||||
stopOnce sync.Once
|
||||
doneOnce sync.Once
|
||||
stop chan any
|
||||
done chan any
|
||||
|
||||
@@ -153,7 +154,9 @@ func (m *Manager) Run(ctx context.Context) {
|
||||
// events, creating a notifier, and publishing bulk dispatch result updates to the store.
|
||||
func (m *Manager) loop(ctx context.Context) error {
|
||||
defer func() {
|
||||
close(m.done)
|
||||
m.doneOnce.Do(func() {
|
||||
close(m.done)
|
||||
})
|
||||
m.log.Info(context.Background(), "notification manager stopped")
|
||||
}()
|
||||
|
||||
@@ -364,7 +367,9 @@ func (m *Manager) Stop(ctx context.Context) error {
|
||||
// If the notifier hasn't been started, we don't need to wait for anything.
|
||||
// This is only really during testing when we want to enqueue messages only but not deliver them.
|
||||
if m.notifier == nil {
|
||||
close(m.done)
|
||||
m.doneOnce.Do(func() {
|
||||
close(m.done)
|
||||
})
|
||||
} else {
|
||||
m.notifier.stop()
|
||||
}
|
||||
|
||||
@@ -1187,7 +1187,6 @@ func createOpts(t *testing.T) *coderdtest.Options {
|
||||
t.Helper()
|
||||
|
||||
dt := coderdtest.DeploymentValues(t)
|
||||
dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
|
||||
return &coderdtest.Options{
|
||||
DeploymentValues: dt,
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func NewReportGenerator(ctx context.Context, logger slog.Logger, db database.Sto
|
||||
return nil
|
||||
}
|
||||
|
||||
err = reportFailedWorkspaceBuilds(ctx, logger, db, enqueuer, clk)
|
||||
err = reportFailedWorkspaceBuilds(ctx, logger, tx, enqueuer, clk)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to generate reports with failed workspace builds: %w", err)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ func createOpts(t *testing.T) *coderdtest.Options {
|
||||
t.Helper()
|
||||
|
||||
dt := coderdtest.DeploymentValues(t)
|
||||
dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
|
||||
return &coderdtest.Options{
|
||||
DeploymentValues: dt,
|
||||
}
|
||||
|
||||
@@ -804,8 +804,12 @@ func DefaultSupportLinks(docsURL string) []LinkConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func removeTrailingVersionInfo(v string) string {
|
||||
return strings.Split(strings.Split(v, "-")[0], "+")[0]
|
||||
}
|
||||
|
||||
func DefaultDocsURL() string {
|
||||
version := strings.Split(buildinfo.Version(), "-")[0]
|
||||
version := removeTrailingVersionInfo(buildinfo.Version())
|
||||
if version == "v0.0.0" {
|
||||
return "https://coder.com/docs"
|
||||
}
|
||||
@@ -2901,7 +2905,7 @@ const (
|
||||
// users to opt-in to via --experimental='*'.
|
||||
// Experiments that are not ready for consumption by all users should
|
||||
// not be included here and will be essentially hidden.
|
||||
var ExperimentsAll = Experiments{ExperimentNotifications}
|
||||
var ExperimentsAll = Experiments{}
|
||||
|
||||
// Experiments is a list of experiments.
|
||||
// Multiple experiments may be enabled at the same time.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package codersdk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRemoveTrailingVersionInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
Version string
|
||||
ExpectedAfterStrippingInfo string
|
||||
}{
|
||||
{
|
||||
Version: "v2.16.0+683a720",
|
||||
ExpectedAfterStrippingInfo: "v2.16.0",
|
||||
},
|
||||
{
|
||||
Version: "v2.16.0-devel+683a720",
|
||||
ExpectedAfterStrippingInfo: "v2.16.0",
|
||||
},
|
||||
{
|
||||
Version: "v2.16.0+683a720-devel",
|
||||
ExpectedAfterStrippingInfo: "v2.16.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
stripped := removeTrailingVersionInfo(tc.Version)
|
||||
require.Equal(t, tc.ExpectedAfterStrippingInfo, stripped)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/yamux"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
"nhooyr.io/websocket"
|
||||
|
||||
@@ -278,9 +280,11 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione
|
||||
type ProvisionerKeyTags map[string]string
|
||||
|
||||
func (p ProvisionerKeyTags) String() string {
|
||||
keys := maps.Keys(p)
|
||||
slices.Sort(keys)
|
||||
tags := []string{}
|
||||
for key, value := range p {
|
||||
tags = append(tags, fmt.Sprintf("%s=%s", key, value))
|
||||
for _, key := range keys {
|
||||
tags = append(tags, fmt.Sprintf("%s=%s", key, p[key]))
|
||||
}
|
||||
return strings.Join(tags, " ")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Appearance (enterprise)
|
||||
# Appearance (enterprise) (premium)
|
||||
|
||||
Customize the look of your Coder deployment to meet your enterprise
|
||||
requirements.
|
||||
@@ -93,7 +93,3 @@ For CLI, use,
|
||||
export CODER_SUPPORT_LINKS='[{"name": "Hello GitHub", "target": "https://github.com/coder/coder", "icon": "bug"}, {"name": "Hello Slack", "target": "https://codercom.slack.com/archives/C014JH42DBJ", "icon": "https://raw.githubusercontent.com/coder/coder/main/site/static/icon/slack.svg"}, {"name": "Hello Discord", "target": "https://discord.gg/coder", "icon": "https://raw.githubusercontent.com/coder/coder/main/site/static/icon/discord.svg"}, {"name": "Hello Foobar", "target": "https://discord.gg/coder", "icon": "/emojis/1f3e1.png"}]'
|
||||
coder-server
|
||||
```
|
||||
|
||||
## Up next
|
||||
|
||||
- [Enterprise](../enterprise.md)
|
||||
|
||||
@@ -122,5 +122,5 @@ entry:
|
||||
|
||||
## Enabling this feature
|
||||
|
||||
This feature is only available with an enterprise license.
|
||||
[Learn more](../enterprise.md)
|
||||
This feature is only available with a
|
||||
[Premium or Enterprise license](https://coder.com/pricing).
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Authentication
|
||||
|
||||
.
|
||||
|
||||
By default, Coder is accessible via password authentication. Coder does not
|
||||
recommend using password authentication in production, and recommends using an
|
||||
authentication provider with properly configured multi-factor authentication
|
||||
@@ -227,7 +225,7 @@ your Coder deployment:
|
||||
CODER_DISABLE_PASSWORD_AUTH=true
|
||||
```
|
||||
|
||||
## SCIM (enterprise)
|
||||
## SCIM (enterprise) (premium)
|
||||
|
||||
Coder supports user provisioning and deprovisioning via SCIM 2.0 with header
|
||||
authentication. Upon deactivation, users are
|
||||
@@ -249,36 +247,50 @@ CODER_TLS_CLIENT_CERT_FILE=/path/to/cert.pem
|
||||
CODER_TLS_CLIENT_KEY_FILE=/path/to/key.pem
|
||||
```
|
||||
|
||||
## Group Sync (enterprise)
|
||||
## Group Sync (enterprise) (premium)
|
||||
|
||||
If your OpenID Connect provider supports group claims, you can configure Coder
|
||||
to synchronize groups in your auth provider to groups within Coder.
|
||||
to synchronize groups in your auth provider to groups within Coder. To enable
|
||||
group sync, ensure that the `groups` claim is being sent by your OpenID
|
||||
provider. You might need to request an additional
|
||||
[scope](../reference/cli/server.md#--oidc-scopes) or additional configuration on
|
||||
the OpenID provider side.
|
||||
|
||||
To enable group sync, ensure that the `groups` claim is set by adding the
|
||||
correct scope to request. If group sync is enabled, the user's groups will be
|
||||
controlled by the OIDC provider. This means manual group additions/removals will
|
||||
be overwritten on the next login.
|
||||
If group sync is enabled, the user's groups will be controlled by the OIDC
|
||||
provider. This means manual group additions/removals will be overwritten on the
|
||||
next user login.
|
||||
|
||||
```env
|
||||
# as an environment variable
|
||||
CODER_OIDC_SCOPES=openid,profile,email,groups
|
||||
There are two ways you can configure group sync:
|
||||
|
||||
<div class="tabs">
|
||||
|
||||
## Server Flags
|
||||
|
||||
First, confirm that your OIDC provider is sending claims by logging in with OIDC
|
||||
and visiting the following URL with an `Owner` account:
|
||||
|
||||
```text
|
||||
https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
|
||||
```
|
||||
|
||||
```shell
|
||||
# as a flag
|
||||
--oidc-scopes openid,profile,email,groups
|
||||
```
|
||||
You should see a field in either `id_token_claims`, `user_info_claims` or both
|
||||
followed by a list of the user's OIDC groups in the response. This is the
|
||||
[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
|
||||
the OIDC provider. See
|
||||
[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
|
||||
|
||||
With the `groups` scope requested, we also need to map the `groups` claim name.
|
||||
Coder recommends using `groups` for the claim name. This step is necessary if
|
||||
your **scope's name** is something other than `groups`.
|
||||
> Depending on the OIDC provider, this claim may be named differently. Common
|
||||
> ones include `groups`, `memberOf`, and `roles`.
|
||||
|
||||
```env
|
||||
Next configure the Coder server to read groups from the claim name with the
|
||||
[OIDC group field](../reference/cli/server.md#--oidc-group-field) server flag:
|
||||
|
||||
```sh
|
||||
# as an environment variable
|
||||
CODER_OIDC_GROUP_FIELD=groups
|
||||
```
|
||||
|
||||
```shell
|
||||
```sh
|
||||
# as a flag
|
||||
--oidc-group-field groups
|
||||
```
|
||||
@@ -288,14 +300,16 @@ names in Coder and removed from groups that the user no longer belongs to.
|
||||
|
||||
For cases when an OIDC provider only returns group IDs ([Azure AD][azure-gids])
|
||||
or you want to have different group names in Coder than in your OIDC provider,
|
||||
you can configure mapping between the two.
|
||||
you can configure mapping between the two with the
|
||||
[OIDC group mapping](../reference/cli/server.md#--oidc-group-mapping) server
|
||||
flag.
|
||||
|
||||
```env
|
||||
```sh
|
||||
# as an environment variable
|
||||
CODER_OIDC_GROUP_MAPPING='{"myOIDCGroupID": "myCoderGroupName"}'
|
||||
```
|
||||
|
||||
```shell
|
||||
```sh
|
||||
# as a flag
|
||||
--oidc-group-mapping '{"myOIDCGroupID": "myCoderGroupName"}'
|
||||
```
|
||||
@@ -313,11 +327,103 @@ coder:
|
||||
From the example above, users that belong to the `myOIDCGroupID` group in your
|
||||
OIDC provider will be added to the `myCoderGroupName` group in Coder.
|
||||
|
||||
> **Note:** Groups are only updated on login.
|
||||
|
||||
[azure-gids]:
|
||||
https://github.com/MicrosoftDocs/azure-docs/issues/59766#issuecomment-664387195
|
||||
|
||||
## Runtime (Organizations)
|
||||
|
||||
> Note: You must have a Premium license with Organizations enabled to use this.
|
||||
> [Contact your account team](https://coder.com/contact) for more details
|
||||
|
||||
For deployments with multiple [organizations](./organizations.md), you must
|
||||
configure group sync at the organization level. In future Coder versions, you
|
||||
will be able to configure this in the UI. For now, you must use CLI commands.
|
||||
|
||||
First confirm you have the [Coder CLI](../install/index.md) installed and are
|
||||
logged in with a user who is an Owner or Organization Admin role. Next, confirm
|
||||
that your OIDC provider is sending a groups claim by logging in with OIDC and
|
||||
visiting the following URL:
|
||||
|
||||
```text
|
||||
https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
|
||||
```
|
||||
|
||||
You should see a field in either `id_token_claims`, `user_info_claims` or both
|
||||
followed by a list of the user's OIDC groups in the response. This is the
|
||||
[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
|
||||
the OIDC provider. See
|
||||
[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
|
||||
|
||||
> Depending on the OIDC provider, this claim may be named differently. Common
|
||||
> ones include `groups`, `memberOf`, and `roles`.
|
||||
|
||||
To fetch the current group sync settings for an organization, run the following:
|
||||
|
||||
```sh
|
||||
coder organizations settings show group-sync \
|
||||
--org <org-name> \
|
||||
> group-sync.json
|
||||
```
|
||||
|
||||
The default for an organization looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "",
|
||||
"mapping": null,
|
||||
"regex_filter": null,
|
||||
"auto_create_missing_groups": false
|
||||
}
|
||||
```
|
||||
|
||||
Below is an example that uses the `groups` claim and maps all groups prefixed by
|
||||
`coder-` into Coder:
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "groups",
|
||||
"mapping": null,
|
||||
"regex_filter": "^coder-.*$",
|
||||
"auto_create_missing_groups": true
|
||||
}
|
||||
```
|
||||
|
||||
> Note: You much specify Coder group IDs instead of group names. The fastest way
|
||||
> to find the ID for a corresponding group is by visiting
|
||||
> `https://coder.example.com/api/v2/groups`.
|
||||
|
||||
Here is another example which maps `coder-admins` from the identity provider to
|
||||
2 groups in Coder and `coder-users` from the identity provider to another group:
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "groups",
|
||||
"mapping": {
|
||||
"coder-admins": [
|
||||
"2ba2a4ff-ddfb-4493-b7cd-1aec2fa4c830",
|
||||
"93371154-150f-4b12-b5f0-261bb1326bb4"
|
||||
],
|
||||
"coder-users": ["2f4bde93-0179-4815-ba50-b757fb3d43dd"]
|
||||
},
|
||||
"regex_filter": null,
|
||||
"auto_create_missing_groups": false
|
||||
}
|
||||
```
|
||||
|
||||
To set these group sync settings, use the following command:
|
||||
|
||||
```sh
|
||||
coder organizations settings set group-sync \
|
||||
--org <org-name> \
|
||||
< group-sync.json
|
||||
```
|
||||
|
||||
Visit the Coder UI to confirm these changes:
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Group allowlist
|
||||
|
||||
You can limit which groups from your identity provider can log in to Coder with
|
||||
@@ -326,11 +432,36 @@ Users who are not in a matching group will see the following error:
|
||||
|
||||

|
||||
|
||||
## Role sync (enterprise)
|
||||
## Role sync (enterprise) (premium)
|
||||
|
||||
If your OpenID Connect provider supports roles claims, you can configure Coder
|
||||
to synchronize roles in your auth provider to deployment-wide roles within
|
||||
Coder.
|
||||
to synchronize roles in your auth provider to roles within Coder.
|
||||
|
||||
There are 2 ways to do role sync. Server Flags assign site wide roles, and
|
||||
runtime org role sync assigns organization roles
|
||||
|
||||
<div class="tabs">
|
||||
|
||||
## Server Flags
|
||||
|
||||
First, confirm that your OIDC provider is sending a roles claim by logging in
|
||||
with OIDC and visiting the following URL with an `Owner` account:
|
||||
|
||||
```text
|
||||
https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
|
||||
```
|
||||
|
||||
You should see a field in either `id_token_claims`, `user_info_claims` or both
|
||||
followed by a list of the user's OIDC roles in the response. This is the
|
||||
[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
|
||||
the OIDC provider. See
|
||||
[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
|
||||
|
||||
> Depending on the OIDC provider, this claim may be named differently.
|
||||
|
||||
Next configure the Coder server to read groups from the claim name with the
|
||||
[OIDC role field](../reference/cli/server.md#--oidc-user-role-field) server
|
||||
flag:
|
||||
|
||||
Set the following in your Coder server [configuration](./configure.md).
|
||||
|
||||
@@ -346,7 +477,136 @@ CODER_OIDC_USER_ROLE_MAPPING='{"TemplateAuthor":["template-admin","user-admin"]}
|
||||
> One role from your identity provider can be mapped to many roles in Coder
|
||||
> (e.g. the example above maps to 2 roles in Coder.)
|
||||
|
||||
## Troubleshooting group/role sync
|
||||
## Runtime (Organizations)
|
||||
|
||||
> Note: You must have a Premium license with Organizations enabled to use this.
|
||||
> [Contact your account team](https://coder.com/contact) for more details
|
||||
|
||||
For deployments with multiple [organizations](./organizations.md), you can
|
||||
configure role sync at the organization level. In future Coder versions, you
|
||||
will be able to configure this in the UI. For now, you must use CLI commands.
|
||||
|
||||
First, confirm that your OIDC provider is sending a roles claim by logging in
|
||||
with OIDC and visiting the following URL with an `Owner` account:
|
||||
|
||||
```text
|
||||
https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
|
||||
```
|
||||
|
||||
You should see a field in either `id_token_claims`, `user_info_claims` or both
|
||||
followed by a list of the user's OIDC roles in the response. This is the
|
||||
[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
|
||||
the OIDC provider. See
|
||||
[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
|
||||
|
||||
> Depending on the OIDC provider, this claim may be named differently.
|
||||
|
||||
To fetch the current group sync settings for an organization, run the following:
|
||||
|
||||
```sh
|
||||
coder organizations settings show role-sync \
|
||||
--org <org-name> \
|
||||
> role-sync.json
|
||||
```
|
||||
|
||||
The default for an organization looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "",
|
||||
"mapping": null
|
||||
}
|
||||
```
|
||||
|
||||
Below is an example that uses the `roles` claim and maps `coder-admins` from the
|
||||
IDP as an `Organization Admin` and also maps to a custom `provisioner-admin`
|
||||
role.
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "roles",
|
||||
"mapping": {
|
||||
"coder-admins": ["organization-admin"],
|
||||
"infra-admins": ["provisioner-admin"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: Be sure to use the `name` field for each role, not the display name. Use
|
||||
> `coder organization roles show --org=<your-org>` to see roles for your
|
||||
> organization.
|
||||
|
||||
To set these role sync settings, use the following command:
|
||||
|
||||
```sh
|
||||
coder organizations settings set role-sync \
|
||||
--org <org-name> \
|
||||
< role-sync.json
|
||||
```
|
||||
|
||||
Visit the Coder UI to confirm these changes:
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
## Organization Sync (Premium)
|
||||
|
||||
> Note: In a future Coder release, this can be managed via the Coder UI instead
|
||||
> of server flags.
|
||||
|
||||
If your OpenID Connect provider supports groups/role claims, you can configure
|
||||
Coder to synchronize claims in your auth provider to organizations within Coder.
|
||||
|
||||
First, confirm that your OIDC provider is sending clainms by logging in with
|
||||
OIDC and visiting the following URL with an `Owner` account:
|
||||
|
||||
```text
|
||||
https://[coder.example.com]/api/v2/debug/[your-username]/debug-link
|
||||
```
|
||||
|
||||
You should see a field in either `id_token_claims`, `user_info_claims` or both
|
||||
followed by a list of the user's OIDC groups in the response. This is the
|
||||
[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) sent by
|
||||
the OIDC provider. See
|
||||
[Troubleshooting](#troubleshooting-grouproleorganization-sync) to debug this.
|
||||
|
||||
> Depending on the OIDC provider, this claim may be named differently. Common
|
||||
> ones include `groups`, `memberOf`, and `roles`.
|
||||
|
||||
Next configure the Coder server to read groups from the claim name with the
|
||||
[OIDC organization field](../reference/cli/server.md#--oidc-organization-field)
|
||||
server flag:
|
||||
|
||||
```sh
|
||||
# as an environment variable
|
||||
CODER_OIDC_ORGANIZATION_FIELD=groups
|
||||
```
|
||||
|
||||
Next, fetch the corresponding organization IDs using the following endpoint:
|
||||
|
||||
```text
|
||||
https://[coder.example.com]/api/v2/organizations
|
||||
```
|
||||
|
||||
Set the following in your Coder server [configuration](./configure.md).
|
||||
|
||||
```env
|
||||
CODER_OIDC_ORGANIZATION_MAPPING='{"data-scientists":["d8d9daef-e273-49ff-a832-11fe2b2d4ab1", "70be0908-61b5-4fb5-aba4-4dfb3a6c5787"]}'
|
||||
```
|
||||
|
||||
> One claim value from your identity provider can be mapped to many
|
||||
> organizations in Coder (e.g. the example above maps to 2 organizations in
|
||||
> Coder.)
|
||||
|
||||
By default, all users are assigned to the default (first) organization. You can
|
||||
disable that with:
|
||||
|
||||
```env
|
||||
CODER_OIDC_ORGANIZATION_ASSIGN_DEFAULT=false
|
||||
```
|
||||
|
||||
## Troubleshooting group/role/organization sync
|
||||
|
||||
Some common issues when enabling group/role sync.
|
||||
|
||||
|
||||
@@ -195,10 +195,10 @@ Optionally, you can request custom scopes:
|
||||
CODER_EXTERNAL_AUTH_0_SCOPES="repo:read repo:write write:gpg_key"
|
||||
```
|
||||
|
||||
### Multiple External Providers (enterprise)
|
||||
### Multiple External Providers (enterprise) (premium)
|
||||
|
||||
Multiple providers are an Enterprise feature. [Learn more](../enterprise.md).
|
||||
Below is an example configuration with multiple providers.
|
||||
Multiple providers are an [Enterprise feature](https://coder.com/pricing). Below
|
||||
is an example configuration with multiple providers.
|
||||
|
||||
```env
|
||||
# Provider 1) github.com
|
||||
|
||||
@@ -9,5 +9,5 @@ access to specific templates. They can be defined via the Coder web UI,
|
||||
|
||||
## Enabling this feature
|
||||
|
||||
This feature is only available with an enterprise license.
|
||||
[Learn more](../enterprise.md)
|
||||
This feature is only available with a
|
||||
[Premium or Enterprise license](https://coder.com/pricing).
|
||||
|
||||
@@ -73,4 +73,3 @@ Then, increase the number of pods.
|
||||
|
||||
- [Networking](../networking/index.md)
|
||||
- [Kubernetes](../install/kubernetes.md)
|
||||
- [Enterprise](../enterprise.md)
|
||||
|
||||
@@ -231,7 +231,7 @@ notification is indicated on the right hand side of this table.
|
||||
|
||||

|
||||
|
||||
## Delivery Preferences (enterprise)
|
||||
## Delivery Preferences (enterprise) (premium)
|
||||
|
||||
Administrators can configure which delivery methods are used for each different
|
||||
[event type](#event-types).
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# Organizations (Premium)
|
||||
|
||||
> Note: Organizations requires a [Premium license](../licensing.md). For more
|
||||
> details, [contact your account team](https://coder.com/contact).
|
||||
|
||||
Organizations can be used to segment and isolate resources inside a Coder
|
||||
deployment for different user groups or projects.
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example of how one could use organizations to run a Coder deployment
|
||||
with multiple platform teams, all with unique resources:
|
||||
|
||||

|
||||
|
||||
## The default organization
|
||||
|
||||
All Coder deployments start with one organization called `Coder`.
|
||||
|
||||
To edit the organization details, navigate to `Deployment -> Organizations` in
|
||||
the top bar:
|
||||
|
||||

|
||||
|
||||
From there, you can manage the name, icon, description, users, and groups:
|
||||
|
||||

|
||||
|
||||
## Additional organizations
|
||||
|
||||
Any additional organizations have unique admins, users, templates, provisioners,
|
||||
groups, and workspaces. Each organization must have at least one
|
||||
[provisioner](./provisioners.md) as the built-in provisioner only applies to the
|
||||
default organization.
|
||||
|
||||
You can configure [organization/role/group sync](./auth.md) from your identity
|
||||
provider to avoid manually assigning users to organizations.
|
||||
|
||||
## Creating an organization
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Coder v2.16+ deployment with Premium license with Organizations enabled
|
||||
([contact your account team](https://coder.com/contact)) for more details.
|
||||
- User with `Owner` role
|
||||
|
||||
### 1. Create the organization
|
||||
|
||||
Within the sidebar, click `New organization` to create an organization. In this
|
||||
example, we'll create the `data-platform` org.
|
||||
|
||||

|
||||
|
||||
From there, let's deploy a provisioner and template for this organization.
|
||||
|
||||
### 2. Deploy a provisioner
|
||||
|
||||
[Provisioners](../admin/provisioners.md) are organization-scoped and are
|
||||
responsible for executing Terraform/OpenTofu to provision the infrastructure for
|
||||
workspaces and testing templates. Before creating templates, we must deploy at
|
||||
least one provisioner as the built-in provisioners are scoped to the default
|
||||
organization.
|
||||
|
||||
Using Coder CLI, run the following command to create a key that will be used to
|
||||
authenticate the provisioner:
|
||||
|
||||
```sh
|
||||
coder provisioner keys create data-cluster-key --org data-platform
|
||||
Successfully created provisioner key data-cluster! Save this authentication token, it will not be shown again.
|
||||
|
||||
< key omitted >
|
||||
```
|
||||
|
||||
Next, start the provisioner with the key on your desired platform. In this
|
||||
example, we'll start it using the Coder CLI on a host with Docker. For
|
||||
instructions on using other platforms like Kubernetes, see our
|
||||
[provisioner documentation](../admin/provisioners.md).
|
||||
|
||||
```sh
|
||||
export CODER_URL=https://<your-coder-url>
|
||||
export CODER_PROVISIONER_DAEMON_KEY=<key>
|
||||
coder provisionerd start --org <org-name>
|
||||
```
|
||||
|
||||
### 3. Create a template
|
||||
|
||||
Once you've started a provisioner, you can create a template. You'll notice the
|
||||
"Create Template" screen now has an organization dropdown:
|
||||
|
||||

|
||||
|
||||
### 5. Add members
|
||||
|
||||
Navigate to `Deployment->Organizations` to add members to your organization.
|
||||
Once added, they will be able to see the organization-specific templates.
|
||||
|
||||

|
||||
|
||||
### 6. Create a workspace
|
||||
|
||||
Now, users in the data platform organization will see the templates related to
|
||||
their organization. Users can be in multiple organizations.
|
||||
|
||||

|
||||
|
||||
## Beta
|
||||
|
||||
Organizations is in beta. If you encounter any issues, please
|
||||
[file an issue](https://github.com/coder/coder/issues/new) or contact your
|
||||
account team.
|
||||
@@ -3,10 +3,10 @@
|
||||
By default, the Coder server runs
|
||||
[built-in provisioner daemons](../reference/cli/server.md#provisioner-daemons),
|
||||
which execute `terraform` during workspace and template builds. However, there
|
||||
are sometimes benefits to running external provisioner daemons:
|
||||
are often benefits to running external provisioner daemons:
|
||||
|
||||
- **Secure build environments:** Run build jobs in isolated containers,
|
||||
preventing malicious templates from gaining shell access to the Coder host.
|
||||
preventing malicious templates from gaining sh access to the Coder host.
|
||||
|
||||
- **Isolate APIs:** Deploy provisioners in isolated environments (on-prem, AWS,
|
||||
Azure) instead of exposing APIs (Docker, Kubernetes, VMware) to the Coder
|
||||
@@ -20,82 +20,101 @@ are sometimes benefits to running external provisioner daemons:
|
||||
times from the Coder server. See
|
||||
[Scaling Coder](scaling/scale-utility.md#recent-scale-tests) for more details.
|
||||
|
||||
Each provisioner can run a single
|
||||
[concurrent workspace build](scaling/scale-testing.md#control-plane-provisionerd).
|
||||
Each provisioner runs a single
|
||||
[concurrent workspace build](scaling/scale-testing.md#control-plane-provisioner).
|
||||
For example, running 30 provisioner containers will allow 30 users to start
|
||||
workspaces at the same time.
|
||||
|
||||
Provisioners are started with the
|
||||
[coder provisionerd start](../reference/cli/provisioner_start.md) command.
|
||||
[`coder provisioner start`](../reference/cli/provisioner_start.md) command in
|
||||
the [full Coder binary](https://github.com/coder/coder/releases). Keep reading
|
||||
to learn how to start provisioners via Docker, Kubernetes, Systemd, etc.
|
||||
|
||||
## Authentication
|
||||
|
||||
The provisioner daemon must authenticate with your Coder deployment.
|
||||
The provisioner daemon must authenticate with your Coder deployment. If you have
|
||||
multiple [organizations](./organizations.md), you'll need at least 1 provisioner
|
||||
running for each organization.
|
||||
|
||||
Set a
|
||||
<div class="tabs">
|
||||
|
||||
## Scoped Key (Recommended)
|
||||
|
||||
We recommend creating finely-scoped keys for provisioners. Keys are scoped to an
|
||||
organization.
|
||||
|
||||
```sh
|
||||
coder provisioner keys create my-key \
|
||||
--org default
|
||||
|
||||
Successfully created provisioner key my-key! Save this authentication token, it will not be shown again.
|
||||
|
||||
<key omitted>
|
||||
```
|
||||
|
||||
Or, restrict the provisioner to jobs with specific tags
|
||||
|
||||
```sh
|
||||
coder provisioner keys create kubernetes-key \
|
||||
--org default \
|
||||
--tag environment=kubernetes
|
||||
|
||||
Successfully created provisioner key kubernetes-key! Save this authentication token, it will not be shown again.
|
||||
|
||||
<key omitted>
|
||||
```
|
||||
|
||||
To start the provisioner:
|
||||
|
||||
```sh
|
||||
export CODER_URL=https://<your-coder-url>
|
||||
export CODER_PROVISIONER_DAEMON_KEY=<key>
|
||||
coder provisioner start
|
||||
```
|
||||
|
||||
Keep reading to see instructions for running provisioners on
|
||||
Kubernetes/Docker/etc.
|
||||
|
||||
## User Tokens
|
||||
|
||||
A user account with the role `Template Admin` or `Owner` can start provisioners
|
||||
using their user account. This may be beneficial if you are running provisioners
|
||||
via [automation](./automation.md).
|
||||
|
||||
```sh
|
||||
coder login https://<your-coder-url>
|
||||
coder provisioner start
|
||||
```
|
||||
|
||||
To start a provisioner with specific tags:
|
||||
|
||||
```sh
|
||||
coder login https://<your-coder-url>
|
||||
coder provisioner start \
|
||||
--tag environment=kubernetes
|
||||
```
|
||||
|
||||
Note: Any user can start [user-scoped provisioners](#User-scoped-Provisioners),
|
||||
but this will also require a template on your deployment with the corresponding
|
||||
tags.
|
||||
|
||||
## Global PSK
|
||||
|
||||
A deployment-wide PSK can be used to authenticate any provisioner. We do not
|
||||
recommend this approach anymore, as it makes key rotation or isolating
|
||||
provisioners far more difficult. To use a global PSK, set a
|
||||
[provisioner daemon pre-shared key (PSK)](../reference/cli/server.md#--provisioner-daemon-psk)
|
||||
on the Coder server and start the provisioner with
|
||||
`coder provisionerd start --psk <your-psk>`. If you are
|
||||
[installing with Helm](../install/kubernetes.md#install-coder-with-helm), see
|
||||
the [Helm example](#example-running-an-external-provisioner-with-helm) below.
|
||||
on the Coder server.
|
||||
|
||||
> Coder still supports authenticating the provisioner daemon with a
|
||||
> [token](../reference/cli/README.md#--token) from a user with the Template
|
||||
> Admin or Owner role. This method is deprecated in favor of the PSK, which only
|
||||
> has permission to access provisioner daemon APIs. We recommend migrating to
|
||||
> the PSK as soon as practical.
|
||||
Next, start the provisioner:
|
||||
|
||||
## Types of provisioners
|
||||
|
||||
Provisioners can broadly be categorized by scope: `organization` or `user`. The
|
||||
scope of a provisioner can be specified with
|
||||
[`-tag=scope=<scope>`](../reference/cli/provisioner_start.md#t---tag) when
|
||||
starting the provisioner daemon. Only users with at least the
|
||||
[Template Admin](../admin/users.md#roles) role or higher may create
|
||||
organization-scoped provisioner daemons.
|
||||
|
||||
There are two exceptions:
|
||||
|
||||
- [Built-in provisioners](../reference/cli/server.md#provisioner-daemons) are
|
||||
always organization-scoped.
|
||||
- External provisioners started using a
|
||||
[pre-shared key (PSK)](../reference/cli/provisioner_start.md#psk) are always
|
||||
organization-scoped.
|
||||
|
||||
### Organization-Scoped Provisioners
|
||||
|
||||
**Organization-scoped Provisioners** can pick up build jobs created by any user.
|
||||
These provisioners always have the implicit tags `scope=organization owner=""`.
|
||||
|
||||
```shell
|
||||
coder provisionerd start --org <organization_name>
|
||||
```sh
|
||||
coder provisioner start --psk <your-psk>
|
||||
```
|
||||
|
||||
If you omit the `--org` argument, the provisioner will be assigned to the
|
||||
default organization.
|
||||
</div>
|
||||
|
||||
```shell
|
||||
coder provisionerd start
|
||||
```
|
||||
|
||||
### User-scoped Provisioners
|
||||
|
||||
**User-scoped Provisioners** can only pick up build jobs created from
|
||||
user-tagged templates. Unlike the other provisioner types, any Coder user can
|
||||
run user provisioners, but they have no impact unless there exists at least one
|
||||
template with the `scope=user` provisioner tag.
|
||||
|
||||
```shell
|
||||
coder provisionerd start \
|
||||
--tag scope=user
|
||||
|
||||
# In another terminal, create/push
|
||||
# a template that requires user provisioners
|
||||
coder templates push on-prem \
|
||||
--provisioner-tag scope=user
|
||||
```
|
||||
|
||||
### Provisioner Tags
|
||||
## Provisioner Tags
|
||||
|
||||
You can use **provisioner tags** to control which provisioners can pick up build
|
||||
jobs from templates (and corresponding workspaces) with matching explicit tags.
|
||||
@@ -110,10 +129,10 @@ automatically.
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
```sh
|
||||
# Start a provisioner with the explicit tags
|
||||
# environment=on_prem and datacenter=chicago
|
||||
coder provisionerd start \
|
||||
coder provisioner start \
|
||||
--tag environment=on_prem \
|
||||
--tag datacenter=chicago
|
||||
|
||||
@@ -129,6 +148,10 @@ coder templates push on-prem-chicago \
|
||||
--provisioner-tag datacenter=chicago
|
||||
```
|
||||
|
||||
Alternatively, a template can target a provisioner via
|
||||
[workspace tags](https://github.com/coder/coder/tree/main/examples/workspace-tags)
|
||||
inside the Terraform.
|
||||
|
||||
A provisioner can run a given build job if one of the below is true:
|
||||
|
||||
1. A job with no explicit tags can only be run on a provisioner with no explicit
|
||||
@@ -176,9 +199,59 @@ This is illustrated in the below table:
|
||||
> copy the output:
|
||||
>
|
||||
> ```
|
||||
> go test -v -count=1 ./coderd/provisionerdserver/ -test.run='^TestAcquirer_MatchTags/GenTable$'
|
||||
> go test -v -count=1 ./coderd/provisionerserver/ -test.run='^TestAcquirer_MatchTags/GenTable$'
|
||||
> ```
|
||||
|
||||
## Types of provisioners
|
||||
|
||||
Provisioners can broadly be categorized by scope: `organization` or `user`. The
|
||||
scope of a provisioner can be specified with
|
||||
[`-tag=scope=<scope>`](../reference/cli/provisioner_start.md#t---tag) when
|
||||
starting the provisioner daemon. Only users with at least the
|
||||
[Template Admin](../admin/users.md#roles) role or higher may create
|
||||
organization-scoped provisioner daemons.
|
||||
|
||||
There are two exceptions:
|
||||
|
||||
- [Built-in provisioners](../reference/cli/server.md#provisioner-daemons) are
|
||||
always organization-scoped.
|
||||
- External provisioners started using a
|
||||
[pre-shared key (PSK)](../reference/cli/provisioner_start.md#psk) are always
|
||||
organization-scoped.
|
||||
|
||||
### Organization-Scoped Provisioners
|
||||
|
||||
**Organization-scoped Provisioners** can pick up build jobs created by any user.
|
||||
These provisioners always have the implicit tags `scope=organization owner=""`.
|
||||
|
||||
```sh
|
||||
coder provisioner start --org <organization_name>
|
||||
```
|
||||
|
||||
If you omit the `--org` argument, the provisioner will be assigned to the
|
||||
default organization.
|
||||
|
||||
```sh
|
||||
coder provisioner start
|
||||
```
|
||||
|
||||
### User-scoped Provisioners
|
||||
|
||||
**User-scoped Provisioners** can only pick up build jobs created from
|
||||
user-tagged templates. Unlike the other provisioner types, any Coder user can
|
||||
run user provisioners, but they have no impact unless there exists at least one
|
||||
template with the `scope=user` provisioner tag.
|
||||
|
||||
```sh
|
||||
coder provisioner start \
|
||||
--tag scope=user
|
||||
|
||||
# In another terminal, create/push
|
||||
# a template that requires user provisioners
|
||||
coder templates push on-prem \
|
||||
--provisioner-tag scope=user
|
||||
```
|
||||
|
||||
## Example: Running an external provisioner with Helm
|
||||
|
||||
Coder provides a Helm chart for running external provisioner daemons, which you
|
||||
@@ -187,21 +260,21 @@ will use in concert with the Helm chart for deploying the Coder server.
|
||||
1. Create a long, random pre-shared key (PSK) and store it in a Kubernetes
|
||||
secret
|
||||
|
||||
```shell
|
||||
```sh
|
||||
kubectl create secret generic coder-provisioner-psk --from-literal=psk=`head /dev/urandom | base64 | tr -dc A-Za-z0-9 | head -c 26`
|
||||
```
|
||||
|
||||
1. Modify your Coder `values.yaml` to include
|
||||
|
||||
```yaml
|
||||
provisionerDaemon:
|
||||
provisioneraemon:
|
||||
pskSecretName: "coder-provisioner-psk"
|
||||
```
|
||||
|
||||
1. Redeploy Coder with the new `values.yaml` to roll out the PSK. You can omit
|
||||
`--version <your version>` to also upgrade Coder to the latest version.
|
||||
|
||||
```shell
|
||||
```sh
|
||||
helm upgrade coder coder-v2/coder \
|
||||
--namespace coder \
|
||||
--version <your version> \
|
||||
@@ -217,7 +290,7 @@ will use in concert with the Helm chart for deploying the Coder server.
|
||||
- name: CODER_URL
|
||||
value: "https://coder.example.com"
|
||||
replicaCount: 10
|
||||
provisionerDaemon:
|
||||
provisioneraemon:
|
||||
pskSecretName: "coder-provisioner-psk"
|
||||
tags:
|
||||
location: auh
|
||||
@@ -235,7 +308,7 @@ will use in concert with the Helm chart for deploying the Coder server.
|
||||
|
||||
1. Install the provisioner daemon chart
|
||||
|
||||
```shell
|
||||
```sh
|
||||
helm install coder-provisioner coder-v2/coder-provisioner \
|
||||
--namespace coder \
|
||||
--version <your version> \
|
||||
@@ -244,26 +317,26 @@ will use in concert with the Helm chart for deploying the Coder server.
|
||||
|
||||
You can verify that your provisioner daemons have successfully connected to
|
||||
Coderd by looking for a debug log message that says
|
||||
`provisionerd: successfully connected to coderd` from each Pod.
|
||||
`provisioner: successfully connected to coderd` from each Pod.
|
||||
|
||||
## Example: Running an external provisioner on a VM
|
||||
|
||||
```shell
|
||||
```sh
|
||||
curl -L https://coder.com/install.sh | sh
|
||||
export CODER_URL=https://coder.example.com
|
||||
export CODER_SESSION_TOKEN=your_token
|
||||
coder provisionerd start
|
||||
coder provisioner start
|
||||
```
|
||||
|
||||
## Example: Running an external provisioner via Docker
|
||||
|
||||
```shell
|
||||
```sh
|
||||
docker run --rm -it \
|
||||
-e CODER_URL=https://coder.example.com/ \
|
||||
-e CODER_SESSION_TOKEN=your_token \
|
||||
--entrypoint /opt/coder \
|
||||
ghcr.io/coder/coder:latest \
|
||||
provisionerd start
|
||||
provisioner start
|
||||
```
|
||||
|
||||
## Disable built-in provisioners
|
||||
@@ -272,7 +345,7 @@ As mentioned above, the Coder server will run built-in provisioners by default.
|
||||
This can be disabled with a server-wide
|
||||
[flag or environment variable](../reference/cli/server.md#provisioner-daemons).
|
||||
|
||||
```shell
|
||||
```sh
|
||||
coder server --provisioner-daemons=0
|
||||
```
|
||||
|
||||
|
||||
@@ -102,5 +102,4 @@ Form will never get held up by quota enforcement.
|
||||
|
||||
## Up next
|
||||
|
||||
- [Enterprise](../enterprise.md)
|
||||
- [Configuring](./configure.md)
|
||||
|
||||
@@ -19,5 +19,5 @@ You can set the following permissions:
|
||||
|
||||
## Enabling this feature
|
||||
|
||||
This feature is only available with an enterprise license.
|
||||
[Learn more](../enterprise.md)
|
||||
This feature is only available with an
|
||||
[Enterprise or Premium license](https://coder.com/pricing).
|
||||
|
||||
@@ -53,7 +53,3 @@ from Winget.
|
||||
```pwsh
|
||||
winget install Coder.Coder
|
||||
```
|
||||
|
||||
## Up Next
|
||||
|
||||
- [Learn how to enable Enterprise features](../enterprise.md).
|
||||
|
||||
@@ -10,7 +10,7 @@ Coder offers these user roles in the community edition:
|
||||
| | Auditor | User Admin | Template Admin | Owner |
|
||||
| ----------------------------------------------------- | ------- | ---------- | -------------- | ----- |
|
||||
| Add and remove Users | | ✅ | | ✅ |
|
||||
| Manage groups (enterprise) | | ✅ | | ✅ |
|
||||
| Manage groups (premium) | | ✅ | | ✅ |
|
||||
| Change User roles | | | | ✅ |
|
||||
| Manage **ALL** Templates | | | ✅ | ✅ |
|
||||
| View **ALL** Workspaces | | | ✅ | ✅ |
|
||||
@@ -22,6 +22,16 @@ Coder offers these user roles in the community edition:
|
||||
A user may have one or more roles. All users have an implicit Member role that
|
||||
may use personal workspaces.
|
||||
|
||||
## Custom Roles (Premium) (Beta)
|
||||
|
||||
Coder v2.16+ deployments can configure custom roles on the
|
||||
[Organization](./organizations.md) level.
|
||||
|
||||

|
||||
|
||||
> Note: This requires a Premium license.
|
||||
> [Contact your account team](https://coder.com/contact) for more details.
|
||||
|
||||
## Security notes
|
||||
|
||||
A malicious Template Admin could write a template that executes commands on the
|
||||
|
||||
@@ -64,7 +64,7 @@ ben@coder.com!
|
||||
Stream Kubernetes event logs to the Coder agent logs to reveal Kuernetes-level
|
||||
issues such as ResourceQuota limitations, invalid images, etc.
|
||||

|
||||
- [OIDC Role Sync](https://coder.com/docs/admin/auth#group-sync-enterprise)
|
||||
- [OIDC Role Sync](https://coder.com/docs/admin/auth#group-sync-enterprise-premium)
|
||||
(Enterprise): Sync roles from your OIDC provider to Coder roles (e.g.
|
||||
`Template Admin`) (#8595) (@Emyrk)
|
||||
- Users can convert their accounts from username/password authentication to SSO
|
||||
|
||||
@@ -1,21 +1,34 @@
|
||||
# Feature stages
|
||||
|
||||
Some Coder features are released as Alpha or Experimental.
|
||||
Some Coder features are released in feature stages before they are generally
|
||||
available.
|
||||
|
||||
## Alpha features
|
||||
If you encounter an issue with any Coder feature, please submit a
|
||||
[GitHub issues](https://github.com/coder/coder/issues) or join the
|
||||
[Coder Discord](https://discord.gg/coder).
|
||||
|
||||
Alpha features are enabled in all Coder deployments but the feature is subject
|
||||
to change, or even be removed. Breaking changes may not be documented in the
|
||||
changelog. In most cases, features will only stay in alpha for 1 month.
|
||||
## Early access features
|
||||
|
||||
We recommend using [GitHub issues](https://github.com/coder/coder/issues) to
|
||||
leave feedback and get support for alpha features.
|
||||
Early access features are neither feature-complete nor stable. We do not
|
||||
recommend using early access features in production deployments.
|
||||
|
||||
Coder releases early access features behind an “unsafe” experiment, where
|
||||
they’re accessible but not easy to find.
|
||||
|
||||
## Experimental features
|
||||
|
||||
These features are disabled by default, and not recommended for use in
|
||||
production as they may cause performance or stability issues. In most cases,
|
||||
features will only stay in experimental for 1-2 weeks of internal testing.
|
||||
experimental features are complete, but require further internal testing and
|
||||
will stay in the experimental stage for one month.
|
||||
|
||||
Coder may make significant changes to experiments or revert features to a
|
||||
feature flag at any time.
|
||||
|
||||
If you plan to activate an experimental feature, we suggest that you use a
|
||||
staging deployment.
|
||||
|
||||
You can opt-out of an experiment after you've enabled it.
|
||||
|
||||
```yaml
|
||||
# Enable all experimental features
|
||||
@@ -27,7 +40,7 @@ coder server --experiments=feature1,feature2
|
||||
# Alternatively, use the `CODER_EXPERIMENTS` environment variable.
|
||||
```
|
||||
|
||||
## Available experimental features
|
||||
### Available experimental features
|
||||
|
||||
<!-- Code generated by scripts/release/docs_update_experiments.sh. DO NOT EDIT. -->
|
||||
<!-- BEGIN: available-experimental-features -->
|
||||
@@ -37,3 +50,14 @@ coder server --experiments=feature1,feature2
|
||||
| `notifications` | Sends notifications via SMTP and webhooks following certain events. | mainline, stable |
|
||||
|
||||
<!-- END: available-experimental-features -->
|
||||
|
||||
## Beta
|
||||
|
||||
Beta features are open to the public, but are tagged with a `Beta` label.
|
||||
|
||||
They’re subject to minor changes and may contain bugs, but are generally ready
|
||||
for use.
|
||||
|
||||
## General Availability (GA)
|
||||
|
||||
All other features have been tested, are stable, and are enabled by default.
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# Enterprise Features
|
||||
|
||||
Coder is free to use and includes some features that are only accessible with a
|
||||
paid license. [Contact Sales](https://coder.com/contact) for pricing or
|
||||
[get a free trial](https://coder.com/trial).
|
||||
|
||||
| Category | Feature | Open Source | Enterprise |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------- | :---------: | :--------: |
|
||||
| Support | Email, Prioritization | ❌ | ✅ |
|
||||
| Scale | [High Availability](./admin/high-availability.md) | ❌ | ✅ |
|
||||
| Scale | [Multiple External Auth Providers](./admin/external-auth.md#multiple-external-providers-enterprise) | ❌ | ✅ |
|
||||
| Scale | [Isolated Terraform Runners](./admin/provisioners.md) | ❌ | ✅ |
|
||||
| Scale | [Workspace Proxies](./admin/workspace-proxies.md) | ❌ | ✅ |
|
||||
| Governance | [Audit Logging](./admin/audit-logs.md) | ❌ | ✅ |
|
||||
| Governance | [Browser Only Connections](./networking/#browser-only-connections-enterprise) | ❌ | ✅ |
|
||||
| Governance | [Groups & Template RBAC](./admin/rbac.md) | ❌ | ✅ |
|
||||
| Cost Control | [Quotas](./admin/quotas.md) | ❌ | ✅ |
|
||||
| Cost Control | [Max Workspace Lifetime](./workspaces.md#max-lifetime) | ❌ | ✅ |
|
||||
| User Management | [Groups](./admin/groups.md) | ❌ | ✅ |
|
||||
| User Management | [Group & role sync](./admin/auth.md#group-sync-enterprise) | ❌ | ✅ |
|
||||
| User Management | [SCIM](./admin/auth.md#scim) | ❌ | ✅ |
|
||||
|
||||
## Adding your license key
|
||||
|
||||
There are two ways to add an enterprise license to a Coder deployment: In the
|
||||
Coder UI or with the Coder CLI.
|
||||
|
||||
### Coder UI
|
||||
|
||||
Click Deployment, Licenses, Add a license then drag or select the license file
|
||||
with the `jwt` extension.
|
||||
|
||||

|
||||
|
||||
### Coder CLI
|
||||
|
||||
### Requirements
|
||||
|
||||
- Your license key
|
||||
- Coder CLI installed
|
||||
|
||||
### Instructions
|
||||
|
||||
1. Save your license key to disk and make note of the path
|
||||
2. Open a terminal
|
||||
3. Ensure you are logged into your Coder deployment
|
||||
|
||||
`coder login <access url>`
|
||||
|
||||
4. Run
|
||||
|
||||
`coder licenses add -f <path to your license key>`
|
||||
|
||||
## Up Next
|
||||
|
||||
- [Learn how to contribute to Coder](./CONTRIBUTING.md).
|
||||
@@ -1,14 +1,13 @@
|
||||
# FAQs
|
||||
|
||||
Frequently asked questions on Coder OSS and Enterprise deployments. These FAQs
|
||||
come from our community and enterprise customers, feel free to
|
||||
Frequently asked questions on Coder OSS and Premium deployments. These FAQs come
|
||||
from our community and enterprise customers, feel free to
|
||||
[contribute to this page](https://github.com/coder/coder/edit/main/docs/faqs.md).
|
||||
|
||||
### How do I add an enterprise license?
|
||||
### How do I add a Premium trial license?
|
||||
|
||||
Visit https://coder.com/trial or contact
|
||||
[sales@coder.com](mailto:sales@coder.com?subject=License) to get a v2 enterprise
|
||||
trial key.
|
||||
[sales@coder.com](mailto:sales@coder.com?subject=License) to get a trial key.
|
||||
|
||||
You can add a license through the UI or CLI.
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ be sent.
|
||||
|
||||
Configure Coder to use these claims for group sync. These claims are present in
|
||||
the `id_token`. See all configuration options for group sync in the
|
||||
[docs](https://coder.com/docs/admin/auth#group-sync-enterprise).
|
||||
[docs](https://coder.com/docs/admin/auth#group-sync-enterprise-premium).
|
||||
|
||||
```bash
|
||||
# Add the 'groups' scope.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Guides and Tutorials
|
||||
|
||||
Here you can find a list of employee-written guides on Coder for OSS and
|
||||
Enterprise. These tutorials are hosted on our
|
||||
Premium. These tutorials are hosted on our
|
||||
[Github](https://github.com/coder/coder/) where you can leave feedback or
|
||||
request new topics to be covered.
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
# Using Organizations
|
||||
|
||||
> Note: Organizations is still under active development and requires a
|
||||
> non-standard enterprise license to use. Do not use organizations on your
|
||||
> production instance!
|
||||
>
|
||||
> For more details, [contact your account team](https://coder.com/contact).
|
||||
|
||||
Organizations allow you to run a Coder deployment with multiple platform teams,
|
||||
all with uniquely scoped templates, provisioners, users, groups, and workspaces.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Coder deployment with non-standard license with Organizations enabled
|
||||
([contact your account team](https://coder.com/contact))
|
||||
- User with `Owner` role
|
||||
- Coder CLI installed on local machine
|
||||
|
||||
## Switch to the preview image and enable the experiment
|
||||
|
||||
To try the latest organizations features, switch to a preview image in your Helm
|
||||
chart and enable the
|
||||
[experimental flag](../reference/cli/server.md#--experiments).
|
||||
|
||||
For example, with Kubernetes, set the following in your `values.yaml`:
|
||||
|
||||
```yaml
|
||||
coderd:
|
||||
image:
|
||||
repo: ghcr.io/coder/coder-preview
|
||||
tag: orgs-preview-aug-16
|
||||
env:
|
||||
- name: CODER_EXPERIMENTS
|
||||
value: multi-organization
|
||||
```
|
||||
|
||||
> See all
|
||||
> [preview images](https://github.com/coder/coder/pkgs/container/coder-preview)
|
||||
> in GitHub. Preview images prefixed with `main-` expire after a week.
|
||||
|
||||
Then, upgrade your deployment:
|
||||
|
||||
```sh
|
||||
helm upgrade coder coder-v2/coder -f values.yaml
|
||||
```
|
||||
|
||||
## The default organization
|
||||
|
||||
All Coder deployments start with one organization called `Default`.
|
||||
|
||||
To edit the organization details, navigate to `Deployment -> Organizations` in
|
||||
the top bar:
|
||||
|
||||

|
||||
|
||||
From there, you can manage the name, icon, description, users, and groups:
|
||||
|
||||

|
||||
|
||||
## Guide: Your first organization
|
||||
|
||||
### 1. Create the organization
|
||||
|
||||
Within the sidebar, click `New organization` to create an organization. In this
|
||||
example, we'll create the `data-platform` org.
|
||||
|
||||

|
||||
|
||||
From there, let's deploy a provisioner and template for this organization.
|
||||
|
||||
### 2. Deploy a provisioner
|
||||
|
||||
[Provisioners](../admin/provisioners.md) are organization-scoped and are
|
||||
responsible for executing Terraform/OpenTofu to provision the infrastructure for
|
||||
workspaces and testing templates. Before creating templates, we must deploy at
|
||||
least one provisioner as the built-in provisioners are scoped to the default
|
||||
organization.
|
||||
|
||||
using Coder CLI, run the following command to create a key that will be used to
|
||||
authenticate the provisioner:
|
||||
|
||||
```sh
|
||||
coder provisioner keys create data-cluster-key --org data-platform
|
||||
Successfully created provisioner key data-cluster! Save this authentication token, it will not be shown again.
|
||||
|
||||
< key omitted >
|
||||
```
|
||||
|
||||
Next, start the provisioner with the key on your desired platform. In this
|
||||
example, we'll start it using the Coder CLI on a host with Docker. For
|
||||
instructions on using other platforms like Kubernetes, see our
|
||||
[provisioner documentation](../admin/provisioners.md).
|
||||
|
||||
```sh
|
||||
export CODER_URL=https://<your-coder-url>
|
||||
export CODER_PROVISIONER_DAEMON_KEY=<key>
|
||||
coder provisionerd start --org <org-name>
|
||||
```
|
||||
|
||||
### 3. Create a template
|
||||
|
||||
Once you've started a provisioner, you can create a template. You'll notice the
|
||||
"Create Template" screen now has an organization dropdown:
|
||||
|
||||

|
||||
|
||||
### 5. Add members
|
||||
|
||||
Navigate to `Deployment->Organizations` to add members to your organization.
|
||||
Once added, they will be able to see the organization-specific templates.
|
||||
|
||||

|
||||
|
||||
### 6. Create a workspace
|
||||
|
||||
Now, users in the data platform organization will see the templates related to
|
||||
their organization. Users can be in multiple organizations.
|
||||
|
||||

|
||||
|
||||
## Planned work
|
||||
|
||||
Organizations is under active development. The work is planned before
|
||||
organizations is generally available:
|
||||
|
||||
- View provisioner health via the Coder UI
|
||||
- Custom Role support in Coder UI
|
||||
- Per-organization quotas
|
||||
- Improved visibility of organization-specific resources throughout the UI
|
||||
- Sync OIDC claims to auto-assign users to organizations / roles + SCIM support
|
||||
|
||||
## Support & Feedback
|
||||
|
||||
[Contact your account team](https://coder.com/contact) if you have any questions
|
||||
or feedback.
|
||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 237 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 20V3.75L5 0L10 3.75V6H20V20H0ZM2 18H4V16H2V18ZM2 14H4V12H2V14ZM2 10H4V8H2V10ZM2 6H4V4H2V6ZM6 6H8V4H6V6ZM6 18H18V8H6V18ZM12 12V10H16V12H12ZM12 16V14H16V16H12ZM8 12V10H10V12H8ZM8 16V14H10V16H8Z" fill="#E8EAED"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 329 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 20V3.75L5 0L10 3.75V6H20V20H0ZM2 18H4V16H2V18ZM2 14H4V12H2V14ZM2 10H4V8H2V10ZM2 6H4V4H2V6ZM6 6H8V4H6V6ZM6 18H18V8H6V18ZM12 12V10H16V12H12ZM12 16V14H16V16H12ZM8 12V10H10V12H8ZM8 16V14H10V16H8Z" fill="#E8EAED"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 326 B |
@@ -0,0 +1,47 @@
|
||||
# Licensing
|
||||
|
||||
Some features are only accessible with a Premium or Enterprise license. See our
|
||||
[pricing page](https://coder.com/pricing) for more details. To try Premium
|
||||
features, you can [request a trial](https://coder.com/trial) or
|
||||
[contact sales](https://coder.com/contact).
|
||||
|
||||
<!-- markdown-link-check-disable -->
|
||||
|
||||
> If you are an existing customer, you can learn more our new Premium plan in
|
||||
> the [Coder v2.16 blog post](https://coder.com/blog/release-recap-2-16-0)
|
||||
|
||||
<!-- markdown-link-check-enable -->
|
||||
|
||||
## Adding your license key
|
||||
|
||||
There are two ways to add a license to a Coder deployment:
|
||||
|
||||
<div class="tabs">
|
||||
|
||||
### Coder UI
|
||||
|
||||
First, ensure you have a license key
|
||||
([request a trial](https://coder.com/trial)).
|
||||
|
||||
With an `Owner` account, navigate to `Deployment -> Licenses`, `Add a license`
|
||||
then drag or select the license file with the `jwt` extension.
|
||||
|
||||

|
||||
|
||||
### Coder CLI
|
||||
|
||||
First, ensure you have a license key
|
||||
([request a trial](https://coder.com/trial)) and the
|
||||
[Coder CLI](./install/index.md) installed.
|
||||
|
||||
1. Save your license key to disk and make note of the path
|
||||
2. Open a terminal
|
||||
3. Ensure you are logged into your Coder deployment
|
||||
|
||||
`coder login <access url>`
|
||||
|
||||
4. Run
|
||||
|
||||
`coder licenses add -f <path to your license key>`
|
||||
|
||||
</div>
|
||||
@@ -282,7 +282,7 @@
|
||||
"title": "Process Logging",
|
||||
"description": "Audit commands in workspaces with exectrace",
|
||||
"path": "./templates/process-logging.md",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "Icons",
|
||||
@@ -393,14 +393,21 @@
|
||||
"description": "Learn how to manage user groups",
|
||||
"path": "./admin/groups.md",
|
||||
"icon_path": "./images/icons/group.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "RBAC",
|
||||
"description": "Learn how to use the role based access control",
|
||||
"title": "Organizations",
|
||||
"description": "Learn how to manage organizations",
|
||||
"path": "./admin/organizations.md",
|
||||
"icon_path": "./images/icons/orgs.svg",
|
||||
"state": ["premium"]
|
||||
},
|
||||
{
|
||||
"title": "Template RBAC",
|
||||
"description": "Learn how to use the role based access control against templates",
|
||||
"path": "./admin/rbac.md",
|
||||
"icon_path": "./images/icons/rbac.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "beta"]
|
||||
},
|
||||
{
|
||||
"title": "Configuration",
|
||||
@@ -443,14 +450,14 @@
|
||||
"description": "Run provisioners isolated from the Coder server",
|
||||
"path": "./admin/provisioners.md",
|
||||
"icon_path": "./images/icons/queue.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "Workspace Proxies",
|
||||
"description": "Run geo distributed workspace proxies",
|
||||
"path": "./admin/workspace-proxies.md",
|
||||
"icon_path": "./images/icons/networking.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "Application Logs",
|
||||
@@ -463,21 +470,21 @@
|
||||
"description": "Learn how to use Audit Logs in your Coder deployment",
|
||||
"path": "./admin/audit-logs.md",
|
||||
"icon_path": "./images/icons/radar.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "Quotas",
|
||||
"description": "Learn how to use Workspace Quotas in Coder",
|
||||
"path": "./admin/quotas.md",
|
||||
"icon_path": "./images/icons/dollar.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "High Availability",
|
||||
"description": "Learn how to configure Coder for High Availability",
|
||||
"path": "./admin/high-availability.md",
|
||||
"icon_path": "./images/icons/hydra.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "Prometheus",
|
||||
@@ -490,7 +497,7 @@
|
||||
"description": "Learn how to configure the appearance of Coder",
|
||||
"path": "./admin/appearance.md",
|
||||
"icon_path": "./images/icons/info.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "Telemetry",
|
||||
@@ -503,7 +510,7 @@
|
||||
"description": "Learn how to encrypt sensitive data at rest in Coder",
|
||||
"path": "./admin/encryption.md",
|
||||
"icon_path": "./images/icons/lock.svg",
|
||||
"state": "enterprise"
|
||||
"state": ["enterprise", "premium"]
|
||||
},
|
||||
{
|
||||
"title": "Deployment Health",
|
||||
@@ -521,23 +528,23 @@
|
||||
"title": "Slack Notifications",
|
||||
"description": "Learn how to setup Slack notifications",
|
||||
"path": "./admin/notifications/slack.md",
|
||||
"state": "beta"
|
||||
"state": ["beta"]
|
||||
},
|
||||
{
|
||||
"title": "Microsoft Teams Notifications",
|
||||
"description": "Learn how to setup Microsoft Teams notifications",
|
||||
"path": "./admin/notifications/teams.md",
|
||||
"state": "beta"
|
||||
"state": ["beta"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Enterprise",
|
||||
"description": "Learn how to enable Enterprise features",
|
||||
"path": "./enterprise.md",
|
||||
"icon_path": "./images/icons/group.svg"
|
||||
"title": "Licensing",
|
||||
"description": "Learn how to enable Premium features",
|
||||
"path": "./licensing.md",
|
||||
"icon_path": "./images/icons/licensing.svg"
|
||||
},
|
||||
{
|
||||
"title": "Contributing",
|
||||
@@ -1337,11 +1344,6 @@
|
||||
"title": "Cloning Git Repositories",
|
||||
"description": "Automatically clone Git repositories into your workspace",
|
||||
"path": "./guides/cloning-git-repositories.md"
|
||||
},
|
||||
{
|
||||
"title": "Using Organizations",
|
||||
"description": "Learn how to use our (early access) Organizations functionality",
|
||||
"path": "./guides/using-organizations.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -157,10 +157,10 @@ $ coder server --derp-config-path derpmap.json
|
||||
|
||||
The dashboard (and web apps opened through the dashboard) are served from the
|
||||
coder server, so they can only be geo-distributed with High Availability mode in
|
||||
our Enterprise Edition. [Reach out to Sales](https://coder.com/contact) to learn
|
||||
more.
|
||||
our Enterprise and Premium Editions.
|
||||
[Reach out to Sales](https://coder.com/contact) to learn more.
|
||||
|
||||
## Browser-only connections (enterprise)
|
||||
## Browser-only connections (enterprise) (premium)
|
||||
|
||||
Some Coder deployments require that all access is through the browser to comply
|
||||
with security policies. In these cases, pass the `--browser-only` flag to
|
||||
|
||||
@@ -129,7 +129,7 @@ resource uses a different method of authentication and **is not impacted by the
|
||||
template's maximum sharing level**, nor the level of a shared port that points
|
||||
to the app.
|
||||
|
||||
### Configure maximum port sharing level (enterprise)
|
||||
### Configure maximum port sharing level (enterprise) (premium)
|
||||
|
||||
Enterprise-licensed template admins can control the maximum port sharing level
|
||||
for workspaces under a given template in the template settings. By default, the
|
||||
|
||||
@@ -83,7 +83,7 @@ coder_app.
|
||||
|
||||

|
||||
|
||||
### Autostop requirement (enterprise)
|
||||
### Autostop requirement (enterprise) (premium)
|
||||
|
||||
Autostop requirement is a template setting that determines how often workspaces
|
||||
using the template must automatically stop. Autostop requirement ignores any
|
||||
@@ -113,7 +113,7 @@ Autostop requirement is disabled when the template is using the deprecated max
|
||||
lifetime feature. Templates can choose to use a max lifetime or an autostop
|
||||
requirement during the deprecation period, but only one can be used at a time.
|
||||
|
||||
### User quiet hours (enterprise)
|
||||
### User quiet hours (enterprise) (premium)
|
||||
|
||||
User quiet hours can be configured in the user's schedule settings page.
|
||||
Workspaces on templates with an autostop requirement will only be forcibly
|
||||
|
||||
@@ -448,7 +448,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
// with the below route, we need to register this route without any mounts or groups to make both work.
|
||||
r.With(
|
||||
apiKeyMiddleware,
|
||||
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentNotifications),
|
||||
httpmw.ExtractNotificationTemplateParam(options.Database),
|
||||
).Put("/notifications/templates/{notification_template}/method", api.updateNotificationTemplateMethod)
|
||||
})
|
||||
|
||||
@@ -23,7 +23,6 @@ func createOpts(t *testing.T) *coderdenttest.Options {
|
||||
t.Helper()
|
||||
|
||||
dt := coderdtest.DeploymentValues(t)
|
||||
dt.Experiments = []string{string(codersdk.ExperimentNotifications)}
|
||||
return &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dt,
|
||||
|
||||
@@ -147,9 +147,13 @@ func (api *API) provisionerKeyDaemons(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
pkDaemons := []codersdk.ProvisionerKeyDaemons{}
|
||||
for _, key := range sdkKeys {
|
||||
// currently we exclude user-auth from this list
|
||||
// The key.OrganizationID for the `user-auth` key is hardcoded to
|
||||
// the default org in the database and we are overwriting it here
|
||||
// to be the correct org we used to query the list.
|
||||
// This will be changed when we update the `user-auth` keys to be
|
||||
// directly tied to a user ID.
|
||||
if key.ID.String() == codersdk.ProvisionerKeyIDUserAuth {
|
||||
continue
|
||||
key.OrganizationID = organization.ID
|
||||
}
|
||||
daemons := []codersdk.ProvisionerDaemon{}
|
||||
for _, daemon := range recentDaemons {
|
||||
|
||||
@@ -174,15 +174,15 @@ require (
|
||||
go.uber.org/atomic v1.11.0
|
||||
go.uber.org/goleak v1.3.1-0.20240429205332-517bace7cc29
|
||||
go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516
|
||||
golang.org/x/crypto v0.27.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
|
||||
golang.org/x/mod v0.21.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/term v0.24.0
|
||||
golang.org/x/text v0.18.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/term v0.27.0
|
||||
golang.org/x/text v0.21.0
|
||||
golang.org/x/tools v0.25.0
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da
|
||||
google.golang.org/api v0.197.0
|
||||
|
||||
@@ -1058,8 +1058,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
|
||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
@@ -1108,8 +1108,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1151,8 +1151,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -1161,8 +1161,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -1174,8 +1174,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
|
||||
@@ -169,12 +169,12 @@ func writeDocs(sections [][]byte) error {
|
||||
|
||||
// Update manifest.json
|
||||
type route struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
IconPath string `json:"icon_path,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Children []route `json:"children,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
IconPath string `json:"icon_path,omitempty"`
|
||||
State []string `json:"state,omitempty"`
|
||||
Children []route `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type manifest struct {
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
|
||||
// route is an individual page object in the docs manifest.json.
|
||||
type route struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
IconPath string `json:"icon_path,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Children []route `json:"children,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
IconPath string `json:"icon_path,omitempty"`
|
||||
State []string `json:"state,omitempty"`
|
||||
Children []route `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// manifest describes the entire documentation index.
|
||||
|
||||
@@ -10,7 +10,7 @@ import { docs } from "utils/docs";
|
||||
* All types of feature that we are currently supporting. Defined as record to
|
||||
* ensure that we can't accidentally make typos when writing the badge text.
|
||||
*/
|
||||
const featureStageBadgeTypes = {
|
||||
export const featureStageBadgeTypes = {
|
||||
beta: "beta",
|
||||
experimental: "experimental",
|
||||
} as const satisfies Record<string, ReactNode>;
|
||||
|
||||
@@ -58,7 +58,7 @@ export const Paywall: FC<PaywallProps> = ({
|
||||
</ul>
|
||||
<div css={styles.learnButton}>
|
||||
<Button
|
||||
href={docs("/enterprise")}
|
||||
href={docs("/licensing")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
startIcon={<span css={{ fontSize: 22 }}>→</span>}
|
||||
|
||||
@@ -62,7 +62,7 @@ export const PopoverPaywall: FC<PopoverPaywallProps> = ({
|
||||
</ul>
|
||||
<div css={styles.learnButton}>
|
||||
<Button
|
||||
href={docs("/enterprise")}
|
||||
href={docs("/licensing")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
startIcon={<span css={{ fontSize: 22 }}>→</span>}
|
||||
|
||||
@@ -27,7 +27,7 @@ import { createDayString } from "utils/createDayString";
|
||||
import { docs } from "utils/docs";
|
||||
import { ProvisionerTag } from "./ProvisionerTag";
|
||||
|
||||
type ProvisionerGroupType = "builtin" | "psk" | "key";
|
||||
type ProvisionerGroupType = "builtin" | "userAuth" | "psk" | "key";
|
||||
|
||||
interface ProvisionerGroupProps {
|
||||
readonly buildInfo: BuildInfoResponse;
|
||||
@@ -103,7 +103,8 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
|
||||
: `${provisionersWithWarnings} provisioners`;
|
||||
|
||||
const hasMultipleTagVariants =
|
||||
type === "psk" && provisioners.some((it) => !isSimpleTagSet(it.tags));
|
||||
(type === "psk" || type === "userAuth") &&
|
||||
provisioners.some((it) => !isSimpleTagSet(it.tags));
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -143,6 +144,8 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "userAuth" && <UserAuthProvisionerTitle />}
|
||||
|
||||
{type === "psk" && <PskProvisionerTitle />}
|
||||
{type === "key" && (
|
||||
<h4 css={styles.groupTitle}>Key group – {keyName}</h4>
|
||||
@@ -249,7 +252,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
|
||||
</span>
|
||||
</div>
|
||||
{hasMultipleTagVariants && (
|
||||
<PskProvisionerTags tags={provisioner.tags} />
|
||||
<InlineProvisionerTags tags={provisioner.tags} />
|
||||
)}
|
||||
</Stack>
|
||||
</div>
|
||||
@@ -335,11 +338,11 @@ const ProvisionerVersionPopover: FC<ProvisionerVersionPopoverProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface PskProvisionerTagsProps {
|
||||
interface InlineProvisionerTagsProps {
|
||||
tags: Record<string, string>;
|
||||
}
|
||||
|
||||
const PskProvisionerTags: FC<PskProvisionerTagsProps> = ({ tags }) => {
|
||||
const InlineProvisionerTags: FC<InlineProvisionerTagsProps> = ({ tags }) => {
|
||||
const daemonScope = tags.scope || "organization";
|
||||
const iconScope =
|
||||
daemonScope === "organization" ? <BusinessIcon /> : <PersonIcon />;
|
||||
@@ -413,6 +416,30 @@ const BuiltinProvisionerTitle: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const UserAuthProvisionerTitle: FC = () => {
|
||||
return (
|
||||
<h4 css={styles.groupTitle}>
|
||||
<Stack direction="row" alignItems="end" spacing={1}>
|
||||
<span>User-authenticated provisioners</span>
|
||||
<HelpTooltip>
|
||||
<HelpTooltipTrigger />
|
||||
<HelpTooltipContent>
|
||||
<HelpTooltipTitle>User-authenticated provisioners</HelpTooltipTitle>
|
||||
<HelpTooltipText>
|
||||
These provisioners are connected by users using the{" "}
|
||||
<code>coder</code> CLI, and are authorized by the users
|
||||
credentials. They can be tagged to only run provisioner jobs for
|
||||
that user. User-authenticated provisioners are only available for
|
||||
the default organization.{" "}
|
||||
<Link href={docs("/")}>Learn more…</Link>
|
||||
</HelpTooltipText>
|
||||
</HelpTooltipContent>
|
||||
</HelpTooltip>
|
||||
</Stack>
|
||||
</h4>
|
||||
);
|
||||
};
|
||||
|
||||
const PskProvisionerTitle: FC = () => {
|
||||
return (
|
||||
<h4 css={styles.groupTitle}>
|
||||
|
||||
@@ -43,6 +43,7 @@ export const NotificationsPage: FC = () => {
|
||||
title="Notifications"
|
||||
description="Control delivery methods for notifications on this deployment."
|
||||
layout="fluid"
|
||||
featureStage={"beta"}
|
||||
>
|
||||
<Tabs active={tab}>
|
||||
<TabsList>
|
||||
|
||||
@@ -7,6 +7,7 @@ import NotificationsIcon from "@mui/icons-material/NotificationsNoneOutlined";
|
||||
import Globe from "@mui/icons-material/PublicOutlined";
|
||||
import ApprovalIcon from "@mui/icons-material/VerifiedUserOutlined";
|
||||
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
|
||||
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
|
||||
import { GitIcon } from "components/Icons/GitIcon";
|
||||
import {
|
||||
Sidebar as BaseSidebar,
|
||||
@@ -51,11 +52,9 @@ export const Sidebar: FC = () => {
|
||||
<SidebarNavItem href="observability" icon={InsertChartIcon}>
|
||||
Observability
|
||||
</SidebarNavItem>
|
||||
{experiments.includes("notifications") && (
|
||||
<SidebarNavItem href="notifications" icon={NotificationsIcon}>
|
||||
Notifications
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
<SidebarNavItem href="notifications" icon={NotificationsIcon}>
|
||||
Notifications <FeatureStageBadge contentType="beta" size="sm" />
|
||||
</SidebarNavItem>
|
||||
</BaseSidebar>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,15 @@ export const LoginPage: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
|
||||
let redirectError: Error | null = null;
|
||||
let redirectUrl: URL | null = null;
|
||||
try {
|
||||
redirectUrl = new URL(redirectTo);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
const isApiRouteRedirect = redirectTo.startsWith("/api/v2");
|
||||
|
||||
useEffect(() => {
|
||||
if (!buildInfoQuery.data || isSignedIn) {
|
||||
@@ -42,41 +51,24 @@ export const LoginPage: FC = () => {
|
||||
}, [isSignedIn, buildInfoQuery.data, user?.id]);
|
||||
|
||||
if (isSignedIn) {
|
||||
if (buildInfoQuery.data) {
|
||||
// This uses `navigator.sendBeacon`, so window.href
|
||||
// will not stop the request from being sent!
|
||||
sendDeploymentEvent(buildInfoQuery.data, {
|
||||
type: "deployment_login",
|
||||
user_id: user?.id,
|
||||
});
|
||||
// The reason we need `window.location.href` for api redirects is that
|
||||
// we need the page to reload and make a request to the backend. If we
|
||||
// use `<Navigate>`, react would handle the redirect itself and never
|
||||
// request the page from the backend.
|
||||
if (isApiRouteRedirect) {
|
||||
const sanitizedUrl = new URL(redirectTo, window.location.origin);
|
||||
window.location.href = sanitizedUrl.pathname + sanitizedUrl.search;
|
||||
// Setting the href should immediately request a new page. Show an
|
||||
// error state if it doesn't.
|
||||
redirectError = new Error("unable to redirect");
|
||||
} else {
|
||||
return (
|
||||
<Navigate
|
||||
to={redirectUrl ? redirectUrl.pathname : redirectTo}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// If the redirect is going to a workspace application, and we
|
||||
// are missing authentication, then we need to change the href location
|
||||
// to trigger a HTTP request. This allows the BE to generate the auth
|
||||
// cookie required. Similarly for the OAuth2 exchange as the authorization
|
||||
// page is served by the backend.
|
||||
// If no redirect is present, then ignore this branched logic.
|
||||
if (redirectTo !== "" && redirectTo !== "/") {
|
||||
try {
|
||||
// This catches any absolute redirects. Relative redirects
|
||||
// will fail the try/catch. Subdomain apps are absolute redirects.
|
||||
const redirectURL = new URL(redirectTo);
|
||||
if (redirectURL.host !== window.location.host) {
|
||||
window.location.href = redirectTo;
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
// Path based apps and OAuth2.
|
||||
if (redirectTo.includes("/apps/") || redirectTo.includes("/oauth2/")) {
|
||||
window.location.href = redirectTo;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return <Navigate to={redirectTo} replace />;
|
||||
}
|
||||
|
||||
if (isConfiguringTheFirstUser) {
|
||||
@@ -90,7 +82,7 @@ export const LoginPage: FC = () => {
|
||||
</Helmet>
|
||||
<LoginPageView
|
||||
authMethods={authMethodsQuery.data}
|
||||
error={signInError}
|
||||
error={signInError ?? redirectError}
|
||||
isLoading={isLoading || authMethodsQuery.isLoading}
|
||||
buildInfo={buildInfoQuery.data}
|
||||
isSigningIn={isSigningIn}
|
||||
@@ -98,6 +90,7 @@ export const LoginPage: FC = () => {
|
||||
await signIn(email, password);
|
||||
navigate("/");
|
||||
}}
|
||||
redirectTo={redirectTo}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Loader } from "components/Loader/Loader";
|
||||
import { type FC, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { getApplicationName, getLogoURL } from "utils/appearance";
|
||||
import { retrieveRedirect } from "utils/redirect";
|
||||
import { SignInForm } from "./SignInForm";
|
||||
import { TermsOfServiceLink } from "./TermsOfServiceLink";
|
||||
|
||||
@@ -17,6 +16,7 @@ export interface LoginPageViewProps {
|
||||
buildInfo?: BuildInfoResponse;
|
||||
isSigningIn: boolean;
|
||||
onSignIn: (credentials: { email: string; password: string }) => void;
|
||||
redirectTo: string;
|
||||
}
|
||||
|
||||
export const LoginPageView: FC<LoginPageViewProps> = ({
|
||||
@@ -26,9 +26,9 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
|
||||
buildInfo,
|
||||
isSigningIn,
|
||||
onSignIn,
|
||||
redirectTo,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const redirectTo = retrieveRedirect(location.search);
|
||||
// This allows messages to be displayed at the top of the sign in form.
|
||||
// Helpful for any redirects that want to inform the user of something.
|
||||
const message = new URLSearchParams(location.search).get("message");
|
||||
|
||||
@@ -69,7 +69,7 @@ export const CreateOrganizationPageView: FC<
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title="New Organization"
|
||||
description="Organize your deployment into multiple platform teams."
|
||||
description="Organize your deployment into multiple platform teams with unique provisioners, templates, groups, and members."
|
||||
/>
|
||||
|
||||
{Boolean(error) && !isApiValidationError(error) && (
|
||||
@@ -92,7 +92,7 @@ export const CreateOrganizationPageView: FC<
|
||||
<PopoverPaywall
|
||||
message="Organizations"
|
||||
description="Create multiple organizations within a single Coder deployment, allowing several platform teams to operate with isolated users, templates, and distinct underlying infrastructure."
|
||||
documentationLink={docs("/guides/using-organizations")}
|
||||
documentationLink={docs("/admin/organizations")}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -104,7 +104,7 @@ export const CreateOrganizationPageView: FC<
|
||||
<Paywall
|
||||
message="Organizations"
|
||||
description="Create multiple organizations within a single Coder deployment, allowing several platform teams to operate with isolated users, templates, and distinct underlying infrastructure."
|
||||
documentationLink={docs("/guides/using-organizations")}
|
||||
documentationLink={docs("/admin/organizations")}
|
||||
/>
|
||||
</Cond>
|
||||
<Cond>
|
||||
|
||||
@@ -21,7 +21,9 @@ export const IdpSyncHelpTooltip: FC = () => {
|
||||
Coder. Use the Coder CLI to configure these mappings.
|
||||
</HelpTooltipText>
|
||||
<HelpTooltipLinksGroup>
|
||||
<HelpTooltipLink href={docs("/admin/auth#group-sync-enterprise")}>
|
||||
<HelpTooltipLink
|
||||
href={docs("/admin/auth#group-sync-enterprise-premium")}
|
||||
>
|
||||
Configure IdP Sync
|
||||
</HelpTooltipLink>
|
||||
</HelpTooltipLinksGroup>
|
||||
|
||||
@@ -74,7 +74,7 @@ export const IdpSyncPage: FC = () => {
|
||||
<Button
|
||||
startIcon={<LaunchOutlined />}
|
||||
component="a"
|
||||
href={docs("/admin/auth#group-sync-enterprise")}
|
||||
href={docs("/admin/auth#group-sync-enterprise-premium")}
|
||||
target="_blank"
|
||||
>
|
||||
Setup IdP Sync
|
||||
@@ -85,7 +85,9 @@ export const IdpSyncPage: FC = () => {
|
||||
<Paywall
|
||||
message="IdP Sync"
|
||||
description="Configure group and role mappings to manage permissions outside of Coder. You need an Premium license to use this feature."
|
||||
documentationLink={docs("/admin/groups")}
|
||||
documentationLink={docs(
|
||||
"/admin/auth#group-sync-enterprise-premium",
|
||||
)}
|
||||
/>
|
||||
</Cond>
|
||||
<Cond>
|
||||
|
||||
@@ -313,7 +313,7 @@ const IdpMappingTable: FC<IdpMappingTableProps> = ({
|
||||
startIcon={<LaunchOutlined />}
|
||||
component="a"
|
||||
href={docs(
|
||||
`/admin/auth#${type.toLowerCase()}-sync-enterprise`,
|
||||
`/admin/auth#${type.toLowerCase()}-sync-enterprise-premium`,
|
||||
)}
|
||||
target="_blank"
|
||||
>
|
||||
@@ -403,7 +403,7 @@ const LegacyGroupSyncHeader: FC = () => {
|
||||
configure IdP sync via the CLI, which enables sync to be
|
||||
configured for any organization, and for those settings to be
|
||||
persisted without manually setting environment variables.{" "}
|
||||
<Link href={docs("/admin/auth#group-sync-enterprise")}>
|
||||
<Link href={docs("/admin/auth#group-sync-enterprise-premium")}>
|
||||
Learn more…
|
||||
</Link>
|
||||
</HelpTooltipText>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
MockProvisionerBuiltinKey,
|
||||
MockProvisionerKey,
|
||||
MockProvisionerPskKey,
|
||||
MockProvisionerUserAuthKey,
|
||||
MockProvisionerWithTags,
|
||||
MockUserProvisioner,
|
||||
mockApiError,
|
||||
@@ -79,6 +80,17 @@ export const Provisioners: Story = {
|
||||
name: `ケイラ-${i}`,
|
||||
})),
|
||||
},
|
||||
{
|
||||
key: MockProvisionerUserAuthKey,
|
||||
daemons: [
|
||||
MockUserProvisioner,
|
||||
{
|
||||
...MockUserProvisioner,
|
||||
id: "mock-user-provisioner-2",
|
||||
name: "Test User Provisioner 2",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
play: async ({ step }) => {
|
||||
|
||||
@@ -92,7 +92,7 @@ const ViewContent: FC<ViewContentProps> = ({ buildInfo, provisioners }) => {
|
||||
target="_blank"
|
||||
href={docs("/admin/provisioners")}
|
||||
>
|
||||
Show me how to create a provisioner
|
||||
Create a provisioner
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
@@ -110,28 +110,16 @@ const ViewContent: FC<ViewContentProps> = ({ buildInfo, provisioners }) => {
|
||||
</div>
|
||||
)}
|
||||
<Stack spacing={4.5}>
|
||||
{provisioners.map((group) => {
|
||||
const type = getGroupType(group.key);
|
||||
|
||||
// We intentionally hide user-authenticated provisioners for now
|
||||
// because there are 1. some grouping issues on the backend and 2. we
|
||||
// should ideally group them by the user who authenticated them, and
|
||||
// not just lump them all together.
|
||||
if (type === "userAuth") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ProvisionerGroup
|
||||
key={group.key.id}
|
||||
buildInfo={buildInfo}
|
||||
keyName={group.key.name}
|
||||
keyTags={group.key.tags}
|
||||
type={type}
|
||||
provisioners={group.daemons}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{provisioners.map((group) => (
|
||||
<ProvisionerGroup
|
||||
key={group.key.id}
|
||||
buildInfo={buildInfo}
|
||||
keyName={group.key.name}
|
||||
keyTags={group.key.tags}
|
||||
type={getGroupType(group.key)}
|
||||
provisioners={group.daemons}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -148,11 +148,12 @@ const DeploymentSettingsNavigation: FC<DeploymentSettingsNavigationProps> = ({
|
||||
Users
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
{experiments.includes("notifications") && (
|
||||
<Stack direction="row" alignItems="center" css={{ gap: 0 }}>
|
||||
<SidebarNavSubItem href="notifications">
|
||||
Notifications
|
||||
</SidebarNavSubItem>
|
||||
)}
|
||||
<FeatureStageBadge contentType="beta" size="sm" />
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -211,7 +211,7 @@ export const SetupPageView: FC<SetupPageViewProps> = ({
|
||||
quotas, and more.
|
||||
</span>
|
||||
<Link
|
||||
href={docs("/enterprise")}
|
||||
href={docs("/licensing")}
|
||||
target="_blank"
|
||||
css={{ marginTop: 4, display: "inline-block", fontSize: 13 }}
|
||||
>
|
||||
|
||||
@@ -99,6 +99,7 @@ export const NotificationsPage: FC = () => {
|
||||
title="Notifications"
|
||||
description="Configure your notification preferences. Icons on the right of each notification indicate delivery method, either SMTP or Webhook."
|
||||
layout="fluid"
|
||||
featureStage="beta"
|
||||
>
|
||||
{ready ? (
|
||||
<Stack spacing={4}>
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import {
|
||||
FeatureStageBadge,
|
||||
type featureStageBadgeTypes,
|
||||
} from "components/FeatureStageBadge/FeatureStageBadge";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import type { FC, ReactNode } from "react";
|
||||
|
||||
type SectionLayout = "fixed" | "fluid";
|
||||
@@ -13,6 +18,7 @@ export interface SectionProps {
|
||||
layout?: SectionLayout;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
featureStage?: keyof typeof featureStageBadgeTypes;
|
||||
}
|
||||
|
||||
export const Section: FC<SectionProps> = ({
|
||||
@@ -24,6 +30,7 @@ export const Section: FC<SectionProps> = ({
|
||||
className = "",
|
||||
children,
|
||||
layout = "fixed",
|
||||
featureStage,
|
||||
}) => {
|
||||
return (
|
||||
<section className={className} id={id} data-testid={id}>
|
||||
@@ -32,16 +39,25 @@ export const Section: FC<SectionProps> = ({
|
||||
<div css={styles.header}>
|
||||
<div>
|
||||
{title && (
|
||||
<h4
|
||||
css={{
|
||||
fontSize: 24,
|
||||
fontWeight: 500,
|
||||
margin: 0,
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</h4>
|
||||
<Stack direction={"row"} alignItems="center">
|
||||
<h4
|
||||
css={{
|
||||
fontSize: 24,
|
||||
fontWeight: 500,
|
||||
margin: 0,
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</h4>
|
||||
{featureStage && (
|
||||
<FeatureStageBadge
|
||||
contentType={featureStage}
|
||||
size="lg"
|
||||
css={{ marginBottom: "5px" }}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
{description && typeof description === "string" && (
|
||||
<p css={styles.description}>{description}</p>
|
||||
|
||||
@@ -6,6 +6,7 @@ import NotificationsIcon from "@mui/icons-material/NotificationsNoneOutlined";
|
||||
import AccountIcon from "@mui/icons-material/Person";
|
||||
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
|
||||
import type { User } from "api/typesGenerated";
|
||||
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
|
||||
import { GitIcon } from "components/Icons/GitIcon";
|
||||
import {
|
||||
Sidebar as BaseSidebar,
|
||||
@@ -57,11 +58,9 @@ export const Sidebar: FC<SidebarProps> = ({ user }) => {
|
||||
<SidebarNavItem href="tokens" icon={VpnKeyOutlined}>
|
||||
Tokens
|
||||
</SidebarNavItem>
|
||||
{experiments.includes("notifications") && (
|
||||
<SidebarNavItem href="notifications" icon={NotificationsIcon}>
|
||||
Notifications
|
||||
</SidebarNavItem>
|
||||
)}
|
||||
<SidebarNavItem href="notifications" icon={NotificationsIcon}>
|
||||
Notifications <FeatureStageBadge contentType="beta" size="sm" />
|
||||
</SidebarNavItem>
|
||||
</BaseSidebar>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -610,7 +610,7 @@ export const MockProvisioner2: TypesGen.ProvisionerDaemon = {
|
||||
};
|
||||
|
||||
export const MockUserProvisioner: TypesGen.ProvisionerDaemon = {
|
||||
...MockProvisioner,
|
||||
...MockUserAuthProvisioner,
|
||||
id: "test-user-provisioner",
|
||||
name: "Test User Provisioner",
|
||||
tags: { scope: "user", owner: "12345678-abcd-1234-abcd-1234567890abcd" },
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"dotfiles.svg",
|
||||
"dotnet.svg",
|
||||
"fedora.svg",
|
||||
"filebrowser.svg",
|
||||
"fleet.svg",
|
||||
"fly.io.svg",
|
||||
"folder.svg",
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xml:space="preserve"
|
||||
width="560"
|
||||
height="560"
|
||||
version="1.1"
|
||||
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
|
||||
viewBox="0 0 560 560"
|
||||
id="svg44"
|
||||
sodipodi:docname="icon_raw.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
inkscape:export-filename="/home/umarcor/filebrowser/logo/icon_raw.svg.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"><metadata
|
||||
id="metadata48"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="711"
|
||||
id="namedview46"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.33714286"
|
||||
inkscape:cx="-172.33051"
|
||||
inkscape:cy="280"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="20"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg44" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<style
|
||||
type="text/css"
|
||||
id="style2">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#FEFEFE}
|
||||
.fil6 {fill:#006498}
|
||||
.fil7 {fill:#0EA5EB}
|
||||
.fil8 {fill:#2979FF}
|
||||
.fil3 {fill:#2BBCFF}
|
||||
.fil0 {fill:#455A64}
|
||||
.fil4 {fill:#53C6FC}
|
||||
.fil5 {fill:#BDEAFF}
|
||||
.fil2 {fill:#332C2B;fill-opacity:0.149020}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g
|
||||
id="g85"
|
||||
transform="translate(-70,-70)"><path
|
||||
class="fil1"
|
||||
d="M 350,71 C 504,71 629,196 629,350 629,504 504,629 350,629 196,629 71,504 71,350 71,196 196,71 350,71 Z"
|
||||
id="path9"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fefefe" /><path
|
||||
class="fil2"
|
||||
d="M 475,236 593,387 C 596,503 444,639 301,585 L 225,486 339,330 c 0,0 138,-95 136,-94 z"
|
||||
id="path11"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#332c2b;fill-opacity:0.14902003" /><path
|
||||
class="fil3"
|
||||
d="m 231,211 h 208 l 38,24 v 246 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 V 219 c 0,-5 3,-8 8,-8 z"
|
||||
id="path13"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#2bbcff" /><path
|
||||
class="fil4"
|
||||
d="m 231,211 h 208 l 38,24 v 2 L 440,214 H 231 c -4,0 -7,3 -7,7 v 263 c -1,-1 -1,-2 -1,-3 V 219 c 0,-5 3,-8 8,-8 z"
|
||||
id="path15"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#53c6fc" /><polygon
|
||||
class="fil5"
|
||||
points="305,212 418,212 418,310 305,310 "
|
||||
id="polygon17"
|
||||
style="fill:#bdeaff" /><path
|
||||
class="fil5"
|
||||
d="m 255,363 h 189 c 3,0 5,2 5,4 V 483 H 250 V 367 c 0,-2 2,-4 5,-4 z"
|
||||
id="path19"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#bdeaff" /><polygon
|
||||
class="fil6"
|
||||
points="250,470 449,470 449,483 250,483 "
|
||||
id="polygon21"
|
||||
style="fill:#006498" /><path
|
||||
class="fil6"
|
||||
d="m 380,226 h 10 c 3,0 6,2 6,5 v 40 c 0,3 -3,6 -6,6 h -10 c -3,0 -6,-3 -6,-6 v -40 c 0,-3 3,-5 6,-5 z"
|
||||
id="path23"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#006498" /><path
|
||||
class="fil1"
|
||||
d="m 254,226 c 10,0 17,7 17,17 0,9 -7,16 -17,16 -9,0 -17,-7 -17,-16 0,-10 8,-17 17,-17 z"
|
||||
id="path25"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fefefe" /><path
|
||||
class="fil6"
|
||||
d="m 267,448 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,3 -3,3 H 267 c -2,0 -3,-2 -3,-3 v 0 c 0,-2 1,-3 3,-3 z"
|
||||
id="path27"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#006498" /><path
|
||||
class="fil6"
|
||||
d="m 267,415 h 165 c 2,0 3,1 3,3 v 0 c 0,1 -1,2 -3,2 H 267 c -2,0 -3,-1 -3,-2 v 0 c 0,-2 1,-3 3,-3 z"
|
||||
id="path29"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#006498" /><path
|
||||
class="fil6"
|
||||
d="m 267,381 h 165 c 2,0 3,2 3,3 v 0 c 0,2 -1,3 -3,3 H 267 c -2,0 -3,-1 -3,-3 v 0 c 0,-1 1,-3 3,-3 z"
|
||||
id="path31"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#006498" /><path
|
||||
class="fil1"
|
||||
d="m 236,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
|
||||
id="path33"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fefefe" /><path
|
||||
class="fil1"
|
||||
d="m 463,472 c 3,0 5,2 5,5 0,2 -2,4 -5,4 -3,0 -5,-2 -5,-4 0,-3 2,-5 5,-5 z"
|
||||
id="path35"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#fefefe" /><polygon
|
||||
class="fil6"
|
||||
points="305,212 284,212 284,310 305,310 "
|
||||
id="polygon37"
|
||||
style="fill:#006498" /><path
|
||||
class="fil7"
|
||||
d="m 477,479 v 2 c 0,5 -3,8 -8,8 H 231 c -5,0 -8,-3 -8,-8 v -2 c 0,4 3,8 8,8 h 238 c 5,0 8,-4 8,-8 z"
|
||||
id="path39"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#0ea5eb" /><path
|
||||
class="fil8"
|
||||
d="M 350,70 C 505,70 630,195 630,350 630,505 505,630 350,630 195,630 70,505 70,350 70,195 195,70 350,70 Z m 0,46 C 479,116 584,221 584,350 584,479 479,584 350,584 221,584 116,479 116,350 116,221 221,116 350,116 Z"
|
||||
id="path41"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#2979ff" /></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |