Skip to content

Commit

Permalink
standalone app push command (#4418)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Mar 18, 2024
1 parent 137bca4 commit 55192a0
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 5 deletions.
53 changes: 53 additions & 0 deletions cli/cmd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...]",
Expand Down Expand Up @@ -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 == "" {
Expand Down
3 changes: 3 additions & 0 deletions cli/cmd/v2/app_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
68 changes: 66 additions & 2 deletions cli/cmd/v2/app_push.go
Original file line number Diff line number Diff line change
@@ -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
}
45 changes: 42 additions & 3 deletions cli/cmd/v2/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 55192a0

Please sign in to comment.