diff --git a/Makefile b/Makefile index f4a9b42fc..e5018ddff 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ default: build GORELEASER_BIN = $(shell pwd)/bin/goreleaser install-goreleaser: ## check license if not exist install go-lint tools - $(call go-get-tool,$(GORELEASER_BIN),github.com/goreleaser/goreleaser@latest) + $(call go-get-tool,$(GORELEASER_BIN),github.com/goreleaser/goreleaser@v1.6.3) build: SHELL:=/bin/bash diff --git a/cmd/sealos/cmd/images.go b/cmd/sealos/cmd/images.go new file mode 100644 index 000000000..65270bfd6 --- /dev/null +++ b/cmd/sealos/cmd/images.go @@ -0,0 +1,42 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newImagesCmd() *cobra.Command { + var imagesCmd = &cobra.Command{ + Use: "images", + Short: "list cloud image", + Example: `sealos images`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewImageService() + if err != nil { + return err + } + return registrySvc.ListImages() + }, + } + return imagesCmd +} + +func init() { + imagesCmd := newImagesCmd() + rootCmd.AddCommand(imagesCmd) +} diff --git a/cmd/sealos/cmd/load.go b/cmd/sealos/cmd/load.go new file mode 100644 index 000000000..0654baf4b --- /dev/null +++ b/cmd/sealos/cmd/load.go @@ -0,0 +1,44 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newLoadCmd() *cobra.Command { + var archiveName string + var loadCmd = &cobra.Command{ + Use: "load", + Short: "load cloud image", + Example: `sealos load -i kubernetes.tar`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewImageService() + if err != nil { + return err + } + return registrySvc.Load(archiveName) + }, + } + loadCmd.Flags().StringVarP(&archiveName, "input", "i", "", "read image from tar archive file") + return loadCmd +} + +func init() { + loadCmd := newLoadCmd() + rootCmd.AddCommand(loadCmd) +} diff --git a/cmd/sealos/cmd/login.go b/cmd/sealos/cmd/login.go new file mode 100644 index 000000000..ddde7b29f --- /dev/null +++ b/cmd/sealos/cmd/login.go @@ -0,0 +1,57 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "os" + + "github.com/fanux/sealos/pkg/image" + "github.com/fanux/sealos/pkg/utils/logger" + + "github.com/spf13/cobra" +) + +func newLoginCmd() *cobra.Command { + var username, password string + var loginCmd = &cobra.Command{ + Use: "login", + Short: "login image repository", + Example: `sealos login registry.cn-qingdao.aliyuncs.com -u [username] -p [password]`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewRegistryService() + if err != nil { + return err + } + return registrySvc.Login(args[0], username, password) + }, + } + loginCmd.Flags().StringVarP(&username, "username", "u", "", "user name for login registry") + loginCmd.Flags().StringVarP(&password, "passwd", "p", "", "password for login registry") + if err := loginCmd.MarkFlagRequired("username"); err != nil { + logger.Error("failed to init flag: %v", err) + os.Exit(1) + } + if err := loginCmd.MarkFlagRequired("passwd"); err != nil { + logger.Error("failed to init flag: %v", err) + os.Exit(1) + } + return loginCmd +} + +func init() { + loginCmd := newLoginCmd() + rootCmd.AddCommand(loginCmd) +} diff --git a/cmd/sealos/cmd/logout.go b/cmd/sealos/cmd/logout.go new file mode 100644 index 000000000..7bf1e8196 --- /dev/null +++ b/cmd/sealos/cmd/logout.go @@ -0,0 +1,42 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newLogoutCmd() *cobra.Command { + var logoutCmd = &cobra.Command{ + Use: "logout", + Short: "logout image repository", + Example: `sealos logout registry.cn-qingdao.aliyuncs.com`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewRegistryService() + if err != nil { + return err + } + return registrySvc.Logout(args[0]) + }, + } + return logoutCmd +} + +func init() { + logoutCmd := newLogoutCmd() + rootCmd.AddCommand(logoutCmd) +} diff --git a/cmd/sealos/cmd/prune.go b/cmd/sealos/cmd/prune.go new file mode 100644 index 000000000..a1054dcdc --- /dev/null +++ b/cmd/sealos/cmd/prune.go @@ -0,0 +1,42 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newPruneCmd() *cobra.Command { + var pruneCmd = &cobra.Command{ + Use: "prune", + Short: "prune image ", + Example: `sealos prune`, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewImageService() + if err != nil { + return err + } + return registrySvc.Prune() + }, + } + return pruneCmd +} + +func init() { + pruneCmd := newPruneCmd() + rootCmd.AddCommand(pruneCmd) +} diff --git a/cmd/sealos/cmd/pull.go b/cmd/sealos/cmd/pull.go new file mode 100644 index 000000000..d427ff452 --- /dev/null +++ b/cmd/sealos/cmd/pull.go @@ -0,0 +1,42 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newPullCmd() *cobra.Command { + var pullCmd = &cobra.Command{ + Use: "pull", + Short: "pull cloud image", + Example: `sealos pull registry.cn-hongkong.aliyuncs.com/sealyun/oci-kubernetes:1.22.8`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewRegistryService() + if err != nil { + return err + } + return registrySvc.Pull(args[0]) + }, + } + return pullCmd +} + +func init() { + pullCmd := newPullCmd() + rootCmd.AddCommand(pullCmd) +} diff --git a/cmd/sealos/cmd/push.go b/cmd/sealos/cmd/push.go new file mode 100644 index 000000000..f88010fbb --- /dev/null +++ b/cmd/sealos/cmd/push.go @@ -0,0 +1,42 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newPushCmd() *cobra.Command { + var pushCmd = &cobra.Command{ + Use: "push", + Short: "push cloud image", + Example: `sealos push registry.cn-hongkong.aliyuncs.com/sealyun/oci-kubernetes:1.22.8`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewRegistryService() + if err != nil { + return err + } + return registrySvc.Push(args[0]) + }, + } + return pushCmd +} + +func init() { + pushCmd := newPushCmd() + rootCmd.AddCommand(pushCmd) +} diff --git a/cmd/sealos/cmd/rmi.go b/cmd/sealos/cmd/rmi.go new file mode 100644 index 000000000..49f0b3d9e --- /dev/null +++ b/cmd/sealos/cmd/rmi.go @@ -0,0 +1,44 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newRMICmd() *cobra.Command { + var force bool + var rmiCmd = &cobra.Command{ + Use: "rmi", + Short: "Remove one or more cloud images", + Example: `sealos rmi [-f] registry.cn-hongkong.aliyuncs.com/sealyun/oci-kubernetes:1.22.8`, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewImageService() + if err != nil { + return err + } + return registrySvc.Remove(force, args...) + }, + } + rmiCmd.Flags().BoolVar(&force, "force", false, "force removal all of the image") + return rmiCmd +} + +func init() { + rmiCmd := newRMICmd() + rootCmd.AddCommand(rmiCmd) +} diff --git a/cmd/sealos/cmd/save.go b/cmd/sealos/cmd/save.go new file mode 100644 index 000000000..4f05e530b --- /dev/null +++ b/cmd/sealos/cmd/save.go @@ -0,0 +1,44 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newSaveCmd() *cobra.Command { + var archiveName string + var saveCmd = &cobra.Command{ + Use: "save", + Short: "save cloud image to a tar file", + Example: `sealos save -o kubernetes.tar localhost/oci-kubernetes:1.22.8`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewImageService() + if err != nil { + return err + } + return registrySvc.Save(args[0], archiveName) + }, + } + saveCmd.Flags().StringVarP(&archiveName, "output", "o", "", "read image from tar archive file") + return saveCmd +} + +func init() { + saveCmd := newSaveCmd() + rootCmd.AddCommand(saveCmd) +} diff --git a/cmd/sealos/cmd/tag.go b/cmd/sealos/cmd/tag.go new file mode 100644 index 000000000..1e68dc1db --- /dev/null +++ b/cmd/sealos/cmd/tag.go @@ -0,0 +1,42 @@ +// Copyright © 2021 sealos. +// +// 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 cmd + +import ( + "github.com/fanux/sealos/pkg/image" + "github.com/spf13/cobra" +) + +func newTagCmd() *cobra.Command { + var tagCmd = &cobra.Command{ + Use: "tag", + Short: "tag a image as a new one", + Example: `sealos tag localhost/oci-kubernetes:1.22.8 registry.cn-hongkong.aliyuncs.com/sealyun/oci-kubernetes:1.22.8`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + registrySvc, err := image.NewImageService() + if err != nil { + return err + } + return registrySvc.Tag(args[0], args[1]) + }, + } + return tagCmd +} + +func init() { + tagCmd := newTagCmd() + rootCmd.AddCommand(tagCmd) +} diff --git a/pkg/checker/cluster_checker.go b/pkg/checker/cluster_checker.go new file mode 100644 index 000000000..f113794e4 --- /dev/null +++ b/pkg/checker/cluster_checker.go @@ -0,0 +1,126 @@ +// Copyright © 2021 cuisongliu@qq.com. +// +// 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 checker + +import ( + "context" + "os" + "text/template" + "time" + + "github.com/fanux/sealos/pkg/utils/logger" + + "github.com/fanux/sealos/pkg/utils/contants" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/fanux/sealos/pkg/client-go/kubernetes" + v2 "github.com/fanux/sealos/pkg/types/v1beta1" +) + +type ClusterChecker struct { +} + +type ClusterStatus struct { + IP string + Node string + KubeAPIServer string + KubeControllerManager string + KubeScheduler string + KubeletErr string +} + +func (n *ClusterChecker) Check(cluster *v2.Cluster, phase string) error { + if phase != PhasePost { + return nil + } + + // checker if all the node is ready + data := contants.NewData(cluster.Name) + c, err := kubernetes.NewKubernetesClient(data.AdminFile(), cluster.GetMaster0IPAPIServer()) + if err != nil { + return err + } + nodes, err := c.Kubernetes().CoreV1().Nodes().List(context.Background(), v1.ListOptions{}) + if err != nil { + return err + } + healthyClient := kubernetes.NewKubeHealthy(c.Kubernetes(), 30*time.Second) + var NodeList []ClusterStatus + for _, node := range nodes.Items { + ip, _ := getNodeStatus(node) + cStatus := ClusterStatus{ + IP: ip, + Node: node.Name, + } + apiPod, err := kubernetes.GetStaticPod(c.Kubernetes(), node.Name, kubernetes.KubeAPIServer) + if err != nil { + return err + } + cStatus.KubeAPIServer = healthyClient.ForHealthyPod(apiPod) + + controllerPod, err := kubernetes.GetStaticPod(c.Kubernetes(), node.Name, kubernetes.KubeControllerManager) + if err != nil { + return err + } + cStatus.KubeControllerManager = healthyClient.ForHealthyPod(controllerPod) + + schedulerPod, err := kubernetes.GetStaticPod(c.Kubernetes(), node.Name, kubernetes.KubeScheduler) + if err != nil { + return err + } + cStatus.KubeScheduler = healthyClient.ForHealthyPod(schedulerPod) + + if err = healthyClient.ForHealthyKubelet(10*time.Second, ip); err != nil { + cStatus.KubeletErr = err.Error() + } else { + cStatus.KubeletErr = "" + } + NodeList = append(NodeList, cStatus) + } + + return n.Output(NodeList) +} + +func (n *ClusterChecker) Output(clusterStatus []ClusterStatus) error { + //t1, err := template.ParseFiles("templates/node_checker.tpl") + t := template.New("cluster_checker") + t, err := t.Parse( + `Cluster Status + Node List: + {{- range .ClusterStatusList }} + NodeName: {{ .Node }} + NodeIP: {{ .IP }} + KubeAPIServer: {{ .KubeAPIServer }} + KubeControllerManager: {{ .KubeControllerManager }} + KubeScheduler: {{ .KubeScheduler }} + KubeletErr: {{.KubeletErr}} + {{- end }} +`) + if err != nil { + panic(err) + } + t = template.Must(t, err) + err = t.Execute(os.Stdout, map[string][]ClusterStatus{"ClusterStatusList": clusterStatus}) + if err != nil { + logger.Error("node checkers template can not excute %s", err) + return err + } + return nil +} + +func NewClusterChecker() Interface { + return &ClusterChecker{} +} diff --git a/pkg/checker/cluster_checker_test.go b/pkg/checker/cluster_checker_test.go new file mode 100644 index 000000000..b15519a3c --- /dev/null +++ b/pkg/checker/cluster_checker_test.go @@ -0,0 +1,43 @@ +// Copyright © 2021 sealos. +// +// 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 checker + +import ( + "testing" +) + +func TestGenerateAll(t *testing.T) { + checker := &ClusterChecker{} + var data []ClusterStatus + data = append(data, ClusterStatus{ + IP: "1.1.1.1", + Node: "test1", + KubeAPIServer: "Running", + KubeControllerManager: "Running", + KubeScheduler: "Running", + KubeletErr: "", + }) + data = append(data, ClusterStatus{ + IP: "2.2.2.2", + Node: "test2", + KubeAPIServer: "Running", + KubeControllerManager: "Running", + KubeScheduler: "Running", + KubeletErr: "[kubelet-check] It seems like the kubelet isn't running or healthy.", + }) + if err := checker.Output(data); err != nil { + t.Error(err) + } +} diff --git a/pkg/cri/cri_unix.go b/pkg/cri/cri_unix.go index e5a5f46f5..469b4089e 100644 --- a/pkg/cri/cri_unix.go +++ b/pkg/cri/cri_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows /* diff --git a/pkg/image/binary/image.go b/pkg/image/binary/image.go index 20ac55ee7..0d07998e8 100644 --- a/pkg/image/binary/image.go +++ b/pkg/image/binary/image.go @@ -18,6 +18,7 @@ package binary import ( "fmt" + "os" "path/filepath" "strings" @@ -110,25 +111,10 @@ func (d *ImageService) Prune() error { return exec.CmdForPipe("bash", "-c", "buildah rmi --prune") } -func (d *ImageService) ListImages() ([]types.ImageInfo, error) { - data := exec.BashEval("buildah images --json") - infos, err := listImage(data) - if err != nil { - return nil, err - } - return infos, nil -} - -func listImage(data string) ([]types.ImageInfo, error) { - if data != "" { - var outStruct []types.ImageInfo - err := json.Unmarshal([]byte(data), &outStruct) - if err != nil { - return nil, errors.Wrap(err, "decode out json from list images failed") - } - return outStruct, nil - } - return nil, errors.New("images output is empty") +func (d *ImageService) ListImages() error { + data, err := exec.RunBashCmd("buildah images") + _, _ = os.Stdout.Write([]byte(data)) + return err } func NewImageService() (types.Service, error) { diff --git a/pkg/image/types/interface.go b/pkg/image/types/interface.go index 36b564bd1..39cb1e4f8 100644 --- a/pkg/image/types/interface.go +++ b/pkg/image/types/interface.go @@ -29,7 +29,7 @@ type Service interface { Inspect(images ...string) (ImageListOCIV1, error) //oci image Build(options *BuildOptions, contextDir, imageName string) error Prune() error - ListImages() ([]ImageInfo, error) + ListImages() error } type ClusterService interface { diff --git a/pkg/utils/exec/cmd.go b/pkg/utils/exec/cmd.go index 8010e552a..0142425b1 100644 --- a/pkg/utils/exec/cmd.go +++ b/pkg/utils/exec/cmd.go @@ -48,14 +48,16 @@ func CmdForPipe(exe string, args ...string) error { outScanner := bufio.NewScanner(outReader) go func() { for outScanner.Scan() { - logger.Info(outScanner.Text()) + //logger.Info() + _, _ = os.Stdout.Write([]byte(outScanner.Text() + "\n")) } }() errScanner := bufio.NewScanner(errReader) go func() { for errScanner.Scan() { - logger.Info(errScanner.Text()) + //logger.Info(errScanner.Text()) + _, _ = os.Stdout.Write([]byte(errScanner.Text() + "\n")) } }() diff --git a/pkg/utils/file/file_v2.go b/pkg/utils/file/file_v2.go index cde872e4a..9c8d9b6f2 100644 --- a/pkg/utils/file/file_v2.go +++ b/pkg/utils/file/file_v2.go @@ -15,7 +15,6 @@ package file import ( - "archive/tar" "bufio" "fmt" "io" @@ -27,7 +26,6 @@ import ( "github.com/fanux/sealos/pkg/utils/logger" "github.com/pkg/errors" - "golang.org/x/sys/unix" ) func Filename(f string) string { @@ -142,7 +140,7 @@ func WriteFile(fileName string, content []byte) error { // copy /root/test/abc /tmp/abc func RecursionCopy(src, dst string) error { if IsDir(src) { - return CopyDir(src, dst, false) + return CopyDirV3(src, dst) } err := os.MkdirAll(filepath.Dir(dst), 0700|0055) @@ -150,96 +148,7 @@ func RecursionCopy(src, dst string) error { return fmt.Errorf("failed to mkdir for recursion copy, err: %v", err) } - _, err = CopySingleFile(src, dst) - return err -} - -// cp -r /roo/test/* /tmp/abc -func CopyDir(srcPath, dstPath string, overlayFs bool) error { - err := os.MkdirAll(dstPath, 0700|0055) - if err != nil { - return err - } - if overlayFs { - opaque, err := Lgetxattr(srcPath, "trusted.overlay.opaque") - if err != nil { - logger.Debug("failed to get trusted.overlay.opaque. err: %v", err) - } - - if len(opaque) == 1 && opaque[0] == 'y' { - err = unix.Setxattr(dstPath, "trusted.overlay.opaque", []byte{'y'}, 0) - if err != nil { - return fmt.Errorf("failed to set trusted.overlay.opaque, err: %v", err) - } - } - } - - fis, err := ioutil.ReadDir(srcPath) - if err != nil { - return err - } - for _, f := range fis { - src := filepath.Join(srcPath, f.Name()) - dst := filepath.Join(dstPath, f.Name()) - if f.IsDir() { - err = CopyDir(src, dst, overlayFs) - if err != nil { - return err - } - } else { - _, err = CopySingleFile(src, dst) - if err != nil { - return err - } - } - } - return nil -} - -func CopySingleFile(src, dst string) (int64, error) { - sourceFileStat, err := os.Stat(src) - if err != nil { - return 0, err - } - - header, err := tar.FileInfoHeader(sourceFileStat, src) - if err != nil { - return 0, fmt.Errorf("failed to get file info header for %s, err: %v", src, err) - } - if sourceFileStat.Mode()&os.ModeCharDevice != 0 && header.Devminor == 0 && header.Devmajor == 0 { - err = unix.Mknod(dst, unix.S_IFCHR, 0) - if err != nil { - return 0, err - } - return 0, os.Chown(dst, header.Uid, header.Gid) - } - - if !sourceFileStat.Mode().IsRegular() { - return 0, fmt.Errorf("%s is not a regular file", src) - } - - source, err := os.Open(filepath.Clean(src)) - if err != nil { - return 0, err - } - defer source.Close() - //will overwrite dst when dst is existed - destination, err := os.Create(dst) - if err != nil { - return 0, err - } - defer destination.Close() - err = destination.Chmod(sourceFileStat.Mode()) - if err != nil { - return 0, err - } - - err = os.Chown(dst, header.Uid, header.Gid) - if err != nil { - return 0, err - } - nBytes, err := io.Copy(destination, source) - return nBytes, err + return Copy(src, dst) } func CleanFile(file *os.File) { diff --git a/pkg/utils/file/file_v3.go b/pkg/utils/file/file_v3.go new file mode 100644 index 000000000..f08b3271f --- /dev/null +++ b/pkg/utils/file/file_v3.go @@ -0,0 +1,261 @@ +// Copyright 2013 com authors +// +// 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 file + +import ( + "errors" + "fmt" + "io" + "os" + "path" + "strings" +) + +func statDir(dirPath, recPath string, includeDir, isDirOnly, followSymlinks bool) ([]string, error) { + dir, err := os.Open(dirPath) + if err != nil { + return nil, err + } + defer dir.Close() + + fis, err := dir.Readdir(0) + if err != nil { + return nil, err + } + + statList := make([]string, 0) + for _, fi := range fis { + if strings.Contains(fi.Name(), ".DS_Store") { + continue + } + + relPath := path.Join(recPath, fi.Name()) + curPath := path.Join(dirPath, fi.Name()) + if fi.IsDir() { + if includeDir { + statList = append(statList, relPath+"/") + } + s, err := statDir(curPath, relPath, includeDir, isDirOnly, followSymlinks) + if err != nil { + return nil, err + } + statList = append(statList, s...) + } else if !isDirOnly { + statList = append(statList, relPath) + } else if followSymlinks && fi.Mode()&os.ModeSymlink != 0 { + link, err := os.Readlink(curPath) + if err != nil { + return nil, err + } + + if IsDir(link) { + if includeDir { + statList = append(statList, relPath+"/") + } + s, err := statDir(curPath, relPath, includeDir, isDirOnly, followSymlinks) + if err != nil { + return nil, err + } + statList = append(statList, s...) + } + } + } + return statList, nil +} + +// StatDir gathers information of given directory by depth-first. +// It returns slice of file list and includes subdirectories if enabled; +// it returns error and nil slice when error occurs in underlying functions, +// or given path is not a directory or does not exist. +// +// Slice does not include given path itself. +// If subdirectories is enabled, they will have suffix '/'. +func StatDir(rootPath string, includeDir ...bool) ([]string, error) { + if !IsDir(rootPath) { + return nil, errors.New("not a directory or does not exist: " + rootPath) + } + + isIncludeDir := false + if len(includeDir) >= 1 { + isIncludeDir = includeDir[0] + } + return statDir(rootPath, "", isIncludeDir, false, false) +} + +// LstatDir gathers information of given directory by depth-first. +// It returns slice of file list, follows symbolic links and includes subdirectories if enabled; +// it returns error and nil slice when error occurs in underlying functions, +// or given path is not a directory or does not exist. +// +// Slice does not include given path itself. +// If subdirectories is enabled, they will have suffix '/'. +func LstatDir(rootPath string, includeDir ...bool) ([]string, error) { + if !IsDir(rootPath) { + return nil, errors.New("not a directory or does not exist: " + rootPath) + } + + isIncludeDir := false + if len(includeDir) >= 1 { + isIncludeDir = includeDir[0] + } + return statDir(rootPath, "", isIncludeDir, false, true) +} + +// GetAllSubDirs returns all subdirectories of given root path. +// Slice does not include given path itself. +func GetAllSubDirs(rootPath string) ([]string, error) { + if !IsDir(rootPath) { + return nil, errors.New("not a directory or does not exist: " + rootPath) + } + return statDir(rootPath, "", true, true, false) +} + +// LgetAllSubDirs returns all subdirectories of given root path, including +// following symbolic links, if any. +// Slice does not include given path itself. +func LgetAllSubDirs(rootPath string) ([]string, error) { + if !IsDir(rootPath) { + return nil, errors.New("not a directory or does not exist: " + rootPath) + } + return statDir(rootPath, "", true, true, true) +} + +// GetFileListBySuffix returns an ordered list of file paths. +// It recognize if given path is a file, and don't do recursive find. +func GetFileListBySuffix(dirPath, suffix string) ([]string, error) { + if !IsExist(dirPath) { + return nil, fmt.Errorf("given path does not exist: %s", dirPath) + } else if IsFile(dirPath) { + return []string{dirPath}, nil + } + + // Given path is a directory. + dir, err := os.Open(dirPath) + if err != nil { + return nil, err + } + + fis, err := dir.Readdir(0) + if err != nil { + return nil, err + } + + files := make([]string, 0, len(fis)) + for _, fi := range fis { + if strings.HasSuffix(fi.Name(), suffix) { + files = append(files, path.Join(dirPath, fi.Name())) + } + } + + return files, nil +} + +// CopyDirV3 copy files recursively from source to target directory. +// +// The filter accepts a function that process the path info. +// and should return true for need to filter. +// +// It returns error when error occurs in underlying functions. +func CopyDirV3(srcPath, destPath string, filters ...func(filePath string) bool) error { + // Check if target directory exists. + //if IsExist(destPath) { + // return errors.New("file or directory alreay exists: " + destPath) + //} + + err := os.MkdirAll(destPath, os.ModePerm) + if err != nil { + return err + } + + // Gather directory info. + infos, err := StatDir(srcPath, true) + if err != nil { + return err + } + + var filter func(filePath string) bool + if len(filters) > 0 { + filter = filters[0] + } + + for _, info := range infos { + if filter != nil && filter(info) { + continue + } + + curPath := path.Join(destPath, info) + if strings.HasSuffix(info, "/") { + err = os.MkdirAll(curPath, os.ModePerm) + } else { + err = Copy(path.Join(srcPath, info), curPath) + } + if err != nil { + return err + } + } + return nil +} + +// Copy copies file from source to target path. +func Copy(src, dest string) error { + // Gather file information to set back later. + si, err := os.Lstat(src) + if err != nil { + return err + } + + // Handle symbolic link. + if si.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(src) + if err != nil { + return err + } + // NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link, + // which will lead "no such file or directory" error. + return os.Symlink(target, dest) + } + + sr, err := os.Open(src) + if err != nil { + return err + } + defer sr.Close() + + dw, err := os.Create(dest) + if err != nil { + return err + } + defer dw.Close() + + if _, err = io.Copy(dw, sr); err != nil { + return err + } + + // Set back file information. + if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil { + return err + } + return os.Chmod(dest, si.Mode()) +} + +// IsFile returns true if given path is a file, +// or returns false when it's a directory or does not exist. +func IsFile(filePath string) bool { + f, e := os.Stat(filePath) + if e != nil { + return false + } + return !f.IsDir() +}