diff --git a/go.mod b/go.mod index 179b3fb..ce21f01 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.21.3 require ( github.com/go-resty/resty/v2 v2.11.0 + github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.4.0 github.com/magiconair/properties v1.8.5 github.com/manifoldco/promptui v0.9.0 @@ -15,6 +16,7 @@ require ( github.com/spf13/viper v1.10.1 github.com/stretchr/testify v1.8.4 golang.org/x/net v0.23.0 + golang.org/x/text v0.14.0 ) require ( @@ -44,7 +46,6 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 82c6fd5..89872ac 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= diff --git a/v1/cmd/deploy/deploy.go b/v1/cmd/deploy/deploy.go index 050aa70..20743ff 100644 --- a/v1/cmd/deploy/deploy.go +++ b/v1/cmd/deploy/deploy.go @@ -51,6 +51,12 @@ var ( podFlag string podNamespace string // pre parsed pod namespace podName string // pre parsed pod name + + mvnCmd string //custom maven command + + // default maven compile command and args, centralized to avoid drift with tests + defaultMavenCmd = "mvn" + defaultMavenArgs = []string{"clean", "package", "-Dmaven.test.skip=true"} ) const ( @@ -71,16 +77,19 @@ We advice you to use this in local dev phase. Scenario 0: Build bundle at current workspace and deploy it to a local running ark container with default port: arkctl deploy -Scenario 1: Build bundle at given path and deploy it to local running ark container with given port: +Scenario 1: Build the bundle using a custom Maven command to compile the project in the current workspace and deploy it to a locally running Ark container on the default port.: + arkctl deploy --mvnCmd ${mvnCmd} + +Scenario 2: Build bundle at given path and deploy it to local running ark container with given port: arkctl deploy --port ${your ark container portFlag} ${path/to/your/project} -Scenario 2: Deploy a local pre-built bundle to local running ark container: +Scenario 3: Deploy a local pre-built bundle to local running ark container: arkctl deploy ${path/to/your/pre/built/bundle.jar} -Scenario 3: Build and deploy a bundle at current dir to a remote running ark container in k8s cluster with default port: +Scenario 4: Build and deploy a bundle at current dir to a remote running ark container in k8s cluster with default port: arkctl deploy --pod ${namespace}/${name} -Scenario 4: Build an maven multi module project and deploy a sub module to a running ark container: +Scenario 5: Build an maven multi module project and deploy a sub module to a running ark container: arkctl deploy --sub ${path/to/your/sub/module} `, Args: func(cmd *cobra.Command, args []string) error { @@ -113,11 +122,13 @@ func execMavenBuild(ctx *contextutil.Context) bool { style.InfoPrefix("Stage").Println("BuildBundle") style.InfoPrefix("BuildDirectory").Println(defaultArg) + compileCmd, compileArg := parseMavenCommand(mvnCmd) + mvn := cmdutil.BuildCommandWithWorkDir( ctx, defaultArg, - "mvn", - "clean", "package", "-Dmaven.test.skip=true") + compileCmd, + compileArg...) style.InfoPrefix("Command").Println(mvn.String()) if err := mvn.Exec(); err != nil { @@ -151,6 +162,19 @@ func execMavenBuild(ctx *contextutil.Context) bool { return true } +func parseMavenCommand(mvnCmd string) (string, []string) { + compileCmd := defaultMavenCmd + compileArg := append([]string(nil), defaultMavenArgs...) + if mvnCmd != "" { + args := strings.Fields(mvnCmd) + if len(args) > 0 { + compileCmd = args[0] + compileArg = args[1:] + } + } + return compileCmd, compileArg +} + func execParseBizModel(ctx *contextutil.Context) bool { style.InfoPrefix("Stage").Println("ParseBizModel") bundlePath := osutil.GetLocalFileProtocol() + defaultArg @@ -450,6 +474,14 @@ func init() { DeployCommand.Flags().StringVar(&podFlag, "pod", "", ` If Provided, arkctl will try to deploy the bundle to the ark container running in given pod. `) + + DeployCommand.Flags().StringVar(&mvnCmd, "mvnCmd", "", ` +If provided, overrides the Maven invocation for building. Example: "mvn -q -T1C" or "./mvnw clean package". +Tokens are split by whitespace (strings.Fields); shell-style quoting is not supported via this single flag. +When empty, defaults to: mvn `+strings.Join(defaultMavenArgs, " ")+`. +Note: providing any non-empty value replaces the default arguments entirely. Supplying just "mvn" or "mvnw" yields no default goals. +`) + DeployCommand.Flags().StringVar(&subBundlePath, "sub", "", ` If Provided, arkctl will try to build the project at current dir and deploy the bundle at subBundlePath. `) diff --git a/v1/cmd/deploy/deploy_test.go b/v1/cmd/deploy/deploy_test.go new file mode 100644 index 0000000..02ea843 --- /dev/null +++ b/v1/cmd/deploy/deploy_test.go @@ -0,0 +1,139 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package deploy + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" +) + +func TestParseMavenCommand(t *testing.T) { + t.Parallel() + + defaultArgs := append([]string(nil), defaultMavenArgs...) + + tests := []struct { + name string + mvnCmd string + wantCmd string + wantArgs []string + }{ + { + name: "empty command", + mvnCmd: "", + wantCmd: defaultMavenCmd, + wantArgs: defaultArgs, + }, + { + name: "custom command without args", + mvnCmd: "mvnw", + wantCmd: "mvnw", + wantArgs: []string{}, + }, + { + name: "custom command with args", + mvnCmd: "mvn clean install", + wantCmd: "mvn", + wantArgs: []string{"clean", "install"}, + }, + { + name: "complex command with args", + mvnCmd: "mvnw clean package -DskipTests", + wantCmd: "mvnw", + wantArgs: []string{"clean", "package", "-DskipTests"}, + }, + { + name: "command with multiple spaces", + mvnCmd: "mvn clean package", + wantCmd: "mvn", + wantArgs: []string{"clean", "package"}, + }, + { + name: "whitespace-only command", + mvnCmd: " \t\n ", + wantCmd: defaultMavenCmd, + wantArgs: defaultArgs, + }, + { + name: "leading and trailing spaces", + mvnCmd: " mvn clean package ", + wantCmd: "mvn", + wantArgs: []string{"clean", "package"}, + }, + { + name: "tabs between tokens", + mvnCmd: "mvn\tclean\tinstall", + wantCmd: "mvn", + wantArgs: []string{"clean", "install"}, + }, + { + name: "windows wrapper with flags", + mvnCmd: "mvnw.cmd -q -T1C", + wantCmd: "mvnw.cmd", + wantArgs: []string{"-q", "-T1C"}, + }, + { + name: "absolute path mvn", + mvnCmd: "/usr/local/bin/mvn -q", + wantCmd: "/usr/local/bin/mvn", + wantArgs: []string{"-q"}, + }, + { + name: "relative path wrapper", + mvnCmd: "./mvnw -q", + wantCmd: "./mvnw", + wantArgs: []string{"-q"}, + }, + { + name: "args with equals and profile", + mvnCmd: "mvn -DskipTests=true -Pprod", + wantCmd: "mvn", + wantArgs: []string{"-DskipTests=true", "-Pprod"}, + }, + { + name: "thread-count with separate value", + mvnCmd: "mvn -T 1C", + wantCmd: "mvn", + wantArgs: []string{"-T", "1C"}, + }, + { + name: "multiple profiles in one flag", + mvnCmd: "mvn -Pprod,dev", + wantCmd: "mvn", + wantArgs: []string{"-Pprod,dev"}, + }, + { + name: "reactor and project list", + mvnCmd: "mvn -am -pl :module", + wantCmd: "mvn", + wantArgs: []string{"-am", "-pl", ":module"}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + gotCmd, gotArgs := parseMavenCommand(tt.mvnCmd) + if gotCmd != tt.wantCmd { + t.Errorf("parseMavenCommand() gotCmd = %q, want %q", gotCmd, tt.wantCmd) + } + if diff := cmp.Diff(tt.wantArgs, gotArgs, cmpopts.EquateEmpty()); diff != "" { + t.Errorf("parseMavenCommand() args mismatch (-want +got):\n%s", diff) + } + }) + } +}