provisioner.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. package provisioner
  2. import (
  3. "fmt"
  4. batchv1 "k8s.io/api/batch/v1"
  5. v1 "k8s.io/api/core/v1"
  6. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  7. "github.com/porter-dev/porter/internal/kubernetes/provisioner/aws"
  8. "github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/ecr"
  9. "github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/eks"
  10. "github.com/porter-dev/porter/internal/kubernetes/provisioner/do"
  11. "github.com/porter-dev/porter/internal/kubernetes/provisioner/do/docr"
  12. "github.com/porter-dev/porter/internal/kubernetes/provisioner/do/doks"
  13. "github.com/porter-dev/porter/internal/kubernetes/provisioner/input"
  14. "github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp"
  15. "github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp/gke"
  16. "github.com/porter-dev/porter/internal/config"
  17. )
  18. // InfraOption is a type of infrastructure that can be provisioned
  19. type InfraOption string
  20. // The list of infra options
  21. const (
  22. Test InfraOption = "test"
  23. ECR InfraOption = "ecr"
  24. EKS InfraOption = "eks"
  25. GCR InfraOption = "gcr"
  26. GKE InfraOption = "gke"
  27. DOCR InfraOption = "docr"
  28. DOKS InfraOption = "doks"
  29. )
  30. // Conf is the config required to start a provisioner container
  31. type Conf struct {
  32. Kind InfraOption
  33. Name string
  34. Namespace string
  35. ID string
  36. Redis *config.RedisConf
  37. Postgres *config.DBConf
  38. Operation ProvisionerOperation
  39. ProvisionerImageTag string
  40. ImagePullSecret string
  41. LastApplied []byte
  42. // provider-specific configurations
  43. // AWS
  44. AWS *aws.Conf
  45. ECR *ecr.Conf
  46. EKS *eks.Conf
  47. // GKE
  48. GCP *gcp.Conf
  49. GKE *gke.Conf
  50. // DO
  51. DO *do.Conf
  52. DOCR *docr.Conf
  53. DOKS *doks.Conf
  54. }
  55. type ProvisionerOperation string
  56. const (
  57. Apply ProvisionerOperation = "apply"
  58. Destroy ProvisionerOperation = "destroy"
  59. )
  60. // GetProvisionerJobTemplate returns the manifest that should be applied to
  61. // create a provisioning job
  62. func (conf *Conf) GetProvisionerJobTemplate() (*batchv1.Job, error) {
  63. operation := string(conf.Operation)
  64. if operation == "" {
  65. operation = string(Apply)
  66. }
  67. env := make([]v1.EnvVar, 0)
  68. env = conf.attachDefaultEnv(env)
  69. ttl := int32(3600)
  70. backoffLimit := int32(1)
  71. labels := map[string]string{
  72. "app": "provisioner",
  73. }
  74. args := make([]string, 0)
  75. switch conf.Kind {
  76. case Test:
  77. args = []string{operation, "test", "hello"}
  78. case ECR:
  79. args = []string{operation, "ecr"}
  80. if len(conf.LastApplied) > 0 {
  81. inputConf, err := input.GetECRInput(conf.LastApplied)
  82. if err != nil {
  83. return nil, err
  84. }
  85. conf.AWS.AWSAccessKeyID = inputConf.AWSAccessKey
  86. conf.AWS.AWSSecretAccessKey = inputConf.AWSSecretKey
  87. conf.AWS.AWSRegion = inputConf.AWSRegion
  88. conf.ECR.ECRName = inputConf.ECRName
  89. } else {
  90. inputConf := &input.ECR{
  91. AWSRegion: conf.AWS.AWSRegion,
  92. AWSAccessKey: conf.AWS.AWSAccessKeyID,
  93. AWSSecretKey: conf.AWS.AWSSecretAccessKey,
  94. ECRName: conf.ECR.ECRName,
  95. }
  96. lastApplied, err := inputConf.GetInput()
  97. if err != nil {
  98. return nil, err
  99. }
  100. conf.LastApplied = lastApplied
  101. }
  102. env = conf.AWS.AttachAWSEnv(env)
  103. env = conf.ECR.AttachECREnv(env)
  104. case EKS:
  105. args = []string{operation, "eks"}
  106. if len(conf.LastApplied) > 0 {
  107. inputConf, err := input.GetEKSInput(conf.LastApplied)
  108. if err != nil {
  109. return nil, err
  110. }
  111. conf.AWS.AWSAccessKeyID = inputConf.AWSAccessKey
  112. conf.AWS.AWSSecretAccessKey = inputConf.AWSSecretKey
  113. conf.AWS.AWSRegion = inputConf.AWSRegion
  114. conf.EKS.ClusterName = inputConf.ClusterName
  115. } else {
  116. inputConf := &input.EKS{
  117. AWSRegion: conf.AWS.AWSRegion,
  118. AWSAccessKey: conf.AWS.AWSAccessKeyID,
  119. AWSSecretKey: conf.AWS.AWSSecretAccessKey,
  120. ClusterName: conf.EKS.ClusterName,
  121. }
  122. lastApplied, err := inputConf.GetInput()
  123. if err != nil {
  124. return nil, err
  125. }
  126. conf.LastApplied = lastApplied
  127. }
  128. env = conf.AWS.AttachAWSEnv(env)
  129. env = conf.EKS.AttachEKSEnv(env)
  130. case GCR:
  131. args = []string{operation, "gcr"}
  132. if len(conf.LastApplied) > 0 {
  133. inputConf, err := input.GetGCRInput(conf.LastApplied)
  134. if err != nil {
  135. return nil, err
  136. }
  137. conf.GCP.GCPKeyData = inputConf.GCPCredentials
  138. conf.GCP.GCPRegion = inputConf.GCPRegion
  139. conf.GCP.GCPProjectID = inputConf.GCPProjectID
  140. } else {
  141. inputConf := &input.GCR{
  142. GCPCredentials: conf.GCP.GCPKeyData,
  143. GCPRegion: conf.GCP.GCPRegion,
  144. GCPProjectID: conf.GCP.GCPProjectID,
  145. }
  146. lastApplied, err := inputConf.GetInput()
  147. if err != nil {
  148. return nil, err
  149. }
  150. conf.LastApplied = lastApplied
  151. }
  152. env = conf.GCP.AttachGCPEnv(env)
  153. case GKE:
  154. args = []string{operation, "gke"}
  155. if len(conf.LastApplied) > 0 {
  156. inputConf, err := input.GetGKEInput(conf.LastApplied)
  157. if err != nil {
  158. return nil, err
  159. }
  160. conf.GCP.GCPKeyData = inputConf.GCPCredentials
  161. conf.GCP.GCPRegion = inputConf.GCPRegion
  162. conf.GCP.GCPProjectID = inputConf.GCPProjectID
  163. conf.GKE.ClusterName = inputConf.ClusterName
  164. } else {
  165. inputConf := &input.GKE{
  166. GCPCredentials: conf.GCP.GCPKeyData,
  167. GCPRegion: conf.GCP.GCPRegion,
  168. GCPProjectID: conf.GCP.GCPProjectID,
  169. ClusterName: conf.GKE.ClusterName,
  170. }
  171. lastApplied, err := inputConf.GetInput()
  172. if err != nil {
  173. return nil, err
  174. }
  175. conf.LastApplied = lastApplied
  176. }
  177. env = conf.GCP.AttachGCPEnv(env)
  178. env = conf.GKE.AttachGKEEnv(env)
  179. case DOCR:
  180. args = []string{operation, "docr"}
  181. if len(conf.LastApplied) > 0 {
  182. inputConf, err := input.GetDOCRInput(conf.LastApplied)
  183. if err != nil {
  184. return nil, err
  185. }
  186. conf.DO.DOToken = inputConf.DOToken
  187. conf.DOCR.DOCRSubscriptionTier = inputConf.DOCRSubscriptionTier
  188. conf.DOCR.DOCRName = inputConf.DOCRName
  189. } else {
  190. inputConf := &input.DOCR{
  191. DOToken: conf.DO.DOToken,
  192. DOCRSubscriptionTier: conf.DOCR.DOCRSubscriptionTier,
  193. DOCRName: conf.DOCR.DOCRName,
  194. }
  195. lastApplied, err := inputConf.GetInput()
  196. if err != nil {
  197. return nil, err
  198. }
  199. conf.LastApplied = lastApplied
  200. }
  201. env = conf.DO.AttachDOEnv(env)
  202. env = conf.DOCR.AttachDOCREnv(env)
  203. case DOKS:
  204. args = []string{operation, "doks"}
  205. if len(conf.LastApplied) > 0 {
  206. inputConf, err := input.GetDOKSInput(conf.LastApplied)
  207. if err != nil {
  208. return nil, err
  209. }
  210. conf.DO.DOToken = inputConf.DOToken
  211. conf.DOKS.DORegion = inputConf.DORegion
  212. conf.DOKS.DOKSClusterName = inputConf.ClusterName
  213. } else {
  214. inputConf := &input.DOKS{
  215. DOToken: conf.DO.DOToken,
  216. DORegion: conf.DOKS.DORegion,
  217. ClusterName: conf.DOKS.DOKSClusterName,
  218. }
  219. lastApplied, err := inputConf.GetInput()
  220. if err != nil {
  221. return nil, err
  222. }
  223. conf.LastApplied = lastApplied
  224. }
  225. env = conf.DO.AttachDOEnv(env)
  226. env = conf.DOKS.AttachDOKSEnv(env)
  227. }
  228. imagePullSecrets := []v1.LocalObjectReference{}
  229. if conf.ImagePullSecret != "" {
  230. imagePullSecrets = append(imagePullSecrets, v1.LocalObjectReference{
  231. Name: conf.ImagePullSecret,
  232. })
  233. }
  234. return &batchv1.Job{
  235. ObjectMeta: metav1.ObjectMeta{
  236. Name: conf.Name,
  237. Namespace: conf.Namespace,
  238. Labels: labels,
  239. },
  240. Spec: batchv1.JobSpec{
  241. TTLSecondsAfterFinished: &ttl,
  242. BackoffLimit: &backoffLimit,
  243. Template: v1.PodTemplateSpec{
  244. ObjectMeta: metav1.ObjectMeta{
  245. Labels: labels,
  246. },
  247. Spec: v1.PodSpec{
  248. RestartPolicy: v1.RestartPolicyNever,
  249. ImagePullSecrets: imagePullSecrets,
  250. Containers: []v1.Container{
  251. {
  252. Name: "provisioner",
  253. Image: "gcr.io/porter-dev-273614/provisioner:" + conf.ProvisionerImageTag,
  254. ImagePullPolicy: v1.PullAlways,
  255. Args: args,
  256. Env: env,
  257. },
  258. },
  259. },
  260. },
  261. },
  262. }, nil
  263. }
  264. // GetRedisStreamID returns the stream id that should be used
  265. func (conf *Conf) GetRedisStreamID() string {
  266. return conf.ID
  267. }
  268. // GetTFWorkspaceID returns the workspace id that should be used
  269. func (conf *Conf) GetTFWorkspaceID() string {
  270. return conf.ID
  271. }
  272. // attaches the env variables required by all provisioner instances
  273. func (conf *Conf) attachDefaultEnv(env []v1.EnvVar) []v1.EnvVar {
  274. env = conf.addRedisEnv(env)
  275. env = conf.addPostgresEnv(env)
  276. env = conf.addTFEnv(env)
  277. return env
  278. }
  279. // adds the env variables required for the Redis stream
  280. func (conf *Conf) addRedisEnv(env []v1.EnvVar) []v1.EnvVar {
  281. env = append(env, v1.EnvVar{
  282. Name: "REDIS_ENABLED",
  283. Value: "true",
  284. })
  285. env = append(env, v1.EnvVar{
  286. Name: "REDIS_HOST",
  287. Value: conf.Redis.Host,
  288. })
  289. env = append(env, v1.EnvVar{
  290. Name: "REDIS_PORT",
  291. Value: conf.Redis.Port,
  292. })
  293. env = append(env, v1.EnvVar{
  294. Name: "REDIS_USER",
  295. Value: conf.Redis.Username,
  296. })
  297. env = append(env, v1.EnvVar{
  298. Name: "REDIS_PASS",
  299. Value: conf.Redis.Password,
  300. // ValueFrom: &v1.EnvVarSource{
  301. // SecretKeyRef: &v1.SecretKeySelector{
  302. // LocalObjectReference: v1.LocalObjectReference{
  303. // Name: "redis",
  304. // },
  305. // Key: "redis-password",
  306. // },
  307. // },
  308. })
  309. env = append(env, v1.EnvVar{
  310. Name: "REDIS_STREAM_ID",
  311. Value: conf.GetRedisStreamID(),
  312. })
  313. return env
  314. }
  315. // adds the env variables required for the PG backend
  316. func (conf *Conf) addPostgresEnv(env []v1.EnvVar) []v1.EnvVar {
  317. env = append(env, v1.EnvVar{
  318. Name: "PG_HOST",
  319. Value: conf.Postgres.Host,
  320. })
  321. env = append(env, v1.EnvVar{
  322. Name: "PG_PORT",
  323. Value: fmt.Sprintf("%d", conf.Postgres.Port),
  324. })
  325. env = append(env, v1.EnvVar{
  326. Name: "PG_USER",
  327. Value: conf.Postgres.Username,
  328. })
  329. env = append(env, v1.EnvVar{
  330. Name: "PG_PASS",
  331. Value: conf.Postgres.Password,
  332. })
  333. return env
  334. }
  335. func (conf *Conf) addTFEnv(env []v1.EnvVar) []v1.EnvVar {
  336. env = append(env, v1.EnvVar{
  337. Name: "TF_DIR",
  338. Value: "./terraform",
  339. })
  340. env = append(env, v1.EnvVar{
  341. Name: "TF_PORTER_BACKEND",
  342. Value: "postgres",
  343. })
  344. env = append(env, v1.EnvVar{
  345. Name: "TF_PORTER_WORKSPACE",
  346. Value: conf.GetTFWorkspaceID(),
  347. })
  348. return env
  349. }