This is an automated email from the ASF dual-hosted git repository.

liujun pushed a commit to branch refactor-with-go
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git


The following commit(s) were added to refs/heads/refactor-with-go by this push:
     new 5f77f765 dubboctl: implement profile related commands and make some 
small fixes (#1102)
5f77f765 is described below

commit 5f77f7658fc4409e05c703e71f885e244839ff91
Author: Scout Wang <[email protected]>
AuthorDate: Sun May 14 11:37:50 2023 +0800

    dubboctl: implement profile related commands and make some small fixes 
(#1102)
---
 go.sum                                             |   1 -
 pkg/dubboctl/cmd/manifest.go                       |  10 +-
 pkg/dubboctl/cmd/manifest_test.go                  |  90 ++++++++-----
 .../{internal/cmd/common.go => cmd/profile.go}     |  21 ++-
 pkg/dubboctl/cmd/profile_test.go                   | 143 +++++++++++++++++++++
 pkg/dubboctl/cmd/root.go                           |   1 +
 .../{internal/cmd => cmd/subcmd}/common.go         |   2 +-
 .../{internal/cmd => cmd/subcmd}/manifest_diff.go  |   2 +-
 .../cmd => cmd/subcmd}/manifest_generate.go        |  13 +-
 .../cmd => cmd/subcmd}/manifest_install.go         |   2 +-
 .../cmd => cmd/subcmd}/manifest_uninstall.go       |   2 +-
 pkg/dubboctl/cmd/subcmd/profile_diff.go            | 101 +++++++++++++++
 pkg/dubboctl/cmd/subcmd/profile_list.go            | 105 +++++++++++++++
 pkg/dubboctl/cmd/testdata/profile/test0.yaml       |  27 ++++
 pkg/dubboctl/cmd/testdata/profile/test1.yaml       |  25 ++++
 .../cmd/testdata/profile/test2_wrong_format.yaml   |  25 ++++
 pkg/dubboctl/internal/kube/client.go               |   1 -
 pkg/dubboctl/internal/kube/object.go               |   9 ++
 pkg/dubboctl/internal/kube/object_test.go          |  57 ++++++++
 pkg/dubboctl/internal/manifest/common.go           |  49 ++++++-
 .../nacos_component-render_manifest.golden.yaml    |   6 +-
 pkg/logger/log.go                                  |  11 +-
 22 files changed, 634 insertions(+), 69 deletions(-)

diff --git a/go.sum b/go.sum
index d16a2183..f68087fe 100644
--- a/go.sum
+++ b/go.sum
@@ -1167,7 +1167,6 @@ github.com/olekukonko/tablewriter 
v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v
 github.com/olekukonko/tablewriter v0.0.5/go.mod 
h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod 
h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
 github.com/onsi/ginkgo v1.6.0/go.mod 
h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
 github.com/onsi/ginkgo v1.7.0/go.mod 
h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo/v2 v2.6.0 
h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc=
 github.com/onsi/gomega v1.4.3/go.mod 
h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
diff --git a/pkg/dubboctl/cmd/manifest.go b/pkg/dubboctl/cmd/manifest.go
index d6a472b9..7bca0664 100644
--- a/pkg/dubboctl/cmd/manifest.go
+++ b/pkg/dubboctl/cmd/manifest.go
@@ -16,7 +16,7 @@
 package cmd
 
 import (
-       "github.com/apache/dubbo-admin/pkg/dubboctl/internal/cmd"
+       subcmd "github.com/apache/dubbo-admin/pkg/dubboctl/cmd/subcmd"
        "github.com/spf13/cobra"
 )
 
@@ -26,9 +26,9 @@ func addManifest(rootCmd *cobra.Command) {
                Short: "Commands related to manifest",
                Long:  "Commands help user to generate manifest and install 
manifest",
        }
-       cmd.ConfigManifestGenerateCmd(manifestCmd)
-       cmd.ConfigManifestInstallCmd(manifestCmd)
-       cmd.ConfigManifestUninstallCmd(manifestCmd)
-       cmd.ConfigManifestDiffCmd(manifestCmd)
+       subcmd.ConfigManifestGenerateCmd(manifestCmd)
+       subcmd.ConfigManifestInstallCmd(manifestCmd)
+       subcmd.ConfigManifestUninstallCmd(manifestCmd)
+       subcmd.ConfigManifestDiffCmd(manifestCmd)
        rootCmd.AddCommand(manifestCmd)
 }
diff --git a/pkg/dubboctl/cmd/manifest_test.go 
b/pkg/dubboctl/cmd/manifest_test.go
index e09fbdc9..0a9d941e 100644
--- a/pkg/dubboctl/cmd/manifest_test.go
+++ b/pkg/dubboctl/cmd/manifest_test.go
@@ -17,19 +17,20 @@ package cmd
 
 import (
        "bytes"
+       "github.com/apache/dubbo-admin/pkg/dubboctl/cmd/subcmd"
        "os"
        "strings"
        "testing"
 
-       "github.com/apache/dubbo-admin/pkg/dubboctl/internal/cmd"
        "sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
 func TestManifestGenerate(t *testing.T) {
        tests := []struct {
-               desc string
-               cmd  string
-               temp string
+               desc    string
+               cmd     string
+               temp    string
+               wantErr bool
        }{
                {
                        desc: "using default configuration without any flag",
@@ -52,6 +53,14 @@ func TestManifestGenerate(t *testing.T) {
                        cmd: "manifest generate --set 
spec.componentsMeta.grafana.repoURL=https://grafana.github.io/helm-charts"; +
                                " --set 
spec.componentsMeta.grafana.version=6.31.0",
                },
+               {
+                       desc: "setting specification of built-in component with 
wrong path",
+                       cmd:  "manifest generate --set 
components.nacos.replicas=3",
+               },
+               {
+                       desc: "setting specification of built-in component with 
wrong path",
+                       cmd:  "manifest generate --set 
components.grafana.replicas=3",
+               },
                {
                        desc: "generate manifest to target path",
                        cmd:  "manifest generate -o ./testdata/temp",
@@ -63,18 +72,21 @@ func TestManifestGenerate(t *testing.T) {
                },
        }
        for _, test := range tests {
-               testExecute(t, test.cmd)
-               // remove temporary dir
-               if test.temp != "" {
-                       os.RemoveAll(test.temp)
-               }
+               t.Run(test.desc, func(t *testing.T) {
+                       testExecute(t, test.cmd, test.wantErr)
+                       // remove temporary dir
+                       if test.temp != "" {
+                               os.RemoveAll(test.temp)
+                       }
+               })
        }
 }
 
 func TestManifestInstall(t *testing.T) {
        tests := []struct {
-               desc string
-               cmd  string
+               desc    string
+               cmd     string
+               wantErr bool
        }{
                {
                        desc: "without any flag",
@@ -82,11 +94,13 @@ func TestManifestInstall(t *testing.T) {
                },
        }
        // For now, we do not use envTest to do black box testing
-       cmd.TestInstallFlag = true
-       cmd.TestCli = fake.NewClientBuilder().Build()
+       subcmd.TestInstallFlag = true
+       subcmd.TestCli = fake.NewClientBuilder().Build()
 
        for _, test := range tests {
-               testExecute(t, test.cmd)
+               t.Run(test.desc, func(t *testing.T) {
+                       testExecute(t, test.cmd, test.wantErr)
+               })
        }
 }
 
@@ -94,8 +108,9 @@ func TestManifestUninstall(t *testing.T) {
        tests := []struct {
                desc string
                // cmd has been executed before
-               before string
-               cmd    string
+               before  string
+               cmd     string
+               wantErr bool
        }{
                {
                        desc:   "without any flag",
@@ -104,13 +119,15 @@ func TestManifestUninstall(t *testing.T) {
                },
        }
        // For now, we do not use envTest to do black box testing
-       cmd.TestInstallFlag = true
-       cmd.TestCli = fake.NewClientBuilder().Build()
+       subcmd.TestInstallFlag = true
+       subcmd.TestCli = fake.NewClientBuilder().Build()
 
        for _, test := range tests {
-               // prepare existing resources
-               testExecute(t, test.before)
-               testExecute(t, test.cmd)
+               t.Run(test.desc, func(t *testing.T) {
+                       // prepare existing resources
+                       testExecute(t, test.before, false)
+                       testExecute(t, test.cmd, test.wantErr)
+               })
        }
 }
 
@@ -120,6 +137,7 @@ func TestManifestDiff(t *testing.T) {
                befores []string
                cmd     string
                temps   []string
+               wantErr bool
        }{
                {
                        desc: "compare two dirs",
@@ -135,25 +153,35 @@ func TestManifestDiff(t *testing.T) {
                },
        }
        for _, test := range tests {
-               for _, before := range test.befores {
-                       testExecute(t, before)
-               }
-               testExecute(t, test.cmd)
-               for _, temp := range test.temps {
-                       if temp != "" {
-                               os.RemoveAll(temp)
+               t.Run(test.desc, func(t *testing.T) {
+                       for _, before := range test.befores {
+                               testExecute(t, before, false)
                        }
-               }
+                       testExecute(t, test.cmd, test.wantErr)
+                       for _, temp := range test.temps {
+                               if temp != "" {
+                                       os.RemoveAll(temp)
+                               }
+                       }
+               })
        }
 }
 
-func testExecute(t *testing.T, cmd string) {
+func testExecute(t *testing.T, cmd string, wantErr bool) string {
        var out bytes.Buffer
        args := strings.Split(cmd, " ")
        rootCmd := getRootCmd(args)
        rootCmd.SetOut(&out)
        if err := rootCmd.Execute(); err != nil {
+               if wantErr {
+                       return ""
+               }
                t.Errorf("execute %s failed, err: %s", cmd, err)
-               return
+               return ""
+       }
+       if wantErr {
+               t.Errorf("want err but got no err")
+               return ""
        }
+       return out.String()
 }
diff --git a/pkg/dubboctl/internal/cmd/common.go b/pkg/dubboctl/cmd/profile.go
similarity index 66%
copy from pkg/dubboctl/internal/cmd/common.go
copy to pkg/dubboctl/cmd/profile.go
index d8de607d..e0c3534b 100644
--- a/pkg/dubboctl/internal/cmd/common.go
+++ b/pkg/dubboctl/cmd/profile.go
@@ -15,10 +15,19 @@
 
 package cmd
 
-import "sigs.k8s.io/controller-runtime/pkg/client"
-
-var (
-       // TestInstallFlag and TestCli are uses for black box testing
-       TestInstallFlag bool
-       TestCli         client.Client
+import (
+       "github.com/apache/dubbo-admin/pkg/dubboctl/cmd/subcmd"
+       "github.com/spf13/cobra"
 )
+
+func addProfile(rootCmd *cobra.Command) {
+       profileCmd := &cobra.Command{
+               Use:   "profile",
+               Short: "Commands related to profiles",
+               Long:  "Commands help user to list and describe profiles",
+       }
+       subcmd.ConfigProfileListArgs(profileCmd)
+       subcmd.ConfigProfileDiffArgs(profileCmd)
+
+       rootCmd.AddCommand(profileCmd)
+}
diff --git a/pkg/dubboctl/cmd/profile_test.go b/pkg/dubboctl/cmd/profile_test.go
new file mode 100644
index 00000000..db49d4d9
--- /dev/null
+++ b/pkg/dubboctl/cmd/profile_test.go
@@ -0,0 +1,143 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 cmd
+
+import "testing"
+
+func TestProfileList(t *testing.T) {
+       tests := []struct {
+               desc    string
+               cmd     string
+               want    string
+               wantErr bool
+       }{
+               {
+                       desc: "list all profiles provided by dubbo-admin",
+                       cmd:  "profile list",
+                       want: `Dubbo-admin profiles:
+    default
+`,
+               },
+               {
+                       desc: "list all profiles in path specified by user",
+                       cmd:  "profile list --profiles ./testdata/profile",
+                       want: `Dubbo-admin profiles:
+    test0
+    test1
+    test2_wrong_format
+`,
+               },
+               {
+                       desc: "display selected profile",
+                       cmd:  "profile list test0 --profiles 
./testdata/profile",
+                       want: `apiVersion: dubbo.apache.org/v1alpha1
+kind: DubboOperator
+metadata:
+  namespace: dubbo-system
+spec:
+  profile: default
+  namespace: dubbo-system
+  componentsMeta:
+    admin:
+      enabled: true
+    nacos:
+      enabled: true`,
+               },
+               {
+                       desc:    "display selected profile with wrong format",
+                       cmd:     "profile list test2_wrong_format --profiles 
./testdata/profile",
+                       wantErr: true,
+               },
+               {
+                       desc:    "display selected profiles",
+                       cmd:     "profile list test0 test1 --profiles 
./testdata/profile",
+                       wantErr: true,
+               },
+               {
+                       desc: "display profile that does not exist",
+                       cmd:  "profile list test2 --profiles 
./testdata/profile",
+                       want: "",
+               },
+               {
+                       desc:    "list profile directory that does not exist",
+                       cmd:     "profile list --profiles 
./testdata/profile/non_exist",
+                       wantErr: true,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.desc, func(t *testing.T) {
+                       res := testExecute(t, test.cmd, test.wantErr)
+                       if test.want != "" && test.want != res {
+                               t.Errorf("want:\n%s\nbutgot:\n%s\n", test.want, 
res)
+                               return
+                       }
+               })
+       }
+}
+
+func TestProfileDiff(t *testing.T) {
+       tests := []struct {
+               desc    string
+               cmd     string
+               want    string
+               wantErr bool
+       }{
+               {
+                       desc: "show the difference between two profiles 
provided by dubbo-admin",
+                       cmd:  "profile diff default default",
+                       want: `two profiles are identical
+`,
+               },
+               {
+                       desc: "show the difference between two profiled 
specified by user",
+                       cmd:  "profile diff test0 test1 --profiles 
./testdata/profile",
+                       want: ` apiVersion: dubbo.apache.org/v1alpha1
+ kind: DubboOperator
+ metadata:
+   namespace: dubbo-system
+ spec:
+   componentsMeta:
+     admin:
+       enabled: true
+-    nacos:
+-      enabled: true
+   namespace: dubbo-system
+   profile: default
+ `,
+               },
+               {
+                       desc:    "do not specify two profiles",
+                       cmd:     "profile diff test0 --profiles 
./testdata/profile",
+                       wantErr: true,
+               },
+               {
+                       desc:    "diff profiles with wrong format",
+                       cmd:     "profile diff test0 test2_wrong_format 
--profiles ./testdata/profile",
+                       wantErr: true,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.desc, func(t *testing.T) {
+                       res := testExecute(t, test.cmd, test.wantErr)
+                       if test.want != "" && test.want != res {
+                               t.Errorf("want:\n%s\nbutgot:\n%s\n", test.want, 
res)
+                               return
+                       }
+               })
+       }
+}
diff --git a/pkg/dubboctl/cmd/root.go b/pkg/dubboctl/cmd/root.go
index e2ea35ce..c19f8752 100644
--- a/pkg/dubboctl/cmd/root.go
+++ b/pkg/dubboctl/cmd/root.go
@@ -40,4 +40,5 @@ func getRootCmd(args []string) *cobra.Command {
 
 func addSubCommands(rootCmd *cobra.Command) {
        addManifest(rootCmd)
+       addProfile(rootCmd)
 }
diff --git a/pkg/dubboctl/internal/cmd/common.go 
b/pkg/dubboctl/cmd/subcmd/common.go
similarity index 98%
rename from pkg/dubboctl/internal/cmd/common.go
rename to pkg/dubboctl/cmd/subcmd/common.go
index d8de607d..e6e72d83 100644
--- a/pkg/dubboctl/internal/cmd/common.go
+++ b/pkg/dubboctl/cmd/subcmd/common.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cmd
+package subcmd
 
 import "sigs.k8s.io/controller-runtime/pkg/client"
 
diff --git a/pkg/dubboctl/internal/cmd/manifest_diff.go 
b/pkg/dubboctl/cmd/subcmd/manifest_diff.go
similarity index 99%
rename from pkg/dubboctl/internal/cmd/manifest_diff.go
rename to pkg/dubboctl/cmd/subcmd/manifest_diff.go
index 8743d0cc..254dde93 100644
--- a/pkg/dubboctl/internal/cmd/manifest_diff.go
+++ b/pkg/dubboctl/cmd/subcmd/manifest_diff.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cmd
+package subcmd
 
 import (
        "errors"
diff --git a/pkg/dubboctl/internal/cmd/manifest_generate.go 
b/pkg/dubboctl/cmd/subcmd/manifest_generate.go
similarity index 94%
rename from pkg/dubboctl/internal/cmd/manifest_generate.go
rename to pkg/dubboctl/cmd/subcmd/manifest_generate.go
index 063d783a..7904b8c5 100644
--- a/pkg/dubboctl/internal/cmd/manifest_generate.go
+++ b/pkg/dubboctl/cmd/subcmd/manifest_generate.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cmd
+package subcmd
 
 import (
        "fmt"
@@ -135,11 +135,11 @@ func addManifestGenerateFlags(cmd *cobra.Command, args 
*ManifestGenerateArgs) {
 func generateValues(mgArgs *ManifestGenerateArgs) (*v1alpha1.DubboConfig, 
string, error) {
        mergedYaml, profile, err := 
manifest.ReadYamlAndProfile(mgArgs.FileNames, mgArgs.SetFlags)
        if err != nil {
-               return nil, "", fmt.Errorf("generateValues err: %v", err)
+               return nil, "", fmt.Errorf("process user specification failed, 
err: %s", err)
        }
-       profileYaml, err := manifest.ReadProfileYaml(mgArgs.ProfilesPath, 
profile)
+       profileYaml, err := 
manifest.ReadOverlayProfileYaml(mgArgs.ProfilesPath, profile)
        if err != nil {
-               return nil, "", err
+               return nil, "", fmt.Errorf("process profile failed, err: %s", 
err)
        }
        finalYaml, err := util.OverlayYAML(profileYaml, mergedYaml)
        if err != nil {
@@ -147,13 +147,12 @@ func generateValues(mgArgs *ManifestGenerateArgs) 
(*v1alpha1.DubboConfig, string
        }
        finalYaml, err = manifest.OverlaySetFlags(finalYaml, mgArgs.SetFlags)
        if err != nil {
-               return nil, "", err
+               return nil, "", fmt.Errorf("process set flags failed, err: %s", 
err)
        }
        cfg := &v1alpha1.DubboConfig{}
        if err := yaml.Unmarshal([]byte(finalYaml), cfg); err != nil {
-               return nil, "", err
+               return nil, "", fmt.Errorf("set flags specification is wrong, 
err: %s", err)
        }
-       // todo: validate op
        // we should ensure that Components field would not be nil
        if cfg.Spec.Components == nil {
                cfg.Spec.Components = &v1alpha1.DubboComponentsSpec{}
diff --git a/pkg/dubboctl/internal/cmd/manifest_install.go 
b/pkg/dubboctl/cmd/subcmd/manifest_install.go
similarity index 99%
rename from pkg/dubboctl/internal/cmd/manifest_install.go
rename to pkg/dubboctl/cmd/subcmd/manifest_install.go
index 2d3e3c6c..23aad1d4 100644
--- a/pkg/dubboctl/internal/cmd/manifest_install.go
+++ b/pkg/dubboctl/cmd/subcmd/manifest_install.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cmd
+package subcmd
 
 import (
        
"github.com/apache/dubbo-admin/pkg/dubboctl/internal/apis/dubbo.apache.org/v1alpha1"
diff --git a/pkg/dubboctl/internal/cmd/manifest_uninstall.go 
b/pkg/dubboctl/cmd/subcmd/manifest_uninstall.go
similarity index 99%
rename from pkg/dubboctl/internal/cmd/manifest_uninstall.go
rename to pkg/dubboctl/cmd/subcmd/manifest_uninstall.go
index 12ea9304..a84de755 100644
--- a/pkg/dubboctl/internal/cmd/manifest_uninstall.go
+++ b/pkg/dubboctl/cmd/subcmd/manifest_uninstall.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cmd
+package subcmd
 
 import (
        
"github.com/apache/dubbo-admin/pkg/dubboctl/internal/apis/dubbo.apache.org/v1alpha1"
diff --git a/pkg/dubboctl/cmd/subcmd/profile_diff.go 
b/pkg/dubboctl/cmd/subcmd/profile_diff.go
new file mode 100644
index 00000000..b093da9a
--- /dev/null
+++ b/pkg/dubboctl/cmd/subcmd/profile_diff.go
@@ -0,0 +1,101 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 subcmd
+
+import (
+       "errors"
+       "fmt"
+       "github.com/apache/dubbo-admin/pkg/dubboctl/identifier"
+       "github.com/apache/dubbo-admin/pkg/dubboctl/internal/kube"
+       "github.com/apache/dubbo-admin/pkg/dubboctl/internal/manifest"
+       "github.com/apache/dubbo-admin/pkg/logger"
+       "github.com/spf13/cobra"
+       "go.uber.org/zap/zapcore"
+)
+
+type ProfileDiffArgs struct {
+       ProfilesPath string
+}
+
+func (pda *ProfileDiffArgs) setDefault() {
+       if pda == nil {
+               return
+       }
+       if pda.ProfilesPath == "" {
+               pda.ProfilesPath = identifier.Profiles
+       }
+}
+
+func ConfigProfileDiffArgs(baseCmd *cobra.Command) {
+       pdArgs := &ProfileDiffArgs{}
+       pdCmd := &cobra.Command{
+               Use:   "diff",
+               Short: "Show the difference between two profiles",
+               Example: `  # show the difference between two profiles provided 
by dubbo-admin
+  dubboctl profile diff default demo
+
+  # show the difference between two profiles specified by user
+  dubboctl profile diff profile_you_specify0 profile_you_specify1 --profiles 
/path/to/profiles
+`,
+               Args: func(cmd *cobra.Command, args []string) error {
+                       if len(args) != 2 {
+                               return errors.New("profile diff needs two 
profiles")
+                       }
+                       return nil
+               },
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       logger.InitCmdSugar(zapcore.AddSync(cmd.OutOrStdout()))
+                       pdArgs.setDefault()
+                       if err := profileDiffCmd(pdArgs, args); err != nil {
+                               return err
+                       }
+                       return nil
+               },
+       }
+       pdCmd.PersistentFlags().StringVarP(&pdArgs.ProfilesPath, "profiles", 
"", "",
+               "Path to profiles directory, this directory contains preset 
profiles")
+
+       baseCmd.AddCommand(pdCmd)
+}
+
+func profileDiffCmd(pdArgs *ProfileDiffArgs, args []string) error {
+       profileA, err := manifest.ReadProfileYaml(pdArgs.ProfilesPath, args[0])
+       if err != nil {
+               return fmt.Errorf("parse %s profile failed, err: %s", args[0], 
err)
+       }
+       profileB, err := manifest.ReadProfileYaml(pdArgs.ProfilesPath, args[1])
+       if err != nil {
+               return fmt.Errorf("parse %s profile failed, err: %s", args[1], 
err)
+       }
+       objA, err := kube.ParseObjectFromManifest(profileA)
+       if err != nil {
+               return fmt.Errorf("parse %s profile to k8s object failed, err: 
%s", args[0], err)
+       }
+       objB, err := kube.ParseObjectFromManifest(profileB)
+       if err != nil {
+               return fmt.Errorf("parse %s profile to k8s object failed, err: 
%s", args[1], err)
+       }
+       diff, err := kube.CompareObject(objA, objB)
+       if err != nil {
+               return fmt.Errorf("compare failed, err: %s", err)
+       }
+       if diff != "" {
+               logger.CmdSugar().Print(diff)
+       } else {
+               logger.CmdSugar().Print("two profiles are identical\n")
+       }
+       return nil
+}
diff --git a/pkg/dubboctl/cmd/subcmd/profile_list.go 
b/pkg/dubboctl/cmd/subcmd/profile_list.go
new file mode 100644
index 00000000..5c0ce938
--- /dev/null
+++ b/pkg/dubboctl/cmd/subcmd/profile_list.go
@@ -0,0 +1,105 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 subcmd
+
+import (
+       "errors"
+       "github.com/apache/dubbo-admin/pkg/dubboctl/identifier"
+       "github.com/apache/dubbo-admin/pkg/dubboctl/internal/manifest"
+       "github.com/apache/dubbo-admin/pkg/dubboctl/internal/util"
+       "github.com/apache/dubbo-admin/pkg/logger"
+       "github.com/spf13/cobra"
+       "go.uber.org/zap/zapcore"
+       "strings"
+)
+
+type ProfileListArgs struct {
+       ProfilesPath string
+}
+
+func (pla *ProfileListArgs) setDefault() {
+       if pla == nil {
+               return
+       }
+       if pla.ProfilesPath == "" {
+               pla.ProfilesPath = identifier.Profiles
+       }
+}
+
+func ConfigProfileListArgs(baseCmd *cobra.Command) {
+       plArgs := &ProfileListArgs{}
+       plCmd := &cobra.Command{
+               Use:   "list",
+               Short: "List all existing profiles specification",
+               Example: `  # list all profiles provided by dubbo-admin
+  dubboctl profile list
+
+  # list all profiles in path specified by user
+  dubboctl profile list --profiles /path/to/profiles
+
+  # display selected profile
+  dubboctl profile list default
+`,
+               Args: func(cmd *cobra.Command, args []string) error {
+                       if len(args) > 1 {
+                               return errors.New("profile list doesn't support 
multi profile")
+                       }
+                       return nil
+               },
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       logger.InitCmdSugar(zapcore.AddSync(cmd.OutOrStdout()))
+                       plArgs.setDefault()
+                       if err := profileListCmd(plArgs, args); err != nil {
+                               return err
+                       }
+                       return nil
+               },
+       }
+       plCmd.PersistentFlags().StringVarP(&plArgs.ProfilesPath, "profiles", 
"", "",
+               "Path to profiles directory, this directory contains preset 
profiles")
+
+       baseCmd.AddCommand(plCmd)
+}
+
+func profileListCmd(plArgs *ProfileListArgs, args []string) error {
+       profiles, err := manifest.ReadProfilesNames(plArgs.ProfilesPath)
+       if err != nil {
+               return err
+       }
+       // list all profiles
+       if len(args) == 0 {
+               var resBuilder strings.Builder
+               resBuilder.WriteString("Dubbo-admin profiles:\n")
+               for _, profile := range profiles {
+                       resBuilder.WriteString("    " + profile + "\n")
+               }
+               logger.CmdSugar().Print(resBuilder.String())
+               return nil
+       }
+
+       for _, profile := range profiles {
+               if profile == args[0] {
+                       res, err := 
manifest.ReadProfileYaml(plArgs.ProfilesPath, profile)
+                       if err != nil {
+                               return err
+                       }
+                       logger.CmdSugar().Print(util.ApplyFilters(res, 
util.LicenseFilter, util.SpaceFilter))
+                       return nil
+               }
+       }
+
+       return nil
+}
diff --git a/pkg/dubboctl/cmd/testdata/profile/test0.yaml 
b/pkg/dubboctl/cmd/testdata/profile/test0.yaml
new file mode 100644
index 00000000..7513bd0d
--- /dev/null
+++ b/pkg/dubboctl/cmd/testdata/profile/test0.yaml
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: dubbo.apache.org/v1alpha1
+kind: DubboOperator
+metadata:
+  namespace: dubbo-system
+spec:
+  profile: default
+  namespace: dubbo-system
+  componentsMeta:
+    admin:
+      enabled: true
+    nacos:
+      enabled: true
\ No newline at end of file
diff --git a/pkg/dubboctl/cmd/testdata/profile/test1.yaml 
b/pkg/dubboctl/cmd/testdata/profile/test1.yaml
new file mode 100644
index 00000000..030a7821
--- /dev/null
+++ b/pkg/dubboctl/cmd/testdata/profile/test1.yaml
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: dubbo.apache.org/v1alpha1
+kind: DubboOperator
+metadata:
+  namespace: dubbo-system
+spec:
+  profile: default
+  namespace: dubbo-system
+  componentsMeta:
+    admin:
+      enabled: true
\ No newline at end of file
diff --git a/pkg/dubboctl/cmd/testdata/profile/test2_wrong_format.yaml 
b/pkg/dubboctl/cmd/testdata/profile/test2_wrong_format.yaml
new file mode 100644
index 00000000..7d7a52f4
--- /dev/null
+++ b/pkg/dubboctl/cmd/testdata/profile/test2_wrong_format.yaml
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: dubbo.apache.org/v1alpha1
+kind: DubboOperator
+metadata:
+  namespace: dubbo-system
+spec:
+  profile: default
+  namespace: dubbo-system
+  componentsMeta:
+    admin:
+      enabled: right
\ No newline at end of file
diff --git a/pkg/dubboctl/internal/kube/client.go 
b/pkg/dubboctl/internal/kube/client.go
index 07080749..876be155 100644
--- a/pkg/dubboctl/internal/kube/client.go
+++ b/pkg/dubboctl/internal/kube/client.go
@@ -230,7 +230,6 @@ func (cli *CtlClient) deleteNamespace(ns string) error {
                }
                return nil
        } else {
-               // todo: learn how to use deleteOption
                if err := cli.Delete(context.Background(), nsObj); err != nil {
                        return fmt.Errorf("failed to delete namespace: %s, err: 
%s", ns, err)
                }
diff --git a/pkg/dubboctl/internal/kube/object.go 
b/pkg/dubboctl/internal/kube/object.go
index 5d78b174..2f0fab11 100644
--- a/pkg/dubboctl/internal/kube/object.go
+++ b/pkg/dubboctl/internal/kube/object.go
@@ -221,3 +221,12 @@ func CompareObjects(objsA, objsB Objects) (string, string, 
string) {
 
        return diffRes.String(), addRes.String(), errRes.String()
 }
+
+// CompareObject compares two objects and returns diff.
+func CompareObject(objA, objB *Object) (string, error) {
+       diff, err := util.DiffYAML(objA.YAML(), objB.YAML())
+       if err != nil {
+               return "", err
+       }
+       return diff, nil
+}
diff --git a/pkg/dubboctl/internal/kube/object_test.go 
b/pkg/dubboctl/internal/kube/object_test.go
index 3fa7dc93..cf255046 100644
--- a/pkg/dubboctl/internal/kube/object_test.go
+++ b/pkg/dubboctl/internal/kube/object_test.go
@@ -16,6 +16,7 @@
 package kube
 
 import (
+       "strings"
        "testing"
 
        "github.com/stretchr/testify/assert"
@@ -294,3 +295,59 @@ error converting YAML to JSON: yaml: line 2: mapping 
values are not allowed in t
                }
        }
 }
+
+func TestCompareObject(t *testing.T) {
+       tests := []struct {
+               desc    string
+               objA    *Object
+               objB    *Object
+               diff    string
+               wantErr bool
+       }{
+               {
+                       desc: "objects could be parsed correctly",
+                       objA: &Object{
+                               yamlStr: `key1: val1
+key2: val2`,
+                       },
+                       objB: &Object{
+                               yamlStr: `key1: val1
+key2: val3`,
+                       },
+                       diff: `key1: val1
+-key2: val2
++key2: val3`,
+               },
+               {
+                       desc: "objects with wrong format",
+                       objA: &Object{
+                               yamlStr: `key1: val1
+  key2: val2`,
+                       },
+                       objB: &Object{
+                               yamlStr: `key1: val1
+key2: val3`,
+                       },
+                       wantErr: true,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.desc, func(t *testing.T) {
+                       diff, err := CompareObject(test.objA, test.objB)
+                       if err != nil {
+                               if !test.wantErr {
+                                       t.Errorf("execute failed, err: %s", err)
+                               }
+                       } else {
+                               if test.wantErr {
+                                       t.Errorf("execution expected to fail, 
but succeed")
+                               } else {
+                                       if strings.TrimSpace(diff) != test.diff 
{
+                                               t.Errorf("want:\n%s\nbut 
got:\n%s", test.diff, diff)
+                                       }
+                               }
+                       }
+               })
+       }
+}
diff --git a/pkg/dubboctl/internal/manifest/common.go 
b/pkg/dubboctl/internal/manifest/common.go
index eee109b7..c5f29cdb 100644
--- a/pkg/dubboctl/internal/manifest/common.go
+++ b/pkg/dubboctl/internal/manifest/common.go
@@ -27,12 +27,19 @@ import (
        "sigs.k8s.io/yaml"
 )
 
-func ReadProfileYaml(profilePath string, profile string) (string, error) {
+func ReadOverlayProfileYaml(profilePath string, profile string) (string, 
error) {
        filePath := path.Join(profilePath, profile+".yaml")
-       out, err := ReadAndOverlayYamls([]string{filePath})
+       defaultPath := path.Join(profilePath, "default.yaml")
+       // overlay selected profile ont default profile
+       out, err := ReadAndOverlayYamls([]string{defaultPath, filePath})
        if err != nil {
                return "", err
        }
+       // unmarshal and validate
+       tempOp := &v1alpha1.DubboConfig{}
+       if err := yaml.Unmarshal([]byte(out), tempOp); err != nil {
+               return "", fmt.Errorf("%s profile on default profile is wrong, 
err: %s", profile, err)
+       }
        return out, nil
 }
 
@@ -44,7 +51,7 @@ func ReadYamlAndProfile(filenames []string, setFlags 
[]string) (string, string,
        // unmarshal and validate
        tempOp := &v1alpha1.DubboConfig{}
        if err := yaml.Unmarshal([]byte(mergedYaml), tempOp); err != nil {
-               return "", "", fmt.Errorf("ReadYamlAndProfile failed, err: %s", 
err)
+               return "", "", fmt.Errorf("user specification is wrong, err: 
%s", err)
        }
        // get profile field and overlay with setFlags
        profile := "default"
@@ -95,3 +102,39 @@ func OverlaySetFlags(base string, setFlags []string) 
(string, error) {
        }
        return string(finalYaml), nil
 }
+
+// ReadProfilesNames reads all profiles in directory specified by profilesPath.
+// It does not traverse recursively.
+// It may add some filters in the future.
+func ReadProfilesNames(profilesPath string) ([]string, error) {
+       var res []string
+       dir, err := os.ReadDir(profilesPath)
+       if err != nil {
+               return nil, err
+       }
+       yamlSuffix := ".yaml"
+       for _, file := range dir {
+               if file.IsDir() {
+                       continue
+               }
+               if strings.HasSuffix(file.Name(), yamlSuffix) {
+                       res = append(res, strings.TrimSuffix(file.Name(), 
".yaml"))
+               }
+       }
+       return res, nil
+}
+
+// ReadProfileYaml reads profile yaml specified by profilePath/profile.yaml 
and validates the content.
+func ReadProfileYaml(profilePath string, profile string) (string, error) {
+       filePath := path.Join(profilePath, profile+".yaml")
+       out, err := ReadAndOverlayYamls([]string{filePath})
+       if err != nil {
+               return "", err
+       }
+       // unmarshal and validate
+       tempOp := &v1alpha1.DubboConfig{}
+       if err := yaml.Unmarshal([]byte(out), tempOp); err != nil {
+               return "", fmt.Errorf("%s profile is wrong, err: %s", profile, 
err)
+       }
+       return out, nil
+}
diff --git 
a/pkg/dubboctl/internal/operator/testdata/want/nacos_component-render_manifest.golden.yaml
 
b/pkg/dubboctl/internal/operator/testdata/want/nacos_component-render_manifest.golden.yaml
index 01a04703..a272fbb0 100644
--- 
a/pkg/dubboctl/internal/operator/testdata/want/nacos_component-render_manifest.golden.yaml
+++ 
b/pkg/dubboctl/internal/operator/testdata/want/nacos_component-render_manifest.golden.yaml
@@ -62,7 +62,9 @@ spec:
               name: raft-rpc
             - containerPort: 7848
               name: old-raft-rpc
-          resources: null
+          resources:
+            limits: {}
+            requests: {}
           startupProbe:
             httpGet:
               path: /nacos/v1/console/health/readiness
@@ -84,7 +86,6 @@ spec:
       volumes:
         - emptyDir: {}
           name: data
-
 ---
 apiVersion: v1
 kind: Service
@@ -116,5 +117,4 @@ spec:
   selector:
     app.kubernetes.io/name: nacos
   type: NodePort
-
 ---
diff --git a/pkg/logger/log.go b/pkg/logger/log.go
index 197a4e21..928e7907 100644
--- a/pkg/logger/log.go
+++ b/pkg/logger/log.go
@@ -25,10 +25,9 @@ import (
 )
 
 var (
-       mutex      = &sync.Mutex{}
-       hasInit    = false
-       cmdHasInit = false
-       encoder    = zapcore.NewConsoleEncoder(
+       mutex   = &sync.Mutex{}
+       hasInit = false
+       encoder = zapcore.NewConsoleEncoder(
                zapcore.EncoderConfig{
                        MessageKey:     "msg",
                        LevelKey:       "level",
@@ -80,10 +79,6 @@ func Init() {
 func InitCmdSugar(ws zapcore.WriteSyncer) {
        mutex.Lock()
        defer mutex.Unlock()
-       if cmdHasInit {
-               return
-       }
-       cmdHasInit = true
 
        core := zapcore.NewCore(encoder, ws, zap.DebugLevel)
        cmdLogger = zap.New(core)


Reply via email to