2
0

config.go 8.8 KB

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