|
|
@@ -0,0 +1,375 @@
|
|
|
+package v2beta1
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "fmt"
|
|
|
+ "os"
|
|
|
+ "strings"
|
|
|
+
|
|
|
+ "github.com/cli/cli/git"
|
|
|
+ "github.com/fatih/color"
|
|
|
+ "github.com/mitchellh/mapstructure"
|
|
|
+ api "github.com/porter-dev/porter/api/client"
|
|
|
+ apiTypes "github.com/porter-dev/porter/api/types"
|
|
|
+ "github.com/porter-dev/porter/cli/cmd/config"
|
|
|
+ "github.com/porter-dev/porter/cli/cmd/deploy"
|
|
|
+ "github.com/porter-dev/switchboard/v2/pkg/types"
|
|
|
+)
|
|
|
+
|
|
|
+type DefaultDriver struct {
|
|
|
+ Vars map[string]string
|
|
|
+ Env map[string]string
|
|
|
+ Builds []*types.Build
|
|
|
+ APIClient *api.Client
|
|
|
+ Namespace string
|
|
|
+
|
|
|
+ allErrors []error
|
|
|
+}
|
|
|
+
|
|
|
+func (d *DefaultDriver) PreApply(resource *types.YAMLNode[*types.Resource]) error {
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (d *DefaultDriver) Apply(resource *types.YAMLNode[*types.Resource]) error {
|
|
|
+ if isPorterApp(resource) {
|
|
|
+ return d.applyPorterApp(resource)
|
|
|
+ }
|
|
|
+
|
|
|
+ // everything else is an addon
|
|
|
+ return d.applyAddon(resource)
|
|
|
+}
|
|
|
+
|
|
|
+func (d *DefaultDriver) PostApply(resource *types.YAMLNode[*types.Resource]) error {
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (d *DefaultDriver) OnError(resource *types.YAMLNode[*types.Resource], errs []error) {
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+func isPorterApp(resource *types.YAMLNode[*types.Resource]) bool {
|
|
|
+ if resource.GetValue().ChartURL.GetValue() == "https://charts.getporter.dev" &&
|
|
|
+ (resource.GetValue().Type.GetValue() == "web" ||
|
|
|
+ resource.GetValue().Type.GetValue() == "worker" ||
|
|
|
+ resource.GetValue().Type.GetValue() == "job") {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+func (d *DefaultDriver) applyPorterApp(resource *types.YAMLNode[*types.Resource]) error {
|
|
|
+ appBuild := &porterAppBuild{}
|
|
|
+ appDeploy := &porterAppDeploy{}
|
|
|
+ buildNode := resource.GetValue().Build.GetRawYAMLNode()
|
|
|
+ deployNode := resource.GetValue().Deploy.GetRawYAMLNode()
|
|
|
+
|
|
|
+ err := buildNode.Decode(appBuild)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err // FIXME: descriptive error
|
|
|
+ }
|
|
|
+
|
|
|
+ err = deployNode.Decode(appDeploy)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err // FIXME: descriptive error
|
|
|
+ }
|
|
|
+
|
|
|
+ var buildConfig *types.Build
|
|
|
+
|
|
|
+ if appBuild.Ref != "" {
|
|
|
+ for _, b := range d.Builds {
|
|
|
+ if b.Name.GetValue() == appBuild.Ref {
|
|
|
+ buildConfig = b
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if buildConfig == nil {
|
|
|
+ // this should not happen
|
|
|
+ return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
|
|
|
+ "error:\n-----\nERROR: invalid build ref given for app '%s'", resource.GetValue().Name.GetValue())
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ buildConfig = appBuild.Build
|
|
|
+ }
|
|
|
+
|
|
|
+ if buildConfig == nil {
|
|
|
+ // this should not happen
|
|
|
+ return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
|
|
|
+ "error:\n-----\nERROR: neither build ref nor build body given for app '%s'", resource.GetValue().Name.GetValue())
|
|
|
+ }
|
|
|
+
|
|
|
+ if resource.GetValue().Type.GetValue() == "job" {
|
|
|
+ jobConfig := &porterJob{}
|
|
|
+ jobNode := resource.GetRawYAMLNode()
|
|
|
+
|
|
|
+ err := jobNode.Decode(jobConfig)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err // FIXME: descriptive error
|
|
|
+ }
|
|
|
+
|
|
|
+ return d.applyJob(resource, buildConfig, appDeploy, jobConfig)
|
|
|
+ } else if oneOf(resource.GetValue().Type.GetValue(), "web", "worker") {
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // this should not happen
|
|
|
+ return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
|
|
|
+ "error:\n-----\nERROR: app '%s' is not one of 'web', 'worker', 'job'", resource.GetValue().Name.GetValue())
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (d *DefaultDriver) applyAddon(resource *types.YAMLNode[*types.Resource]) error {
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (d *DefaultDriver) applyJob(
|
|
|
+ resource *types.YAMLNode[*types.Resource],
|
|
|
+ buildConfig *types.Build,
|
|
|
+ appDeploy *porterAppDeploy,
|
|
|
+ jobConfig *porterJob,
|
|
|
+) error {
|
|
|
+ _, err := d.APIClient.GetRelease(
|
|
|
+ context.Background(),
|
|
|
+ config.GetCLIConfig().Project,
|
|
|
+ config.GetCLIConfig().Cluster,
|
|
|
+ d.Namespace,
|
|
|
+ resource.GetValue().Name.GetValue(),
|
|
|
+ )
|
|
|
+
|
|
|
+ exists := err == nil
|
|
|
+
|
|
|
+ flattenedBuildEnv := make(map[string]string)
|
|
|
+
|
|
|
+ for k, v := range buildConfig.Env {
|
|
|
+ flattenedBuildEnv[k.GetValue()] = v.GetValue()
|
|
|
+ }
|
|
|
+
|
|
|
+ var flattenedBuildEnvGroup []apiTypes.EnvGroupMeta
|
|
|
+
|
|
|
+ for _, egName := range buildConfig.EnvGroups {
|
|
|
+ flattenedBuildEnvGroup = append(flattenedBuildEnvGroup, apiTypes.EnvGroupMeta{
|
|
|
+ Name: egName.GetValue(),
|
|
|
+ Namespace: d.Namespace,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ tag := getImageTag()
|
|
|
+
|
|
|
+ sharedOpts := &deploy.SharedOpts{
|
|
|
+ ProjectID: config.GetCLIConfig().Project,
|
|
|
+ ClusterID: config.GetCLIConfig().Cluster,
|
|
|
+ Namespace: d.Namespace,
|
|
|
+ LocalPath: buildConfig.Context.GetValue(),
|
|
|
+ LocalDockerfile: buildConfig.Dockerfile.GetValue(),
|
|
|
+ OverrideTag: tag,
|
|
|
+ Method: deploy.DeployBuildType(buildConfig.Method.GetValue()),
|
|
|
+ AdditionalEnv: flattenedBuildEnv,
|
|
|
+ EnvGroups: flattenedBuildEnvGroup,
|
|
|
+ }
|
|
|
+
|
|
|
+ if buildConfig.Method.GetValue() == "pack" && buildConfig.UseCache != nil {
|
|
|
+ sharedOpts.UseCache = buildConfig.UseCache.GetValue()
|
|
|
+ }
|
|
|
+
|
|
|
+ if exists {
|
|
|
+ if jobConfig.Once {
|
|
|
+ // since the job already exists and was marked 'once', simply return
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ updateAgent, err := deploy.NewDeployAgent(d.APIClient, resource.GetValue().Name.GetValue(), &deploy.DeployOpts{
|
|
|
+ SharedOpts: sharedOpts,
|
|
|
+ Local: buildConfig.Method.GetValue() != "registry",
|
|
|
+ })
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("[porter.yaml v2][app:%s] error creating deploy agent to update app: %w",
|
|
|
+ resource.GetValue().Name.GetValue(), err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // if the build method is registry, we do not trigger a build
|
|
|
+ if buildConfig.Method.GetValue() != "registry" {
|
|
|
+ buildEnv, err := updateAgent.GetBuildEnv(&deploy.GetBuildEnvOpts{
|
|
|
+ UseNewConfig: true,
|
|
|
+ // NewConfig: appConf.Values,
|
|
|
+ })
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err // FIXME
|
|
|
+ }
|
|
|
+
|
|
|
+ err = updateAgent.SetBuildEnv(buildEnv)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err // FIXME
|
|
|
+ }
|
|
|
+
|
|
|
+ var bc *apiTypes.BuildConfig
|
|
|
+
|
|
|
+ if buildConfig.Method.GetValue() == "pack" {
|
|
|
+ // FIXME: temporary fix
|
|
|
+ var bp []string
|
|
|
+
|
|
|
+ for _, b := range buildConfig.Buildpacks {
|
|
|
+ bp = append(bp, b.GetValue())
|
|
|
+ }
|
|
|
+
|
|
|
+ bc = &apiTypes.BuildConfig{
|
|
|
+ Builder: buildConfig.Builder.GetValue(),
|
|
|
+ Buildpacks: bp,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ err = updateAgent.Build(bc)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err // FIXME
|
|
|
+ }
|
|
|
+
|
|
|
+ // if !appConf.Build.UseCache { // FIXME
|
|
|
+ err = updateAgent.Push()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err // FIXME
|
|
|
+ }
|
|
|
+ // }
|
|
|
+ }
|
|
|
+
|
|
|
+ // err = updateAgent.UpdateImageAndValues(appConf.Values) // FIXME
|
|
|
+
|
|
|
+ // if err != nil {
|
|
|
+ // return err // FIXME
|
|
|
+ // }
|
|
|
+ } else { // create the job
|
|
|
+ // attempt to get repo suffix from environment variables
|
|
|
+ var repoSuffix string
|
|
|
+
|
|
|
+ if repoName := os.Getenv("PORTER_REPO_NAME"); repoName != "" {
|
|
|
+ if repoOwner := os.Getenv("PORTER_REPO_OWNER"); repoOwner != "" {
|
|
|
+ repoSuffix = strings.ToLower(strings.ReplaceAll(fmt.Sprintf("%s-%s", repoOwner, repoName), "_", "-"))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var registryURL string
|
|
|
+
|
|
|
+ if buildConfig.ImageRepoURI != nil {
|
|
|
+ registryURL = buildConfig.ImageRepoURI.GetValue()
|
|
|
+ }
|
|
|
+
|
|
|
+ if registryURL == "" {
|
|
|
+ regList, err := d.APIClient.ListRegistries(context.Background(), config.GetCLIConfig().Project)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("error fetching list of registries while trying to choose registry to deploy new"+
|
|
|
+ " image for app '%s': %w", resource.GetValue().Name.GetValue(), err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(*regList) == 0 {
|
|
|
+ return fmt.Errorf("no registries linked with project, needed to deploy new image for app '%s'",
|
|
|
+ resource.GetValue().Name.GetValue())
|
|
|
+ } else {
|
|
|
+ registryURL = (*regList)[0].URL
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ createAgent := &deploy.CreateAgent{
|
|
|
+ Client: d.APIClient,
|
|
|
+ CreateOpts: &deploy.CreateOpts{
|
|
|
+ SharedOpts: sharedOpts,
|
|
|
+ Kind: resource.GetValue().Type.GetValue(),
|
|
|
+ ReleaseName: resource.GetValue().Name.GetValue(),
|
|
|
+ RegistryURL: registryURL,
|
|
|
+ RepoSuffix: repoSuffix,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ if buildConfig.Method.GetValue() == "registry" {
|
|
|
+ flattenedDeployMap := make(map[string]any)
|
|
|
+
|
|
|
+ for k, v := range resource.GetValue().Deploy.GetValue() {
|
|
|
+ flattenedDeployMap[k.GetValue()] = v.GetValue()
|
|
|
+ }
|
|
|
+
|
|
|
+ values := &porterWebChartValues{}
|
|
|
+
|
|
|
+ // delete the aliases from the deploy section
|
|
|
+ delete(flattenedDeployMap, "command")
|
|
|
+ delete(flattenedDeployMap, "cpu")
|
|
|
+ delete(flattenedDeployMap, "memory")
|
|
|
+
|
|
|
+ // replace alias values to the original expect yaml values
|
|
|
+ values.Container.Command = appDeploy.Command
|
|
|
+ values.Container.Env.Build = flattenedBuildEnv
|
|
|
+ values.Container.Env.Normal = appDeploy.Env
|
|
|
+ // values.Container.Env.Synced
|
|
|
+ values.Resources.Requests.CPU = appDeploy.CPU
|
|
|
+ values.Resources.Requests.Memory = appDeploy.Memory
|
|
|
+ if len(appDeploy.Hosts) > 0 {
|
|
|
+ values.Ingress.CustomDomain = true
|
|
|
+ values.Ingress.Hosts = appDeploy.Hosts
|
|
|
+ }
|
|
|
+
|
|
|
+ overrideValues := make(map[string]any)
|
|
|
+
|
|
|
+ err = mapstructure.Decode(values, &overrideValues)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return err // FIXME
|
|
|
+ }
|
|
|
+
|
|
|
+ _, err := createAgent.CreateFromRegistry("", overrideValues)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("[porter.yaml v2][app:%s] error creating job: %w", resource.GetValue().Name.GetValue(), err)
|
|
|
+ }
|
|
|
+ } else if oneOf(buildConfig.Method.GetValue(), "pack", "docker") {
|
|
|
+ _, err := createAgent.CreateFromDocker(nil, "", nil)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("[porter.yaml v2][app:%s] error creating job: %w", resource.GetValue().Name.GetValue(), err)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // this should not happen
|
|
|
+ return fmt.Errorf("internal error: please let the Porter team know about this and quote the following "+
|
|
|
+ "error:\n-----\nERROR: build method was not one of 'pack', 'docker', 'registry' for app '%s'",
|
|
|
+ resource.GetValue().Name.GetValue())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// fetching the image tag works in 3 steps
|
|
|
+// - read PORTER_TAG env var
|
|
|
+// - read the git SHA from the current directory
|
|
|
+// - default to 'latest' tag
|
|
|
+func getImageTag() string {
|
|
|
+ tag := os.Getenv("PORTER_TAG")
|
|
|
+
|
|
|
+ if tag == "" {
|
|
|
+ commit, err := git.LastCommit()
|
|
|
+
|
|
|
+ if err == nil {
|
|
|
+ tag = commit.Sha[:7]
|
|
|
+
|
|
|
+ color.New(color.FgBlue).Printf("[porter.yaml v2] PORTER_TAG not defined, falling back to image tag '%s'"+
|
|
|
+ " from git SHA\n", tag)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ color.New(color.FgBlue).Printf("[porter.yaml v2] Using image tag '%s' from PORTER_TAG environment variable\n", tag)
|
|
|
+ }
|
|
|
+
|
|
|
+ if tag == "" {
|
|
|
+ color.New(color.FgBlue).Println("[porter.yaml v2] PORTER_TAG not defined, not a git repository, falling back" +
|
|
|
+ " to image tag 'latest'")
|
|
|
+
|
|
|
+ tag = "latest"
|
|
|
+ }
|
|
|
+
|
|
|
+ return tag
|
|
|
+}
|