apply.go 10 KB

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