provisioner.go 10 KB

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