config.go 8.3 KB

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