apply.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. package v2beta1
  2. import (
  3. "context"
  4. "fmt"
  5. "regexp"
  6. api "github.com/porter-dev/porter/api/client"
  7. "github.com/porter-dev/porter/cli/cmd/config"
  8. "github.com/porter-dev/switchboard/pkg/types"
  9. "gopkg.in/yaml.v3"
  10. )
  11. type PreviewApplier struct {
  12. apiClient api.Client
  13. cliConfig config.CLIConfig
  14. rawBytes []byte
  15. namespace string
  16. parsed *PorterYAML
  17. }
  18. // NewApplier returns an applier for preview environments
  19. func NewApplier(client api.Client, cliConfig config.CLIConfig, raw []byte, namespace string) (*PreviewApplier, error) {
  20. // replace all instances of ${{ porter.env.FOO }} with { .get-env.FOO }
  21. re := regexp.MustCompile(`\$\{\{\s*porter\.env\.(.*)\s*\}\}`)
  22. raw = re.ReplaceAll(raw, []byte("{.get-env.$1}"))
  23. parsed := &PorterYAML{}
  24. err := yaml.Unmarshal(raw, parsed)
  25. if err != nil {
  26. errMsg := composePreviewMessage("error parsing porter.yaml", Error)
  27. return nil, fmt.Errorf("%s: %w", errMsg, err)
  28. }
  29. err = cliConfig.ValidateCLIEnvironment()
  30. if err != nil {
  31. errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
  32. return nil, fmt.Errorf("%s: %w", errMsg, err)
  33. }
  34. return &PreviewApplier{
  35. apiClient: client,
  36. cliConfig: cliConfig,
  37. rawBytes: raw,
  38. namespace: namespace,
  39. parsed: parsed,
  40. }, nil
  41. }
  42. func (a *PreviewApplier) Apply() error {
  43. // for v2beta1, check if the namespace exists in the current project-cluster pair
  44. //
  45. // this is a sanity check to ensure that the user does not see any internal
  46. // errors that are caused by the namespace not existing
  47. nsList, err := a.apiClient.GetK8sNamespaces(
  48. context.TODO(), // can not change because of switchboard
  49. a.cliConfig.Project,
  50. a.cliConfig.Cluster,
  51. )
  52. if err != nil {
  53. errMsg := composePreviewMessage(fmt.Sprintf("error listing namespaces for project '%d', cluster '%d'",
  54. a.cliConfig.Project, a.cliConfig.Cluster), Error)
  55. return fmt.Errorf("%s: %w", errMsg, err)
  56. }
  57. namespaces := *nsList
  58. nsFound := false
  59. for _, ns := range namespaces {
  60. if ns.Name == a.namespace {
  61. nsFound = true
  62. break
  63. }
  64. }
  65. if !nsFound {
  66. // errMsg := composePreviewMessage(fmt.Sprintf("namespace '%s' does not exist in project '%d', cluster '%d'",
  67. // a.namespace, config.GetCLIConfig().Project, config.GetCLIConfig().Cluster), Error)
  68. // return fmt.Errorf("%s: %w", errMsg, err)
  69. }
  70. printInfoMessage(fmt.Sprintf("Applying porter.yaml with the following attributes:\n"+
  71. "\tHost: %s\n\tProject ID: %d\n\tCluster ID: %d\n\tNamespace: %s",
  72. a.cliConfig.Host,
  73. a.cliConfig.Project,
  74. a.cliConfig.Cluster,
  75. a.namespace),
  76. )
  77. // err = a.readOSEnv()
  78. // if err != nil {
  79. // errMsg := composePreviewMessage("error reading OS environment variables", Error)
  80. // return fmt.Errorf("%s: %w", errMsg, err)
  81. // }
  82. // err = a.processVariables()
  83. // if err != nil {
  84. // return err
  85. // }
  86. // err = a.processEnvGroups()
  87. // if err != nil {
  88. // return err
  89. // }
  90. return nil
  91. }
  92. func (a *PreviewApplier) DowngradeToV1() (*types.ResourceGroup, error) {
  93. err := a.Apply()
  94. if err != nil {
  95. return nil, err
  96. }
  97. v1File := &types.ResourceGroup{
  98. Version: "v1",
  99. Resources: []*types.Resource{
  100. {
  101. Name: "get-env",
  102. Driver: "os-env",
  103. },
  104. },
  105. }
  106. buildRefs := make(map[string]*Build)
  107. for _, b := range a.parsed.Builds {
  108. if b == nil {
  109. continue
  110. }
  111. buildRefs[b.GetName()] = b
  112. bi, err := b.getV1BuildImage()
  113. if err != nil {
  114. return nil, err
  115. }
  116. pi, err := b.getV1PushImage()
  117. if err != nil {
  118. return nil, err
  119. }
  120. v1File.Resources = append(v1File.Resources, bi, pi)
  121. }
  122. for _, app := range a.parsed.Apps {
  123. if app == nil {
  124. continue
  125. }
  126. if _, ok := buildRefs[app.GetBuildRef()]; !ok {
  127. errMsg := composePreviewMessage(fmt.Sprintf("build_ref '%s' referenced by app '%s' does not exist",
  128. app.GetBuildRef(), app.GetName()), Error)
  129. return nil, fmt.Errorf("%s: %w", errMsg, err)
  130. }
  131. ai, err := app.getV1Resource(buildRefs[app.GetBuildRef()])
  132. if err != nil {
  133. return nil, err
  134. }
  135. v1File.Resources = append(v1File.Resources, ai)
  136. }
  137. for _, addon := range a.parsed.Addons {
  138. if addon == nil {
  139. continue
  140. }
  141. ai, err := addon.getV1Addon()
  142. if err != nil {
  143. return nil, err
  144. }
  145. v1File.Resources = append(v1File.Resources, ai)
  146. }
  147. return v1File, nil
  148. }
  149. // func (a *PreviewApplier) readOSEnv() error {
  150. // printInfoMessage("Reading OS environment variables")
  151. // env := os.Environ()
  152. // osEnv := make(map[string]string)
  153. // for _, e := range env {
  154. // k, v, _ := strings.Cut(e, "=")
  155. // kCopy := k
  156. // if k != "" && v != "" && strings.HasPrefix(k, "PORTER_APPLY_") {
  157. // // we only read in env variables that start with PORTER_APPLY_
  158. // for strings.HasPrefix(k, "PORTER_APPLY_") {
  159. // k = strings.TrimPrefix(k, "PORTER_APPLY_")
  160. // }
  161. // if k == "" {
  162. // printWarningMessage(fmt.Sprintf("Ignoring invalid OS environment variable '%s'", kCopy))
  163. // }
  164. // osEnv[k] = v
  165. // }
  166. // }
  167. // a.osEnv = osEnv
  168. // return nil
  169. // }
  170. // func (a *PreviewApplier) processVariables() error {
  171. // printInfoMessage("Processing variables")
  172. // constantsMap := make(map[string]string)
  173. // variablesMap := make(map[string]string)
  174. // for _, v := range a.parsed.Variables {
  175. // if v == nil {
  176. // continue
  177. // }
  178. // if v.Once != nil && *v.Once {
  179. // // a constant which should be stored in the env group on first run
  180. // if exists, err := a.constantExistsInEnvGroup(*v.Name); err == nil {
  181. // if exists == nil {
  182. // // this should not happen
  183. // return fmt.Errorf("internal error: please let the Porter team know about this and quote the following " +
  184. // "error:\n-----\nERROR: checking for constant existence in env group returned nil with no error")
  185. // }
  186. // val := *exists
  187. // if !val {
  188. // // create the constant in the env group
  189. // if *v.Value != "" {
  190. // constantsMap[*v.Name] = *v.Value
  191. // } else if v.Random != nil && *v.Random {
  192. // constantsMap[*v.Name] = randomString(*v.Length, defaultCharset)
  193. // } else {
  194. // // this should not happen
  195. // return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
  196. // "error:\n-----\nERROR: for variable '%s', random is false and value is empty", *v.Name)
  197. // }
  198. // }
  199. // } else {
  200. // return fmt.Errorf("error checking for existence of constant %s: %w", *v.Name, err)
  201. // }
  202. // } else {
  203. // if v.Value != nil && *v.Value != "" {
  204. // variablesMap[*v.Name] = *v.Value
  205. // } else if v.Random != nil && *v.Random {
  206. // variablesMap[*v.Name] = randomString(*v.Length, defaultCharset)
  207. // } else {
  208. // // this should not happen
  209. // return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
  210. // "error:\n-----\nERROR: for variable '%s', random is false and value is empty", *v.Name)
  211. // }
  212. // }
  213. // }
  214. // if len(constantsMap) > 0 {
  215. // // we need to create these constants in the env group
  216. // _, err := a.apiClient.CreateEnvGroup(
  217. // ctx,
  218. // config.GetCLIConfig().Project,
  219. // config.GetCLIConfig().Cluster,
  220. // a.namespace,
  221. // &apiTypes.CreateEnvGroupRequest{
  222. // Name: constantsEnvGroup,
  223. // Variables: constantsMap,
  224. // },
  225. // )
  226. // if err != nil {
  227. // return fmt.Errorf("error creating constants (variables with once set to true) in env group: %w", err)
  228. // }
  229. // for k, v := range constantsMap {
  230. // variablesMap[k] = v
  231. // }
  232. // }
  233. // a.variablesMap = variablesMap
  234. // return nil
  235. // }
  236. // func (a *PreviewApplier) constantExistsInEnvGroup(name string) (*bool, error) {
  237. // apiResponse, err := a.apiClient.GetEnvGroup(
  238. // ctx,
  239. // config.GetCLIConfig().Project,
  240. // config.GetCLIConfig().Cluster,
  241. // a.namespace,
  242. // &apiTypes.GetEnvGroupRequest{
  243. // Name: constantsEnvGroup,
  244. // // we do not care about the version because it always needs to be the latest
  245. // },
  246. // )
  247. // if err != nil {
  248. // if strings.Contains(err.Error(), "env group not found") {
  249. // return booleanptr(false), nil
  250. // }
  251. // return nil, err
  252. // }
  253. // if _, ok := apiResponse.Variables[name]; ok {
  254. // return booleanptr(true), nil
  255. // }
  256. // return booleanptr(false), nil
  257. // }
  258. // func (a *PreviewApplier) processEnvGroups() error {
  259. // printInfoMessage("Processing env groups")
  260. // for _, eg := range a.parsed.EnvGroups {
  261. // if eg == nil {
  262. // continue
  263. // }
  264. // if eg.Name == nil || *eg.Name == "" {
  265. // }
  266. // envGroup, err := a.apiClient.GetEnvGroup(
  267. // ctx,
  268. // config.GetCLIConfig().Project,
  269. // config.GetCLIConfig().Cluster,
  270. // a.namespace,
  271. // &apiTypes.GetEnvGroupRequest{
  272. // Name: *eg.Name,
  273. // },
  274. // )
  275. // if err != nil && strings.Contains(err.Error(), "env group not found") {
  276. // if eg.CloneFrom == nil {
  277. // return fmt.Errorf(composePreviewMessage(fmt.Sprintf("empty clone_from for env group '%s'", *eg.Name), Error))
  278. // }
  279. // egNS, egName, found := strings.Cut(*eg.CloneFrom, "/")
  280. // if !found {
  281. // return fmt.Errorf("error parsing clone_from for env group '%s': invalid format", *eg.Name)
  282. // }
  283. // // clone the env group
  284. // envGroup, err := a.apiClient.CloneEnvGroup(
  285. // ctx,
  286. // config.GetCLIConfig().Project,
  287. // config.GetCLIConfig().Cluster,
  288. // egNS,
  289. // &apiTypes.CloneEnvGroupRequest{
  290. // SourceName: egName,
  291. // TargetNamespace: a.namespace,
  292. // TargetName: *eg.Name,
  293. // },
  294. // )
  295. // if err != nil {
  296. // return fmt.Errorf("error cloning env group '%s' from '%s': %w", egName, egNS, err)
  297. // }
  298. // a.envGroups[*eg.Name] = &apiTypes.EnvGroup{
  299. // Name: envGroup.Name,
  300. // Variables: envGroup.Variables,
  301. // }
  302. // } else if err != nil {
  303. // return fmt.Errorf("error checking for env group '%s': %w", *eg.Name, err)
  304. // } else {
  305. // a.envGroups[*eg.Name] = &apiTypes.EnvGroup{
  306. // Name: envGroup.Name,
  307. // Variables: envGroup.Variables,
  308. // }
  309. // }
  310. // }
  311. // return nil
  312. // }