Преглед изворни кода

standalone app push command (#4418)

ianedwards пре 2 година
родитељ
комит
55192a0ed9
4 измењених фајлова са 164 додато и 5 уклоњено
  1. 53 0
      cli/cmd/commands/app.go
  2. 3 0
      cli/cmd/v2/app_build.go
  3. 66 2
      cli/cmd/v2/app_push.go
  4. 42 3
      cli/cmd/v2/build.go

+ 53 - 0
cli/cmd/commands/app.go

@@ -113,6 +113,34 @@ buildpacks using the --builder and --attach-buildpacks flags:
 	)
 	appCmd.AddCommand(appBuildCommand)
 
+	appPushCommand := &cobra.Command{
+		Use:   "push [application]",
+		Args:  cobra.MinimumNArgs(1),
+		Short: "Pushes your application to a remote registry.",
+		Long: fmt.Sprintf(`
+	%s
+
+Pushes the specified app to your default Porter registry. If no tag is specified, the latest
+commit SHA from the current branch will be used as the tag.
+
+You can specify a tag using the --tag flag:
+
+	%s
+`,
+			color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter app push\":"),
+			color.New(color.FgGreen, color.Bold).Sprintf("porter app push example-app --tag v1.0.0"),
+		),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return checkLoginAndRunWithConfig(cmd, cliConf, args, appPush)
+		},
+	}
+	appPushCommand.PersistentFlags().String(
+		flags.App_ImageTag,
+		"",
+		"set the image tag to use for the push",
+	)
+	appCmd.AddCommand(appPushCommand)
+
 	// appRunCmd represents the "porter app run" subcommand
 	appRunCmd := &cobra.Command{
 		Use:   "run [application] -- COMMAND [args...]",
@@ -304,6 +332,31 @@ func appBuild(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client
 	return nil
 }
 
+func appPush(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, cmd *cobra.Command, args []string) error {
+	appName := args[0]
+	if appName == "" {
+		return fmt.Errorf("app name must be specified")
+	}
+
+	tag, err := cmd.Flags().GetString(flags.App_ImageTag)
+	if err != nil {
+		return fmt.Errorf("error getting tag: %w", err)
+	}
+
+	err = v2.AppPush(ctx, v2.AppPushInput{
+		CLIConfig:            cliConfig,
+		Client:               client,
+		AppName:              appName,
+		DeploymentTargetName: deploymentTargetName,
+		ImageTag:             tag,
+	})
+	if err != nil {
+		return fmt.Errorf("failed to push image for app: %w", err)
+	}
+
+	return nil
+}
+
 func appManifests(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConfig config.CLIConfig, _ config.FeatureFlags, _ *cobra.Command, args []string) error {
 	appName := args[0]
 	if appName == "" {

+ 3 - 0
cli/cmd/v2/app_build.go

@@ -106,6 +106,9 @@ func AppBuild(ctx context.Context, inp AppBuildInput) error {
 		return fmt.Errorf("error creating build input from build settings: %w", err)
 	}
 
+	// skip push when only a build is requested
+	buildInput.SkipPush = true
+
 	buildOutput := build(ctx, client, buildInput)
 	if buildOutput.Error != nil {
 		return fmt.Errorf("error building app: %w", buildOutput.Error)

+ 66 - 2
cli/cmd/v2/app_push.go

@@ -1,11 +1,75 @@
 package v2
 
-import "context"
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/porter-dev/porter/cli/cmd/config"
+)
 
 // AppPushInput is the input to the AppPush function
-type AppPushInput struct{}
+type AppPushInput struct {
+	// CLIConfig is the CLI configuration
+	CLIConfig config.CLIConfig
+	// Client is the Porter API client
+	Client api.Client
+	// AppName is the name of the app
+	AppName string
+	// DeploymentTargetName is the name of the deployment target, if provided
+	DeploymentTargetName string
+	// ImageTag is the image tag to use for the app build
+	ImageTag string
+}
 
 // AppPush pushes an app to a remote registry
 func AppPush(ctx context.Context, inp AppPushInput) error {
+	cliConf := inp.CLIConfig
+	client := inp.Client
+
+	if cliConf.Project == 0 {
+		return errors.New("project must be set")
+	}
+
+	if cliConf.Cluster == 0 {
+		return errors.New("cluster must be set")
+	}
+
+	latest, err := client.CurrentAppRevision(ctx, api.CurrentAppRevisionInput{
+		ProjectID:            cliConf.Project,
+		ClusterID:            cliConf.Cluster,
+		AppName:              inp.AppName,
+		DeploymentTargetName: inp.DeploymentTargetName,
+	})
+	if err != nil {
+		return fmt.Errorf("error getting latest app revision: %s", err)
+	}
+
+	settings, err := client.GetBuildFromRevision(ctx, api.GetBuildFromRevisionInput{
+		ProjectID:     cliConf.Project,
+		ClusterID:     cliConf.Cluster,
+		AppName:       inp.AppName,
+		AppRevisionID: latest.AppRevision.ID,
+	})
+	if err != nil {
+		return fmt.Errorf("error getting build from revision: %w", err)
+	}
+
+	tagForPush, err := tagFromCommitSHAOrFlag(inp.ImageTag)
+	if err != nil {
+		return fmt.Errorf("error getting tag for build: %w", err)
+	}
+
+	// push the image to the remote registry
+	err = push(ctx, client, pushInput{
+		ProjectID:     cliConf.Project,
+		ImageTag:      tagForPush,
+		RepositoryURL: settings.Image.Repository,
+	})
+	if err != nil {
+		return fmt.Errorf("error pushing image: %w", err)
+	}
+
 	return nil
 }

+ 42 - 3
cli/cmd/v2/build.go

@@ -42,6 +42,8 @@ type buildInput struct {
 	PullImageBeforeBuild bool
 
 	Env map[string]string
+	// SkipPush is used to skip pushing the image to the registry
+	SkipPush bool
 }
 
 type buildOutput struct {
@@ -171,13 +173,50 @@ func build(ctx context.Context, client api.Client, inp buildInput) buildOutput {
 		return output
 	}
 
+	if !inp.SkipPush {
+		err = dockerAgent.PushImage(ctx, fmt.Sprintf("%s:%s", repositoryURL, tag))
+		if err != nil {
+			output.Error = fmt.Errorf("error pushing image: %w", err)
+			return output
+		}
+	}
+
+	return output
+}
+
+type pushInput struct {
+	ProjectID     uint
+	ImageTag      string
+	RepositoryURL string
+}
+
+func push(ctx context.Context, client api.Client, inp pushInput) error {
+	if inp.ProjectID == 0 {
+		return errors.New("must specify a project id")
+	}
+	projectID := inp.ProjectID
+
+	if inp.ImageTag == "" {
+		return errors.New("must specify an image tag")
+	}
+	tag := inp.ImageTag
+
+	if inp.RepositoryURL == "" {
+		return errors.New("must specify a registry url")
+	}
+	repositoryURL := strings.TrimPrefix(inp.RepositoryURL, "https://")
+
+	dockerAgent, err := docker.NewAgentWithAuthGetter(ctx, client, projectID)
+	if err != nil {
+		return fmt.Errorf("error getting docker agent: %w", err)
+	}
+
 	err = dockerAgent.PushImage(ctx, fmt.Sprintf("%s:%s", repositoryURL, tag))
 	if err != nil {
-		output.Error = fmt.Errorf("error pushing image: %w", err)
-		return output
+		return fmt.Errorf("error pushing image: %w", err)
 	}
 
-	return output
+	return nil
 }
 
 func createImageRepositoryIfNotExists(ctx context.Context, client api.Client, projectID uint, imageURL string) error {