Files
coder/coderd/database/db2sdk/db2sdk.go

887 lines
29 KiB
Go

// Package db2sdk provides common conversion routines from database types to codersdk types
package db2sdk
import (
"encoding/json"
"fmt"
"net/url"
"slices"
"sort"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"github.com/hashicorp/hcl/v2"
"golang.org/x/xerrors"
"tailscale.com/tailcfg"
previewtypes "github.com/coder/preview/types"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/coderd/render"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/tailnet"
)
// List is a helper function to reduce boilerplate when converting slices of
// database types to slices of codersdk types.
// Only works if the function takes a single argument.
func List[F any, T any](list []F, convert func(F) T) []T {
return ListLazy(convert)(list)
}
// ListLazy returns the converter function for a list, but does not eval
// the input. Helpful for combining the Map and the List functions.
func ListLazy[F any, T any](convert func(F) T) func(list []F) []T {
return func(list []F) []T {
into := make([]T, 0, len(list))
for _, item := range list {
into = append(into, convert(item))
}
return into
}
}
type ExternalAuthMeta struct {
Authenticated bool
ValidateError string
}
func ExternalAuths(auths []database.ExternalAuthLink, meta map[string]ExternalAuthMeta) []codersdk.ExternalAuthLink {
out := make([]codersdk.ExternalAuthLink, 0, len(auths))
for _, auth := range auths {
out = append(out, ExternalAuth(auth, meta[auth.ProviderID]))
}
return out
}
func ExternalAuth(auth database.ExternalAuthLink, meta ExternalAuthMeta) codersdk.ExternalAuthLink {
return codersdk.ExternalAuthLink{
ProviderID: auth.ProviderID,
CreatedAt: auth.CreatedAt,
UpdatedAt: auth.UpdatedAt,
HasRefreshToken: auth.OAuthRefreshToken != "",
Expires: auth.OAuthExpiry,
Authenticated: meta.Authenticated,
ValidateError: meta.ValidateError,
}
}
func WorkspaceBuildParameter(p database.WorkspaceBuildParameter) codersdk.WorkspaceBuildParameter {
return codersdk.WorkspaceBuildParameter{
Name: p.Name,
Value: p.Value,
}
}
func WorkspaceBuildParameters(params []database.WorkspaceBuildParameter) []codersdk.WorkspaceBuildParameter {
return List(params, WorkspaceBuildParameter)
}
func TemplateVersionParameters(params []database.TemplateVersionParameter) ([]codersdk.TemplateVersionParameter, error) {
out := make([]codersdk.TemplateVersionParameter, 0, len(params))
for _, p := range params {
np, err := TemplateVersionParameter(p)
if err != nil {
return nil, xerrors.Errorf("convert template version parameter %q: %w", p.Name, err)
}
out = append(out, np)
}
return out, nil
}
func TemplateVersionParameterFromPreview(param previewtypes.Parameter) (codersdk.TemplateVersionParameter, error) {
descriptionPlaintext, err := render.PlaintextFromMarkdown(param.Description)
if err != nil {
return codersdk.TemplateVersionParameter{}, err
}
sdkParam := codersdk.TemplateVersionParameter{
Name: param.Name,
DisplayName: param.DisplayName,
Description: param.Description,
DescriptionPlaintext: descriptionPlaintext,
Type: string(param.Type),
FormType: string(param.FormType),
Mutable: param.Mutable,
DefaultValue: param.DefaultValue.AsString(),
Icon: param.Icon,
Required: param.Required,
Ephemeral: param.Ephemeral,
Options: List(param.Options, TemplateVersionParameterOptionFromPreview),
// Validation set after
}
if len(param.Validations) > 0 {
validation := param.Validations[0]
sdkParam.ValidationError = validation.Error
if validation.Monotonic != nil {
sdkParam.ValidationMonotonic = codersdk.ValidationMonotonicOrder(*validation.Monotonic)
}
if validation.Regex != nil {
sdkParam.ValidationRegex = *validation.Regex
}
if validation.Min != nil {
//nolint:gosec // No other choice
sdkParam.ValidationMin = ptr.Ref(int32(*validation.Min))
}
if validation.Max != nil {
//nolint:gosec // No other choice
sdkParam.ValidationMax = ptr.Ref(int32(*validation.Max))
}
}
return sdkParam, nil
}
func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk.TemplateVersionParameter, error) {
options, err := templateVersionParameterOptions(param.Options)
if err != nil {
return codersdk.TemplateVersionParameter{}, err
}
descriptionPlaintext, err := render.PlaintextFromMarkdown(param.Description)
if err != nil {
return codersdk.TemplateVersionParameter{}, err
}
var validationMin *int32
if param.ValidationMin.Valid {
validationMin = &param.ValidationMin.Int32
}
var validationMax *int32
if param.ValidationMax.Valid {
validationMax = &param.ValidationMax.Int32
}
return codersdk.TemplateVersionParameter{
Name: param.Name,
DisplayName: param.DisplayName,
Description: param.Description,
DescriptionPlaintext: descriptionPlaintext,
Type: param.Type,
FormType: string(param.FormType),
Mutable: param.Mutable,
DefaultValue: param.DefaultValue,
Icon: param.Icon,
Options: options,
ValidationRegex: param.ValidationRegex,
ValidationMin: validationMin,
ValidationMax: validationMax,
ValidationError: param.ValidationError,
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
Required: param.Required,
Ephemeral: param.Ephemeral,
}, nil
}
func ReducedUser(user database.User) codersdk.ReducedUser {
return codersdk.ReducedUser{
MinimalUser: codersdk.MinimalUser{
ID: user.ID,
Username: user.Username,
AvatarURL: user.AvatarURL,
},
Email: user.Email,
Name: user.Name,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
LastSeenAt: user.LastSeenAt,
Status: codersdk.UserStatus(user.Status),
LoginType: codersdk.LoginType(user.LoginType),
}
}
func UserFromGroupMember(member database.GroupMember) database.User {
return database.User{
ID: member.UserID,
Email: member.UserEmail,
Username: member.UserUsername,
HashedPassword: member.UserHashedPassword,
CreatedAt: member.UserCreatedAt,
UpdatedAt: member.UserUpdatedAt,
Status: member.UserStatus,
RBACRoles: member.UserRbacRoles,
LoginType: member.UserLoginType,
AvatarURL: member.UserAvatarUrl,
Deleted: member.UserDeleted,
LastSeenAt: member.UserLastSeenAt,
QuietHoursSchedule: member.UserQuietHoursSchedule,
Name: member.UserName,
GithubComUserID: member.UserGithubComUserID,
}
}
func ReducedUserFromGroupMember(member database.GroupMember) codersdk.ReducedUser {
return ReducedUser(UserFromGroupMember(member))
}
func ReducedUsersFromGroupMembers(members []database.GroupMember) []codersdk.ReducedUser {
return List(members, ReducedUserFromGroupMember)
}
func ReducedUsers(users []database.User) []codersdk.ReducedUser {
return List(users, ReducedUser)
}
func User(user database.User, organizationIDs []uuid.UUID) codersdk.User {
convertedUser := codersdk.User{
ReducedUser: ReducedUser(user),
OrganizationIDs: organizationIDs,
Roles: SlimRolesFromNames(user.RBACRoles),
}
return convertedUser
}
func Users(users []database.User, organizationIDs map[uuid.UUID][]uuid.UUID) []codersdk.User {
return List(users, func(user database.User) codersdk.User {
return User(user, organizationIDs[user.ID])
})
}
func Group(row database.GetGroupsRow, members []database.GroupMember, totalMemberCount int) codersdk.Group {
return codersdk.Group{
ID: row.Group.ID,
Name: row.Group.Name,
DisplayName: row.Group.DisplayName,
OrganizationID: row.Group.OrganizationID,
AvatarURL: row.Group.AvatarURL,
Members: ReducedUsersFromGroupMembers(members),
TotalMemberCount: totalMemberCount,
QuotaAllowance: int(row.Group.QuotaAllowance),
Source: codersdk.GroupSource(row.Group.Source),
OrganizationName: row.OrganizationName,
OrganizationDisplayName: row.OrganizationDisplayName,
}
}
func TemplateInsightsParameters(parameterRows []database.GetTemplateParameterInsightsRow) ([]codersdk.TemplateParameterUsage, error) {
// Use a stable sort, similarly to how we would sort in the query, note that
// we don't sort in the query because order varies depending on the table
// collation.
//
// ORDER BY utp.name, utp.type, utp.display_name, utp.description, utp.options, wbp.value
slices.SortFunc(parameterRows, func(a, b database.GetTemplateParameterInsightsRow) int {
if a.Name != b.Name {
return strings.Compare(a.Name, b.Name)
}
if a.Type != b.Type {
return strings.Compare(a.Type, b.Type)
}
if a.DisplayName != b.DisplayName {
return strings.Compare(a.DisplayName, b.DisplayName)
}
if a.Description != b.Description {
return strings.Compare(a.Description, b.Description)
}
if string(a.Options) != string(b.Options) {
return strings.Compare(string(a.Options), string(b.Options))
}
return strings.Compare(a.Value, b.Value)
})
parametersUsage := []codersdk.TemplateParameterUsage{}
indexByNum := make(map[int64]int)
for _, param := range parameterRows {
if _, ok := indexByNum[param.Num]; !ok {
var opts []codersdk.TemplateVersionParameterOption
err := json.Unmarshal(param.Options, &opts)
if err != nil {
return nil, err
}
plaintextDescription, err := render.PlaintextFromMarkdown(param.Description)
if err != nil {
return nil, err
}
parametersUsage = append(parametersUsage, codersdk.TemplateParameterUsage{
TemplateIDs: param.TemplateIDs,
Name: param.Name,
Type: param.Type,
DisplayName: param.DisplayName,
Description: plaintextDescription,
Options: opts,
})
indexByNum[param.Num] = len(parametersUsage) - 1
}
i := indexByNum[param.Num]
parametersUsage[i].Values = append(parametersUsage[i].Values, codersdk.TemplateParameterValue{
Value: param.Value,
Count: param.Count,
})
}
return parametersUsage, nil
}
func templateVersionParameterOptions(rawOptions json.RawMessage) ([]codersdk.TemplateVersionParameterOption, error) {
var protoOptions []*proto.RichParameterOption
err := json.Unmarshal(rawOptions, &protoOptions)
if err != nil {
return nil, err
}
options := make([]codersdk.TemplateVersionParameterOption, 0)
for _, option := range protoOptions {
options = append(options, codersdk.TemplateVersionParameterOption{
Name: option.Name,
Description: option.Description,
Value: option.Value,
Icon: option.Icon,
})
}
return options, nil
}
func TemplateVersionParameterOptionFromPreview(option *previewtypes.ParameterOption) codersdk.TemplateVersionParameterOption {
return codersdk.TemplateVersionParameterOption{
Name: option.Name,
Description: option.Description,
Value: option.Value.AsString(),
Icon: option.Icon,
}
}
func OAuth2ProviderApp(accessURL *url.URL, dbApp database.OAuth2ProviderApp) codersdk.OAuth2ProviderApp {
return codersdk.OAuth2ProviderApp{
ID: dbApp.ID,
Name: dbApp.Name,
CallbackURL: dbApp.CallbackURL,
Icon: dbApp.Icon,
Endpoints: codersdk.OAuth2AppEndpoints{
Authorization: accessURL.ResolveReference(&url.URL{
Path: "/oauth2/authorize",
}).String(),
Token: accessURL.ResolveReference(&url.URL{
Path: "/oauth2/tokens",
}).String(),
// We do not currently support DeviceAuth.
DeviceAuth: "",
},
}
}
func OAuth2ProviderApps(accessURL *url.URL, dbApps []database.OAuth2ProviderApp) []codersdk.OAuth2ProviderApp {
return List(dbApps, func(dbApp database.OAuth2ProviderApp) codersdk.OAuth2ProviderApp {
return OAuth2ProviderApp(accessURL, dbApp)
})
}
func convertDisplayApps(apps []database.DisplayApp) []codersdk.DisplayApp {
dapps := make([]codersdk.DisplayApp, 0, len(apps))
for _, app := range apps {
switch codersdk.DisplayApp(app) {
case codersdk.DisplayAppVSCodeDesktop, codersdk.DisplayAppVSCodeInsiders, codersdk.DisplayAppPortForward, codersdk.DisplayAppWebTerminal, codersdk.DisplayAppSSH:
dapps = append(dapps, codersdk.DisplayApp(app))
}
}
return dapps
}
func WorkspaceAgentEnvironment(workspaceAgent database.WorkspaceAgent) (map[string]string, error) {
var envs map[string]string
if workspaceAgent.EnvironmentVariables.Valid {
err := json.Unmarshal(workspaceAgent.EnvironmentVariables.RawMessage, &envs)
if err != nil {
return nil, xerrors.Errorf("unmarshal environment variables: %w", err)
}
}
return envs, nil
}
func WorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordinator,
dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, scripts []codersdk.WorkspaceAgentScript, logSources []codersdk.WorkspaceAgentLogSource,
agentInactiveDisconnectTimeout time.Duration, agentFallbackTroubleshootingURL string,
) (codersdk.WorkspaceAgent, error) {
envs, err := WorkspaceAgentEnvironment(dbAgent)
if err != nil {
return codersdk.WorkspaceAgent{}, err
}
troubleshootingURL := agentFallbackTroubleshootingURL
if dbAgent.TroubleshootingURL != "" {
troubleshootingURL = dbAgent.TroubleshootingURL
}
subsystems := make([]codersdk.AgentSubsystem, len(dbAgent.Subsystems))
for i, subsystem := range dbAgent.Subsystems {
subsystems[i] = codersdk.AgentSubsystem(subsystem)
}
legacyStartupScriptBehavior := codersdk.WorkspaceAgentStartupScriptBehaviorNonBlocking
for _, script := range scripts {
if !script.RunOnStart {
continue
}
if !script.StartBlocksLogin {
continue
}
legacyStartupScriptBehavior = codersdk.WorkspaceAgentStartupScriptBehaviorBlocking
}
workspaceAgent := codersdk.WorkspaceAgent{
ID: dbAgent.ID,
ParentID: dbAgent.ParentID,
CreatedAt: dbAgent.CreatedAt,
UpdatedAt: dbAgent.UpdatedAt,
ResourceID: dbAgent.ResourceID,
InstanceID: dbAgent.AuthInstanceID.String,
Name: dbAgent.Name,
Architecture: dbAgent.Architecture,
OperatingSystem: dbAgent.OperatingSystem,
Scripts: scripts,
StartupScriptBehavior: legacyStartupScriptBehavior,
LogsLength: dbAgent.LogsLength,
LogsOverflowed: dbAgent.LogsOverflowed,
LogSources: logSources,
Version: dbAgent.Version,
APIVersion: dbAgent.APIVersion,
EnvironmentVariables: envs,
Directory: dbAgent.Directory,
ExpandedDirectory: dbAgent.ExpandedDirectory,
Apps: apps,
ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds,
TroubleshootingURL: troubleshootingURL,
LifecycleState: codersdk.WorkspaceAgentLifecycle(dbAgent.LifecycleState),
Subsystems: subsystems,
DisplayApps: convertDisplayApps(dbAgent.DisplayApps),
}
node := coordinator.Node(dbAgent.ID)
if node != nil {
workspaceAgent.DERPLatency = map[string]codersdk.DERPRegion{}
for rawRegion, latency := range node.DERPLatency {
regionParts := strings.SplitN(rawRegion, "-", 2)
regionID, err := strconv.Atoi(regionParts[0])
if err != nil {
return codersdk.WorkspaceAgent{}, xerrors.Errorf("convert derp region id %q: %w", rawRegion, err)
}
region, found := derpMap.Regions[regionID]
if !found {
// It's possible that a workspace agent is using an old DERPMap
// and reports regions that do not exist. If that's the case,
// report the region as unknown!
region = &tailcfg.DERPRegion{
RegionID: regionID,
RegionName: fmt.Sprintf("Unnamed %d", regionID),
}
}
workspaceAgent.DERPLatency[region.RegionName] = codersdk.DERPRegion{
Preferred: node.PreferredDERP == regionID,
LatencyMilliseconds: latency * 1000,
}
}
}
status := dbAgent.Status(agentInactiveDisconnectTimeout)
workspaceAgent.Status = codersdk.WorkspaceAgentStatus(status.Status)
workspaceAgent.FirstConnectedAt = status.FirstConnectedAt
workspaceAgent.LastConnectedAt = status.LastConnectedAt
workspaceAgent.DisconnectedAt = status.DisconnectedAt
if dbAgent.StartedAt.Valid {
workspaceAgent.StartedAt = &dbAgent.StartedAt.Time
}
if dbAgent.ReadyAt.Valid {
workspaceAgent.ReadyAt = &dbAgent.ReadyAt.Time
}
switch {
case workspaceAgent.Status != codersdk.WorkspaceAgentConnected && workspaceAgent.LifecycleState == codersdk.WorkspaceAgentLifecycleOff:
workspaceAgent.Health.Reason = "agent is not running"
case workspaceAgent.Status == codersdk.WorkspaceAgentTimeout:
workspaceAgent.Health.Reason = "agent is taking too long to connect"
case workspaceAgent.Status == codersdk.WorkspaceAgentDisconnected:
workspaceAgent.Health.Reason = "agent has lost connection"
// Note: We could also handle codersdk.WorkspaceAgentLifecycleStartTimeout
// here, but it's more of a soft issue, so we don't want to mark the agent
// as unhealthy.
case workspaceAgent.LifecycleState == codersdk.WorkspaceAgentLifecycleStartError:
workspaceAgent.Health.Reason = "agent startup script exited with an error"
case workspaceAgent.LifecycleState.ShuttingDown():
workspaceAgent.Health.Reason = "agent is shutting down"
default:
workspaceAgent.Health.Healthy = true
}
return workspaceAgent, nil
}
func AppSubdomain(dbApp database.WorkspaceApp, agentName, workspaceName, ownerName string) string {
if !dbApp.Subdomain || agentName == "" || ownerName == "" || workspaceName == "" {
return ""
}
appSlug := dbApp.Slug
if appSlug == "" {
appSlug = dbApp.DisplayName
}
return appurl.ApplicationURL{
// We never generate URLs with a prefix. We only allow prefixes when
// parsing URLs from the hostname. Users that want this feature can
// write out their own URLs.
Prefix: "",
AppSlugOrPort: appSlug,
AgentName: agentName,
WorkspaceName: workspaceName,
Username: ownerName,
}.String()
}
func Apps(dbApps []database.WorkspaceApp, statuses []database.WorkspaceAppStatus, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) []codersdk.WorkspaceApp {
sort.Slice(dbApps, func(i, j int) bool {
if dbApps[i].DisplayOrder != dbApps[j].DisplayOrder {
return dbApps[i].DisplayOrder < dbApps[j].DisplayOrder
}
if dbApps[i].DisplayName != dbApps[j].DisplayName {
return dbApps[i].DisplayName < dbApps[j].DisplayName
}
return dbApps[i].Slug < dbApps[j].Slug
})
statusesByAppID := map[uuid.UUID][]database.WorkspaceAppStatus{}
for _, status := range statuses {
statusesByAppID[status.AppID] = append(statusesByAppID[status.AppID], status)
}
apps := make([]codersdk.WorkspaceApp, 0)
for _, dbApp := range dbApps {
statuses := statusesByAppID[dbApp.ID]
apps = append(apps, codersdk.WorkspaceApp{
ID: dbApp.ID,
URL: dbApp.Url.String,
External: dbApp.External,
Slug: dbApp.Slug,
DisplayName: dbApp.DisplayName,
Command: dbApp.Command.String,
Icon: dbApp.Icon,
Subdomain: dbApp.Subdomain,
SubdomainName: AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName),
SharingLevel: codersdk.WorkspaceAppSharingLevel(dbApp.SharingLevel),
Healthcheck: codersdk.Healthcheck{
URL: dbApp.HealthcheckUrl,
Interval: dbApp.HealthcheckInterval,
Threshold: dbApp.HealthcheckThreshold,
},
Health: codersdk.WorkspaceAppHealth(dbApp.Health),
Group: dbApp.DisplayGroup.String,
Hidden: dbApp.Hidden,
OpenIn: codersdk.WorkspaceAppOpenIn(dbApp.OpenIn),
Statuses: WorkspaceAppStatuses(statuses),
})
}
return apps
}
func WorkspaceAppStatuses(statuses []database.WorkspaceAppStatus) []codersdk.WorkspaceAppStatus {
return List(statuses, WorkspaceAppStatus)
}
func WorkspaceAppStatus(status database.WorkspaceAppStatus) codersdk.WorkspaceAppStatus {
return codersdk.WorkspaceAppStatus{
ID: status.ID,
CreatedAt: status.CreatedAt,
WorkspaceID: status.WorkspaceID,
AgentID: status.AgentID,
AppID: status.AppID,
URI: status.Uri.String,
Message: status.Message,
State: codersdk.WorkspaceAppStatusState(status.State),
}
}
func ProvisionerDaemon(dbDaemon database.ProvisionerDaemon) codersdk.ProvisionerDaemon {
result := codersdk.ProvisionerDaemon{
ID: dbDaemon.ID,
OrganizationID: dbDaemon.OrganizationID,
CreatedAt: dbDaemon.CreatedAt,
LastSeenAt: codersdk.NullTime{NullTime: dbDaemon.LastSeenAt},
Name: dbDaemon.Name,
Tags: dbDaemon.Tags,
Version: dbDaemon.Version,
APIVersion: dbDaemon.APIVersion,
KeyID: dbDaemon.KeyID,
}
for _, provisionerType := range dbDaemon.Provisioners {
result.Provisioners = append(result.Provisioners, codersdk.ProvisionerType(provisionerType))
}
return result
}
func RecentProvisionerDaemons(now time.Time, staleInterval time.Duration, daemons []database.ProvisionerDaemon) []codersdk.ProvisionerDaemon {
results := []codersdk.ProvisionerDaemon{}
for _, daemon := range daemons {
// Daemon never connected, skip.
if !daemon.LastSeenAt.Valid {
continue
}
// Daemon has gone away, skip.
if now.Sub(daemon.LastSeenAt.Time) > staleInterval {
continue
}
results = append(results, ProvisionerDaemon(daemon))
}
// Ensure stable order for display and for tests
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
return results
}
func SlimRole(role rbac.Role) codersdk.SlimRole {
orgID := ""
if role.Identifier.OrganizationID != uuid.Nil {
orgID = role.Identifier.OrganizationID.String()
}
return codersdk.SlimRole{
DisplayName: role.DisplayName,
Name: role.Identifier.Name,
OrganizationID: orgID,
}
}
func SlimRolesFromNames(names []string) []codersdk.SlimRole {
convertedRoles := make([]codersdk.SlimRole, 0, len(names))
for _, name := range names {
convertedRoles = append(convertedRoles, SlimRoleFromName(name))
}
return convertedRoles
}
func SlimRoleFromName(name string) codersdk.SlimRole {
rbacRole, err := rbac.RoleByName(rbac.RoleIdentifier{Name: name})
var convertedRole codersdk.SlimRole
if err == nil {
convertedRole = SlimRole(rbacRole)
} else {
convertedRole = codersdk.SlimRole{Name: name}
}
return convertedRole
}
func RBACRole(role rbac.Role) codersdk.Role {
slim := SlimRole(role)
orgPerms := role.Org[slim.OrganizationID]
return codersdk.Role{
Name: slim.Name,
OrganizationID: slim.OrganizationID,
DisplayName: slim.DisplayName,
SitePermissions: List(role.Site, RBACPermission),
OrganizationPermissions: List(orgPerms, RBACPermission),
UserPermissions: List(role.User, RBACPermission),
}
}
func Role(role database.CustomRole) codersdk.Role {
orgID := ""
if role.OrganizationID.UUID != uuid.Nil {
orgID = role.OrganizationID.UUID.String()
}
return codersdk.Role{
Name: role.Name,
OrganizationID: orgID,
DisplayName: role.DisplayName,
SitePermissions: List(role.SitePermissions, Permission),
OrganizationPermissions: List(role.OrgPermissions, Permission),
UserPermissions: List(role.UserPermissions, Permission),
}
}
func Permission(permission database.CustomRolePermission) codersdk.Permission {
return codersdk.Permission{
Negate: permission.Negate,
ResourceType: codersdk.RBACResource(permission.ResourceType),
Action: codersdk.RBACAction(permission.Action),
}
}
func RBACPermission(permission rbac.Permission) codersdk.Permission {
return codersdk.Permission{
Negate: permission.Negate,
ResourceType: codersdk.RBACResource(permission.ResourceType),
Action: codersdk.RBACAction(permission.Action),
}
}
func Organization(organization database.Organization) codersdk.Organization {
return codersdk.Organization{
MinimalOrganization: codersdk.MinimalOrganization{
ID: organization.ID,
Name: organization.Name,
DisplayName: organization.DisplayName,
Icon: organization.Icon,
},
Description: organization.Description,
CreatedAt: organization.CreatedAt,
UpdatedAt: organization.UpdatedAt,
IsDefault: organization.IsDefault,
}
}
func CryptoKeys(keys []database.CryptoKey) []codersdk.CryptoKey {
return List(keys, CryptoKey)
}
func CryptoKey(key database.CryptoKey) codersdk.CryptoKey {
return codersdk.CryptoKey{
Feature: codersdk.CryptoKeyFeature(key.Feature),
Sequence: key.Sequence,
StartsAt: key.StartsAt,
DeletesAt: key.DeletesAt.Time,
Secret: key.Secret.String,
}
}
func MatchedProvisioners(provisionerDaemons []database.ProvisionerDaemon, now time.Time, staleInterval time.Duration) codersdk.MatchedProvisioners {
minLastSeenAt := now.Add(-staleInterval)
mostRecentlySeen := codersdk.NullTime{}
var matched codersdk.MatchedProvisioners
for _, provisioner := range provisionerDaemons {
if !provisioner.LastSeenAt.Valid {
continue
}
matched.Count++
if provisioner.LastSeenAt.Time.After(minLastSeenAt) {
matched.Available++
}
if provisioner.LastSeenAt.Time.After(mostRecentlySeen.Time) {
matched.MostRecentlySeen.Valid = true
matched.MostRecentlySeen.Time = provisioner.LastSeenAt.Time
}
}
return matched
}
func TemplateRoleActions(role codersdk.TemplateRole) []policy.Action {
switch role {
case codersdk.TemplateRoleAdmin:
return []policy.Action{policy.WildcardSymbol}
case codersdk.TemplateRoleUse:
return []policy.Action{policy.ActionRead, policy.ActionUse}
}
return []policy.Action{}
}
func AuditActionFromAgentProtoConnectionAction(action agentproto.Connection_Action) (database.AuditAction, error) {
switch action {
case agentproto.Connection_CONNECT:
return database.AuditActionConnect, nil
case agentproto.Connection_DISCONNECT:
return database.AuditActionDisconnect, nil
default:
// Also Connection_ACTION_UNSPECIFIED, no mapping.
return "", xerrors.Errorf("unknown agent connection action %q", action)
}
}
func AgentProtoConnectionActionToAuditAction(action database.AuditAction) (agentproto.Connection_Action, error) {
switch action {
case database.AuditActionConnect:
return agentproto.Connection_CONNECT, nil
case database.AuditActionDisconnect:
return agentproto.Connection_DISCONNECT, nil
default:
return agentproto.Connection_ACTION_UNSPECIFIED, xerrors.Errorf("unknown agent connection action %q", action)
}
}
func PreviewParameter(param previewtypes.Parameter) codersdk.PreviewParameter {
return codersdk.PreviewParameter{
PreviewParameterData: codersdk.PreviewParameterData{
Name: param.Name,
DisplayName: param.DisplayName,
Description: param.Description,
Type: codersdk.OptionType(param.Type),
FormType: codersdk.ParameterFormType(param.FormType),
Styling: codersdk.PreviewParameterStyling{
Placeholder: param.Styling.Placeholder,
Disabled: param.Styling.Disabled,
Label: param.Styling.Label,
MaskInput: param.Styling.MaskInput,
},
Mutable: param.Mutable,
DefaultValue: PreviewHCLString(param.DefaultValue),
Icon: param.Icon,
Options: List(param.Options, PreviewParameterOption),
Validations: List(param.Validations, PreviewParameterValidation),
Required: param.Required,
Order: param.Order,
Ephemeral: param.Ephemeral,
},
Value: PreviewHCLString(param.Value),
Diagnostics: PreviewDiagnostics(param.Diagnostics),
}
}
func HCLDiagnostics(d hcl.Diagnostics) []codersdk.FriendlyDiagnostic {
return PreviewDiagnostics(previewtypes.Diagnostics(d))
}
func PreviewDiagnostics(d previewtypes.Diagnostics) []codersdk.FriendlyDiagnostic {
f := d.FriendlyDiagnostics()
return List(f, func(f previewtypes.FriendlyDiagnostic) codersdk.FriendlyDiagnostic {
return codersdk.FriendlyDiagnostic{
Severity: codersdk.DiagnosticSeverityString(f.Severity),
Summary: f.Summary,
Detail: f.Detail,
Extra: codersdk.DiagnosticExtra{
Code: f.Extra.Code,
},
}
})
}
func PreviewHCLString(h previewtypes.HCLString) codersdk.NullHCLString {
n := h.NullHCLString()
return codersdk.NullHCLString{
Value: n.Value,
Valid: n.Valid,
}
}
func PreviewParameterOption(o *previewtypes.ParameterOption) codersdk.PreviewParameterOption {
if o == nil {
// This should never be sent
return codersdk.PreviewParameterOption{}
}
return codersdk.PreviewParameterOption{
Name: o.Name,
Description: o.Description,
Value: PreviewHCLString(o.Value),
Icon: o.Icon,
}
}
func PreviewParameterValidation(v *previewtypes.ParameterValidation) codersdk.PreviewParameterValidation {
if v == nil {
// This should never be sent
return codersdk.PreviewParameterValidation{}
}
return codersdk.PreviewParameterValidation{
Error: v.Error,
Regex: v.Regex,
Min: v.Min,
Max: v.Max,
Monotonic: v.Monotonic,
}
}