2
0
Эх сурвалжийг харах

provisioner manifest generation

Alexander Belanger 5 жил өмнө
parent
commit
d82a5f4d16

+ 1 - 0
go.sum

@@ -1926,6 +1926,7 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 k8s.io/api v0.16.8/go.mod h1:a8EOdYHO8en+YHhPBLiW5q+3RfHTr7wxTqqp7emJ7PM=
 k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4=
 k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY=
+k8s.io/api v0.20.1 h1:ud1c3W3YNzGd6ABJlbFfKXBKXO+1KdGfcgGGNgFR03E=
 k8s.io/apiextensions-apiserver v0.18.8 h1:pkqYPKTHa0/3lYwH7201RpF9eFm0lmZDFBNzhN+k/sA=
 k8s.io/apiextensions-apiserver v0.18.8/go.mod h1:7f4ySEkkvifIr4+BRrRWriKKIJjPyg9mb/p63dJKnlM=
 k8s.io/apimachinery v0.16.8/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE=

+ 70 - 0
internal/kubernetes/agent.go

@@ -7,9 +7,15 @@ import (
 	"io"
 	"strings"
 
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner"
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws"
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/ecr"
+	"github.com/porter-dev/porter/internal/models/integrations"
+
 	"github.com/gorilla/websocket"
 	"github.com/porter-dev/porter/internal/helm/grapher"
 	appsv1 "k8s.io/api/apps/v1"
+	batchv1 "k8s.io/api/batch/v1"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
@@ -213,3 +219,67 @@ func (a *Agent) StreamControllerStatus(conn *websocket.Conn, kind string) error
 		}
 	}
 }
+
+// ProvisionECR spawns a new provisioning pod that creates an ECR instance
+func (a *Agent) ProvisionECR(
+	projectID uint,
+	awsConf *integrations.AWSIntegration,
+	ecrName string,
+) (*batchv1.Job, error) {
+	prov := &provisioner.Conf{
+		ID:   fmt.Sprintf("%s-%d", ecrName, projectID),
+		Name: fmt.Sprintf("prov-%s-%d", ecrName, projectID),
+		Kind: provisioner.ECR,
+		AWS: &aws.Conf{
+			AWSRegion:          awsConf.AWSRegion,
+			AWSAccessKeyID:     string(awsConf.AWSAccessKeyID),
+			AWSSecretAccessKey: string(awsConf.AWSSecretAccessKey),
+		},
+		ECR: &ecr.Conf{
+			ECRName: ecrName,
+		},
+	}
+
+	return a.provision(prov)
+}
+
+// ProvisionTest spawns a new provisioning pod that tests provisioning
+func (a *Agent) ProvisionTest(
+	projectID uint,
+) (*batchv1.Job, error) {
+	prov := &provisioner.Conf{
+		ID:   fmt.Sprintf("%s-%d", "testing", projectID),
+		Name: fmt.Sprintf("prov-%s-%d", "testing", projectID),
+		Kind: provisioner.Test,
+	}
+
+	return a.provision(prov)
+}
+
+func (a *Agent) provision(
+	prov *provisioner.Conf,
+) (*batchv1.Job, error) {
+	prov.Namespace = "default"
+
+	prov.Redis = &provisioner.RedisConf{
+		Host: "redis-master.default.svc.cluster.local",
+		Port: "6379",
+	}
+
+	prov.Postgres = &provisioner.PostgresConf{
+		Host: "postgres-postgresql.default.svc.cluster.local",
+		Port: "5432",
+	}
+
+	job, err := prov.GetProvisionerJobTemplate()
+
+	if err != nil {
+		return nil, err
+	}
+
+	return a.Clientset.BatchV1().Jobs(prov.Namespace).Create(
+		context.TODO(),
+		job,
+		metav1.CreateOptions{},
+	)
+}

+ 30 - 0
internal/kubernetes/provisioner/aws/aws.go

@@ -0,0 +1,30 @@
+package aws
+
+import (
+	v1 "k8s.io/api/core/v1"
+)
+
+// Conf wraps the AWS integration model
+type Conf struct {
+	AWSRegion, AWSAccessKeyID, AWSSecretAccessKey string
+}
+
+// AttachAWSEnv adds the relevant AWS env for the provisioner
+func (conf *Conf) AttachAWSEnv(env []v1.EnvVar) []v1.EnvVar {
+	env = append(env, v1.EnvVar{
+		Name:  "AWS_REGION",
+		Value: conf.AWSRegion,
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "AWS_ACCESS_KEY_ID",
+		Value: conf.AWSAccessKeyID,
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "AWS_SECRET_ACCESS_KEY",
+		Value: conf.AWSSecretAccessKey,
+	})
+
+	return env
+}

+ 18 - 0
internal/kubernetes/provisioner/aws/ecr/ecr.go

@@ -0,0 +1,18 @@
+package ecr
+
+import v1 "k8s.io/api/core/v1"
+
+// Conf is the ECR cluster config required for the provisioner
+type Conf struct {
+	ECRName string
+}
+
+// AttachECREnv adds the relevant ECR env for the provisioner
+func (conf *Conf) AttachECREnv(env []v1.EnvVar) []v1.EnvVar {
+	env = append(env, v1.EnvVar{
+		Name:  "ECR_NAME",
+		Value: conf.ECRName,
+	})
+
+	return env
+}

+ 18 - 0
internal/kubernetes/provisioner/aws/eks/eks.go

@@ -0,0 +1,18 @@
+package eks
+
+import v1 "k8s.io/api/core/v1"
+
+// Conf is the EKS cluster config required for the provisioner
+type Conf struct {
+	ClusterName string
+}
+
+// AttachEKSEnv adds the relevant EKS env for the provisioner
+func (conf *Conf) AttachEKSEnv(env []v1.EnvVar) []v1.EnvVar {
+	env = append(env, v1.EnvVar{
+		Name:  "EKS_CLUSTER_NAME",
+		Value: conf.ClusterName,
+	})
+
+	return env
+}

+ 240 - 0
internal/kubernetes/provisioner/provisioner.go

@@ -0,0 +1,240 @@
+package provisioner
+
+import (
+	batchv1 "k8s.io/api/batch/v1"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws"
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/ecr"
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/eks"
+)
+
+// InfraOption is a type of infrastructure that can be provisioned
+type InfraOption string
+
+// The list of infra options
+const (
+	Test InfraOption = "test"
+	ECR  InfraOption = "ecr"
+	EKS  InfraOption = "eks"
+)
+
+// Conf is the config required to start a provisioner container
+type Conf struct {
+	Kind      InfraOption
+	Name      string
+	Namespace string
+	ID        string
+	Redis     *RedisConf
+	Postgres  *PostgresConf
+
+	// provider-specific configurations
+	AWS *aws.Conf
+	ECR *ecr.Conf
+	EKS *eks.Conf
+}
+
+// RedisConf is the redis config required for the provisioner container
+type RedisConf struct {
+	Host string
+	Port string
+}
+
+// PostgresConf is the postgres config for the provisioner container
+type PostgresConf struct {
+	Host string
+	Port string
+}
+
+// GetProvisionerJobTemplate returns the manifest that should be applied to
+// create a provisioning job
+func (conf *Conf) GetProvisionerJobTemplate() (*batchv1.Job, error) {
+	env := make([]v1.EnvVar, 0)
+
+	env = conf.attachDefaultEnv(env)
+
+	ttl := int32(3600)
+	backoffLimit := int32(3)
+
+	labels := map[string]string{
+		"app": "provisioner",
+	}
+
+	args := make([]string, 0)
+
+	if conf.Kind == Test {
+		args = []string{"test", "hello"}
+	} else if conf.Kind == ECR {
+		args = []string{"ecr"}
+		env = conf.AWS.AttachAWSEnv(env)
+		env = conf.ECR.AttachECREnv(env)
+	} else if conf.Kind == EKS {
+		args = []string{"eks"}
+		env = conf.AWS.AttachAWSEnv(env)
+		env = conf.EKS.AttachEKSEnv(env)
+	}
+
+	return &batchv1.Job{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      conf.Name,
+			Namespace: conf.Namespace,
+			Labels:    labels,
+		},
+		Spec: batchv1.JobSpec{
+			TTLSecondsAfterFinished: &ttl,
+			BackoffLimit:            &backoffLimit,
+			Template: v1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: labels,
+				},
+				Spec: v1.PodSpec{
+					RestartPolicy: v1.RestartPolicyOnFailure,
+					Containers: []v1.Container{
+						{
+							Name:  "provisioner",
+							Image: "gcr.io/porter-dev-273614/provisioner:latest",
+							Args:  args,
+							Env:   env,
+							VolumeMounts: []v1.VolumeMount{
+								v1.VolumeMount{
+									MountPath: "/.terraform/plugin-cache",
+									Name:      "tf-cache",
+									ReadOnly:  true,
+								},
+							},
+						},
+					},
+					Volumes: []v1.Volume{
+						v1.Volume{
+							Name: "tf-cache",
+							VolumeSource: v1.VolumeSource{
+								PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
+									ClaimName: "tf-cache-pvc",
+									ReadOnly:  true,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}, nil
+}
+
+// GetRedisStreamID returns the stream id that should be used
+func (conf *Conf) GetRedisStreamID() string {
+	return conf.ID
+}
+
+// GetTFWorkspaceID returns the workspace id that should be used
+func (conf *Conf) GetTFWorkspaceID() string {
+	return conf.ID
+}
+
+// attaches the env variables required by all provisioner instances
+func (conf *Conf) attachDefaultEnv(env []v1.EnvVar) []v1.EnvVar {
+	env = conf.addRedisEnv(env)
+	env = conf.addPostgresEnv(env)
+	env = conf.addTFEnv(env)
+
+	return env
+}
+
+// adds the env variables required for the Redis stream
+func (conf *Conf) addRedisEnv(env []v1.EnvVar) []v1.EnvVar {
+	env = append(env, v1.EnvVar{
+		Name:  "REDIS_ENABLED",
+		Value: "true",
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "REDIS_HOST",
+		Value: conf.Redis.Host,
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "REDIS_PORT",
+		Value: conf.Redis.Port,
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "REDIS_USER",
+		Value: "default",
+	})
+
+	env = append(env, v1.EnvVar{
+		Name: "REDIS_PASS",
+		ValueFrom: &v1.EnvVarSource{
+			SecretKeyRef: &v1.SecretKeySelector{
+				LocalObjectReference: v1.LocalObjectReference{
+					Name: "redis",
+				},
+				Key: "redis-password",
+			},
+		},
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "REDIS_STREAM_ID",
+		Value: conf.GetRedisStreamID(),
+	})
+
+	return env
+}
+
+// adds the env variables required for the PG backend
+func (conf *Conf) addPostgresEnv(env []v1.EnvVar) []v1.EnvVar {
+	env = append(env, v1.EnvVar{
+		Name:  "PG_HOST",
+		Value: conf.Postgres.Host,
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "PG_PORT",
+		Value: conf.Postgres.Port,
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "PG_USER",
+		Value: "postgres",
+	})
+
+	env = append(env, v1.EnvVar{
+		Name: "PG_PASS",
+		ValueFrom: &v1.EnvVarSource{
+			SecretKeyRef: &v1.SecretKeySelector{
+				LocalObjectReference: v1.LocalObjectReference{
+					Name: "postgres-postgresql",
+				},
+				Key: "postgresql-password",
+			},
+		},
+	})
+
+	return env
+}
+
+func (conf *Conf) addTFEnv(env []v1.EnvVar) []v1.EnvVar {
+	env = append(env, v1.EnvVar{
+		Name:  "TF_DIR",
+		Value: "./terraform",
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "TF_PLUGIN_CACHE_DIR",
+		Value: "/.terraform/plugin-cache",
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "TF_PORTER_BACKEND",
+		Value: "postgres",
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "TF_PORTER_WORKSPACE",
+		Value: conf.GetTFWorkspaceID(),
+	})
+
+	return env
+}