// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: Apache-2.0 package arvados import ( "crypto/tls" "encoding/json" "errors" "fmt" "net/url" "os" "time" "git.arvados.org/arvados.git/sdk/go/config" ) var DefaultConfigFile = func() string { if path := os.Getenv("ARVADOS_CONFIG"); path != "" { return path } return "/etc/arvados/config.yml" }() type Config struct { Clusters map[string]Cluster AutoReloadConfig bool SourceTimestamp time.Time SourceSHA256 string } // GetConfig returns the current system config, loading it from // configFile if needed. func GetConfig(configFile string) (*Config, error) { var cfg Config err := config.LoadFile(&cfg, configFile) return &cfg, err } // GetCluster returns the cluster ID and config for the given // cluster, or the default/only configured cluster if clusterID is "". func (sc *Config) GetCluster(clusterID string) (*Cluster, error) { if clusterID == "" { if len(sc.Clusters) == 0 { return nil, fmt.Errorf("no clusters configured") } else if len(sc.Clusters) > 1 { return nil, fmt.Errorf("multiple clusters configured, cannot choose") } else { for id, cc := range sc.Clusters { cc.ClusterID = id return &cc, nil } } } cc, ok := sc.Clusters[clusterID] if !ok { return nil, fmt.Errorf("cluster %q is not configured", clusterID) } cc.ClusterID = clusterID return &cc, nil } type WebDAVCacheConfig struct { TTL Duration DiskCacheSize ByteSizeOrPercent MaxCollectionBytes ByteSize MaxSessions int } type UploadDownloadPermission struct { Upload bool Download bool } type UploadDownloadRolePermissions struct { User UploadDownloadPermission Admin UploadDownloadPermission } type ManagedProperties map[string]struct { Value interface{} Function string Protected bool } type Cluster struct { ClusterID string `json:"-"` ManagementToken string SystemRootToken string Services Services InstanceTypes InstanceTypeMap Containers ContainersConfig RemoteClusters map[string]RemoteCluster PostgreSQL PostgreSQL API struct { AsyncPermissionsUpdateInterval Duration DisabledAPIs StringSet MaxIndexDatabaseRead int MaxItemsPerResponse int MaxConcurrentRailsRequests int MaxConcurrentRequests int MaxQueuedRequests int MaxGatewayTunnels int MaxQueueTimeForLockRequests Duration LogCreateRequestFraction float64 MaxKeepBlobBuffers int MaxRequestAmplification int MaxRequestSize int MaxTokenLifetime Duration RequestTimeout Duration SendTimeout Duration WebsocketClientEventQueue int WebsocketServerEventQueue int KeepServiceRequestTimeout Duration VocabularyPath string FreezeProjectRequiresDescription bool FreezeProjectRequiresProperties StringSet UnfreezeProjectRequiresAdmin bool LockBeforeUpdate bool } AuditLogs struct { MaxAge Duration MaxDeleteBatch int UnloggedAttributes StringSet } Collections struct { BlobSigning bool BlobSigningKey string BlobSigningTTL Duration BlobTrash bool BlobTrashLifetime Duration BlobTrashCheckInterval Duration BlobTrashConcurrency int BlobDeleteConcurrency int BlobReplicateConcurrency int CollectionVersioning bool DefaultTrashLifetime Duration DefaultReplication int ManagedProperties ManagedProperties PreserveVersionIfIdle Duration TrashSweepInterval Duration TrustAllContent bool ForwardSlashNameSubstitution string S3FolderObjects bool BlobMissingReport string BalancePeriod Duration BalanceCollectionBatch int BalanceCollectionBuffers int BalanceTimeout Duration BalanceUpdateLimit int BalancePullLimit int BalanceTrashLimit int WebDAVCache WebDAVCacheConfig KeepproxyPermission UploadDownloadRolePermissions WebDAVPermission UploadDownloadRolePermissions WebDAVLogEvents bool } Git struct { GitCommand string GitoliteHome string Repositories string } Login struct { LDAP struct { Enable bool URL URL StartTLS bool InsecureTLS bool MinTLSVersion TLSVersion StripDomain string AppendDomain string SearchAttribute string SearchBindUser string SearchBindPassword string SearchBase string SearchFilters string EmailAttribute string UsernameAttribute string } Google struct { Enable bool ClientID string ClientSecret string AlternateEmailAddresses bool AuthenticationRequestParameters map[string]string } OpenIDConnect struct { Enable bool Issuer string ClientID string ClientSecret string EmailClaim string EmailVerifiedClaim string UsernameClaim string AcceptAccessToken bool AcceptAccessTokenScope string AuthenticationRequestParameters map[string]string } PAM struct { Enable bool Service string DefaultEmailDomain string } Test struct { Enable bool Users map[string]TestUser } LoginCluster string RemoteTokenRefresh Duration TokenLifetime Duration TrustedClients map[URL]struct{} TrustPrivateNetworks bool IssueTrustedTokens bool } Mail struct { MailchimpAPIKey string MailchimpListID string SendUserSetupNotificationEmail bool IssueReporterEmailFrom string IssueReporterEmailTo string SupportEmailAddress string EmailFrom string } SystemLogs struct { LogLevel string Format string MaxRequestLogParamsSize int RequestQueueDumpDirectory string } TLS struct { Certificate string Key string Insecure bool ACME struct { Server string } } Users struct { ActivatedUsersAreVisibleToOthers bool AnonymousUserToken string AdminNotifierEmailFrom string AutoAdminFirstUser bool AutoAdminUserWithEmail string AutoSetupNewUsers bool AutoSetupNewUsersWithRepository bool AutoSetupNewUsersWithVmUUID string AutoSetupUsernameBlacklist StringSet EmailSubjectPrefix string NewInactiveUserNotificationRecipients StringSet NewUserNotificationRecipients StringSet NewUsersAreActive bool UserNotifierEmailFrom string UserNotifierEmailBcc StringSet UserProfileNotificationAddress string PreferDomainForUsername string UserSetupMailText string RoleGroupsVisibleToAll bool CanCreateRoleGroups bool ActivityLoggingPeriod Duration SyncIgnoredGroups []string SyncRequiredGroups []string SyncUserAccounts bool SyncUserAPITokens bool SyncUserGroups bool SyncUserSSHKeys bool } StorageClasses map[string]StorageClassConfig Volumes map[string]Volume Workbench struct { ActivationContactLink string ArvadosDocsite string ArvadosPublicDataDocURL string DisableSharingURLsUI bool FileViewersConfigURL string ShowUserAgreementInline bool SiteName string Theme string UserProfileFormFields map[string]struct { Type string FormFieldTitle string FormFieldDescription string Required bool Position int Options map[string]struct{} } UserProfileFormMessage string WelcomePageHTML string InactivePageHTML string SSHHelpPageHTML string SSHHelpHostSuffix string IdleTimeout Duration BannerUUID string } } type StorageClassConfig struct { Default bool Priority int } type Volume struct { AccessViaHosts map[URL]VolumeAccess ReadOnly bool AllowTrashWhenReadOnly bool Replication int StorageClasses map[string]bool Driver string DriverParameters json.RawMessage } type S3VolumeDriverParameters struct { IAMRole string AccessKeyID string SecretAccessKey string Endpoint string Region string Bucket string LocationConstraint bool V2Signature bool IndexPageSize int ConnectTimeout Duration ReadTimeout Duration RaceWindow Duration UnsafeDelete bool PrefixLength int } type AzureVolumeDriverParameters struct { StorageAccountName string StorageAccountKey string StorageBaseURL string ContainerName string RequestTimeout Duration ListBlobsRetryDelay Duration ListBlobsMaxAttempts int } type DirectoryVolumeDriverParameters struct { Root string Serialize bool } type VolumeAccess struct { ReadOnly bool } type Services struct { Composer Service Controller Service DispatchCloud Service DispatchLSF Service DispatchSLURM Service GitHTTP Service GitSSH Service Health Service Keepbalance Service Keepproxy Service Keepstore Service RailsAPI Service WebDAVDownload Service WebDAV Service WebShell Service Websocket Service Workbench1 Service Workbench2 Service } type Service struct { InternalURLs map[URL]ServiceInstance ExternalURL URL } type TestUser struct { Email string Password string } // URL is a url.URL that is also usable as a JSON key/value. type URL url.URL // UnmarshalText implements encoding.TextUnmarshaler so URL can be // used as a JSON key/value. func (su *URL) UnmarshalText(text []byte) error { u, err := url.Parse(string(text)) if err == nil { *su = URL(*u) if su.Path == "" && su.Host != "" { // http://example really means http://example/ su.Path = "/" } } return err } func (su URL) MarshalText() ([]byte, error) { return []byte(su.String()), nil } func (su URL) String() string { return (*url.URL)(&su).String() } type TLSVersion uint16 func (v TLSVersion) MarshalText() ([]byte, error) { switch v { case 0: return []byte{}, nil case tls.VersionTLS10: return []byte("1.0"), nil case tls.VersionTLS11: return []byte("1.1"), nil case tls.VersionTLS12: return []byte("1.2"), nil case tls.VersionTLS13: return []byte("1.3"), nil default: return nil, fmt.Errorf("unsupported TLSVersion %x", v) } } func (v *TLSVersion) UnmarshalJSON(text []byte) error { if len(text) > 0 && text[0] == '"' { var s string err := json.Unmarshal(text, &s) if err != nil { return err } text = []byte(s) } switch string(text) { case "": *v = 0 case "1.0": *v = tls.VersionTLS10 case "1.1": *v = tls.VersionTLS11 case "1.2": *v = tls.VersionTLS12 case "1.3": *v = tls.VersionTLS13 default: return fmt.Errorf("unsupported TLSVersion %q", text) } return nil } type ServiceInstance struct { ListenURL URL Rendezvous string `json:",omitempty"` } type PostgreSQL struct { Connection PostgreSQLConnection ConnectionPool int } type PostgreSQLConnection map[string]string type RemoteCluster struct { Host string Proxy bool Scheme string Insecure bool ActivateUsers bool } type CUDAFeatures struct { DriverVersion string HardwareCapability string DeviceCount int } type InstanceType struct { Name string `json:"-"` ProviderType string VCPUs int RAM ByteSize Scratch ByteSize `json:"-"` IncludedScratch ByteSize AddedScratch ByteSize Price float64 Preemptible bool CUDA CUDAFeatures } type ContainersConfig struct { CloudVMs CloudVMsConfig CrunchRunCommand string CrunchRunArgumentsList []string DefaultKeepCacheRAM ByteSize DispatchPrivateKey string LogReuseDecisions bool MaxDispatchAttempts int MaxRetryAttempts int MinRetryPeriod Duration ReserveExtraRAM ByteSize StaleLockTimeout Duration SupportedDockerImageFormats StringSet AlwaysUsePreemptibleInstances bool PreemptiblePriceFactor float64 MaximumPriceFactor float64 RuntimeEngine string LocalKeepBlobBuffersPerVCPU int LocalKeepLogsToContainerLog string JobsAPI struct { Enable string GitInternalDir string } Logging struct { MaxAge Duration SweepInterval Duration LogBytesPerEvent int LogSecondsBetweenEvents Duration LogThrottlePeriod Duration LogThrottleBytes int LogThrottleLines int LimitLogBytesPerJob int LogPartialLineThrottlePeriod Duration LogUpdatePeriod Duration LogUpdateSize ByteSize } ShellAccess struct { Admin bool User bool } SLURM struct { PrioritySpread int64 SbatchArgumentsList []string SbatchEnvironmentVariables map[string]string Managed struct { DNSServerConfDir string DNSServerConfTemplate string DNSServerReloadCommand string DNSServerUpdateCommand string ComputeNodeDomain string ComputeNodeNameservers StringSet AssignNodeHostname string } } LSF struct { BsubSudoUser string BsubArgumentsList []string BsubCUDAArguments []string MaxRunTimeOverhead Duration MaxRunTimeDefault Duration } } type CloudVMsConfig struct { Enable bool BootProbeCommand string InstanceInitCommand string DeployRunnerBinary string DeployPublicKey bool ImageID string MaxCloudOpsPerSecond int MaxProbesPerSecond int MaxConcurrentInstanceCreateOps int MaxInstances int InitialQuotaEstimate int SupervisorFraction float64 PollInterval Duration ProbeInterval Duration SSHPort string SyncInterval Duration TimeoutBooting Duration TimeoutIdle Duration TimeoutProbe Duration TimeoutShutdown Duration TimeoutSignal Duration TimeoutStaleRunLock Duration TimeoutTERM Duration ResourceTags map[string]string TagKeyPrefix string Driver string DriverParameters json.RawMessage } type InstanceTypeMap map[string]InstanceType var errDuplicateInstanceTypeName = errors.New("duplicate instance type name") // UnmarshalJSON does special handling of InstanceTypes: // // - populate computed fields (Name and Scratch) // // - error out if InstancesTypes are populated as an array, which was // deprecated in Arvados 1.2.0 func (it *InstanceTypeMap) UnmarshalJSON(data []byte) error { fixup := func(t InstanceType) (InstanceType, error) { if t.ProviderType == "" { t.ProviderType = t.Name } // If t.Scratch is set in the configuration file, it will be ignored and overwritten. // It will also generate a "deprecated or unknown config entry" warning. t.Scratch = t.IncludedScratch + t.AddedScratch return t, nil } if len(data) > 0 && data[0] == '[' { return fmt.Errorf("InstanceTypes must be specified as a map, not an array, see https://doc.arvados.org/admin/config.html") } var hash map[string]InstanceType err := json.Unmarshal(data, &hash) if err != nil { return err } // Fill in Name field (and ProviderType field, if not // specified) using hash key. *it = InstanceTypeMap(hash) for name, t := range *it { t.Name = name t, err := fixup(t) if err != nil { return err } (*it)[name] = t } return nil } type StringSet map[string]struct{} // UnmarshalJSON handles old config files that provide an array of // instance types instead of a hash. func (ss *StringSet) UnmarshalJSON(data []byte) error { if len(data) > 0 && data[0] == '[' { var arr []string err := json.Unmarshal(data, &arr) if err != nil { return err } if len(arr) == 0 { *ss = nil return nil } *ss = make(map[string]struct{}, len(arr)) for _, t := range arr { (*ss)[t] = struct{}{} } return nil } var hash map[string]struct{} err := json.Unmarshal(data, &hash) if err != nil { return err } *ss = make(map[string]struct{}, len(hash)) for t := range hash { (*ss)[t] = struct{}{} } return nil } type ServiceName string const ( ServiceNameController ServiceName = "arvados-controller" ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud" ServiceNameDispatchLSF ServiceName = "arvados-dispatch-lsf" ServiceNameDispatchSLURM ServiceName = "crunch-dispatch-slurm" ServiceNameGitHTTP ServiceName = "arvados-git-httpd" ServiceNameHealth ServiceName = "arvados-health" ServiceNameKeepbalance ServiceName = "keep-balance" ServiceNameKeepproxy ServiceName = "keepproxy" ServiceNameKeepstore ServiceName = "keepstore" ServiceNameKeepweb ServiceName = "keep-web" ServiceNameRailsAPI ServiceName = "arvados-api-server" ServiceNameWebsocket ServiceName = "arvados-ws" ServiceNameWorkbench1 ServiceName = "arvados-workbench1" ServiceNameWorkbench2 ServiceName = "arvados-workbench2" ) // Map returns all services as a map, suitable for iterating over all // services or looking up a service by name. func (svcs Services) Map() map[ServiceName]Service { return map[ServiceName]Service{ ServiceNameController: svcs.Controller, ServiceNameDispatchCloud: svcs.DispatchCloud, ServiceNameDispatchLSF: svcs.DispatchLSF, ServiceNameDispatchSLURM: svcs.DispatchSLURM, ServiceNameGitHTTP: svcs.GitHTTP, ServiceNameHealth: svcs.Health, ServiceNameKeepbalance: svcs.Keepbalance, ServiceNameKeepproxy: svcs.Keepproxy, ServiceNameKeepstore: svcs.Keepstore, ServiceNameKeepweb: svcs.WebDAV, ServiceNameRailsAPI: svcs.RailsAPI, ServiceNameWebsocket: svcs.Websocket, ServiceNameWorkbench1: svcs.Workbench1, ServiceNameWorkbench2: svcs.Workbench2, } }