apply.go 11 KB

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