refactor: introduce parameter structs for push command functions
Replace long parameter lists in buildAndPush, tagAndPush, runPush, and performAttachmentPush with buildConfig, imagePush, pushRequest, and attachmentPushRequest structs. Reduces parameter counts from 8-11 to 2-3.
- commit
- 1929353ecb283d37fdad04d8c7b335478b871dbd
- author
- brice · · 2026-03-14 17:22 UTC
- parent
- 8fce1c3
- signature
- unsigned
3 files · +125 -50
M internal/adapters/in/cli/attachments_push.go +36 -13
| @@ -18,6 +18,13 @@ var ( | |||
| 18 | 18 | determineVersionFn = determineVersion | |
| 19 | 19 | ) | |
| 20 | 20 | ||
| 21 | + | // attachmentPushRequest holds all inputs for the attachments push command. | |
| 22 | + | type attachmentPushRequest struct { | |
| 23 | + | ImageArg string | |
| 24 | + | Tag string | |
| 25 | + | Build buildConfig | |
| 26 | + | } | |
| 27 | + | ||
| 21 | 28 | func newAttachmentsPushCmd() *cobra.Command { | |
| 22 | 29 | var ( | |
| 23 | 30 | build bool | |
| @@ -32,7 +39,11 @@ func newAttachmentsPushCmd() *cobra.Command { | |||
| 32 | 39 | Short: "Build, tag, and push an attachment image", | |
| 33 | 40 | Args: cobra.ExactArgs(1), | |
| 34 | 41 | RunE: func(cmd *cobra.Command, args []string) error { | |
| 35 | - | return runAttachmentsPush(cmd.Context(), args[0], tag, build, platform, dockerfile, buildArgs, cmd.OutOrStdout()) | |
| 42 | + | return runAttachmentsPush(cmd.Context(), attachmentPushRequest{ | |
| 43 | + | ImageArg: args[0], | |
| 44 | + | Tag: tag, | |
| 45 | + | Build: buildConfig{Enabled: build, Platform: platform, Dockerfile: dockerfile, BuildArgs: buildArgs}, | |
| 46 | + | }, cmd.OutOrStdout()) | |
| 36 | 47 | }, | |
| 37 | 48 | } | |
| 38 | 49 | ||
| @@ -45,46 +56,58 @@ func newAttachmentsPushCmd() *cobra.Command { | |||
| 45 | 56 | return cmd | |
| 46 | 57 | } | |
| 47 | 58 | ||
| 48 | - | func runAttachmentsPush(ctx context.Context, imageArg, tag string, build bool, platform string, dockerfile string, buildArgs []string, out io.Writer) error { | |
| 59 | + | func runAttachmentsPush(ctx context.Context, req attachmentPushRequest, out io.Writer) error { | |
| 49 | 60 | handle, err := resolveControlPlaneFn(configPath) | |
| 50 | 61 | if err != nil { | |
| 51 | 62 | return err | |
| 52 | 63 | } | |
| 53 | 64 | defer handle.close() | |
| 54 | 65 | ||
| 55 | - | dockerfile, err = resolveDockerfile(dockerfile, build) | |
| 66 | + | dockerfile, err := resolveDockerfile(req.Build.Dockerfile, req.Build.Enabled) | |
| 56 | 67 | if err != nil { | |
| 57 | 68 | return err | |
| 58 | 69 | } | |
| 59 | 70 | ||
| 60 | - | for _, ba := range buildArgs { | |
| 71 | + | for _, ba := range req.Build.BuildArgs { | |
| 61 | 72 | if err := validateBuildArg(ba); err != nil { | |
| 62 | 73 | return err | |
| 63 | 74 | } | |
| 64 | 75 | } | |
| 65 | 76 | ||
| 66 | - | registry, imageName, targets, err := resolveAttachmentImage(ctx, handle.plane, imageArg) | |
| 77 | + | registry, imageName, targets, err := resolveAttachmentImage(ctx, handle.plane, req.ImageArg) | |
| 67 | 78 | if err != nil { | |
| 68 | 79 | return err | |
| 69 | 80 | } | |
| 70 | 81 | ||
| 71 | - | version, err := resolveVersionWithFn(ctx, tag) | |
| 82 | + | version, err := resolveVersionWithFn(ctx, req.Tag) | |
| 72 | 83 | if err != nil { | |
| 73 | 84 | return err | |
| 74 | 85 | } | |
| 75 | 86 | ||
| 76 | - | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | |
| 87 | + | img := imagePush{ | |
| 88 | + | Registry: registry, | |
| 89 | + | ImageName: imageName, | |
| 90 | + | Version: version, | |
| 91 | + | } | |
| 92 | + | img.VersionRef, img.LatestRef = resolveImageRefs(registry, imageName, version) | |
| 77 | 93 | ||
| 78 | 94 | imageOps, err := newImageOpsFn() | |
| 79 | 95 | if err != nil { | |
| 80 | 96 | return err | |
| 81 | 97 | } | |
| 82 | 98 | ||
| 83 | - | if err := printAttachmentPushInfo(out, imageArg, targets, versionRef, latestRef, version); err != nil { | |
| 99 | + | if err := printAttachmentPushInfo(out, req.ImageArg, targets, img.VersionRef, img.LatestRef, version); err != nil { | |
| 84 | 100 | return err | |
| 85 | 101 | } | |
| 86 | 102 | ||
| 87 | - | if err := performAttachmentPush(ctx, imageOps, build, version, platform, dockerfile, buildArgs, registry, imageName, versionRef, latestRef); err != nil { | |
| 103 | + | build := buildConfig{ | |
| 104 | + | Enabled: req.Build.Enabled, | |
| 105 | + | Platform: req.Build.Platform, | |
| 106 | + | Dockerfile: dockerfile, | |
| 107 | + | BuildArgs: req.Build.BuildArgs, | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | if err := performAttachmentPush(ctx, imageOps, build, img); err != nil { | |
| 88 | 111 | return err | |
| 89 | 112 | } | |
| 90 | 113 | ||
| @@ -109,11 +132,11 @@ func printAttachmentPushInfo(out io.Writer, imageArg string, targets []string, v | |||
| 109 | 132 | return nil | |
| 110 | 133 | } | |
| 111 | 134 | ||
| 112 | - | func performAttachmentPush(ctx context.Context, ops pushImageOps, build bool, version, platform, dockerfile string, buildArgs []string, registry, imageName, versionRef, latestRef string) error { | |
| 113 | - | if build { | |
| 114 | - | return buildAndPush(ctx, ops, version, platform, dockerfile, buildArgs, versionRef, latestRef) | |
| 135 | + | func performAttachmentPush(ctx context.Context, ops pushImageOps, build buildConfig, img imagePush) error { | |
| 136 | + | if build.Enabled { | |
| 137 | + | return buildAndPush(ctx, ops, build, img) | |
| 115 | 138 | } | |
| 116 | - | return tagAndPush(ctx, ops, registry, imageName, version, versionRef, latestRef) | |
| 139 | + | return tagAndPush(ctx, ops, img) | |
| 117 | 140 | } | |
| 118 | 141 | ||
| 119 | 142 | func resolveAttachmentImage(ctx context.Context, cp ControlPlane, imageArg string) (registry, imageName string, targets []string, err error) { | |
| @@ -18,6 +18,13 @@ var ( | |||||
| 18 | determineVersionFn = determineVersion | 18 | determineVersionFn = determineVersion | ||
| 19 | ) | 19 | ) | ||
| 20 | 20 | ||||
| 21 | + | // attachmentPushRequest holds all inputs for the attachments push command. | |||
| 22 | + | type attachmentPushRequest struct { | |||
| 23 | + | ImageArg string | |||
| 24 | + | Tag string | |||
| 25 | + | Build buildConfig | |||
| 26 | + | } | |||
| 27 | + | ||||
| 21 | func newAttachmentsPushCmd() *cobra.Command { | 28 | func newAttachmentsPushCmd() *cobra.Command { | ||
| 22 | var ( | 29 | var ( | ||
| 23 | build bool | 30 | build bool | ||
| @@ -32,7 +39,11 @@ func newAttachmentsPushCmd() *cobra.Command { | |||||
| 32 | Short: "Build, tag, and push an attachment image", | 39 | Short: "Build, tag, and push an attachment image", | ||
| 33 | Args: cobra.ExactArgs(1), | 40 | Args: cobra.ExactArgs(1), | ||
| 34 | RunE: func(cmd *cobra.Command, args []string) error { | 41 | RunE: func(cmd *cobra.Command, args []string) error { | ||
| 35 | - | return runAttachmentsPush(cmd.Context(), args[0], tag, build, platform, dockerfile, buildArgs, cmd.OutOrStdout()) | 42 | + | return runAttachmentsPush(cmd.Context(), attachmentPushRequest{ |
| 43 | + | ImageArg: args[0], | |||
| 44 | + | Tag: tag, | |||
| 45 | + | Build: buildConfig{Enabled: build, Platform: platform, Dockerfile: dockerfile, BuildArgs: buildArgs}, | |||
| 46 | + | }, cmd.OutOrStdout()) | |||
| 36 | }, | 47 | }, | ||
| 37 | } | 48 | } | ||
| 38 | 49 | ||||
| @@ -45,46 +56,58 @@ func newAttachmentsPushCmd() *cobra.Command { | |||||
| 45 | return cmd | 56 | return cmd | ||
| 46 | } | 57 | } | ||
| 47 | 58 | ||||
| 48 | - | func runAttachmentsPush(ctx context.Context, imageArg, tag string, build bool, platform string, dockerfile string, buildArgs []string, out io.Writer) error { | 59 | + | func runAttachmentsPush(ctx context.Context, req attachmentPushRequest, out io.Writer) error { |
| 49 | handle, err := resolveControlPlaneFn(configPath) | 60 | handle, err := resolveControlPlaneFn(configPath) | ||
| 50 | if err != nil { | 61 | if err != nil { | ||
| 51 | return err | 62 | return err | ||
| 52 | } | 63 | } | ||
| 53 | defer handle.close() | 64 | defer handle.close() | ||
| 54 | 65 | ||||
| 55 | - | dockerfile, err = resolveDockerfile(dockerfile, build) | 66 | + | dockerfile, err := resolveDockerfile(req.Build.Dockerfile, req.Build.Enabled) |
| 56 | if err != nil { | 67 | if err != nil { | ||
| 57 | return err | 68 | return err | ||
| 58 | } | 69 | } | ||
| 59 | 70 | ||||
| 60 | - | for _, ba := range buildArgs { | 71 | + | for _, ba := range req.Build.BuildArgs { |
| 61 | if err := validateBuildArg(ba); err != nil { | 72 | if err := validateBuildArg(ba); err != nil { | ||
| 62 | return err | 73 | return err | ||
| 63 | } | 74 | } | ||
| 64 | } | 75 | } | ||
| 65 | 76 | ||||
| 66 | - | registry, imageName, targets, err := resolveAttachmentImage(ctx, handle.plane, imageArg) | 77 | + | registry, imageName, targets, err := resolveAttachmentImage(ctx, handle.plane, req.ImageArg) |
| 67 | if err != nil { | 78 | if err != nil { | ||
| 68 | return err | 79 | return err | ||
| 69 | } | 80 | } | ||
| 70 | 81 | ||||
| 71 | - | version, err := resolveVersionWithFn(ctx, tag) | 82 | + | version, err := resolveVersionWithFn(ctx, req.Tag) |
| 72 | if err != nil { | 83 | if err != nil { | ||
| 73 | return err | 84 | return err | ||
| 74 | } | 85 | } | ||
| 75 | 86 | ||||
| 76 | - | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | 87 | + | img := imagePush{ |
| 88 | + | Registry: registry, | |||
| 89 | + | ImageName: imageName, | |||
| 90 | + | Version: version, | |||
| 91 | + | } | |||
| 92 | + | img.VersionRef, img.LatestRef = resolveImageRefs(registry, imageName, version) | |||
| 77 | 93 | ||||
| 78 | imageOps, err := newImageOpsFn() | 94 | imageOps, err := newImageOpsFn() | ||
| 79 | if err != nil { | 95 | if err != nil { | ||
| 80 | return err | 96 | return err | ||
| 81 | } | 97 | } | ||
| 82 | 98 | ||||
| 83 | - | if err := printAttachmentPushInfo(out, imageArg, targets, versionRef, latestRef, version); err != nil { | 99 | + | if err := printAttachmentPushInfo(out, req.ImageArg, targets, img.VersionRef, img.LatestRef, version); err != nil { |
| 84 | return err | 100 | return err | ||
| 85 | } | 101 | } | ||
| 86 | 102 | ||||
| 87 | - | if err := performAttachmentPush(ctx, imageOps, build, version, platform, dockerfile, buildArgs, registry, imageName, versionRef, latestRef); err != nil { | 103 | + | build := buildConfig{ |
| 104 | + | Enabled: req.Build.Enabled, | |||
| 105 | + | Platform: req.Build.Platform, | |||
| 106 | + | Dockerfile: dockerfile, | |||
| 107 | + | BuildArgs: req.Build.BuildArgs, | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | if err := performAttachmentPush(ctx, imageOps, build, img); err != nil { | |||
| 88 | return err | 111 | return err | ||
| 89 | } | 112 | } | ||
| 90 | 113 | ||||
| @@ -109,11 +132,11 @@ func printAttachmentPushInfo(out io.Writer, imageArg string, targets []string, v | |||||
| 109 | return nil | 132 | return nil | ||
| 110 | } | 133 | } | ||
| 111 | 134 | ||||
| 112 | - | func performAttachmentPush(ctx context.Context, ops pushImageOps, build bool, version, platform, dockerfile string, buildArgs []string, registry, imageName, versionRef, latestRef string) error { | 135 | + | func performAttachmentPush(ctx context.Context, ops pushImageOps, build buildConfig, img imagePush) error { |
| 113 | - | if build { | 136 | + | if build.Enabled { |
| 114 | - | return buildAndPush(ctx, ops, version, platform, dockerfile, buildArgs, versionRef, latestRef) | 137 | + | return buildAndPush(ctx, ops, build, img) |
| 115 | } | 138 | } | ||
| 116 | - | return tagAndPush(ctx, ops, registry, imageName, version, versionRef, latestRef) | 139 | + | return tagAndPush(ctx, ops, img) |
| 117 | } | 140 | } | ||
| 118 | 141 | ||||
| 119 | func resolveAttachmentImage(ctx context.Context, cp ControlPlane, imageArg string) (registry, imageName string, targets []string, err error) { | 142 | func resolveAttachmentImage(ctx context.Context, cp ControlPlane, imageArg string) (registry, imageName string, targets []string, err error) { | ||
M internal/adapters/in/cli/push.go +82 -36
| @@ -23,6 +23,33 @@ import ( | |||
| 23 | 23 | ||
| 24 | 24 | var newImageOpsFn = newImageOpsFromFlags | |
| 25 | 25 | ||
| 26 | + | // buildConfig holds image build settings for push commands. | |
| 27 | + | type buildConfig struct { | |
| 28 | + | Enabled bool | |
| 29 | + | Platform string | |
| 30 | + | Dockerfile string | |
| 31 | + | BuildArgs []string | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | // imagePush holds resolved image identity for push operations. | |
| 35 | + | type imagePush struct { | |
| 36 | + | Registry string | |
| 37 | + | ImageName string | |
| 38 | + | Version string | |
| 39 | + | VersionRef string | |
| 40 | + | LatestRef string | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // pushRequest holds all inputs for the push command. | |
| 44 | + | type pushRequest struct { | |
| 45 | + | ImageArg string | |
| 46 | + | Domain string | |
| 47 | + | Tag string | |
| 48 | + | Build buildConfig | |
| 49 | + | NoDeploy bool | |
| 50 | + | NoConfirm bool | |
| 51 | + | } | |
| 52 | + | ||
| 26 | 53 | func newPushCmd() *cobra.Command { | |
| 27 | 54 | var ( | |
| 28 | 55 | noDeploy bool | |
| @@ -60,7 +87,14 @@ Examples: | |||
| 60 | 87 | if len(args) > 0 { | |
| 61 | 88 | imageArg = args[0] | |
| 62 | 89 | } | |
| 63 | - | return runPush(cmd.Context(), imageArg, domainFlag, tag, build, platform, dockerfile, buildArgs, noDeploy, noConfirm) | |
| 90 | + | return runPush(cmd.Context(), pushRequest{ | |
| 91 | + | ImageArg: imageArg, | |
| 92 | + | Domain: domainFlag, | |
| 93 | + | Tag: tag, | |
| 94 | + | Build: buildConfig{Enabled: build, Platform: platform, Dockerfile: dockerfile, BuildArgs: buildArgs}, | |
| 95 | + | NoDeploy: noDeploy, | |
| 96 | + | NoConfirm: noConfirm, | |
| 97 | + | }) | |
| 64 | 98 | }, | |
| 65 | 99 | } | |
| 66 | 100 | ||
| @@ -120,70 +154,82 @@ func resolveVersion(ctx context.Context, tag string) (string, error) { | |||
| 120 | 154 | return version, nil | |
| 121 | 155 | } | |
| 122 | 156 | ||
| 123 | - | func runPush(ctx context.Context, imageArg, domainFlag, tag string, build bool, platform string, dockerfile string, buildArgs []string, noDeploy bool, noConfirm bool) error { | |
| 157 | + | func runPush(ctx context.Context, req pushRequest) error { | |
| 124 | 158 | handle, err := resolveControlPlane(configPath) | |
| 125 | 159 | if err != nil { | |
| 126 | 160 | return err | |
| 127 | 161 | } | |
| 128 | 162 | defer handle.close() | |
| 129 | 163 | ||
| 130 | - | dockerfile, err = resolveDockerfile(dockerfile, build) | |
| 164 | + | dockerfile, err := resolveDockerfile(req.Build.Dockerfile, req.Build.Enabled) | |
| 131 | 165 | if err != nil { | |
| 132 | 166 | return err | |
| 133 | 167 | } | |
| 134 | 168 | ||
| 135 | - | registry, imageName, pushDomain, err := resolveRoute(ctx, handle.plane, imageArg, domainFlag, dockerfile) | |
| 169 | + | registry, imageName, pushDomain, err := resolveRoute(ctx, handle.plane, req.ImageArg, req.Domain, dockerfile) | |
| 136 | 170 | if err != nil { | |
| 137 | 171 | return err | |
| 138 | 172 | } | |
| 139 | 173 | ||
| 140 | - | version, err := resolveVersion(ctx, tag) | |
| 174 | + | version, err := resolveVersion(ctx, req.Tag) | |
| 141 | 175 | if err != nil { | |
| 142 | 176 | return err | |
| 143 | 177 | } | |
| 144 | 178 | ||
| 145 | - | for _, ba := range buildArgs { | |
| 179 | + | for _, ba := range req.Build.BuildArgs { | |
| 146 | 180 | if err := validateBuildArg(ba); err != nil { | |
| 147 | 181 | return err | |
| 148 | 182 | } | |
| 149 | 183 | } | |
| 150 | 184 | ||
| 151 | - | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | |
| 185 | + | img := imagePush{ | |
| 186 | + | Registry: registry, | |
| 187 | + | ImageName: imageName, | |
| 188 | + | Version: version, | |
| 189 | + | } | |
| 190 | + | img.VersionRef, img.LatestRef = resolveImageRefs(registry, imageName, version) | |
| 152 | 191 | ||
| 153 | 192 | imageOps, err := newImageOpsFn() | |
| 154 | 193 | if err != nil { | |
| 155 | 194 | return err | |
| 156 | 195 | } | |
| 157 | 196 | ||
| 158 | - | fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(versionRef)) | |
| 197 | + | fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(img.VersionRef)) | |
| 159 | 198 | if version != "latest" { | |
| 160 | - | fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(latestRef)) | |
| 199 | + | fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(img.LatestRef)) | |
| 161 | 200 | } | |
| 162 | 201 | fmt.Printf("Domain: %s\n", styles.Theme.Bold.Render(pushDomain)) | |
| 163 | 202 | ||
| 164 | 203 | // Signal the server to suppress event-based deploys for this image. | |
| 165 | 204 | // The CLI will trigger an explicit deploy after push completes. | |
| 166 | - | if !noDeploy { | |
| 205 | + | if !req.NoDeploy { | |
| 167 | 206 | if err := handle.plane.DeployIntent(ctx, imageName); err != nil { | |
| 168 | 207 | // Non-fatal: worst case we get a redundant deploy via event | |
| 169 | 208 | fmt.Fprintf(os.Stderr, "warning: failed to register deploy intent: %v\n", err) | |
| 170 | 209 | } | |
| 171 | 210 | } | |
| 172 | 211 | ||
| 173 | - | if build { | |
| 174 | - | if err := buildAndPush(ctx, imageOps, version, platform, dockerfile, buildArgs, versionRef, latestRef); err != nil { | |
| 212 | + | build := buildConfig{ | |
| 213 | + | Enabled: req.Build.Enabled, | |
| 214 | + | Platform: req.Build.Platform, | |
| 215 | + | Dockerfile: dockerfile, | |
| 216 | + | BuildArgs: req.Build.BuildArgs, | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | if build.Enabled { | |
| 220 | + | if err := buildAndPush(ctx, imageOps, build, img); err != nil { | |
| 175 | 221 | return err | |
| 176 | 222 | } | |
| 177 | 223 | } else { | |
| 178 | - | if err := tagAndPush(ctx, imageOps, registry, imageName, version, versionRef, latestRef); err != nil { | |
| 224 | + | if err := tagAndPush(ctx, imageOps, img); err != nil { | |
| 179 | 225 | return err | |
| 180 | 226 | } | |
| 181 | 227 | } | |
| 182 | 228 | ||
| 183 | 229 | fmt.Println(styles.RenderSuccess("Push complete")) | |
| 184 | 230 | ||
| 185 | - | if !noDeploy { | |
| 186 | - | return deployAfterPush(ctx, handle.plane, pushDomain, noConfirm) | |
| 231 | + | if !req.NoDeploy { | |
| 232 | + | return deployAfterPush(ctx, handle.plane, pushDomain, req.NoConfirm) | |
| 187 | 233 | } | |
| 188 | 234 | ||
| 189 | 235 | return nil | |
| @@ -474,26 +520,26 @@ func parseTagRef(ref string) string { | |||
| 474 | 520 | return tag | |
| 475 | 521 | } | |
| 476 | 522 | ||
| 477 | - | func buildAndPush(ctx context.Context, ops pushImageOps, version, platform, dockerfile string, buildArgs []string, versionRef, latestRef string) error { | |
| 478 | - | if _, err := os.Stat(dockerfile); os.IsNotExist(err) { | |
| 479 | - | return fmt.Errorf("dockerfile not found: %s", dockerfile) | |
| 523 | + | func buildAndPush(ctx context.Context, ops pushImageOps, build buildConfig, img imagePush) error { | |
| 524 | + | if _, err := os.Stat(build.Dockerfile); os.IsNotExist(err) { | |
| 525 | + | return fmt.Errorf("dockerfile not found: %s", build.Dockerfile) | |
| 480 | 526 | } | |
| 481 | 527 | ||
| 482 | 528 | // Build and load into local daemon (NOT --push). | |
| 483 | 529 | // The native registry push client handles chunked uploads to stay | |
| 484 | 530 | // within Cloudflare's 100MB per-request limit. | |
| 485 | 531 | fmt.Println("\nBuilding image...") | |
| 486 | - | if err := ops.Build(ctx, buildImageArgs(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef)); err != nil { | |
| 532 | + | if err := ops.Build(ctx, buildImageArgs(ctx, img.Version, build.Platform, build.Dockerfile, build.BuildArgs, img.VersionRef, img.LatestRef)); err != nil { | |
| 487 | 533 | return err | |
| 488 | 534 | } | |
| 489 | 535 | ||
| 490 | 536 | fmt.Println("Pushing...") | |
| 491 | - | if err := ops.Push(ctx, latestRef); err != nil { | |
| 492 | - | return fmt.Errorf("failed to push %s: %w", latestRef, err) | |
| 537 | + | if err := ops.Push(ctx, img.LatestRef); err != nil { | |
| 538 | + | return fmt.Errorf("failed to push %s: %w", img.LatestRef, err) | |
| 493 | 539 | } | |
| 494 | - | if version != "latest" { | |
| 495 | - | if err := ops.Push(ctx, versionRef); err != nil { | |
| 496 | - | return fmt.Errorf("failed to push %s: %w", versionRef, err) | |
| 540 | + | if img.Version != "latest" { | |
| 541 | + | if err := ops.Push(ctx, img.VersionRef); err != nil { | |
| 542 | + | return fmt.Errorf("failed to push %s: %w", img.VersionRef, err) | |
| 497 | 543 | } | |
| 498 | 544 | } | |
| 499 | 545 | ||
| @@ -550,8 +596,8 @@ func buildImageArgs(ctx context.Context, version, platform, dockerfile string, b | |||
| 550 | 596 | return args | |
| 551 | 597 | } | |
| 552 | 598 | ||
| 553 | - | func tagAndPush(ctx context.Context, ops pushImageOps, registry, imageName, version, versionRef, latestRef string) error { | |
| 554 | - | localImage := fmt.Sprintf("%s/%s", registry, imageName) | |
| 599 | + | func tagAndPush(ctx context.Context, ops pushImageOps, img imagePush) error { | |
| 600 | + | localImage := fmt.Sprintf("%s/%s", img.Registry, img.ImageName) | |
| 555 | 601 | ||
| 556 | 602 | fmt.Println("\nChecking local image...") | |
| 557 | 603 | exists, err := ops.Exists(ctx, localImage) | |
| @@ -563,22 +609,22 @@ func tagAndPush(ctx context.Context, ops pushImageOps, registry, imageName, vers | |||
| 563 | 609 | } | |
| 564 | 610 | ||
| 565 | 611 | fmt.Println("Tagging...") | |
| 566 | - | if err := ops.Tag(ctx, localImage, versionRef); err != nil { | |
| 567 | - | return fmt.Errorf("failed to tag %s: %w", versionRef, err) | |
| 612 | + | if err := ops.Tag(ctx, localImage, img.VersionRef); err != nil { | |
| 613 | + | return fmt.Errorf("failed to tag %s: %w", img.VersionRef, err) | |
| 568 | 614 | } | |
| 569 | - | if version != "latest" { | |
| 570 | - | if err := ops.Tag(ctx, localImage, latestRef); err != nil { | |
| 571 | - | return fmt.Errorf("failed to tag %s: %w", latestRef, err) | |
| 615 | + | if img.Version != "latest" { | |
| 616 | + | if err := ops.Tag(ctx, localImage, img.LatestRef); err != nil { | |
| 617 | + | return fmt.Errorf("failed to tag %s: %w", img.LatestRef, err) | |
| 572 | 618 | } | |
| 573 | 619 | } | |
| 574 | 620 | ||
| 575 | 621 | fmt.Println("Pushing...") | |
| 576 | - | if err := ops.Push(ctx, versionRef); err != nil { | |
| 577 | - | return fmt.Errorf("failed to push %s: %w", versionRef, err) | |
| 622 | + | if err := ops.Push(ctx, img.VersionRef); err != nil { | |
| 623 | + | return fmt.Errorf("failed to push %s: %w", img.VersionRef, err) | |
| 578 | 624 | } | |
| 579 | - | if version != "latest" { | |
| 580 | - | if err := ops.Push(ctx, latestRef); err != nil { | |
| 581 | - | return fmt.Errorf("failed to push %s: %w", latestRef, err) | |
| 625 | + | if img.Version != "latest" { | |
| 626 | + | if err := ops.Push(ctx, img.LatestRef); err != nil { | |
| 627 | + | return fmt.Errorf("failed to push %s: %w", img.LatestRef, err) | |
| 582 | 628 | } | |
| 583 | 629 | } | |
| 584 | 630 | return nil | |
| @@ -23,6 +23,33 @@ import ( | |||||
| 23 | 23 | ||||
| 24 | var newImageOpsFn = newImageOpsFromFlags | 24 | var newImageOpsFn = newImageOpsFromFlags | ||
| 25 | 25 | ||||
| 26 | + | // buildConfig holds image build settings for push commands. | |||
| 27 | + | type buildConfig struct { | |||
| 28 | + | Enabled bool | |||
| 29 | + | Platform string | |||
| 30 | + | Dockerfile string | |||
| 31 | + | BuildArgs []string | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | // imagePush holds resolved image identity for push operations. | |||
| 35 | + | type imagePush struct { | |||
| 36 | + | Registry string | |||
| 37 | + | ImageName string | |||
| 38 | + | Version string | |||
| 39 | + | VersionRef string | |||
| 40 | + | LatestRef string | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | // pushRequest holds all inputs for the push command. | |||
| 44 | + | type pushRequest struct { | |||
| 45 | + | ImageArg string | |||
| 46 | + | Domain string | |||
| 47 | + | Tag string | |||
| 48 | + | Build buildConfig | |||
| 49 | + | NoDeploy bool | |||
| 50 | + | NoConfirm bool | |||
| 51 | + | } | |||
| 52 | + | ||||
| 26 | func newPushCmd() *cobra.Command { | 53 | func newPushCmd() *cobra.Command { | ||
| 27 | var ( | 54 | var ( | ||
| 28 | noDeploy bool | 55 | noDeploy bool | ||
| @@ -60,7 +87,14 @@ Examples: | |||||
| 60 | if len(args) > 0 { | 87 | if len(args) > 0 { | ||
| 61 | imageArg = args[0] | 88 | imageArg = args[0] | ||
| 62 | } | 89 | } | ||
| 63 | - | return runPush(cmd.Context(), imageArg, domainFlag, tag, build, platform, dockerfile, buildArgs, noDeploy, noConfirm) | 90 | + | return runPush(cmd.Context(), pushRequest{ |
| 91 | + | ImageArg: imageArg, | |||
| 92 | + | Domain: domainFlag, | |||
| 93 | + | Tag: tag, | |||
| 94 | + | Build: buildConfig{Enabled: build, Platform: platform, Dockerfile: dockerfile, BuildArgs: buildArgs}, | |||
| 95 | + | NoDeploy: noDeploy, | |||
| 96 | + | NoConfirm: noConfirm, | |||
| 97 | + | }) | |||
| 64 | }, | 98 | }, | ||
| 65 | } | 99 | } | ||
| 66 | 100 | ||||
| @@ -120,70 +154,82 @@ func resolveVersion(ctx context.Context, tag string) (string, error) { | |||||
| 120 | return version, nil | 154 | return version, nil | ||
| 121 | } | 155 | } | ||
| 122 | 156 | ||||
| 123 | - | func runPush(ctx context.Context, imageArg, domainFlag, tag string, build bool, platform string, dockerfile string, buildArgs []string, noDeploy bool, noConfirm bool) error { | 157 | + | func runPush(ctx context.Context, req pushRequest) error { |
| 124 | handle, err := resolveControlPlane(configPath) | 158 | handle, err := resolveControlPlane(configPath) | ||
| 125 | if err != nil { | 159 | if err != nil { | ||
| 126 | return err | 160 | return err | ||
| 127 | } | 161 | } | ||
| 128 | defer handle.close() | 162 | defer handle.close() | ||
| 129 | 163 | ||||
| 130 | - | dockerfile, err = resolveDockerfile(dockerfile, build) | 164 | + | dockerfile, err := resolveDockerfile(req.Build.Dockerfile, req.Build.Enabled) |
| 131 | if err != nil { | 165 | if err != nil { | ||
| 132 | return err | 166 | return err | ||
| 133 | } | 167 | } | ||
| 134 | 168 | ||||
| 135 | - | registry, imageName, pushDomain, err := resolveRoute(ctx, handle.plane, imageArg, domainFlag, dockerfile) | 169 | + | registry, imageName, pushDomain, err := resolveRoute(ctx, handle.plane, req.ImageArg, req.Domain, dockerfile) |
| 136 | if err != nil { | 170 | if err != nil { | ||
| 137 | return err | 171 | return err | ||
| 138 | } | 172 | } | ||
| 139 | 173 | ||||
| 140 | - | version, err := resolveVersion(ctx, tag) | 174 | + | version, err := resolveVersion(ctx, req.Tag) |
| 141 | if err != nil { | 175 | if err != nil { | ||
| 142 | return err | 176 | return err | ||
| 143 | } | 177 | } | ||
| 144 | 178 | ||||
| 145 | - | for _, ba := range buildArgs { | 179 | + | for _, ba := range req.Build.BuildArgs { |
| 146 | if err := validateBuildArg(ba); err != nil { | 180 | if err := validateBuildArg(ba); err != nil { | ||
| 147 | return err | 181 | return err | ||
| 148 | } | 182 | } | ||
| 149 | } | 183 | } | ||
| 150 | 184 | ||||
| 151 | - | versionRef, latestRef := resolveImageRefs(registry, imageName, version) | 185 | + | img := imagePush{ |
| 186 | + | Registry: registry, | |||
| 187 | + | ImageName: imageName, | |||
| 188 | + | Version: version, | |||
| 189 | + | } | |||
| 190 | + | img.VersionRef, img.LatestRef = resolveImageRefs(registry, imageName, version) | |||
| 152 | 191 | ||||
| 153 | imageOps, err := newImageOpsFn() | 192 | imageOps, err := newImageOpsFn() | ||
| 154 | if err != nil { | 193 | if err != nil { | ||
| 155 | return err | 194 | return err | ||
| 156 | } | 195 | } | ||
| 157 | 196 | ||||
| 158 | - | fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(versionRef)) | 197 | + | fmt.Printf("Image: %s\n", styles.Theme.Bold.Render(img.VersionRef)) |
| 159 | if version != "latest" { | 198 | if version != "latest" { | ||
| 160 | - | fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(latestRef)) | 199 | + | fmt.Printf("Also: %s\n", styles.Theme.Bold.Render(img.LatestRef)) |
| 161 | } | 200 | } | ||
| 162 | fmt.Printf("Domain: %s\n", styles.Theme.Bold.Render(pushDomain)) | 201 | fmt.Printf("Domain: %s\n", styles.Theme.Bold.Render(pushDomain)) | ||
| 163 | 202 | ||||
| 164 | // Signal the server to suppress event-based deploys for this image. | 203 | // Signal the server to suppress event-based deploys for this image. | ||
| 165 | // The CLI will trigger an explicit deploy after push completes. | 204 | // The CLI will trigger an explicit deploy after push completes. | ||
| 166 | - | if !noDeploy { | 205 | + | if !req.NoDeploy { |
| 167 | if err := handle.plane.DeployIntent(ctx, imageName); err != nil { | 206 | if err := handle.plane.DeployIntent(ctx, imageName); err != nil { | ||
| 168 | // Non-fatal: worst case we get a redundant deploy via event | 207 | // Non-fatal: worst case we get a redundant deploy via event | ||
| 169 | fmt.Fprintf(os.Stderr, "warning: failed to register deploy intent: %v\n", err) | 208 | fmt.Fprintf(os.Stderr, "warning: failed to register deploy intent: %v\n", err) | ||
| 170 | } | 209 | } | ||
| 171 | } | 210 | } | ||
| 172 | 211 | ||||
| 173 | - | if build { | 212 | + | build := buildConfig{ |
| 174 | - | if err := buildAndPush(ctx, imageOps, version, platform, dockerfile, buildArgs, versionRef, latestRef); err != nil { | 213 | + | Enabled: req.Build.Enabled, |
| 214 | + | Platform: req.Build.Platform, | |||
| 215 | + | Dockerfile: dockerfile, | |||
| 216 | + | BuildArgs: req.Build.BuildArgs, | |||
| 217 | + | } | |||
| 218 | + | ||||
| 219 | + | if build.Enabled { | |||
| 220 | + | if err := buildAndPush(ctx, imageOps, build, img); err != nil { | |||
| 175 | return err | 221 | return err | ||
| 176 | } | 222 | } | ||
| 177 | } else { | 223 | } else { | ||
| 178 | - | if err := tagAndPush(ctx, imageOps, registry, imageName, version, versionRef, latestRef); err != nil { | 224 | + | if err := tagAndPush(ctx, imageOps, img); err != nil { |
| 179 | return err | 225 | return err | ||
| 180 | } | 226 | } | ||
| 181 | } | 227 | } | ||
| 182 | 228 | ||||
| 183 | fmt.Println(styles.RenderSuccess("Push complete")) | 229 | fmt.Println(styles.RenderSuccess("Push complete")) | ||
| 184 | 230 | ||||
| 185 | - | if !noDeploy { | 231 | + | if !req.NoDeploy { |
| 186 | - | return deployAfterPush(ctx, handle.plane, pushDomain, noConfirm) | 232 | + | return deployAfterPush(ctx, handle.plane, pushDomain, req.NoConfirm) |
| 187 | } | 233 | } | ||
| 188 | 234 | ||||
| 189 | return nil | 235 | return nil | ||
| @@ -474,26 +520,26 @@ func parseTagRef(ref string) string { | |||||
| 474 | return tag | 520 | return tag | ||
| 475 | } | 521 | } | ||
| 476 | 522 | ||||
| 477 | - | func buildAndPush(ctx context.Context, ops pushImageOps, version, platform, dockerfile string, buildArgs []string, versionRef, latestRef string) error { | 523 | + | func buildAndPush(ctx context.Context, ops pushImageOps, build buildConfig, img imagePush) error { |
| 478 | - | if _, err := os.Stat(dockerfile); os.IsNotExist(err) { | 524 | + | if _, err := os.Stat(build.Dockerfile); os.IsNotExist(err) { |
| 479 | - | return fmt.Errorf("dockerfile not found: %s", dockerfile) | 525 | + | return fmt.Errorf("dockerfile not found: %s", build.Dockerfile) |
| 480 | } | 526 | } | ||
| 481 | 527 | ||||
| 482 | // Build and load into local daemon (NOT --push). | 528 | // Build and load into local daemon (NOT --push). | ||
| 483 | // The native registry push client handles chunked uploads to stay | 529 | // The native registry push client handles chunked uploads to stay | ||
| 484 | // within Cloudflare's 100MB per-request limit. | 530 | // within Cloudflare's 100MB per-request limit. | ||
| 485 | fmt.Println("\nBuilding image...") | 531 | fmt.Println("\nBuilding image...") | ||
| 486 | - | if err := ops.Build(ctx, buildImageArgs(ctx, version, platform, dockerfile, buildArgs, versionRef, latestRef)); err != nil { | 532 | + | if err := ops.Build(ctx, buildImageArgs(ctx, img.Version, build.Platform, build.Dockerfile, build.BuildArgs, img.VersionRef, img.LatestRef)); err != nil { |
| 487 | return err | 533 | return err | ||
| 488 | } | 534 | } | ||
| 489 | 535 | ||||
| 490 | fmt.Println("Pushing...") | 536 | fmt.Println("Pushing...") | ||
| 491 | - | if err := ops.Push(ctx, latestRef); err != nil { | 537 | + | if err := ops.Push(ctx, img.LatestRef); err != nil { |
| 492 | - | return fmt.Errorf("failed to push %s: %w", latestRef, err) | 538 | + | return fmt.Errorf("failed to push %s: %w", img.LatestRef, err) |
| 493 | } | 539 | } | ||
| 494 | - | if version != "latest" { | 540 | + | if img.Version != "latest" { |
| 495 | - | if err := ops.Push(ctx, versionRef); err != nil { | 541 | + | if err := ops.Push(ctx, img.VersionRef); err != nil { |
| 496 | - | return fmt.Errorf("failed to push %s: %w", versionRef, err) | 542 | + | return fmt.Errorf("failed to push %s: %w", img.VersionRef, err) |
| 497 | } | 543 | } | ||
| 498 | } | 544 | } | ||
| 499 | 545 | ||||
| @@ -550,8 +596,8 @@ func buildImageArgs(ctx context.Context, version, platform, dockerfile string, b | |||||
| 550 | return args | 596 | return args | ||
| 551 | } | 597 | } | ||
| 552 | 598 | ||||
| 553 | - | func tagAndPush(ctx context.Context, ops pushImageOps, registry, imageName, version, versionRef, latestRef string) error { | 599 | + | func tagAndPush(ctx context.Context, ops pushImageOps, img imagePush) error { |
| 554 | - | localImage := fmt.Sprintf("%s/%s", registry, imageName) | 600 | + | localImage := fmt.Sprintf("%s/%s", img.Registry, img.ImageName) |
| 555 | 601 | ||||
| 556 | fmt.Println("\nChecking local image...") | 602 | fmt.Println("\nChecking local image...") | ||
| 557 | exists, err := ops.Exists(ctx, localImage) | 603 | exists, err := ops.Exists(ctx, localImage) | ||
| @@ -563,22 +609,22 @@ func tagAndPush(ctx context.Context, ops pushImageOps, registry, imageName, vers | |||||
| 563 | } | 609 | } | ||
| 564 | 610 | ||||
| 565 | fmt.Println("Tagging...") | 611 | fmt.Println("Tagging...") | ||
| 566 | - | if err := ops.Tag(ctx, localImage, versionRef); err != nil { | 612 | + | if err := ops.Tag(ctx, localImage, img.VersionRef); err != nil { |
| 567 | - | return fmt.Errorf("failed to tag %s: %w", versionRef, err) | 613 | + | return fmt.Errorf("failed to tag %s: %w", img.VersionRef, err) |
| 568 | } | 614 | } | ||
| 569 | - | if version != "latest" { | 615 | + | if img.Version != "latest" { |
| 570 | - | if err := ops.Tag(ctx, localImage, latestRef); err != nil { | 616 | + | if err := ops.Tag(ctx, localImage, img.LatestRef); err != nil { |
| 571 | - | return fmt.Errorf("failed to tag %s: %w", latestRef, err) | 617 | + | return fmt.Errorf("failed to tag %s: %w", img.LatestRef, err) |
| 572 | } | 618 | } | ||
| 573 | } | 619 | } | ||
| 574 | 620 | ||||
| 575 | fmt.Println("Pushing...") | 621 | fmt.Println("Pushing...") | ||
| 576 | - | if err := ops.Push(ctx, versionRef); err != nil { | 622 | + | if err := ops.Push(ctx, img.VersionRef); err != nil { |
| 577 | - | return fmt.Errorf("failed to push %s: %w", versionRef, err) | 623 | + | return fmt.Errorf("failed to push %s: %w", img.VersionRef, err) |
| 578 | } | 624 | } | ||
| 579 | - | if version != "latest" { | 625 | + | if img.Version != "latest" { |
| 580 | - | if err := ops.Push(ctx, latestRef); err != nil { | 626 | + | if err := ops.Push(ctx, img.LatestRef); err != nil { |
| 581 | - | return fmt.Errorf("failed to push %s: %w", latestRef, err) | 627 | + | return fmt.Errorf("failed to push %s: %w", img.LatestRef, err) |
| 582 | } | 628 | } | ||
| 583 | } | 629 | } | ||
| 584 | return nil | 630 | return nil | ||
M internal/adapters/in/cli/push_integration_test.go +7 -1
| @@ -51,7 +51,13 @@ func TestIntegration_TagAndPush_NativeRegistry(t *testing.T) { | |||
| 51 | 51 | versionRef := registry + "/testapp:v1.0.0" | |
| 52 | 52 | latestRef := registry + "/testapp:latest" | |
| 53 | 53 | ||
| 54 | - | err = tagAndPush(ctx, ops, registry, "testapp", "v1.0.0", versionRef, latestRef) | |
| 54 | + | err = tagAndPush(ctx, ops, imagePush{ | |
| 55 | + | Registry: registry, | |
| 56 | + | ImageName: "testapp", | |
| 57 | + | Version: "v1.0.0", | |
| 58 | + | VersionRef: versionRef, | |
| 59 | + | LatestRef: latestRef, | |
| 60 | + | }) | |
| 55 | 61 | require.NoError(t, err) | |
| 56 | 62 | ||
| 57 | 63 | harness.RequireManifest(t, "v1.0.0") | |
| @@ -51,7 +51,13 @@ func TestIntegration_TagAndPush_NativeRegistry(t *testing.T) { | |||||
| 51 | versionRef := registry + "/testapp:v1.0.0" | 51 | versionRef := registry + "/testapp:v1.0.0" | ||
| 52 | latestRef := registry + "/testapp:latest" | 52 | latestRef := registry + "/testapp:latest" | ||
| 53 | 53 | ||||
| 54 | - | err = tagAndPush(ctx, ops, registry, "testapp", "v1.0.0", versionRef, latestRef) | 54 | + | err = tagAndPush(ctx, ops, imagePush{ |
| 55 | + | Registry: registry, | |||
| 56 | + | ImageName: "testapp", | |||
| 57 | + | Version: "v1.0.0", | |||
| 58 | + | VersionRef: versionRef, | |||
| 59 | + | LatestRef: latestRef, | |||
| 60 | + | }) | |||
| 55 | require.NoError(t, err) | 61 | require.NoError(t, err) | ||
| 56 | 62 | ||||
| 57 | harness.RequireManifest(t, "v1.0.0") | 63 | harness.RequireManifest(t, "v1.0.0") | ||