config.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package config
  2. import (
  3. "fmt"
  4. "log"
  5. "time"
  6. redis "github.com/go-redis/redis/v8"
  7. "github.com/joeshaw/envdecode"
  8. "github.com/porter-dev/porter/api/server/shared/apierrors/alerter"
  9. "github.com/porter-dev/porter/api/server/shared/config/env"
  10. "github.com/porter-dev/porter/internal/adapter"
  11. "github.com/porter-dev/porter/internal/analytics"
  12. "github.com/porter-dev/porter/internal/kubernetes"
  13. klocal "github.com/porter-dev/porter/internal/kubernetes/local"
  14. "github.com/porter-dev/porter/internal/oauth"
  15. "github.com/porter-dev/porter/internal/repository"
  16. "github.com/porter-dev/porter/internal/repository/credentials"
  17. "github.com/porter-dev/porter/internal/repository/gorm"
  18. "github.com/porter-dev/porter/pkg/logger"
  19. "github.com/porter-dev/porter/provisioner/integrations/provisioner"
  20. "github.com/porter-dev/porter/provisioner/integrations/provisioner/k8s"
  21. "github.com/porter-dev/porter/provisioner/integrations/provisioner/local"
  22. "github.com/porter-dev/porter/provisioner/integrations/storage"
  23. "github.com/porter-dev/porter/provisioner/integrations/storage/s3"
  24. "golang.org/x/oauth2"
  25. _gorm "gorm.io/gorm"
  26. )
  27. var (
  28. InstanceCredentialBackend credentials.CredentialStorage
  29. InstanceEnvConf *EnvConf
  30. )
  31. func sharedInit() {
  32. var envDecoderConf EnvDecoderConf = EnvDecoderConf{}
  33. if err := envdecode.StrictDecode(&envDecoderConf); err != nil {
  34. log.Fatalf("Failed to decode server conf: %s", err)
  35. }
  36. InstanceEnvConf = &EnvConf{
  37. ProvisionerConf: &envDecoderConf.ProvisionerConf,
  38. DBConf: &envDecoderConf.DBConf,
  39. RedisConf: envDecoderConf.RedisConf,
  40. }
  41. }
  42. type Config struct {
  43. ProvisionerConf *ProvisionerConf
  44. DBConf *env.DBConf
  45. RedisConf *env.RedisConf
  46. StorageManager storage.StorageManager
  47. Repo repository.Repository
  48. // Logger for logging
  49. Logger *logger.Logger
  50. // Alerter to send alerts to a third-party aggregator
  51. Alerter alerter.Alerter
  52. DB *_gorm.DB
  53. // DOConf is the configuration for a DigitalOcean OAuth client
  54. DOConf *oauth2.Config
  55. RedisClient *redis.Client
  56. Provisioner provisioner.Provisioner
  57. // AnalyticsClient if Segment analytics reporting is enabled on the API instance
  58. AnalyticsClient analytics.AnalyticsSegmentClient
  59. }
  60. // ProvisionerConf is the env var configuration for the provisioner server
  61. type ProvisionerConf struct {
  62. Debug bool `env:"DEBUG,default=false"`
  63. Port int `env:"PROV_PORT,default=8082"`
  64. TimeoutRead time.Duration `env:"SERVER_TIMEOUT_READ,default=5s"`
  65. TimeoutWrite time.Duration `env:"SERVER_TIMEOUT_WRITE,default=10s"`
  66. TimeoutIdle time.Duration `env:"SERVER_TIMEOUT_IDLE,default=15s"`
  67. StaticAuthToken string `env:"STATIC_AUTH_TOKEN"`
  68. SentryDSN string `env:"SENTRY_DSN"`
  69. SentryEnv string `env:"SENTRY_ENV,default=dev"`
  70. // Configuration for the S3 storage backend
  71. S3AWSAccessKeyID string `env:"S3_AWS_ACCESS_KEY_ID"`
  72. S3AWSSecretKey string `env:"S3_AWS_SECRET_KEY"`
  73. S3AWSRegion string `env:"S3_AWS_REGION"`
  74. S3BucketName string `env:"S3_BUCKET_NAME"`
  75. S3EncryptionKey string `env:"S3_ENCRYPTION_KEY,default=__random_strong_encryption_key__"`
  76. // Configuration for the digitalocean client
  77. DOClientID string `env:"DO_CLIENT_ID"`
  78. DOClientSecret string `env:"DO_CLIENT_SECRET"`
  79. DOClientServerURL string `env:"DO_CLIENT_SERVER_URL"`
  80. // ProvisionerMethod defines the method to use for provisioner: options are "local" or "kubernetes"
  81. ProvisionerMethod string `env:"PROVISIONER_METHOD,default=local"`
  82. ProvisionerBackendURL string `env:"PROV_BACKEND_URL,default=http://localhost:8082"`
  83. ProvisionerCredExchangeURL string `env:"PROV_CRED_EXCHANGE_URL,default=http://localhost:8082"`
  84. // Options to configure for the "kubernetes" provisioner method
  85. ProvisionerCluster string `env:"PROVISIONER_CLUSTER"`
  86. SelfKubeconfig string `env:"SELF_KUBECONFIG"`
  87. ProvisionerImageRepo string `env:"PROV_IMAGE_REPO,default=gcr.io/porter-dev-273614/provisioner"`
  88. ProvisionerImageTag string `env:"PROV_IMAGE_TAG,default=latest"`
  89. ProvisionerImagePullSecret string `env:"PROV_IMAGE_PULL_SECRET"`
  90. ProvisionerJobNamespace string `env:"PROV_JOB_NAMESPACE,default=default"`
  91. // Options to configure for the "local" provisioner method
  92. LocalTerraformDirectory string `env:"LOCAL_TERRAFORM_DIRECTORY"`
  93. // Client key for segment to report provisioning events
  94. SegmentClientKey string `env:"SEGMENT_CLIENT_KEY"`
  95. }
  96. type EnvConf struct {
  97. *ProvisionerConf
  98. *env.DBConf
  99. env.RedisConf
  100. }
  101. type EnvDecoderConf struct {
  102. ProvisionerConf ProvisionerConf
  103. DBConf env.DBConf
  104. RedisConf env.RedisConf
  105. }
  106. // FromEnv generates a configuration from environment variables
  107. func FromEnv() (*EnvConf, error) {
  108. return InstanceEnvConf, nil
  109. }
  110. func GetConfig(envConf *EnvConf) (*Config, error) {
  111. res := &Config{
  112. ProvisionerConf: envConf.ProvisionerConf,
  113. DBConf: envConf.DBConf,
  114. RedisConf: &envConf.RedisConf,
  115. Logger: logger.NewConsole(envConf.ProvisionerConf.Debug),
  116. }
  117. db, err := adapter.New(envConf.DBConf)
  118. if err != nil {
  119. return nil, err
  120. }
  121. res.DB = db
  122. var key [32]byte
  123. for i, b := range []byte(envConf.DBConf.EncryptionKey) {
  124. key[i] = b
  125. }
  126. res.Repo = gorm.NewRepository(db, &key, InstanceCredentialBackend)
  127. if envConf.ProvisionerConf.SentryDSN != "" {
  128. res.Alerter, err = alerter.NewSentryAlerter(envConf.ProvisionerConf.SentryDSN, envConf.ProvisionerConf.SentryEnv)
  129. }
  130. // load a storage backend; if correct env vars are not set, throw an error
  131. if envConf.ProvisionerConf.S3AWSAccessKeyID != "" && envConf.ProvisionerConf.S3AWSSecretKey != "" && envConf.ProvisionerConf.S3EncryptionKey != "" {
  132. var s3Key [32]byte
  133. for i, b := range []byte(envConf.ProvisionerConf.S3EncryptionKey) {
  134. s3Key[i] = b
  135. }
  136. res.StorageManager, err = s3.NewS3StorageClient(&s3.S3Options{
  137. AWSRegion: envConf.ProvisionerConf.S3AWSRegion,
  138. AWSAccessKeyID: envConf.ProvisionerConf.S3AWSAccessKeyID,
  139. AWSSecretKey: envConf.ProvisionerConf.S3AWSSecretKey,
  140. AWSBucketName: envConf.ProvisionerConf.S3BucketName,
  141. EncryptionKey: &s3Key,
  142. })
  143. if err != nil {
  144. return nil, err
  145. }
  146. } else {
  147. return nil, fmt.Errorf("no storage backend is available")
  148. }
  149. if envConf.RedisConf.Enabled {
  150. redis, err := adapter.NewRedisClient(&envConf.RedisConf)
  151. if err != nil {
  152. return nil, fmt.Errorf("redis connection failed: %v", err)
  153. }
  154. res.RedisClient = redis
  155. } else {
  156. return nil, fmt.Errorf("no redis client is available")
  157. }
  158. if envConf.ProvisionerMethod == "local" {
  159. res.Provisioner = local.NewLocalProvisioner(&local.LocalProvisionerConfig{
  160. ProvisionerBackendURL: envConf.ProvisionerBackendURL,
  161. LocalTerraformDirectory: envConf.LocalTerraformDirectory,
  162. })
  163. } else if envConf.ProvisionerMethod == "kubernetes" {
  164. provAgent, err := getProvisionerAgent(envConf.ProvisionerConf)
  165. if err != nil {
  166. return nil, err
  167. }
  168. res.Provisioner = k8s.NewKubernetesProvisioner(provAgent.Clientset, &k8s.KubernetesProvisionerConfig{
  169. ProvisionerImageRepo: envConf.ProvisionerImageRepo,
  170. ProvisionerImageTag: envConf.ProvisionerImageTag,
  171. ProvisionerImagePullSecret: envConf.ProvisionerImagePullSecret,
  172. ProvisionerJobNamespace: envConf.ProvisionerJobNamespace,
  173. ProvisionerBackendURL: envConf.ProvisionerBackendURL,
  174. })
  175. }
  176. if envConf.ProvisionerConf.DOClientID != "" && envConf.ProvisionerConf.DOClientSecret != "" && envConf.ProvisionerConf.DOClientServerURL != "" {
  177. res.DOConf = oauth.NewDigitalOceanClient(&oauth.Config{
  178. ClientID: envConf.ProvisionerConf.DOClientID,
  179. ClientSecret: envConf.ProvisionerConf.DOClientSecret,
  180. Scopes: []string{"read", "write"},
  181. BaseURL: envConf.ProvisionerConf.DOClientServerURL,
  182. })
  183. }
  184. res.AnalyticsClient = analytics.InitializeAnalyticsSegmentClient(envConf.ProvisionerConf.SegmentClientKey, res.Logger)
  185. return res, nil
  186. }
  187. func getProvisionerAgent(conf *ProvisionerConf) (*kubernetes.Agent, error) {
  188. if conf.ProvisionerCluster == "kubeconfig" && conf.SelfKubeconfig != "" {
  189. agent, err := klocal.GetSelfAgentFromFileConfig(conf.SelfKubeconfig)
  190. if err != nil {
  191. return nil, fmt.Errorf("could not get in-cluster agent: %v", err)
  192. }
  193. return agent, nil
  194. } else if conf.ProvisionerCluster == "kubeconfig" {
  195. return nil, fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
  196. }
  197. agent, _ := kubernetes.GetAgentInClusterConfig(conf.ProvisionerJobNamespace)
  198. return agent, nil
  199. }