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

zhongxjian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git


The following commit(s) were added to refs/heads/master by this push:
     new d794962a [dubboctl] Update create logic (#540)
d794962a is described below

commit d794962aba7d561d3de4a94fc5e077d4d52032c6
Author: Jian Zhong <[email protected]>
AuthorDate: Tue Jan 14 20:03:31 2025 +0800

    [dubboctl] Update create logic (#540)
---
 dubboctl/cmd/create.go                             |  49 ++-
 dubboctl/pkg/filesystem/filesystem.go              | 376 +++++++++++++++++++++
 dubboctl/pkg/sdk/client.go                         | 220 ++----------
 dubboctl/pkg/sdk/dubbo/config.go                   |  63 ++++
 dubboctl/pkg/sdk/template.go                       |  20 ++
 dubboctl/pkg/sdk/templates.go                      |   5 +
 .../pkg/template/go/{common => sample}/.gitignore  |   0
 .../template/go/{common => sample}/api/api.pb.go   |   0
 .../template/go/{common => sample}/api/api.proto   |   0
 .../go/{common => sample}/api/api_triple.pb.go     |   0
 .../pkg/template/go/{common => sample}/cmd/app.go  |   0
 .../go/{common => sample}/conf/dubbogo.yaml        |   0
 dubboctl/pkg/template/go/{common => sample}/go.mod |   0
 dubboctl/pkg/template/go/{common => sample}/go.sum |   0
 .../go/{common => sample}/pkg/service/service.go   |   0
 .../template/java/{common => sample}/.gitignore    |   0
 .../pkg/template/java/{common => sample}/pom.xml   |   0
 .../java/com/example/demo/DemoApplication.java     |   0
 .../example/demo/demos/web/BasicController.java    |   0
 .../demo/demos/web/PathVariableController.java     |   0
 .../main/java/com/example/demo/demos/web/User.java |   0
 .../com/example/demo/dubbo/api/DemoService.java    |   0
 .../com/example/demo/dubbo/consumer/Consumer.java  |   0
 .../demo/dubbo/service/DemoServiceImpl.java        |   0
 .../src/main/resources/application.yml             |   0
 .../src/main/resources/log4j.properties            |   0
 .../src/main/resources/static/index.html           |   0
 .../com/example/demo/DemoApplicationTests.java     |   0
 go.mod                                             |  13 +-
 go.sum                                             |  13 +
 30 files changed, 552 insertions(+), 207 deletions(-)

diff --git a/dubboctl/cmd/create.go b/dubboctl/cmd/create.go
index 32883f06..11e135b7 100644
--- a/dubboctl/cmd/create.go
+++ b/dubboctl/cmd/create.go
@@ -3,9 +3,12 @@ package cmd
 import (
        "fmt"
        "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
        "github.com/apache/dubbo-kubernetes/operator/cmd/cluster"
        "github.com/apache/dubbo-kubernetes/operator/pkg/util/clog"
        "github.com/apache/dubbo-kubernetes/pkg/kube"
+       "github.com/ory/viper"
        "github.com/spf13/cobra"
        "os"
        "path/filepath"
@@ -75,16 +78,54 @@ func sdkGenerateCmd(ctx cli.Context, _ *cluster.RootArgs, 
tempArgs *templateArgs
 }
 
 type createArgs struct {
-       dirname string
-       path    string
-       create  string
+       Path       string
+       Runtime    string
+       Template   string
+       Name       string
+       Initialzed bool
 }
 
 func runCreate(kc kube.CLIClient, tempArgs *templateArgs, cl clog.Logger) 
error {
+       dcfg, err := newCreate(kc, tempArgs, cl)
+       if err != nil {
+               return err
+       }
+       var newClient sdk.ClientFactory
+       var cmd *cobra.Command
+       client, cancel := newClient()
+       defer cancel()
+       _, err = client.Initialize(&dubbo.DubboConfig{
+               Root:     dcfg.Path,
+               Name:     dcfg.Name,
+               Runtime:  dcfg.Runtime,
+               Template: dcfg.Template,
+       }, dcfg.Initialzed, cmd)
+       if err != nil {
+               return err
+       }
+       fmt.Fprintf(cmd.OutOrStderr(), "Created %v dubbo application in %v\n", 
dcfg.Runtime, dcfg.Path)
        return nil
 }
 
-func newCreate(kc kube.CLIClient, tempArgs *templateArgs, cl clog.Logger) 
(createArgs, error) {
+func newCreate(kc kube.CLIClient, tempArgs *templateArgs, cl clog.Logger) 
(dcfg createArgs, err error) {
+       var (
+               path         string
+               dirName      string
+               absolutePath string
+       )
+       dirName, absolutePath = deriveNameAndAbsolutePathFromPath(path)
+
+       dcfg = createArgs{
+               Path:     absolutePath,
+               Runtime:  viper.GetString("language"),
+               Template: viper.GetString("template"),
+               Name:     dirName,
+       }
+
+       fmt.Printf("Path:         %v\n", dcfg.Path)
+       fmt.Printf("Language:     %v\n", dcfg.Runtime)
+       fmt.Printf("Template:     %v\n", dcfg.Template)
+
        return createArgs{}, nil
 }
 
diff --git a/dubboctl/pkg/filesystem/filesystem.go 
b/dubboctl/pkg/filesystem/filesystem.go
new file mode 100644
index 00000000..38172059
--- /dev/null
+++ b/dubboctl/pkg/filesystem/filesystem.go
@@ -0,0 +1,376 @@
+package filesystem
+
+import (
+       "archive/zip"
+       "embed"
+       "errors"
+       "fmt"
+       "io"
+       "io/fs"
+       "net/url"
+       "os"
+       "path"
+       "path/filepath"
+       "strings"
+)
+
+import (
+       billy "github.com/go-git/go-billy/v5"
+)
+
+// Filesystems
+// Wrap the implementations of FS with their subtle differences into the
+// common interface for accessing template files defined herein.
+// os:    standard for on-disk extensible template repositories.
+// zip:   embedded filesystem backed by the byte array representing zipfile.
+// billy: go-git library's filesystem used for remote git template repos.
+type Filesystem interface {
+       fs.ReadDirFS
+       fs.StatFS
+       Readlink(link string) (string, error)
+}
+
+type zipFS struct {
+       archive *zip.Reader
+}
+
+func NewZipFS(archive *zip.Reader) zipFS {
+       return zipFS{archive: archive}
+}
+
+func (z zipFS) Open(name string) (fs.File, error) {
+       return z.archive.Open(name)
+}
+
+func (z zipFS) ReadDir(name string) ([]fs.DirEntry, error) {
+       var dirEntries []fs.DirEntry
+       for _, file := range z.archive.File {
+               cleanName := strings.TrimRight(file.Name, "/")
+               if path.Dir(cleanName) == name {
+                       f, err := z.archive.Open(cleanName)
+                       if err != nil {
+                               return nil, err
+                       }
+                       fi, err := f.Stat()
+                       if err != nil {
+                               return nil, err
+                       }
+                       dirEntries = append(dirEntries, dirEntry{FileInfo: fi})
+               }
+       }
+       return dirEntries, nil
+}
+
+func (z zipFS) Readlink(link string) (string, error) {
+       f, err := z.archive.Open(link)
+       if err != nil {
+               return "", err
+       }
+       defer f.Close()
+
+       fi, err := f.Stat()
+       if err != nil {
+               return "", err
+       }
+
+       if fi.Mode()&fs.ModeSymlink == 0 {
+               return "", &fs.PathError{Op: "readlink", Path: link, Err: 
fs.ErrInvalid}
+       }
+
+       bs, err := io.ReadAll(f)
+       if err != nil {
+               return "", err
+       }
+
+       return string(bs), nil
+}
+
+func (z zipFS) Stat(name string) (fs.FileInfo, error) {
+       f, err := z.archive.Open(name)
+       if err != nil {
+               return nil, err
+       }
+       return f.Stat()
+}
+
+// BillyFilesystem is a template file accessor backed by a billy FS
+type BillyFilesystem struct{ fs billy.Filesystem }
+
+func NewBillyFilesystem(fs billy.Filesystem) BillyFilesystem {
+       return BillyFilesystem{fs: fs}
+}
+
+func (b BillyFilesystem) Readlink(link string) (string, error) {
+       return b.fs.Readlink(link)
+}
+
+type bfsFile struct {
+       billy.File
+       stats func() (fs.FileInfo, error)
+}
+
+func (b bfsFile) Stat() (fs.FileInfo, error) {
+       return b.stats()
+}
+
+func (b BillyFilesystem) Open(name string) (fs.File, error) {
+       f, err := b.fs.Open(name)
+       if err != nil {
+               return nil, err
+       }
+       return bfsFile{
+               File: f,
+               stats: func() (fs.FileInfo, error) {
+                       return b.fs.Stat(name)
+               },
+       }, nil
+}
+
+type dirEntry struct {
+       fs.FileInfo
+}
+
+func (d dirEntry) Type() fs.FileMode {
+       return d.Mode().Type()
+}
+
+func (d dirEntry) Info() (fs.FileInfo, error) {
+       return d, nil
+}
+
+func (b BillyFilesystem) ReadDir(name string) ([]fs.DirEntry, error) {
+       fis, err := b.fs.ReadDir(name)
+       if err != nil {
+               return nil, err
+       }
+       des := make([]fs.DirEntry, len(fis))
+       for i, fi := range fis {
+               des[i] = dirEntry{fi}
+       }
+       return des, nil
+}
+
+func (b BillyFilesystem) Stat(name string) (fs.FileInfo, error) {
+       return b.fs.Lstat(name)
+}
+
+// osFilesystem is a template file accessor backed by the os.
+type osFilesystem struct{ root string }
+
+func NewOsFilesystem(root string) osFilesystem {
+       return osFilesystem{root: root}
+}
+
+func (o osFilesystem) Open(name string) (fs.File, error) {
+       name = filepath.FromSlash(name)
+       return os.Open(filepath.Join(o.root, name))
+}
+
+func (o osFilesystem) ReadDir(name string) ([]fs.DirEntry, error) {
+       name = filepath.FromSlash(name)
+       return os.ReadDir(filepath.Join(o.root, name))
+}
+
+func (o osFilesystem) Stat(name string) (fs.FileInfo, error) {
+       name = filepath.FromSlash(name)
+       return os.Lstat(filepath.Join(o.root, name))
+}
+
+func (o osFilesystem) Readlink(link string) (string, error) {
+       link = filepath.FromSlash(link)
+       return os.Readlink(filepath.Join(o.root, link))
+}
+
+// subFS exposes subdirectory of underlying FS, this is similar to `chroot`.
+type subFS struct {
+       root string
+       fs   Filesystem
+}
+
+func NewSubFS(root string, fs Filesystem) subFS {
+       return subFS{root: root, fs: fs}
+}
+
+func (o subFS) Open(name string) (fs.File, error) {
+       return o.fs.Open(path.Join(o.root, name))
+}
+
+func (o subFS) ReadDir(name string) ([]fs.DirEntry, error) {
+       return o.fs.ReadDir(path.Join(o.root, name))
+}
+
+func (o subFS) Stat(name string) (fs.FileInfo, error) {
+       return o.fs.Stat(path.Join(o.root, name))
+}
+
+func (o subFS) Readlink(link string) (string, error) {
+       return o.fs.Readlink(path.Join(o.root, link))
+}
+
+type maskingFS struct {
+       masked func(path string) bool
+       fs     Filesystem
+}
+
+func NewMaskingFS(masked func(path string) bool, fs Filesystem) maskingFS {
+       return maskingFS{masked: masked, fs: fs}
+}
+
+func (m maskingFS) Open(name string) (fs.File, error) {
+       if m.masked(name) {
+               return nil, &fs.PathError{Op: "open", Path: name, Err: 
fs.ErrNotExist}
+       }
+       return m.fs.Open(name)
+}
+
+func (m maskingFS) ReadDir(name string) ([]fs.DirEntry, error) {
+       if m.masked(name) {
+               return nil, &fs.PathError{Op: "readdir", Path: name, Err: 
fs.ErrNotExist}
+       }
+       des, err := m.fs.ReadDir(name)
+       if err != nil {
+               return nil, err
+       }
+       result := make([]fs.DirEntry, 0, len(des))
+       for _, de := range des {
+               if !m.masked(path.Join(name, de.Name())) {
+                       result = append(result, de)
+               }
+       }
+       return result, nil
+}
+
+func (m maskingFS) Stat(name string) (fs.FileInfo, error) {
+       if m.masked(name) {
+               return nil, &fs.PathError{Op: "stat", Path: name, Err: 
fs.ErrNotExist}
+       }
+       return m.fs.Stat(name)
+}
+
+func (m maskingFS) Readlink(link string) (string, error) {
+       if m.masked(link) {
+               return "", &fs.PathError{Op: "readlink", Path: link, Err: 
fs.ErrNotExist}
+       }
+       return m.fs.Readlink(link)
+}
+
+const (
+       EmbedSchema = "embed"
+)
+
+// UnionFS is an os.FS and embed.FS union fs.
+// Files in embed.FS has the header "embed://", and files in os.FS don't have 
this header.
+type UnionFS struct {
+       embedFS embed.FS
+}
+
+func NewUnionFS(embedFS embed.FS) UnionFS {
+       return UnionFS{
+               embedFS: embedFS,
+       }
+}
+
+func (u UnionFS) ReadFile(name string) ([]byte, error) {
+       uri, _ := url.Parse(name)
+       if uri != nil && uri.Scheme == EmbedSchema {
+               name = strings.TrimLeft(uri.Opaque, "/")
+               return u.embedFS.ReadFile(name)
+       }
+
+       return os.ReadFile(name)
+}
+
+func (u UnionFS) Open(name string) (fs.File, error) {
+       uri, _ := url.Parse(name)
+       if uri != nil && uri.Scheme == EmbedSchema {
+               name = strings.TrimLeft(uri.Opaque, "/")
+               return u.embedFS.Open(name)
+       }
+
+       return os.Open(name)
+}
+
+func (u UnionFS) ReadDir(name string) ([]fs.DirEntry, error) {
+       uri, _ := url.Parse(name)
+       if uri != nil && uri.Scheme == EmbedSchema {
+               name = strings.TrimLeft(uri.Opaque, "/")
+               return u.embedFS.ReadDir(name)
+       }
+
+       return os.ReadDir(name)
+}
+
+func (u UnionFS) Stat(name string) (fs.FileInfo, error) {
+       f, err := u.Open(name)
+       if err != nil {
+               return nil, err
+       }
+
+       return f.Stat()
+}
+
+func (u UnionFS) Readlink(link string) (string, error) {
+       uri, _ := url.Parse(link)
+       if uri != nil && uri.Scheme == EmbedSchema {
+               return "", errors.New("embed FS not support read link")
+       }
+
+       return os.Readlink(link)
+}
+
+// CopyFromFS copies files from the `src` dir on the accessor Filesystem to 
local filesystem into `dest` dir.
+// The src path uses slashes as their separator.
+// The dest path uses OS specific separator.
+func CopyFromFS(root, dest string, fsys Filesystem) (err error) {
+       // Walks the filesystem rooted at root.
+       return fs.WalkDir(fsys, root, func(path string, de fs.DirEntry, err 
error) error {
+               if err != nil {
+                       return err
+               }
+
+               p, err := filepath.Rel(filepath.FromSlash(root), 
filepath.FromSlash(path))
+               if err != nil {
+                       return err
+               }
+
+               dest := filepath.Join(dest, p)
+
+               switch {
+               case de.IsDir():
+                       // Ideally we should use the file mode of the src node
+                       // but it seems the git module is reporting directories
+                       // as 0644 instead of 0755. For now, just do it this 
way.
+                       // See https://github.com/go-git/go-git/issues/364
+                       // Upon resolution, return accessor.Stat(src).Mode()
+                       return os.MkdirAll(dest, 0o755)
+               case de.Type()&fs.ModeSymlink != 0:
+                       var symlinkTarget string
+                       symlinkTarget, err = fsys.Readlink(path)
+                       if err != nil {
+                               return err
+                       }
+                       return os.Symlink(symlinkTarget, dest)
+               case de.Type().IsRegular():
+                       fi, err := de.Info()
+                       if err != nil {
+                               return err
+                       }
+                       destFile, err := os.OpenFile(dest, 
os.O_RDWR|os.O_CREATE|os.O_TRUNC, fi.Mode())
+                       if err != nil {
+                               return err
+                       }
+                       defer destFile.Close()
+
+                       srcFile, err := fsys.Open(path)
+                       if err != nil {
+                               return err
+                       }
+                       defer srcFile.Close()
+
+                       _, err = io.Copy(destFile, srcFile)
+                       return err
+               default:
+                       return fmt.Errorf("unsuported file type: %s", 
de.Type().String())
+               }
+       })
+}
diff --git a/dubboctl/pkg/sdk/client.go b/dubboctl/pkg/sdk/client.go
index ead010a6..f2968576 100644
--- a/dubboctl/pkg/sdk/client.go
+++ b/dubboctl/pkg/sdk/client.go
@@ -2,132 +2,38 @@ package sdk
 
 import (
        "bufio"
-       "context"
        "fmt"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
        "github.com/pkg/errors"
        "github.com/spf13/cobra"
-       "gopkg.in/yaml.v3"
        "os"
        "path/filepath"
+       "sigs.k8s.io/yaml"
        "strings"
        "time"
 )
 
-const (
-       DubboFile       = "dubbo.yaml"
-       RunDataDir      = ".dubbo"
-       DefaultTemplate = "common"
-)
-
 type Client struct {
        templates *Templates
 }
-type Option func(*Client)
-
-func New(options ...Option) *Client {
-       c := &Client{}
-       for _, o := range options {
-               o(c)
-       }
-
-       c.templates = newTemplates(c)
-
-       return c
-}
-
-func newTemplates(client *Client) *Templates {
-       return &Templates{client: client}
-}
-
-type Templates struct {
-       client *Client
-}
-
-type DubboConfig struct {
-       Root     string    `yaml:"-"`
-       Name     string    `yaml:"name,omitempty" 
jsonschema:"pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"`
-       Runtime  string    `yaml:"runtime,omitempty"`
-       Template string    `yaml:"template,omitempty"`
-       Created  time.Time `yaml:"created,omitempty"`
-}
-
-func (f *DubboConfig) Validate() error {
-       if f.Root == "" {
-               return errors.New("dubbo root path is required")
-       }
-
-       var ctr int
-       errs := [][]string{
-               validateOptions(),
-       }
-
-       var b strings.Builder
-       b.WriteString(fmt.Sprintf("'%v' contains errors:", DubboFile))
-
-       for _, ee := range errs {
-               if len(ee) > 0 {
-                       b.WriteString("\n") // Precede each group of errors 
with a linebreak
-               }
-               for _, e := range ee {
-                       ctr++
-                       b.WriteString("\t" + e)
-               }
-       }
 
-       if ctr == 0 {
-               return nil // Return nil if there were no validation errors.
-       }
+type Option func(*Client)
 
-       return errors.New(b.String())
-}
-
-func validateOptions() []string {
-       return nil
-}
+type ClientFactory func(...Option) (*Client, func())
 
 func (c *Client) Templates() *Templates {
        return c.templates
 }
 
-func (f *DubboConfig) Write() (err error) {
-       if err = f.Validate(); err != nil {
-               return
-       }
-       dubboyamlpath := filepath.Join(f.Root, DubboFile)
-       var dubbobytes []byte
-       if dubbobytes, err = yaml.Marshal(f); err != nil {
-               return
-       }
-       if err = os.WriteFile(dubboyamlpath, dubbobytes, 0644); err != nil {
-               return
-       }
-       return
-}
-
-type Template interface {
-       Name() string
-       Runtime() string
-       Repository() string
-       Fullname() string
-       Write(ctx context.Context, f *DubboConfig) error
-}
-
-type template struct {
-       name    string
-       runtime string
-}
-
-func (t template) Name() string {
-       return t.name
+func (c *Client) Runtimes() ([]string, error) {
+       // TODO
+       return nil, nil
 }
 
-func (t template) Runtime() string {
-       return t.runtime
-}
-
-func (c *Client) Create(dcfg *DubboConfig, initial bool, cmd *cobra.Command) 
(*DubboConfig, error) {
+func (c *Client) Initialize(dcfg *dubbo.DubboConfig, initialized bool, _ 
*cobra.Command) (*dubbo.DubboConfig, error) {
        var err error
        oldRoot := dcfg.Root
+
        dcfg.Root, err = filepath.Abs(dcfg.Root)
        if err != nil {
                return dcfg, err
@@ -137,12 +43,12 @@ func (c *Client) Create(dcfg *DubboConfig, initial bool, 
cmd *cobra.Command) (*D
                return dcfg, err
        }
 
-       has, err := hasInitializedApplication(dcfg.Root)
+       has, err := hasInitialized(dcfg.Root)
        if err != nil {
                return dcfg, err
        }
        if has {
-               return dcfg, fmt.Errorf("application at '%v' already 
initialized", dcfg.Root)
+               return dcfg, fmt.Errorf("%v already initialized", dcfg.Root)
        }
 
        if dcfg.Root == "" {
@@ -150,90 +56,40 @@ func (c *Client) Create(dcfg *DubboConfig, initial bool, 
cmd *cobra.Command) (*D
                        return dcfg, err
                }
        }
-
        if dcfg.Name == "" {
                dcfg.Name = nameFromPath(dcfg.Root)
        }
 
-       if !initial {
+       if !initialized {
                if err := assertEmptyRoot(dcfg.Root); err != nil {
                        return dcfg, err
                }
        }
 
-       f := NewDubboWithTemplate(dcfg, initial)
-
+       f := dubbo.NewDubboConfigWithTemplate(dcfg, initialized)
        if err = EnsureRunDataDir(f.Root); err != nil {
                return f, err
        }
 
        f.Created = time.Now()
-       err = f.Write()
-       if err != nil {
-               return f, err
-       }
-
-       return NewDubbo(oldRoot)
-}
-
-func NewDubbo(path string) (*DubboConfig, error) {
-       var err error
-       f := &DubboConfig{}
-       // Path defaults to current working directory, and if provided 
explicitly
-       // Path must exist and be a directory
-       if path == "" {
-               if path, err = os.Getwd(); err != nil {
-                       return f, err
-               }
-       }
-       f.Root = path // path is not persisted, as this is the purview of the FS
-
-       // Path must exist and be a directory
-       fd, err := os.Stat(path)
-       if err != nil {
-               return f, err
-       }
-       if !fd.IsDir() {
-               return f, fmt.Errorf("function path must be a directory")
-       }
-
-       // If no dubbo.yaml in directory, return the default function which will
-       // have f.Initialized() == false
-       filename := filepath.Join(path, DubboFile)
-       if _, err = os.Stat(filename); err != nil {
-               if os.IsNotExist(err) {
-                       err = nil
-               }
-               return f, err
-       }
-
-       // Path is valid and dubbo.yaml exists: load it
-       bb, err := os.ReadFile(filename)
-       if err != nil {
-               return f, err
-       }
-       err = yaml.Unmarshal(bb, f)
-       if err != nil {
-               return f, err
-       }
-       return f, nil
+       return dubbo.NewDubboConfig(oldRoot)
 }
 
-func hasInitializedApplication(path string) (bool, error) {
+func hasInitialized(path string) (bool, error) {
        var err error
-       filename := filepath.Join(path, DubboFile)
+       filename := filepath.Join(path, dubbo.DubboFile)
 
        if _, err = os.Stat(filename); err != nil {
                if os.IsNotExist(err) {
                        return false, nil
                }
-               return false, err
+               return false, err // invalid path or access error
        }
        bb, err := os.ReadFile(filename)
        if err != nil {
                return false, err
        }
-       f := DubboConfig{}
+       f := dubbo.DubboConfig{}
        if err = yaml.Unmarshal(bb, &f); err != nil {
                return false, err
        }
@@ -241,10 +97,6 @@ func hasInitializedApplication(path string) (bool, error) {
        return f.Initialized(), nil
 }
 
-func (f *DubboConfig) Initialized() bool {
-       return !f.Created.IsZero()
-}
-
 func nameFromPath(path string) string {
        pathParts := strings.Split(strings.TrimRight(path, 
string(os.PathSeparator)), string(os.PathSeparator))
        return pathParts[len(pathParts)-1]
@@ -261,15 +113,12 @@ func nameFromPath(path string) string {
 }
 
 func assertEmptyRoot(path string) (err error) {
-       // If there exists contentious files (config files for instance), this 
function may have already been initialized.
        files, err := contentiousFilesIn(path)
        if err != nil {
                return
        } else if len(files) > 0 {
                return fmt.Errorf("the chosen directory '%v' contains 
contentious files: %v.  Has the Service function already been created?  Try 
either using a different directory, deleting the function if it exists, or 
manually removing the files", path, files)
        }
-
-       // Ensure there are no non-hidden files, and again none of the 
aforementioned contentious files.
        empty, err := isEffectivelyEmpty(path)
        if err != nil {
                return
@@ -281,11 +130,10 @@ func assertEmptyRoot(path string) (err error) {
 }
 
 var contentiousFiles = []string{
-       DubboFile,
+       dubbo.DubboFile,
        ".gitignore",
 }
 
-// contentiousFilesIn the given directory
 func contentiousFilesIn(dir string) (contentious []string, err error) {
        files, err := os.ReadDir(dir)
        for _, file := range files {
@@ -313,18 +161,9 @@ func isEffectivelyEmpty(dir string) (bool, error) {
 }
 
 func EnsureRunDataDir(root string) error {
-       // Ensure the runtime directory exists
-       if err := os.MkdirAll(filepath.Join(root, RunDataDir), os.ModePerm); 
err != nil {
+       if err := os.MkdirAll(filepath.Join(root, dubbo.DataDir), os.ModePerm); 
err != nil {
                return err
        }
-
-       // Update .gitignore
-       //
-       // Ensure .dubbo is added to .gitignore unless the user explicitly
-       // commented out the ignore line for some awful reason.
-       // Also creates the .gitignore in the application's root directory if 
it does
-       // not already exist (note that this may not be in the root of the repo
-       // if the function is at a subpart of a monorepo)
        filePath := filepath.Join(root, ".gitignore")
        roFile, err := os.Open(filePath)
        if err != nil && !os.IsNotExist(err) {
@@ -334,19 +173,17 @@ func EnsureRunDataDir(root string) error {
        if !os.IsNotExist(err) { // if no error openeing it
                s := bufio.NewScanner(roFile) // create a scanner
                for s.Scan() {                // scan each line
-                       if strings.HasPrefix(s.Text(), "# /"+RunDataDir) { // 
if it was commented
+                       if strings.HasPrefix(s.Text(), "# /"+dubbo.DataDir) { 
// if it was commented
                                return nil // user wants it
                        }
-                       if strings.HasPrefix(s.Text(), "#/"+RunDataDir) {
+                       if strings.HasPrefix(s.Text(), "#/"+dubbo.DataDir) {
                                return nil // user wants it
                        }
-                       if strings.HasPrefix(s.Text(), "/"+RunDataDir) { // if 
it is there
+                       if strings.HasPrefix(s.Text(), "/"+dubbo.DataDir) { // 
if it is there
                                return nil // we're done
                        }
                }
        }
-       // Either .gitignore does not exist or it does not have the ignore
-       // directive for .func yet.
        roFile.Close()
        rwFile, err := os.OpenFile(filePath, 
os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
        if err != nil {
@@ -369,14 +206,3 @@ func EnsureRunDataDir(root string) error {
        }
        return nil
 }
-
-func NewDubboWithTemplate(defaults *DubboConfig, init bool) *DubboConfig {
-       if !init {
-               if defaults.Template == "" {
-                       defaults.Template = DefaultTemplate
-               }
-       } else {
-               defaults.Template = "init"
-       }
-       return defaults
-}
diff --git a/dubboctl/pkg/sdk/dubbo/config.go b/dubboctl/pkg/sdk/dubbo/config.go
new file mode 100644
index 00000000..9ce9dd45
--- /dev/null
+++ b/dubboctl/pkg/sdk/dubbo/config.go
@@ -0,0 +1,63 @@
+package dubbo
+
+import (
+       "gopkg.in/yaml.v3"
+       "os"
+       "path/filepath"
+       "time"
+)
+
+const (
+       DubboFile = "dubbo.yaml"
+       DataDir   = ".dubbo"
+)
+
+type DubboConfig struct {
+       Root     string    `yaml:"-"`
+       Name     string    `yaml:"name,omitempty" 
jsonschema:"pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"`
+       Runtime  string    `yaml:"runtime,omitempty"`
+       Template string    `yaml:"template,omitempty"`
+       Created  time.Time `yaml:"created,omitempty"`
+}
+
+func NewDubboConfig(path string) (*DubboConfig, error) {
+       var err error
+
+       fd, err := os.Stat(path)
+       if err != nil {
+               return nil, err
+       }
+       if !fd.IsDir() {
+               return nil, nil
+       }
+       filename := filepath.Join(path, DubboFile)
+       if _, err = os.Stat(filename); err != nil {
+               if os.IsNotExist(err) {
+                       err = nil
+               }
+               return nil, err
+       }
+
+       bb, err := os.ReadFile(filename)
+       if err != nil {
+               return nil, err
+       }
+       err = yaml.Unmarshal(bb, nil)
+       if err != nil {
+               return nil, err
+       }
+       return nil, nil
+}
+
+func NewDubboConfigWithTemplate(defaults *DubboConfig, initialized bool) 
*DubboConfig {
+       if !initialized {
+               if defaults.Template == "" {
+                       defaults.Template = "sample"
+               }
+       }
+       return defaults
+}
+
+func (dc *DubboConfig) Initialized() bool {
+       return !dc.Created.IsZero()
+}
diff --git a/dubboctl/pkg/sdk/template.go b/dubboctl/pkg/sdk/template.go
new file mode 100644
index 00000000..7ca369c4
--- /dev/null
+++ b/dubboctl/pkg/sdk/template.go
@@ -0,0 +1,20 @@
+package sdk
+
+import (
+       "context"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/filesystem"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
+)
+
+type template struct {
+       name    string
+       runtime string
+       fs      filesystem.Filesystem
+}
+
+type Template interface {
+       Name() string
+       Fullname() string
+       Runtime() string
+       Write(ctx context.Context, f *dubbo.DubboConfig) error
+}
diff --git a/dubboctl/pkg/sdk/templates.go b/dubboctl/pkg/sdk/templates.go
new file mode 100644
index 00000000..9c0759ac
--- /dev/null
+++ b/dubboctl/pkg/sdk/templates.go
@@ -0,0 +1,5 @@
+package sdk
+
+type Templates struct {
+       client *Client
+}
diff --git a/dubboctl/pkg/template/go/common/.gitignore 
b/dubboctl/pkg/template/go/sample/.gitignore
similarity index 100%
rename from dubboctl/pkg/template/go/common/.gitignore
rename to dubboctl/pkg/template/go/sample/.gitignore
diff --git a/dubboctl/pkg/template/go/common/api/api.pb.go 
b/dubboctl/pkg/template/go/sample/api/api.pb.go
similarity index 100%
rename from dubboctl/pkg/template/go/common/api/api.pb.go
rename to dubboctl/pkg/template/go/sample/api/api.pb.go
diff --git a/dubboctl/pkg/template/go/common/api/api.proto 
b/dubboctl/pkg/template/go/sample/api/api.proto
similarity index 100%
rename from dubboctl/pkg/template/go/common/api/api.proto
rename to dubboctl/pkg/template/go/sample/api/api.proto
diff --git a/dubboctl/pkg/template/go/common/api/api_triple.pb.go 
b/dubboctl/pkg/template/go/sample/api/api_triple.pb.go
similarity index 100%
rename from dubboctl/pkg/template/go/common/api/api_triple.pb.go
rename to dubboctl/pkg/template/go/sample/api/api_triple.pb.go
diff --git a/dubboctl/pkg/template/go/common/cmd/app.go 
b/dubboctl/pkg/template/go/sample/cmd/app.go
similarity index 100%
rename from dubboctl/pkg/template/go/common/cmd/app.go
rename to dubboctl/pkg/template/go/sample/cmd/app.go
diff --git a/dubboctl/pkg/template/go/common/conf/dubbogo.yaml 
b/dubboctl/pkg/template/go/sample/conf/dubbogo.yaml
similarity index 100%
rename from dubboctl/pkg/template/go/common/conf/dubbogo.yaml
rename to dubboctl/pkg/template/go/sample/conf/dubbogo.yaml
diff --git a/dubboctl/pkg/template/go/common/go.mod 
b/dubboctl/pkg/template/go/sample/go.mod
similarity index 100%
rename from dubboctl/pkg/template/go/common/go.mod
rename to dubboctl/pkg/template/go/sample/go.mod
diff --git a/dubboctl/pkg/template/go/common/go.sum 
b/dubboctl/pkg/template/go/sample/go.sum
similarity index 100%
rename from dubboctl/pkg/template/go/common/go.sum
rename to dubboctl/pkg/template/go/sample/go.sum
diff --git a/dubboctl/pkg/template/go/common/pkg/service/service.go 
b/dubboctl/pkg/template/go/sample/pkg/service/service.go
similarity index 100%
rename from dubboctl/pkg/template/go/common/pkg/service/service.go
rename to dubboctl/pkg/template/go/sample/pkg/service/service.go
diff --git a/dubboctl/pkg/template/java/common/.gitignore 
b/dubboctl/pkg/template/java/sample/.gitignore
similarity index 100%
rename from dubboctl/pkg/template/java/common/.gitignore
rename to dubboctl/pkg/template/java/sample/.gitignore
diff --git a/dubboctl/pkg/template/java/common/pom.xml 
b/dubboctl/pkg/template/java/sample/pom.xml
similarity index 100%
rename from dubboctl/pkg/template/java/common/pom.xml
rename to dubboctl/pkg/template/java/sample/pom.xml
diff --git 
a/dubboctl/pkg/template/java/common/src/main/java/com/example/demo/DemoApplication.java
 
b/dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/DemoApplication.java
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/java/com/example/demo/DemoApplication.java
rename to 
dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/DemoApplication.java
diff --git 
a/dubboctl/pkg/template/java/common/src/main/java/com/example/demo/demos/web/BasicController.java
 
b/dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/demos/web/BasicController.java
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/java/com/example/demo/demos/web/BasicController.java
rename to 
dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/demos/web/BasicController.java
diff --git 
a/dubboctl/pkg/template/java/common/src/main/java/com/example/demo/demos/web/PathVariableController.java
 
b/dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/demos/web/PathVariableController.java
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/java/com/example/demo/demos/web/PathVariableController.java
rename to 
dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/demos/web/PathVariableController.java
diff --git 
a/dubboctl/pkg/template/java/common/src/main/java/com/example/demo/demos/web/User.java
 
b/dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/demos/web/User.java
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/java/com/example/demo/demos/web/User.java
rename to 
dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/demos/web/User.java
diff --git 
a/dubboctl/pkg/template/java/common/src/main/java/com/example/demo/dubbo/api/DemoService.java
 
b/dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/dubbo/api/DemoService.java
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/java/com/example/demo/dubbo/api/DemoService.java
rename to 
dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/dubbo/api/DemoService.java
diff --git 
a/dubboctl/pkg/template/java/common/src/main/java/com/example/demo/dubbo/consumer/Consumer.java
 
b/dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/dubbo/consumer/Consumer.java
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/java/com/example/demo/dubbo/consumer/Consumer.java
rename to 
dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/dubbo/consumer/Consumer.java
diff --git 
a/dubboctl/pkg/template/java/common/src/main/java/com/example/demo/dubbo/service/DemoServiceImpl.java
 
b/dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/dubbo/service/DemoServiceImpl.java
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/java/com/example/demo/dubbo/service/DemoServiceImpl.java
rename to 
dubboctl/pkg/template/java/sample/src/main/java/com/example/demo/dubbo/service/DemoServiceImpl.java
diff --git 
a/dubboctl/pkg/template/java/common/src/main/resources/application.yml 
b/dubboctl/pkg/template/java/sample/src/main/resources/application.yml
similarity index 100%
rename from dubboctl/pkg/template/java/common/src/main/resources/application.yml
rename to dubboctl/pkg/template/java/sample/src/main/resources/application.yml
diff --git 
a/dubboctl/pkg/template/java/common/src/main/resources/log4j.properties 
b/dubboctl/pkg/template/java/sample/src/main/resources/log4j.properties
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/resources/log4j.properties
rename to dubboctl/pkg/template/java/sample/src/main/resources/log4j.properties
diff --git 
a/dubboctl/pkg/template/java/common/src/main/resources/static/index.html 
b/dubboctl/pkg/template/java/sample/src/main/resources/static/index.html
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/main/resources/static/index.html
rename to dubboctl/pkg/template/java/sample/src/main/resources/static/index.html
diff --git 
a/dubboctl/pkg/template/java/common/src/test/java/com/example/demo/DemoApplicationTests.java
 
b/dubboctl/pkg/template/java/sample/src/test/java/com/example/demo/DemoApplicationTests.java
similarity index 100%
rename from 
dubboctl/pkg/template/java/common/src/test/java/com/example/demo/DemoApplicationTests.java
rename to 
dubboctl/pkg/template/java/sample/src/test/java/com/example/demo/DemoApplicationTests.java
diff --git a/go.mod b/go.mod
index 5473dfe5..14eb4d83 100644
--- a/go.mod
+++ b/go.mod
@@ -58,15 +58,15 @@ require (
        github.com/spf13/cobra v1.8.1
        github.com/spf13/pflag v1.0.5
        github.com/spiffe/go-spiffe/v2 v2.1.7
-       github.com/stretchr/testify v1.9.0
+       github.com/stretchr/testify v1.10.0
        go.opentelemetry.io/otel/trace v1.28.0
        go.uber.org/atomic v1.10.0
        go.uber.org/multierr v1.11.0
        go.uber.org/zap v1.27.0
        golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
-       golang.org/x/net v0.33.0
+       golang.org/x/net v0.34.0
        golang.org/x/sync v0.10.0
-       golang.org/x/sys v0.28.0
+       golang.org/x/sys v0.29.0
        golang.org/x/text v0.21.0
        google.golang.org/genproto/googleapis/rpc 
v0.0.0-20240826202546-f6391c0de4c7
        google.golang.org/grpc v1.65.0
@@ -120,7 +120,7 @@ require (
        github.com/coreos/go-semver v0.3.1 // indirect
        github.com/coreos/go-systemd/v22 v22.5.0 // indirect
        github.com/creasty/defaults v1.5.2 // indirect
-       github.com/cyphar/filepath-securejoin v0.2.4 // indirect
+       github.com/cyphar/filepath-securejoin v0.3.6 // indirect
        github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 
indirect
        github.com/dgraph-io/ristretto v0.0.1 // indirect
        github.com/dlclark/regexp2 v1.7.0 // indirect
@@ -135,6 +135,7 @@ require (
        github.com/gabriel-vasile/mimetype v1.4.2 // indirect
        github.com/gin-contrib/sse v0.1.0 // indirect
        github.com/go-errors/errors v1.4.2 // indirect
+       github.com/go-git/go-billy/v5 v5.6.2 // indirect
        github.com/go-logr/stdr v1.2.2 // indirect
        github.com/go-ole/go-ole v1.2.6 // indirect
        github.com/go-openapi/jsonpointer v0.21.0 // indirect
@@ -245,10 +246,10 @@ require (
        go.opentelemetry.io/otel/sdk v1.28.0 // indirect
        go.opentelemetry.io/proto/otlp v1.3.1 // indirect
        golang.org/x/arch v0.3.0 // indirect
-       golang.org/x/crypto v0.31.0 // indirect
+       golang.org/x/crypto v0.32.0 // indirect
        golang.org/x/mod v0.22.0 // indirect
        golang.org/x/oauth2 v0.23.0 // indirect
-       golang.org/x/term v0.27.0 // indirect
+       golang.org/x/term v0.28.0 // indirect
        golang.org/x/time v0.7.0 // indirect
        golang.org/x/tools v0.28.0 // indirect
        gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 666216d4..abe27870 100644
--- a/go.sum
+++ b/go.sum
@@ -201,6 +201,8 @@ github.com/creasty/defaults v1.5.2 
h1:/VfB6uxpyp6h0fr7SPp7n8WJBoV8jfxQXPCnkVSjyl
 github.com/creasty/defaults v1.5.2/go.mod 
h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY=
 github.com/cyphar/filepath-securejoin v0.2.4 
h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
 github.com/cyphar/filepath-securejoin v0.2.4/go.mod 
h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
+github.com/cyphar/filepath-securejoin v0.3.6 
h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
+github.com/cyphar/filepath-securejoin v0.3.6/go.mod 
h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
 github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc 
h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -302,6 +304,8 @@ github.com/go-co-op/gocron v1.9.0/go.mod 
h1:DbJm9kdgr1sEvWpHCA7dFFs/PGHPMil9/97E
 github.com/go-errors/errors v1.0.1/go.mod 
h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-errors/errors v1.4.2 
h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
 github.com/go-errors/errors v1.4.2/go.mod 
h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
+github.com/go-git/go-billy/v5 v5.6.2 
h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
+github.com/go-git/go-billy/v5 v5.6.2/go.mod 
h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod 
h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod 
h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod 
h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -950,6 +954,7 @@ github.com/stretchr/testify v1.8.2/go.mod 
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
 github.com/stretchr/testify v1.8.4/go.mod 
h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.9.0 
h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.2.0/go.mod 
h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/subosito/gotenv v1.6.0 
h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 github.com/subosito/gotenv v1.6.0/go.mod 
h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
@@ -1117,6 +1122,8 @@ golang.org/x/crypto 
v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.3.0/go.mod 
h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
 golang.org/x/crypto v0.31.0/go.mod 
h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+golang.org/x/crypto v0.32.0/go.mod 
h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod 
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1212,6 +1219,8 @@ golang.org/x/net 
v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod 
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod 
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod 
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1321,11 +1330,15 @@ golang.org/x/sys v0.2.0/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
 golang.org/x/term v0.27.0/go.mod 
h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
+golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
+golang.org/x/term v0.28.0/go.mod 
h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod 
h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod 
h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=


Reply via email to