| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- package config
- import (
- "context"
- "fmt"
- "log"
- "time"
- redis "github.com/go-redis/redis/v8"
- "github.com/joeshaw/envdecode"
- "github.com/porter-dev/porter/api/server/shared/apierrors/alerter"
- "github.com/porter-dev/porter/api/server/shared/config/env"
- "github.com/porter-dev/porter/internal/adapter"
- "github.com/porter-dev/porter/internal/analytics"
- "github.com/porter-dev/porter/internal/features"
- "github.com/porter-dev/porter/internal/kubernetes"
- klocal "github.com/porter-dev/porter/internal/kubernetes/local"
- "github.com/porter-dev/porter/internal/oauth"
- "github.com/porter-dev/porter/internal/repository"
- "github.com/porter-dev/porter/internal/repository/credentials"
- "github.com/porter-dev/porter/internal/repository/gorm"
- "github.com/porter-dev/porter/pkg/logger"
- "github.com/porter-dev/porter/provisioner/integrations/provisioner"
- "github.com/porter-dev/porter/provisioner/integrations/provisioner/k8s"
- "github.com/porter-dev/porter/provisioner/integrations/provisioner/local"
- "github.com/porter-dev/porter/provisioner/integrations/storage"
- "github.com/porter-dev/porter/provisioner/integrations/storage/s3"
- "golang.org/x/oauth2"
- _gorm "gorm.io/gorm"
- )
- var (
- InstanceCredentialBackend credentials.CredentialStorage
- InstanceEnvConf *EnvConf
- )
- func sharedInit() {
- var envDecoderConf EnvDecoderConf = EnvDecoderConf{}
- if err := envdecode.StrictDecode(&envDecoderConf); err != nil {
- log.Fatalf("Failed to decode server conf: %s", err)
- }
- InstanceEnvConf = &EnvConf{
- ProvisionerConf: &envDecoderConf.ProvisionerConf,
- DBConf: &envDecoderConf.DBConf,
- RedisConf: envDecoderConf.RedisConf,
- }
- }
- type Config struct {
- ProvisionerConf *ProvisionerConf
- DBConf *env.DBConf
- RedisConf *env.RedisConf
- StorageManager storage.StorageManager
- Repo repository.Repository
- // Logger for logging
- Logger *logger.Logger
- // Alerter to send alerts to a third-party aggregator
- Alerter alerter.Alerter
- DB *_gorm.DB
- // DOConf is the configuration for a DigitalOcean OAuth client
- DOConf *oauth2.Config
- // LaunchDarklyClient is the client for the LaunchDarkly feature flag service
- LaunchDarklyClient *features.Client
- RedisClient *redis.Client
- Provisioner provisioner.Provisioner
- // AnalyticsClient if Segment analytics reporting is enabled on the API instance
- AnalyticsClient analytics.AnalyticsSegmentClient
- }
- // ProvisionerConf is the env var configuration for the provisioner server
- type ProvisionerConf struct {
- Debug bool `env:"DEBUG,default=false"`
- Port int `env:"PROV_PORT,default=8082"`
- TimeoutRead time.Duration `env:"SERVER_TIMEOUT_READ,default=5s"`
- TimeoutWrite time.Duration `env:"SERVER_TIMEOUT_WRITE,default=10s"`
- TimeoutIdle time.Duration `env:"SERVER_TIMEOUT_IDLE,default=15s"`
- StaticAuthToken string `env:"STATIC_AUTH_TOKEN"`
- SentryDSN string `env:"SENTRY_DSN"`
- SentryEnv string `env:"SENTRY_ENV,default=dev"`
- // Configuration for the S3 storage backend
- S3AWSAccessKeyID string `env:"S3_AWS_ACCESS_KEY_ID"`
- S3AWSSecretKey string `env:"S3_AWS_SECRET_KEY"`
- S3AWSRegion string `env:"S3_AWS_REGION"`
- S3BucketName string `env:"S3_BUCKET_NAME"`
- S3EncryptionKey string `env:"S3_ENCRYPTION_KEY,default=__random_strong_encryption_key__"`
- // Configuration for the digitalocean client
- DOClientID string `env:"DO_CLIENT_ID"`
- DOClientSecret string `env:"DO_CLIENT_SECRET"`
- DOClientServerURL string `env:"DO_CLIENT_SERVER_URL"`
- // ProvisionerMethod defines the method to use for provisioner: options are "local" or "kubernetes"
- ProvisionerMethod string `env:"PROVISIONER_METHOD,default=local"`
- ProvisionerBackendURL string `env:"PROV_BACKEND_URL,default=http://localhost:8082"`
- ProvisionerCredExchangeURL string `env:"PROV_CRED_EXCHANGE_URL,default=http://localhost:8082"`
- // Options to configure for the "kubernetes" provisioner method
- ProvisionerCluster string `env:"PROVISIONER_CLUSTER"`
- SelfKubeconfig string `env:"SELF_KUBECONFIG"`
- ProvisionerImageRepo string `env:"PROV_IMAGE_REPO,default=gcr.io/porter-dev-273614/provisioner"`
- ProvisionerImageTag string `env:"PROV_IMAGE_TAG,default=latest"`
- ProvisionerImagePullSecret string `env:"PROV_IMAGE_PULL_SECRET"`
- ProvisionerJobNamespace string `env:"PROV_JOB_NAMESPACE,default=default"`
- // Options to configure for the "local" provisioner method
- LocalTerraformDirectory string `env:"LOCAL_TERRAFORM_DIRECTORY"`
- // Client key for segment to report provisioning events
- SegmentClientKey string `env:"SEGMENT_CLIENT_KEY"`
- // FeatureFlagClient controls which client to use (database or launch_darkly)
- FeatureFlagClient string `env:"FEATURE_FLAG_CLIENT,default=launch_darkly"`
- // Launch Darkly SDK key
- LaunchDarklySDKKey string `env:"LAUNCHDARKLY_SDK_KEY"`
- }
- type EnvConf struct {
- *ProvisionerConf
- *env.DBConf
- env.RedisConf
- }
- type EnvDecoderConf struct {
- ProvisionerConf ProvisionerConf
- DBConf env.DBConf
- RedisConf env.RedisConf
- }
- // FromEnv generates a configuration from environment variables
- func FromEnv() (*EnvConf, error) {
- return InstanceEnvConf, nil
- }
- func GetConfig(envConf *EnvConf) (*Config, error) {
- ctx := context.Background()
- res := &Config{
- ProvisionerConf: envConf.ProvisionerConf,
- DBConf: envConf.DBConf,
- RedisConf: &envConf.RedisConf,
- Logger: logger.NewConsole(envConf.ProvisionerConf.Debug),
- }
- db, err := adapter.New(envConf.DBConf)
- if err != nil {
- return nil, err
- }
- res.DB = db
- var key [32]byte
- for i, b := range []byte(envConf.DBConf.EncryptionKey) {
- key[i] = b
- }
- res.Repo = gorm.NewRepository(db, &key, InstanceCredentialBackend)
- launchDarklyClient, err := features.GetClient(envConf.FeatureFlagClient, envConf.LaunchDarklySDKKey)
- if err != nil {
- return nil, fmt.Errorf("could not create launch darkly client: %s", err)
- }
- res.LaunchDarklyClient = launchDarklyClient
- if envConf.ProvisionerConf.SentryDSN != "" {
- res.Alerter, err = alerter.NewSentryAlerter(envConf.ProvisionerConf.SentryDSN, envConf.ProvisionerConf.SentryEnv)
- }
- // load a storage backend; if correct env vars are not set, throw an error
- if envConf.ProvisionerConf.S3AWSAccessKeyID != "" && envConf.ProvisionerConf.S3AWSSecretKey != "" && envConf.ProvisionerConf.S3EncryptionKey != "" {
- var s3Key [32]byte
- for i, b := range []byte(envConf.ProvisionerConf.S3EncryptionKey) {
- s3Key[i] = b
- }
- res.StorageManager, err = s3.NewS3StorageClient(&s3.S3Options{
- AWSRegion: envConf.ProvisionerConf.S3AWSRegion,
- AWSAccessKeyID: envConf.ProvisionerConf.S3AWSAccessKeyID,
- AWSSecretKey: envConf.ProvisionerConf.S3AWSSecretKey,
- AWSBucketName: envConf.ProvisionerConf.S3BucketName,
- EncryptionKey: &s3Key,
- })
- if err != nil {
- return nil, err
- }
- } else {
- return nil, fmt.Errorf("no storage backend is available")
- }
- if envConf.RedisConf.Enabled {
- redis, err := adapter.NewRedisClient(&envConf.RedisConf)
- if err != nil {
- return nil, fmt.Errorf("redis connection failed: %v", err)
- }
- res.RedisClient = redis
- } else {
- return nil, fmt.Errorf("no redis client is available")
- }
- if envConf.ProvisionerMethod == "local" {
- res.Provisioner = local.NewLocalProvisioner(&local.LocalProvisionerConfig{
- ProvisionerBackendURL: envConf.ProvisionerBackendURL,
- LocalTerraformDirectory: envConf.LocalTerraformDirectory,
- })
- } else if envConf.ProvisionerMethod == "kubernetes" {
- provAgent, err := getProvisionerAgent(ctx, envConf.ProvisionerConf)
- if err != nil {
- return nil, err
- }
- res.Provisioner = k8s.NewKubernetesProvisioner(provAgent.Clientset, &k8s.KubernetesProvisionerConfig{
- ProvisionerImageRepo: envConf.ProvisionerImageRepo,
- ProvisionerImageTag: envConf.ProvisionerImageTag,
- ProvisionerImagePullSecret: envConf.ProvisionerImagePullSecret,
- ProvisionerJobNamespace: envConf.ProvisionerJobNamespace,
- ProvisionerBackendURL: envConf.ProvisionerBackendURL,
- })
- }
- if envConf.ProvisionerConf.DOClientID != "" && envConf.ProvisionerConf.DOClientSecret != "" && envConf.ProvisionerConf.DOClientServerURL != "" {
- res.DOConf = oauth.NewDigitalOceanClient(&oauth.Config{
- ClientID: envConf.ProvisionerConf.DOClientID,
- ClientSecret: envConf.ProvisionerConf.DOClientSecret,
- Scopes: []string{"read", "write"},
- BaseURL: envConf.ProvisionerConf.DOClientServerURL,
- })
- }
- res.AnalyticsClient = analytics.InitializeAnalyticsSegmentClient(envConf.ProvisionerConf.SegmentClientKey, res.Logger)
- return res, nil
- }
- func getProvisionerAgent(ctx context.Context, conf *ProvisionerConf) (*kubernetes.Agent, error) {
- if conf.ProvisionerCluster == "kubeconfig" && conf.SelfKubeconfig != "" {
- agent, err := klocal.GetSelfAgentFromFileConfig(conf.SelfKubeconfig)
- if err != nil {
- return nil, fmt.Errorf("could not get in-cluster agent: %v", err)
- }
- return agent, nil
- } else if conf.ProvisionerCluster == "kubeconfig" {
- return nil, fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
- }
- agent, _ := kubernetes.GetAgentInClusterConfig(ctx, conf.ProvisionerJobNamespace)
- return agent, nil
- }
|