config.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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. // FeatureFlagClient controls which client to use (database or launch_darkly)
  100. FeatureFlagClient string `env:"FEATURE_FLAG_CLIENT,default=launch_darkly"`
  101. // Launch Darkly SDK key
  102. LaunchDarklySDKKey string `env:"LAUNCHDARKLY_SDK_KEY"`
  103. }
  104. type EnvConf struct {
  105. *ProvisionerConf
  106. *env.DBConf
  107. env.RedisConf
  108. }
  109. type EnvDecoderConf struct {
  110. ProvisionerConf ProvisionerConf
  111. DBConf env.DBConf
  112. RedisConf env.RedisConf
  113. }
  114. // FromEnv generates a configuration from environment variables
  115. func FromEnv() (*EnvConf, error) {
  116. return InstanceEnvConf, nil
  117. }
  118. func GetConfig(envConf *EnvConf) (*Config, error) {
  119. ctx := context.Background()
  120. res := &Config{
  121. ProvisionerConf: envConf.ProvisionerConf,
  122. DBConf: envConf.DBConf,
  123. RedisConf: &envConf.RedisConf,
  124. Logger: logger.NewConsole(envConf.ProvisionerConf.Debug),
  125. }
  126. db, err := adapter.New(envConf.DBConf)
  127. if err != nil {
  128. return nil, err
  129. }
  130. res.DB = db
  131. var key [32]byte
  132. for i, b := range []byte(envConf.DBConf.EncryptionKey) {
  133. key[i] = b
  134. }
  135. res.Repo = gorm.NewRepository(db, &key, InstanceCredentialBackend)
  136. launchDarklyClient, err := features.GetClient(envConf.FeatureFlagClient, envConf.LaunchDarklySDKKey)
  137. if err != nil {
  138. return nil, fmt.Errorf("could not create launch darkly client: %s", err)
  139. }
  140. res.LaunchDarklyClient = launchDarklyClient
  141. if envConf.ProvisionerConf.SentryDSN != "" {
  142. res.Alerter, err = alerter.NewSentryAlerter(envConf.ProvisionerConf.SentryDSN, envConf.ProvisionerConf.SentryEnv)
  143. }
  144. // load a storage backend; if correct env vars are not set, throw an error
  145. if envConf.ProvisionerConf.S3AWSAccessKeyID != "" && envConf.ProvisionerConf.S3AWSSecretKey != "" && envConf.ProvisionerConf.S3EncryptionKey != "" {
  146. var s3Key [32]byte
  147. for i, b := range []byte(envConf.ProvisionerConf.S3EncryptionKey) {
  148. s3Key[i] = b
  149. }
  150. res.StorageManager, err = s3.NewS3StorageClient(&s3.S3Options{
  151. AWSRegion: envConf.ProvisionerConf.S3AWSRegion,
  152. AWSAccessKeyID: envConf.ProvisionerConf.S3AWSAccessKeyID,
  153. AWSSecretKey: envConf.ProvisionerConf.S3AWSSecretKey,
  154. AWSBucketName: envConf.ProvisionerConf.S3BucketName,
  155. EncryptionKey: &s3Key,
  156. })
  157. if err != nil {
  158. return nil, err
  159. }
  160. } else {
  161. return nil, fmt.Errorf("no storage backend is available")
  162. }
  163. if envConf.RedisConf.Enabled {
  164. redis, err := adapter.NewRedisClient(&envConf.RedisConf)
  165. if err != nil {
  166. return nil, fmt.Errorf("redis connection failed: %v", err)
  167. }
  168. res.RedisClient = redis
  169. } else {
  170. return nil, fmt.Errorf("no redis client is available")
  171. }
  172. if envConf.ProvisionerMethod == "local" {
  173. res.Provisioner = local.NewLocalProvisioner(&local.LocalProvisionerConfig{
  174. ProvisionerBackendURL: envConf.ProvisionerBackendURL,
  175. LocalTerraformDirectory: envConf.LocalTerraformDirectory,
  176. })
  177. } else if envConf.ProvisionerMethod == "kubernetes" {
  178. provAgent, err := getProvisionerAgent(ctx, envConf.ProvisionerConf)
  179. if err != nil {
  180. return nil, err
  181. }
  182. res.Provisioner = k8s.NewKubernetesProvisioner(provAgent.Clientset, &k8s.KubernetesProvisionerConfig{
  183. ProvisionerImageRepo: envConf.ProvisionerImageRepo,
  184. ProvisionerImageTag: envConf.ProvisionerImageTag,
  185. ProvisionerImagePullSecret: envConf.ProvisionerImagePullSecret,
  186. ProvisionerJobNamespace: envConf.ProvisionerJobNamespace,
  187. ProvisionerBackendURL: envConf.ProvisionerBackendURL,
  188. })
  189. }
  190. if envConf.ProvisionerConf.DOClientID != "" && envConf.ProvisionerConf.DOClientSecret != "" && envConf.ProvisionerConf.DOClientServerURL != "" {
  191. res.DOConf = oauth.NewDigitalOceanClient(&oauth.Config{
  192. ClientID: envConf.ProvisionerConf.DOClientID,
  193. ClientSecret: envConf.ProvisionerConf.DOClientSecret,
  194. Scopes: []string{"read", "write"},
  195. BaseURL: envConf.ProvisionerConf.DOClientServerURL,
  196. })
  197. }
  198. res.AnalyticsClient = analytics.InitializeAnalyticsSegmentClient(envConf.ProvisionerConf.SegmentClientKey, res.Logger)
  199. return res, nil
  200. }
  201. func getProvisionerAgent(ctx context.Context, conf *ProvisionerConf) (*kubernetes.Agent, error) {
  202. if conf.ProvisionerCluster == "kubeconfig" && conf.SelfKubeconfig != "" {
  203. agent, err := klocal.GetSelfAgentFromFileConfig(conf.SelfKubeconfig)
  204. if err != nil {
  205. return nil, fmt.Errorf("could not get in-cluster agent: %v", err)
  206. }
  207. return agent, nil
  208. } else if conf.ProvisionerCluster == "kubeconfig" {
  209. return nil, fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
  210. }
  211. agent, _ := kubernetes.GetAgentInClusterConfig(ctx, conf.ProvisionerJobNamespace)
  212. return agent, nil
  213. }