github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow@v0.28.1-0.20240311201729-34c6856b157f/pkg/command/quarkus/build.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package quarkus 21 22 import ( 23 "fmt" 24 "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/common" 25 "github.com/apache/incubator-kie-tools/packages/kn-plugin-workflow/pkg/metadata" 26 "github.com/ory/viper" 27 "github.com/spf13/cobra" 28 "regexp" 29 "strconv" 30 "strings" 31 ) 32 33 type BuildCmdConfig struct { 34 Image string // full image name 35 Registry string // image registry (overrides image name) 36 Repository string // image repository (overrides image name) 37 ImageName string // image name (overrides image name) 38 Tag string // image tag (overrides image name) 39 40 // Build strategy options 41 Jib bool // use Jib extension to build the image and push it to a remote registry 42 JibPodman bool // use Jib extension to build the image and save it in podman 43 Push bool // choose to push an image to a remote registry or not (Docker only) 44 45 Test bool // choose to run the project tests 46 } 47 48 func NewBuildCommand() *cobra.Command { 49 var cmd = &cobra.Command{ 50 Use: "build", 51 Short: "Build a Quarkus SonataFlow project and generate a container image", 52 Long: ` 53 Builds a Quarkus SonataFlow project in the current directory 54 resulting in a container image. 55 By default the resultant container image will have the project name. It can be 56 overridden with the --image or with others image options, see help for more information. 57 58 During the build, a knative.yml file will be generated on the ./target/kubernetes folder. 59 If your workflow uses eventing, an additional kogito.yml is also generated. 60 The deploy command uses both these files. 61 62 Authentication is required if you want to push the resultant image to a private registry. 63 To authenticate to your registry, use "docker login" or any other equivalent method. 64 `, 65 Example: ` 66 # Build from the local directory 67 # The full image name will be determined automatically based on the 68 # project's directory name 69 {{.Name}} build 70 71 # Build from the local directory, specifying the full image name 72 {{.Name}} build --image quay.io/myuser/myworkflow:1.0 73 74 # Build from the local directory, specifying the full image name and pushing 75 # it to the remote registry (authentication can be necessary, use docker login) 76 # NOTE: If no registry is specfied in the image full name, quay.io will be used. 77 {{.Name}} build --image quay.io/mysuer/myworkflow:1.0 --push 78 79 # Build from the local directory, passing separately image options 80 {{.Name}} build --image-registry docker.io --image-repository myuser --image-name myworkflow --image-tag 1.0 81 82 # Build using Jib instead of Docker. (Read more: https://kiegroup.github.io/kogito-docs/serverlessworkflow/main/cloud/build-workflow-image-with-quarkus-cli.html) 83 # Docker is still required to save the image if the push flag is not used 84 {{.Name}} build --jib 85 86 # Build using Jib and save the image in podman 87 # Can't use the "push" or "jib" flag for this build strategy 88 {{.Name}} build --jib-podman 89 `, 90 SuggestFor: []string{"biuld", "buidl", "built"}, 91 PreRunE: common.BindEnv("image", "image-registry", "image-repository", "image-name", "image-tag", "jib", "jib-podman", "push", "test"), 92 } 93 94 cmd.RunE = func(cmd *cobra.Command, args []string) error { 95 _, err := runBuild(cmd) 96 return err 97 } 98 99 cmd.Flags().StringP("image", "i", "", "Full image name in the form of [registry]/[repository]/[name]:[tag]") 100 cmd.Flags().String("image-registry", "", "Image registry, ex: quay.io, if the --image flag is in use this option overrides image [registry]") 101 cmd.Flags().String("image-repository", "", "Image repository, ex: registry-user or registry-project, if the --image flag is in use, this option overrides image [repository]") 102 cmd.Flags().String("image-name", "", "Image name, ex: new-project, if the --image flag is in use, this option overrides the image [name]") 103 cmd.Flags().String("image-tag", "", "Image tag, ex: 1.0, if the --image flag is in use, this option overrides the image [tag]") 104 105 cmd.Flags().Bool("jib", false, "Use Jib extension to generate the image (Docker is still required to save the generated image if push is not used)") 106 cmd.Flags().Bool("jib-podman", false, "Use Jib extension to generate the image and save it in podman (can't use --push)") 107 cmd.Flags().Bool("push", false, "Attempt to push the genereated image after being successfully built") 108 109 cmd.Flags().Bool("test", false, "Run the project tests") 110 111 cmd.SetHelpFunc(common.DefaultTemplatedHelp) 112 return cmd 113 } 114 115 func runBuild(cmd *cobra.Command) (out string, err error) { 116 fmt.Println("🔨 Building your Quarkus SonataFlow project...") 117 118 cfg, err := runBuildCmdConfig(cmd) 119 if err != nil { 120 err = fmt.Errorf("initializing build config: %w", err) 121 return 122 } 123 124 if err = common.CheckJavaDependencies(); err != nil { 125 return 126 } 127 128 if cfg.JibPodman { 129 if err = common.CheckPodman(); err != nil { 130 return 131 } 132 } else if (cfg.Jib && !cfg.Push) || (!cfg.Jib) { 133 if err = common.CheckDocker(); err != nil { 134 return 135 } 136 } 137 138 if err != nil { 139 return 140 } 141 142 if err = runAddExtension(cfg); err != nil { 143 return 144 } 145 146 if out, err = runBuildImage(cfg); err != nil { 147 return 148 } 149 150 fmt.Println("✅ Quarkus SonataFlow project successfully built") 151 152 return 153 } 154 155 func runBuildCmdConfig(cmd *cobra.Command) (cfg BuildCmdConfig, err error) { 156 cfg = BuildCmdConfig{ 157 Image: viper.GetString("image"), 158 Registry: viper.GetString("image-registry"), 159 Repository: viper.GetString("image-repository"), 160 ImageName: viper.GetString("image-name"), 161 Tag: viper.GetString("image-tag"), 162 163 Jib: viper.GetBool("jib"), 164 JibPodman: viper.GetBool("jib-podman"), 165 Push: viper.GetBool("push"), 166 167 Test: viper.GetBool("test"), 168 } 169 if len(cfg.Image) == 0 && len(cfg.ImageName) == 0 { 170 fmt.Println("ERROR: either --image or --image-name should be used") 171 err = fmt.Errorf("missing flags") 172 } 173 174 if cfg.JibPodman && cfg.Push { 175 fmt.Println("ERROR: can't use --jib-podman with --push") 176 err = fmt.Errorf("invalid flags") 177 } 178 179 if cfg.JibPodman && cfg.Jib { 180 fmt.Println("ERROR: can't use --jib-podman with --jib") 181 err = fmt.Errorf("invalid flags") 182 } 183 184 return 185 } 186 187 // This function removes the extension that is not going to be used (if present) 188 // and updates the chosen one. The entire operation is handled as an extension addition. 189 // Therefore the removal is hidden from the user 190 func runAddExtension(cfg BuildCmdConfig) error { 191 if cfg.Jib || cfg.JibPodman { 192 fmt.Printf(" - Adding Quarkus Jib extension\n") 193 if err := common.RunExtensionCommand( 194 "quarkus:remove-extension", 195 metadata.QuarkusContainerImageDocker, 196 ); err != nil { 197 return err 198 } 199 if err := common.RunExtensionCommand( 200 "quarkus:add-extension", 201 metadata.QuarkusContainerImageJib, 202 ); err != nil { 203 return err 204 } 205 } else { 206 fmt.Printf(" - Adding Quarkus Docker extension\n") 207 if err := common.RunExtensionCommand( 208 "quarkus:remove-extension", 209 metadata.QuarkusContainerImageJib, 210 ); err != nil { 211 return err 212 } 213 if err := common.RunExtensionCommand( 214 "quarkus:add-extension", 215 metadata.QuarkusContainerImageDocker, 216 ); err != nil { 217 return err 218 } 219 } 220 221 fmt.Println("✅ Quarkus extension was successfully added to the project pom.xml") 222 return nil 223 } 224 225 func runBuildImage(cfg BuildCmdConfig) (out string, err error) { 226 registry, repository, name, tag := getImageConfig(cfg) 227 if err = checkImageName(name); err != nil { 228 return 229 } 230 231 skipTestsConfig := getSkipTestsConfig(cfg) 232 builderConfig := getBuilderConfig(cfg) 233 executableName := getExecutableNameConfig(cfg) 234 235 build := common.ExecCommand("mvn", "package", 236 skipTestsConfig, 237 "-Dquarkus.kubernetes.deployment-target=knative", 238 fmt.Sprintf("-Dquarkus.knative.name=%s", name), 239 "-Dquarkus.container-image.build=true", 240 fmt.Sprintf("-Dquarkus.container-image.registry=%s", registry), 241 fmt.Sprintf("-Dquarkus.container-image.group=%s", repository), 242 fmt.Sprintf("-Dquarkus.container-image.name=%s", name), 243 fmt.Sprintf("-Dquarkus.container-image.tag=%s", tag), 244 fmt.Sprintf("-Dquarkus.container-image.push=%s", strconv.FormatBool(cfg.Push)), 245 builderConfig, 246 executableName, 247 ) 248 249 if err = common.RunCommand( 250 build, 251 "build", 252 ); err != nil { 253 if cfg.Push { 254 fmt.Println("ERROR: Image build failed.") 255 fmt.Println("If you're using a private registry, check if you're authenticated") 256 } 257 return 258 } 259 260 out = getImage(registry, repository, name, tag) 261 if cfg.Push { 262 fmt.Printf("Created and pushed an image to registry: %s\n", out) 263 } else { 264 fmt.Printf("Created a local image: %s\n", out) 265 } 266 267 fmt.Println("✅ Build success") 268 return 269 } 270 271 func checkImageName(name string) (err error) { 272 matched, err := regexp.MatchString("^[a-z]([-a-z0-9]*[a-z0-9])?$", name) 273 if !matched { 274 fmt.Println(` 275 ERROR: Image name should match [a-z]([-a-z0-9]*[a-z0-9])? 276 The name needs to start with a lower case letter and then it can be composed exclusvely of lower case letters, numbers or dashes ('-') 277 Example of valid names: "test-0-0-1", "test", "t1" 278 Example of invalid names: "1-test", "test.1", "test/1" 279 `) 280 err = fmt.Errorf("invalid image name") 281 } 282 return 283 } 284 285 // Use the --image-registry, --image-repository, --image-name, --image-tag to override the --image flag 286 func getImageConfig(cfg BuildCmdConfig) (string, string, string, string) { 287 imageTagArray := strings.Split(cfg.Image, ":") 288 imageArray := strings.SplitN(imageTagArray[0], "/", 3) 289 290 var registry = "" 291 if len(cfg.Registry) > 0 { 292 registry = cfg.Registry 293 } else if len(imageArray) > 1 { 294 registry = imageArray[0] 295 } 296 297 var repository = "" 298 if len(cfg.Repository) > 0 { 299 repository = cfg.Repository 300 } else if len(imageArray) == 3 { 301 repository = imageArray[1] 302 } 303 304 var name = "" 305 if len(cfg.ImageName) > 0 { 306 name = cfg.ImageName 307 } else if len(imageArray) == 1 { 308 name = imageArray[0] 309 } else if len(imageArray) == 2 { 310 name = imageArray[1] 311 } else if len(imageArray) == 3 { 312 name = imageArray[2] 313 } 314 315 var tag = metadata.DefaultTag 316 if len(cfg.Tag) > 0 { 317 tag = cfg.Tag 318 } else if len(imageTagArray) > 1 && len(imageTagArray[1]) > 0 { 319 tag = imageTagArray[1] 320 } 321 322 return registry, repository, name, tag 323 } 324 325 func getImage(registry string, repository string, name string, tag string) string { 326 if len(registry) == 0 && len(repository) == 0 { 327 return fmt.Sprintf("%s:%s", name, tag) 328 } else if len(registry) == 0 { 329 return fmt.Sprintf("%s/%s:%s", repository, name, tag) 330 } else if len(repository) == 0 { 331 return fmt.Sprintf("%s/%s:%s", registry, name, tag) 332 } 333 return fmt.Sprintf("%s/%s/%s:%s", registry, repository, name, tag) 334 } 335 336 func getSkipTestsConfig(cfg BuildCmdConfig) string { 337 skipTests := "-DskipTests=" 338 if cfg.Test { 339 skipTests += "false" 340 } else { 341 skipTests += "true" 342 } 343 return skipTests 344 } 345 346 func getBuilderConfig(cfg BuildCmdConfig) string { 347 builder := "-Dquarkus.container-image.builder=" 348 if cfg.Jib || cfg.JibPodman { 349 builder += "jib" 350 } else { 351 builder += "docker" 352 } 353 return builder 354 } 355 356 func getExecutableNameConfig(cfg BuildCmdConfig) string { 357 executableName := "-Dquarkus.jib.docker-executable-name=" 358 if cfg.JibPodman { 359 executableName += "podman" 360 } else { 361 executableName += "docker" 362 } 363 return executableName 364 }