Sweep: run guest command one by one and support run patch-type image (#3677)

* refactor: run guest command one by one and support run patch-type image

Signed-off-by: fengxsong <fengxsong@outlook.com>

* fix: ci lints and remove mounts parameter

Signed-off-by: fengxsong <fengxsong@outlook.com>

* fix: test case

Signed-off-by: fengxsong <fengxsong@outlook.com>

* fix: vip address rendering

Signed-off-by: fengxsong <fengxsong@outlook.com>

* fix: test case

Signed-off-by: fengxsong <fengxsong@outlook.com>

* feature(main): add test case

Signed-off-by: cuisongliu <cuisongliu@qq.com>

* fix: inject rootfs env into bootstrap process

Signed-off-by: fengxsong <fengxsong@outlook.com>

* feature(main): add join using guest process

Signed-off-by: cuisongliu <cuisongliu@qq.com>

* fix: add targetHosts parameter as guest commands are not always applied to all hosts

Signed-off-by: fengxsong <fengxsong@outlook.com>

* feature(main): add patch image

Signed-off-by: cuisongliu <cuisongliu@qq.com>

* refactor: add context parameter and pass it along side to the end

Signed-off-by: fengxsong <fengxsong@outlook.com>

* test(main): add test for patch image

Signed-off-by: cuisongliu <cuisongliu@qq.com>

* test(main): add test for patch image

Signed-off-by: cuisongliu <cuisongliu@qq.com>

* test(main): add test for patch image

Signed-off-by: cuisongliu <cuisongliu@qq.com>

* test(main): add test for patch image

Signed-off-by: cuisongliu <cuisongliu@qq.com>

---------

Signed-off-by: fengxsong <fengxsong@outlook.com>
Signed-off-by: cuisongliu <cuisongliu@qq.com>
Co-authored-by: cuisongliu <cuisongliu@qq.com>
This commit is contained in:
fengxsong
2023-08-17 21:23:04 +08:00
committed by GitHub
parent 0c70a4cbd6
commit 95493cfa3e
49 changed files with 741 additions and 325 deletions
+1
View File
@@ -44,6 +44,7 @@ jobs:
E2E_sealos_apply_docker_test,
E2E_sealos_apply_other_test,
E2E_sealos_filesystem_test,
E2E_sealos_run_patchimage_test,
E2E_sealos_runtime_version_119_test,
E2E_sealos_runtime_version_120_test,
E2E_sealos_runtime_version_121_test,
+1 -1
View File
@@ -32,7 +32,7 @@ func newApplyCmd() *cobra.Command {
Example: `sealos apply -f Clusterfile`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
applier, err := apply.NewApplierFromFile(clusterFile, applyArgs)
applier, err := apply.NewApplierFromFile(cmd, clusterFile, applyArgs)
if err != nil {
return err
}
+1 -1
View File
@@ -56,7 +56,7 @@ func newGenCmd() *cobra.Command {
Long: `generate a Clusterfile of the kubernetes cluster, which can be applied by 'sealos apply' command`,
Example: exampleGen,
RunE: func(cmd *cobra.Command, args []string) error {
data, err := apply.NewClusterFromGenArgs(args, genArgs)
data, err := apply.NewClusterFromGenArgs(cmd, genArgs, args)
if err != nil {
return err
}
+1 -1
View File
@@ -47,7 +47,7 @@ func newResetCmd() *cobra.Command {
}
return err
}
applier, err := apply.NewApplierFromResetArgs(resetArgs)
applier, err := apply.NewApplierFromResetArgs(cmd, resetArgs)
if err != nil {
return err
}
+2 -2
View File
@@ -70,7 +70,7 @@ func newRunCmd() *cobra.Command {
return err
}
applier, err := apply.NewApplierFromArgs(images, runArgs)
applier, err := apply.NewApplierFromArgs(cmd, runArgs, images)
if err != nil {
return err
}
@@ -87,7 +87,7 @@ func newRunCmd() *cobra.Command {
runArgs.RegisterFlags(runCmd.Flags())
runCmd.Flags().BoolVar(new(bool), "single", false, "run cluster in single mode")
if err := runCmd.Flags().MarkDeprecated("single", "it defaults to running cluster in single mode when there are no master and node"); err != nil {
logger.Error(err)
logger.Fatal(err)
}
runCmd.Flags().BoolVarP(&processor.ForceOverride, "force", "f", false, "force override app in this cluster")
runCmd.Flags().StringVarP(&transport, "transport", "t", buildah.OCIArchive,
+3 -2
View File
@@ -27,6 +27,7 @@ import (
"github.com/labring/sealos/pkg/constants"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/version"
versionutils "github.com/labring/sealos/pkg/version/utils"
)
var shortPrint bool
@@ -75,8 +76,8 @@ func PrintInfo() error {
}
return nil
}
OutputInfo.KubernetesVersion = version.GetKubernetesVersion(cluster)
OutputInfo.CriRuntimeVersion = version.GetCriRuntimeVersion()
OutputInfo.KubernetesVersion = versionutils.GetKubernetesVersion(cluster)
OutputInfo.CriRuntimeVersion = versionutils.GetCriRuntimeVersion()
err = PrintToStd(OutputInfo)
if err != nil {
+2 -1
View File
@@ -1,7 +1,8 @@
FROM scratch
LABEL image="__lvscare__"
LABEL sealos.io.type="patch"
COPY images ./images
COPY registry ./registry
COPY sealctl ./opt/sealctl
COPY image-cri-shim ./cri/image-cri-shim
COPY upgrade.sh ./upgrade.sh
CMD ["bash upgrade.sh"]
+1 -1
View File
@@ -22,7 +22,7 @@ mkdir -p images/shim
echo "${IMAGE}" > images/shim/lvscareImage
sed -i "s#__lvscare__#${IMAGE}#g" Dockerfile
sudo sealos build -t "${PATCH}" --platform linux/"${ARCH}" -f Dockerfile .
sudo sealos build -t "${PATCH}" --label=sealos.io.type=patch --label=image="${IMAGE}" --platform linux/"${ARCH}" -f Dockerfile .
# save patch image
cd - && sudo sealos save -o patch-"${ARCH}".tar "${PATCH}"
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
systemctl stop image-cri-shim
cp cri/image-cri-shim /usr/bin/image-cri-shim
systemctl start image-cri-shim
image-cri-shim -v
+6 -2
View File
@@ -18,16 +18,17 @@ package apply
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/labring/sealos/pkg/apply/applydrivers"
"github.com/labring/sealos/pkg/clusterfile"
"github.com/labring/sealos/pkg/constants"
)
func NewApplierFromFile(path string, args *Args) (applydrivers.Interface, error) {
func NewApplierFromFile(cmd *cobra.Command, path string, args *Args) (applydrivers.Interface, error) {
if !filepath.IsAbs(path) {
pa, err := os.Getwd()
if err != nil {
@@ -59,7 +60,10 @@ func NewApplierFromFile(path string, args *Args) (applydrivers.Interface, error)
}
currentCluster := cf.GetCluster()
ctx := withCommonContext(cmd.Context(), cmd)
return &applydrivers.Applier{
Context: ctx,
ClusterDesired: cluster,
ClusterFile: Clusterfile,
ClusterCurrent: currentCluster,
+8 -2
View File
@@ -20,6 +20,8 @@ import (
"reflect"
"testing"
"github.com/spf13/cobra"
"github.com/labring/sealos/pkg/apply/applydrivers"
)
@@ -74,7 +76,9 @@ func Test_NewApplierFromFile(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewApplierFromFile("../clusterfile/testdata/clusterfile.yaml",
_, err := NewApplierFromFile(&cobra.Command{
Use: "mock",
}, "../clusterfile/testdata/clusterfile.yaml",
&Args{
Values: tt.args.values,
Sets: tt.args.sets,
@@ -103,7 +107,9 @@ func TestNewApplierFromFile(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewApplierFromFile(tt.args.path, tt.args.args)
got, err := NewApplierFromFile(&cobra.Command{
Use: "mock",
}, tt.args.path, tt.args.args)
if (err != nil) != tt.wantErr {
t.Errorf("NewApplierFromFile() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -15,12 +15,11 @@
package applydrivers
import (
"context"
"errors"
"fmt"
"os"
"github.com/labring/sealos/pkg/utils/confirm"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/version"
@@ -29,12 +28,13 @@ import (
"github.com/labring/sealos/pkg/clusterfile"
"github.com/labring/sealos/pkg/constants"
v2 "github.com/labring/sealos/pkg/types/v1beta1"
"github.com/labring/sealos/pkg/utils/confirm"
"github.com/labring/sealos/pkg/utils/iputils"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/utils/yaml"
)
func NewDefaultApplier(cluster *v2.Cluster, cf clusterfile.Interface, images []string) (Interface, error) {
func NewDefaultApplier(ctx context.Context, cluster *v2.Cluster, cf clusterfile.Interface, images []string) (Interface, error) {
if cluster.Name == "" {
return nil, fmt.Errorf("cluster name cannot be empty")
}
@@ -47,6 +47,7 @@ func NewDefaultApplier(cluster *v2.Cluster, cf clusterfile.Interface, images []s
}
return &Applier{
Context: ctx,
ClusterDesired: cluster,
ClusterFile: cf,
ClusterCurrent: cf.GetCluster(),
@@ -54,12 +55,13 @@ func NewDefaultApplier(cluster *v2.Cluster, cf clusterfile.Interface, images []s
}, nil
}
func NewDefaultScaleApplier(current, cluster *v2.Cluster) (Interface, error) {
func NewDefaultScaleApplier(ctx context.Context, current, cluster *v2.Cluster) (Interface, error) {
if cluster.Name == "" {
cluster.Name = current.Name
}
cFile := clusterfile.NewClusterFile(constants.Clusterfile(cluster.Name))
return &Applier{
Context: ctx,
ClusterDesired: cluster,
ClusterFile: cFile,
ClusterCurrent: current,
@@ -67,6 +69,7 @@ func NewDefaultScaleApplier(current, cluster *v2.Cluster) (Interface, error) {
}
type Applier struct {
context.Context
ClusterDesired *v2.Cluster
ClusterCurrent *v2.Cluster
ClusterFile clusterfile.Interface
@@ -185,7 +188,7 @@ func (c *Applier) reconcileCluster() (clusterErr error, appErr error) {
func (c *Applier) initCluster() error {
logger.Info("Start to create a new cluster: master %s, worker %s, registry %s", c.ClusterDesired.GetMasterIPList(), c.ClusterDesired.GetNodeIPList(), c.ClusterDesired.GetRegistryIP())
createProcessor, err := processor.NewCreateProcessor(c.ClusterDesired.Name, c.ClusterFile)
createProcessor, err := processor.NewCreateProcessor(c.Context, c.ClusterDesired.Name, c.ClusterFile)
if err != nil {
return err
}
@@ -205,7 +208,7 @@ func (c *Applier) installApp(images []string) error {
if err != nil {
return err
}
installProcessor, err := processor.NewInstallProcessor(c.ClusterFile, images)
installProcessor, err := processor.NewInstallProcessor(c.Context, c.ClusterFile, images)
if err != nil {
return err
}
-4
View File
@@ -60,7 +60,6 @@ type RunArgs struct {
CustomEnv []string
CustomCMD []string
CustomConfigFiles []string
fs *pflag.FlagSet
}
func (arg *RunArgs) RegisterFlags(fs *pflag.FlagSet) {
@@ -69,7 +68,6 @@ func (arg *RunArgs) RegisterFlags(fs *pflag.FlagSet) {
fs.StringSliceVarP(&arg.CustomEnv, "env", "e", []string{}, "environment variables to set during command execution")
fs.StringSliceVar(&arg.CustomCMD, "cmd", []string{}, "override CMD directive in images")
fs.StringSliceVar(&arg.CustomConfigFiles, "config-file", []string{}, "path of custom config files, to use to replace the resource")
arg.fs = fs
}
type Args struct {
@@ -89,13 +87,11 @@ func (arg *Args) RegisterFlags(fs *pflag.FlagSet) {
type ResetArgs struct {
*Cluster
*SSH
fs *pflag.FlagSet
}
func (arg *ResetArgs) RegisterFlags(fs *pflag.FlagSet) {
arg.Cluster.RegisterFlags(fs, "be reset", "reset")
arg.SSH.RegisterFlags(fs)
arg.fs = fs
}
type ScaleArgs struct {
+4 -3
View File
@@ -19,15 +19,16 @@ package apply
import (
"fmt"
"github.com/labring/sealos/pkg/utils/iputils"
"github.com/spf13/cobra"
"github.com/labring/sealos/pkg/apply/processor"
"github.com/labring/sealos/pkg/buildah"
"github.com/labring/sealos/pkg/runtime"
"github.com/labring/sealos/pkg/types/v1beta1"
"github.com/labring/sealos/pkg/utils/iputils"
)
func NewClusterFromGenArgs(imageNames []string, args *RunArgs) ([]byte, error) {
func NewClusterFromGenArgs(cmd *cobra.Command, args *RunArgs, imageNames []string) ([]byte, error) {
cluster := initCluster(args.ClusterName)
c := &ClusterArgs{
clusterName: args.ClusterName,
@@ -39,7 +40,7 @@ func NewClusterFromGenArgs(imageNames []string, args *RunArgs) ([]byte, error) {
args.Cluster.Masters = localIpv4
}
if err := c.runArgs(imageNames, args); err != nil {
if err := c.runArgs(cmd, args, imageNames); err != nil {
return nil, err
}
+3 -3
View File
@@ -48,7 +48,6 @@ func TestNewClusterFromGenArgs(t *testing.T) {
CustomEnv: nil,
CustomCMD: nil,
CustomConfigFiles: nil,
fs: nil,
},
},
wantErr: false,
@@ -67,7 +66,6 @@ func TestNewClusterFromGenArgs(t *testing.T) {
CustomEnv: nil,
CustomCMD: nil,
CustomConfigFiles: nil,
fs: nil,
},
},
wantErr: false,
@@ -79,7 +77,9 @@ func TestNewClusterFromGenArgs(t *testing.T) {
Short: "test",
})
t.Run(tt.name, func(t *testing.T) {
got, _ := NewClusterFromGenArgs(tt.args.imageName, tt.args.args)
got, _ := NewClusterFromGenArgs(&cobra.Command{
Use: "mock",
}, tt.args.args, tt.args.imageName)
t.Logf("%s", string(got))
})
}
+47
View File
@@ -0,0 +1,47 @@
/*
Copyright 2023 fengxsong@outlook.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 processor
import "context"
var commandKey struct{}
func WithCommands(ctx context.Context, commands []string) context.Context {
return context.WithValue(ctx, commandKey, commands)
}
func GetCommands(ctx context.Context) []string {
v := ctx.Value(commandKey)
if v != nil {
return v.([]string)
}
return nil
}
var envKey struct{}
func WithEnvs(ctx context.Context, envs map[string]string) context.Context {
return context.WithValue(ctx, envKey, envs)
}
func GetEnvs(ctx context.Context) map[string]string {
v := ctx.Value(commandKey)
if v != nil {
return v.(map[string]string)
}
return nil
}
+9 -2
View File
@@ -31,6 +31,7 @@ import (
"github.com/labring/sealos/pkg/runtime"
v2 "github.com/labring/sealos/pkg/types/v1beta1"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/utils/maps"
"github.com/labring/sealos/pkg/utils/yaml"
)
@@ -39,6 +40,7 @@ type CreateProcessor struct {
Buildah buildah.Interface
Runtime runtime.Interface
Guest guest.Interface
ExtraEnvs map[string]string // parsing from CLI arguments
}
func (c *CreateProcessor) Execute(cluster *v2.Cluster) error {
@@ -94,6 +96,10 @@ func (c *CreateProcessor) preProcess(cluster *v2.Cluster) error {
if err := MountClusterImages(c.Buildah, cluster, false); err != nil {
return err
}
// extra env must been set at the very first
for i := range cluster.Status.Mounts {
cluster.Status.Mounts[i].Env = maps.MergeMap(cluster.Status.Mounts[i].Env, c.ExtraEnvs)
}
runTime, err := runtime.NewDefaultRuntime(cluster, c.ClusterFile.GetKubeadmConfig())
if err != nil {
return fmt.Errorf("failed to init runtime, %v", err)
@@ -162,14 +168,14 @@ func (c *CreateProcessor) Join(cluster *v2.Cluster) error {
func (c *CreateProcessor) RunGuest(cluster *v2.Cluster) error {
logger.Info("Executing pipeline RunGuest in CreateProcessor.")
err := c.Guest.Apply(cluster, cluster.Status.Mounts)
err := c.Guest.Apply(cluster, cluster.Status.Mounts, cluster.GetAllIPS())
if err != nil {
return fmt.Errorf("%s: %w", RunGuestFailed, err)
}
return nil
}
func NewCreateProcessor(name string, clusterFile clusterfile.Interface) (Interface, error) {
func NewCreateProcessor(ctx context.Context, name string, clusterFile clusterfile.Interface) (Interface, error) {
bder, err := buildah.New(name)
if err != nil {
return nil, err
@@ -183,5 +189,6 @@ func NewCreateProcessor(name string, clusterFile clusterfile.Interface) (Interfa
ClusterFile: clusterFile,
Buildah: bder,
Guest: gs,
ExtraEnvs: GetEnvs(ctx),
}, nil
}
-47
View File
@@ -1,47 +0,0 @@
/*
Copyright 2022.
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 processor
type CheckError struct {
err error
}
func (e *CheckError) Error() string {
return e.err.Error()
}
func NewCheckError(err error) error {
if err != nil {
err = &CheckError{err: err}
}
return err
}
type PreProcessError struct {
err error
}
func (e *PreProcessError) Error() string {
return e.err.Error()
}
func NewPreProcessError(err error) error {
if err != nil {
err = &PreProcessError{err: err}
}
return err
}
+30
View File
@@ -33,3 +33,33 @@ func IsRunGuestFailed(err error) bool {
}
var ErrCancelled = errors.New("cancelled")
type CheckError struct {
err error
}
func (e *CheckError) Error() string {
return e.err.Error()
}
func NewCheckError(err error) error {
if err != nil {
err = &CheckError{err: err}
}
return err
}
type PreProcessError struct {
err error
}
func (e *PreProcessError) Error() string {
return e.err.Error()
}
func NewPreProcessError(err error) error {
if err != nil {
err = &PreProcessError{err: err}
}
return err
}
+7 -2
View File
@@ -31,6 +31,7 @@ import (
v2 "github.com/labring/sealos/pkg/types/v1beta1"
"github.com/labring/sealos/pkg/utils/confirm"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/utils/maps"
"github.com/labring/sealos/pkg/utils/rand"
)
@@ -43,6 +44,7 @@ type InstallProcessor struct {
Guest guest.Interface
NewMounts []v2.MountImage
NewImages []string
ExtraEnvs map[string]string // parsing from CLI arguments
imagesToOverride []string
}
@@ -162,6 +164,8 @@ func (c *InstallProcessor) PreProcess(cluster *v2.Cluster) error {
if err = OCIToImageMount(c.Buildah, mount); err != nil {
return err
}
mount.Env = maps.MergeMap(mount.Env, c.ExtraEnvs)
cluster.SetMountImage(mount)
c.NewMounts = append(c.NewMounts, *mount)
}
@@ -248,10 +252,10 @@ func (c *InstallProcessor) RunGuest(cluster *v2.Cluster) error {
if len(c.NewMounts) == 0 {
return nil
}
return c.Guest.Apply(cluster, c.NewMounts)
return c.Guest.Apply(cluster, c.NewMounts, cluster.GetAllIPS())
}
func NewInstallProcessor(clusterFile clusterfile.Interface, images []string) (Interface, error) {
func NewInstallProcessor(ctx context.Context, clusterFile clusterfile.Interface, images []string) (Interface, error) {
bder, err := buildah.New(clusterFile.GetCluster().Name)
if err != nil {
return nil, err
@@ -267,5 +271,6 @@ func NewInstallProcessor(clusterFile clusterfile.Interface, images []string) (In
Buildah: bder,
Guest: gs,
NewImages: images,
ExtraEnvs: GetEnvs(ctx),
}, nil
}
+31
View File
@@ -18,6 +18,8 @@ import (
"context"
"fmt"
"github.com/labring/sealos/pkg/guest"
"golang.org/x/sync/errgroup"
"github.com/labring/sealos/pkg/bootstrap"
@@ -44,6 +46,7 @@ type ScaleProcessor struct {
NodesToJoin []string
NodesToDelete []string
IsScaleUp bool
Guest guest.Interface
}
func (c *ScaleProcessor) Execute(cluster *v2.Cluster) error {
@@ -73,6 +76,7 @@ func (c *ScaleProcessor) GetPipeLine() ([]func(cluster *v2.Cluster) error, error
c.Bootstrap,
//s.GetPhasePluginFunc(plugin.PhasePreJoin),
c.Join,
c.RunGuest,
//s.GetPhasePluginFunc(plugin.PhasePostJoin),
)
return todoList, nil
@@ -89,6 +93,27 @@ func (c *ScaleProcessor) GetPipeLine() ([]func(cluster *v2.Cluster) error, error
return todoList, nil
}
func (c *ScaleProcessor) skipAppMounts(allMount []v2.MountImage) []v2.MountImage {
mounts := make([]v2.MountImage, 0)
for _, m := range allMount {
if !m.IsApplication() {
mount := m.DeepCopy()
mounts = append(mounts, *mount)
}
}
return mounts
}
func (c *ScaleProcessor) RunGuest(cluster *v2.Cluster) error {
logger.Info("Executing pipeline RunGuest in ScaleProcessor.")
hosts := append(c.MastersToJoin, c.NodesToJoin...)
err := c.Guest.Apply(cluster, c.skipAppMounts(cluster.Status.Mounts), hosts)
if err != nil {
return fmt.Errorf("%s: %w", RunGuestFailed, err)
}
return nil
}
func (c *ScaleProcessor) Delete(cluster *v2.Cluster) error {
logger.Info("Executing pipeline Delete in ScaleProcessor.")
err := c.Runtime.DeleteMasters(c.MastersToDelete)
@@ -275,6 +300,11 @@ func NewScaleProcessor(clusterFile clusterfile.Interface, name string, images v2
if err != nil {
return nil, err
}
gs, err := guest.NewGuestManager()
if err != nil {
return nil, err
}
return &ScaleProcessor{
MastersToDelete: masterToDelete,
MastersToJoin: masterToJoin,
@@ -284,5 +314,6 @@ func NewScaleProcessor(clusterFile clusterfile.Interface, name string, images v2
Buildah: bder,
pullImages: images,
IsScaleUp: len(masterToJoin) > 0 || len(nodeToJoin) > 0,
Guest: gs,
}, nil
}
+9 -20
View File
@@ -17,6 +17,8 @@ package apply
import (
"fmt"
"github.com/spf13/cobra"
"github.com/labring/sealos/pkg/apply/applydrivers"
"github.com/labring/sealos/pkg/clusterfile"
"github.com/labring/sealos/pkg/constants"
@@ -26,7 +28,7 @@ import (
stringsutil "github.com/labring/sealos/pkg/utils/strings"
)
func NewApplierFromResetArgs(args *ResetArgs) (applydrivers.Interface, error) {
func NewApplierFromResetArgs(cmd *cobra.Command, args *ResetArgs) (applydrivers.Interface, error) {
clusterPath := constants.Clusterfile(args.ClusterName)
cf := clusterfile.NewClusterFile(clusterPath)
err := cf.Process()
@@ -41,35 +43,22 @@ func NewApplierFromResetArgs(args *ResetArgs) (applydrivers.Interface, error) {
clusterName: cluster.Name,
cluster: cluster,
}
if err = c.resetArgs(args); err != nil {
if err = c.resetArgs(cmd, args); err != nil {
return nil, err
}
return applydrivers.NewDefaultApplier(c.cluster, cf, nil)
return applydrivers.NewDefaultApplier(cmd.Context(), c.cluster, cf, nil)
}
func (r *ClusterArgs) resetArgs(args *ResetArgs) error {
func (r *ClusterArgs) resetArgs(cmd *cobra.Command, args *ResetArgs) error {
if args.Cluster.ClusterName == "" {
return fmt.Errorf("cluster name can not be empty")
}
if err := PreProcessIPList(args.Cluster); err != nil {
return err
}
if args.fs != nil {
if args.fs.Changed("user") || r.cluster.Spec.SSH.User == "" {
r.cluster.Spec.SSH.User = args.SSH.User
}
if args.fs.Changed("pk") || r.cluster.Spec.SSH.Pk == "" {
r.cluster.Spec.SSH.Pk = args.SSH.Pk
}
if args.fs.Changed("pk-passwd") || r.cluster.Spec.SSH.PkPasswd == "" {
r.cluster.Spec.SSH.PkPasswd = args.SSH.PkPassword
}
if args.fs.Changed("port") || r.cluster.Spec.SSH.Port == 0 {
r.cluster.Spec.SSH.Port = args.SSH.Port
}
if args.fs.Changed("passwd") || r.cluster.Spec.SSH.Passwd == "" {
r.cluster.Spec.SSH.Passwd = args.SSH.Password
}
override := getSSHFromCommand(cmd)
if override != nil {
r.cluster.Spec.SSH = *override
}
if len(args.Cluster.Masters) > 0 {
+5 -1
View File
@@ -18,6 +18,8 @@ package apply
import (
"testing"
"github.com/spf13/cobra"
)
func TestNewApplierFromResetArgs(t *testing.T) {
@@ -70,7 +72,9 @@ func TestNewApplierFromResetArgs(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewApplierFromResetArgs(tt.args.args)
_, err := NewApplierFromResetArgs(&cobra.Command{
Use: "mock",
}, tt.args.args)
if (err != nil) != tt.wantErr {
t.Errorf("NewApplierFromResetArgs() error = %v, wantErr %v", err, tt.wantErr)
return
+34 -33
View File
@@ -15,17 +15,23 @@
package apply
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"github.com/spf13/cobra"
"github.com/labring/sealos/pkg/apply/applydrivers"
"github.com/labring/sealos/pkg/apply/processor"
"github.com/labring/sealos/pkg/clusterfile"
"github.com/labring/sealos/pkg/constants"
"github.com/labring/sealos/pkg/ssh"
v2 "github.com/labring/sealos/pkg/types/v1beta1"
"github.com/labring/sealos/pkg/utils/iputils"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/utils/maps"
stringsutil "github.com/labring/sealos/pkg/utils/strings"
)
@@ -35,7 +41,7 @@ type ClusterArgs struct {
clusterName string
}
func NewApplierFromArgs(imageName []string, args *RunArgs) (applydrivers.Interface, error) {
func NewApplierFromArgs(cmd *cobra.Command, args *RunArgs, imageName []string) (applydrivers.Interface, error) {
clusterPath := constants.Clusterfile(args.ClusterName)
cf := clusterfile.NewClusterFile(clusterPath,
clusterfile.WithCustomConfigFiles(args.CustomConfigFiles),
@@ -61,23 +67,38 @@ func NewApplierFromArgs(imageName []string, args *RunArgs) (applydrivers.Interfa
clusterName: cluster.Name,
cluster: cluster,
}
if err = c.runArgs(imageName, args); err != nil {
if err = c.runArgs(cmd, args, imageName); err != nil {
return nil, err
}
return applydrivers.NewDefaultApplier(c.cluster, cf, imageName)
ctx := withCommonContext(cmd.Context(), cmd)
return applydrivers.NewDefaultApplier(ctx, c.cluster, cf, imageName)
}
func (r *ClusterArgs) runArgs(imageList []string, args *RunArgs) error {
if args.Cluster.ClusterName == "" {
return fmt.Errorf("cluster name can not be empty")
func withCommonContext(ctx context.Context, cmd *cobra.Command) context.Context {
if flagChanged(cmd, "cmd") {
v, _ := cmd.Flags().GetStringSlice("cmd")
ctx = processor.WithCommands(ctx, v)
}
//the first run check
if flagChanged(cmd, "env") {
v, _ := cmd.Flags().GetStringSlice("env")
ctx = processor.WithEnvs(ctx, maps.ListToMap(v))
}
return ctx
}
func (r *ClusterArgs) runArgs(cmd *cobra.Command, args *RunArgs, imageList []string) error {
if args.Cluster.ClusterName == "" {
return errors.New("cluster name can not be empty")
}
// the first run check
if r.cluster.CreationTimestamp.IsZero() {
if len(imageList) == 0 {
return fmt.Errorf("image can not be empty")
return errors.New("image can not be empty")
}
if len(args.Cluster.Masters) == 0 {
return fmt.Errorf("master ip(s) must specified")
return errors.New("master ip(s) must specified")
}
} else {
if r.cluster.Status.Phase != v2.ClusterSuccess {
@@ -88,30 +109,10 @@ func (r *ClusterArgs) runArgs(imageList []string, args *RunArgs) error {
if err := PreProcessIPList(args.Cluster); err != nil {
return err
}
if args.fs != nil {
if args.fs.Changed("env") || len(r.cluster.Spec.Env) == 0 {
r.cluster.Spec.Env = make([]string, len(args.CustomEnv))
copy(r.cluster.Spec.Env, args.CustomEnv)
}
if args.fs.Changed("cmd") || len(r.cluster.Spec.Command) == 0 {
r.cluster.Spec.Command = make([]string, len(args.CustomCMD))
copy(r.cluster.Spec.Command, args.CustomCMD)
}
if args.fs.Changed("user") || r.cluster.Spec.SSH.User == "" {
r.cluster.Spec.SSH.User = args.SSH.User
}
if args.fs.Changed("pk") || r.cluster.Spec.SSH.Pk == "" {
r.cluster.Spec.SSH.Pk = args.SSH.Pk
}
if args.fs.Changed("pk-passwd") || r.cluster.Spec.SSH.PkPasswd == "" {
r.cluster.Spec.SSH.PkPasswd = args.SSH.PkPassword
}
if args.fs.Changed("port") || r.cluster.Spec.SSH.Port == 0 {
r.cluster.Spec.SSH.Port = args.SSH.Port
}
if args.fs.Changed("passwd") || r.cluster.Spec.SSH.Passwd == "" {
r.cluster.Spec.SSH.Passwd = args.SSH.Password
}
override := getSSHFromCommand(cmd)
if override != nil {
r.cluster.Spec.SSH = *override
}
r.cluster.SetNewImages(imageList)
+7 -4
View File
@@ -18,7 +18,7 @@ import (
"reflect"
"testing"
"github.com/spf13/pflag"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/labring/sealos/pkg/clusterfile"
@@ -79,7 +79,6 @@ func TestClusterArgs_SetClusterRunArgs(t *testing.T) {
CustomCMD: []string{
"echo hello",
},
fs: pflag.NewFlagSet("test", pflag.ExitOnError),
},
},
wantErr: false,
@@ -92,7 +91,9 @@ func TestClusterArgs_SetClusterRunArgs(t *testing.T) {
hosts: tt.fields.hosts,
clusterName: tt.fields.clusterName,
}
if err := r.runArgs(tt.args.imageList, tt.args.runArgs); (err != nil) != tt.wantErr {
if err := r.runArgs(&cobra.Command{
Use: "mock",
}, tt.args.runArgs, tt.args.imageList); (err != nil) != tt.wantErr {
t.Errorf("runArgs() error = %v, wantErr %v", err, tt.wantErr)
}
})
@@ -182,7 +183,9 @@ func TestNewApplierFromArgs(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewApplierFromArgs(tt.args.imageName, tt.args.args)
got, err := NewApplierFromArgs(&cobra.Command{
Use: "mock",
}, tt.args.args, tt.args.imageName)
if (err != nil) != tt.wantErr {
t.Errorf("NewApplierFromArgs() error = %v, wantErr %v", err, tt.wantErr)
return
+1 -1
View File
@@ -65,7 +65,7 @@ func NewScaleApplierFromArgs(cmd *cobra.Command, scaleArgs *ScaleArgs) (applydri
return nil, err
}
return applydrivers.NewDefaultScaleApplier(curr, cluster)
return applydrivers.NewDefaultScaleApplier(cmd.Context(), curr, cluster)
}
func getSSHFromCommand(cmd *cobra.Command) *v2.SSH {
+13 -2
View File
@@ -20,6 +20,8 @@ import (
"github.com/labring/sealos/pkg/remote"
"github.com/labring/sealos/pkg/ssh"
v2 "github.com/labring/sealos/pkg/types/v1beta1"
"github.com/labring/sealos/pkg/utils/maps"
stringsutil "github.com/labring/sealos/pkg/utils/strings"
)
type Context interface {
@@ -60,12 +62,21 @@ func (ctx realContext) GetRemoter() remote.Interface {
func NewContextFrom(cluster *v2.Cluster) Context {
execer := ssh.NewSSHByCluster(cluster, true)
envProcessor := env.NewEnvProcessor(cluster, cluster.Status.Mounts)
envProcessor := env.NewEnvProcessor(cluster)
remoter := remote.New(cluster.GetName(), execer)
rootfsImage := cluster.GetRootfsImage()
rootfsEnvs := v2.MergeEnvWithBuiltinKeys(rootfsImage.Env, *rootfsImage)
// bootstrap process depends on the envs in the rootfs image
shellWrapper := func(host, shell string) string {
envs := maps.MergeMap(rootfsEnvs, envProcessor.Getenv(host))
return stringsutil.RenderShellFromEnv(shell, envs)
}
return &realContext{
cluster: cluster,
execer: execer,
bash: constants.NewBash(cluster.GetName(), cluster.GetImageLabels(), envProcessor.WrapperShell),
bash: constants.NewBash(cluster.GetName(), cluster.GetImageLabels(), shellWrapper),
data: constants.NewData(cluster.GetName()),
remoter: remoter,
}
+52 -38
View File
@@ -22,53 +22,55 @@ import (
"path/filepath"
"strings"
"github.com/labring/sealos/pkg/version"
"github.com/labring/sealos/pkg/template"
"github.com/labring/sealos/pkg/types/v1beta1"
fileutil "github.com/labring/sealos/pkg/utils/file"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/utils/maps"
strings2 "github.com/labring/sealos/pkg/utils/strings"
stringsutil "github.com/labring/sealos/pkg/utils/strings"
)
const templateSuffix = ".tmpl"
type Interface interface {
// WrapperShell :If host already set env like DATADISK=/data
// WrapShell :If host already set env like DATADISK=/data
// This function add env to the shell, like:
// Input shell: cat /etc/hosts
// Output shell: DATADISK=/data cat /etc/hosts
// So that you can get env values in you shell script
WrapperShell(host, shell string) string
WrapShell(host, shell string) string
// RenderAll :render env to all the files in dir
RenderAll(host, dir string) error
WrapperEnv(host string) map[string]string
RenderAll(host, dir string, envs map[string]string) error
Getenv(host string) map[string]string
}
type processor struct {
*v1beta1.Cluster
//types.ImageListOCIV1
mounts []v1beta1.MountImage
cache map[string]map[string]string
}
func NewEnvProcessor(cluster *v1beta1.Cluster, mounts []v1beta1.MountImage) Interface {
return &processor{Cluster: cluster, mounts: mounts}
func NewEnvProcessor(cluster *v1beta1.Cluster) Interface {
return &processor{
Cluster: cluster,
cache: make(map[string]map[string]string),
}
}
func (p *processor) WrapperEnv(host string) map[string]string {
func (p *processor) Getenv(host string) map[string]string {
env := make(map[string]string)
envs := p.getHostEnv(host)
envs := p.getHostEnvInCache(host)
for k, v := range envs {
env[k] = v
}
return env
}
func (p *processor) WrapperShell(host, shell string) string {
envs := p.getHostEnv(host)
return strings2.RenderShellFromEnv(shell, envs)
func (p *processor) WrapShell(host, shell string) string {
envs := p.getHostEnvInCache(host)
return stringsutil.RenderShellFromEnv(shell, envs)
}
func (p *processor) RenderAll(host, dir string) error {
func (p *processor) RenderAll(host, dir string, envs map[string]string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, errIn error) error {
if errIn != nil {
return errIn
@@ -100,19 +102,30 @@ func (p *processor) RenderAll(host, dir string) error {
return fmt.Errorf("failed to create template: %s %v", path, err)
}
if host != "" {
if err := t.Execute(writer, p.getHostEnv(host)); err != nil {
data := maps.MergeMap(envs, p.getHostEnvInCache(host))
if err := t.Execute(writer, data); err != nil {
return fmt.Errorf("failed to render env template: %s %v", path, err)
}
}
} else {
return errors.New("convert template failed")
return errors.New("parse template failed")
}
return nil
})
}
func (p *processor) getHostEnvInCache(hostIP string) map[string]string {
if v, ok := p.cache[hostIP]; ok {
return v
}
v := p.getHostEnv(hostIP)
p.cache[hostIP] = v
return v
}
// Merge the host ENV and global env, the host env will overwrite cluster.Spec.Env
func (p *processor) getHostEnv(hostIP string) map[string]string {
// TODO: what if hostIP not found?
var hostEnv []string
for _, host := range p.Spec.Hosts {
for _, ip := range host.IPS {
@@ -121,30 +134,31 @@ func (p *processor) getHostEnv(hostIP string) map[string]string {
}
}
}
hostEnvMap := maps.ListToMap(hostEnv)
specEnvMap := maps.ListToMap(p.Spec.Env)
var imageEnvMap map[string]string
for _, img := range p.mounts {
imageEnvMap = maps.MergeMap(imageEnvMap, img.Env)
if img.Type == v1beta1.RootfsImage {
imageEnvMap[v1beta1.ImageKubeVersionEnvSysKey] = img.Labels[v1beta1.ImageKubeVersionKey]
imageEnvMap[v1beta1.ImageSealosVersionEnvSysKey] = version.Get().GitVersion
excludeSysEnv := func(m map[string]string) map[string]string {
m, exclude := ExcludeKeysWithPrefix(m, "SEALOS_SYS")
if len(exclude) > 0 {
logger.Warn("skip %s cause envs with prefix SEALOS_SYS are sealos system only", strings.Join(exclude, ", "))
}
return m
}
filterSysEnv := func(env map[string]string) map[string]string {
outEnv := make(map[string]string, 0)
for k, v := range env {
if strings.HasPrefix(k, "SEALOS_SYS") {
logger.Warn("skip %s env , SEALOS_SYS prefix env is sealos system env", k)
} else {
outEnv[k] = v
}
}
return outEnv
}
envs := maps.MergeMap(imageEnvMap, filterSysEnv(specEnvMap), filterSysEnv(hostEnvMap))
envs := maps.MergeMap(excludeSysEnv(specEnvMap), excludeSysEnv(hostEnvMap))
return envs
}
func ExcludeKeysWithPrefix(m map[string]string, prefix string) (map[string]string, []string) {
out := make(map[string]string, 0)
var exclude []string
for k, v := range m {
if strings.HasPrefix(k, prefix) {
exclude = append(exclude, k)
} else {
out[k] = v
}
}
return out, exclude
}
+2 -2
View File
@@ -145,9 +145,9 @@ func Test_shim_RenderAll(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
p := &processor{
Cluster: tt.fields.Cluster,
mounts: tt.fields.Cluster.Status.Mounts,
cache: make(map[string]map[string]string),
}
if err := p.RenderAll(tt.args.host, tt.args.dir); (err != nil) != tt.wantErr {
if err := p.RenderAll(tt.args.host, tt.args.dir, map[string]string{}); (err != nil) != tt.wantErr {
t.Errorf("RenderAll() error = %v, wantErr %v", err, tt.wantErr)
}
})
+4 -2
View File
@@ -70,8 +70,9 @@ func Test_processor_WrapperShell(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
p := &processor{
Cluster: tt.fields.Cluster,
cache: make(map[string]map[string]string),
}
got := p.WrapperShell(tt.args.host, tt.args.shell)
got := p.WrapShell(tt.args.host, tt.args.shell)
for _, want := range tt.want {
if !strings.Contains(got, want) {
t.Errorf("WrapperShell() = %v, want %v", got, want)
@@ -110,8 +111,9 @@ func Test_processor_RenderAll(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
p := &processor{
Cluster: tt.fields.Cluster,
cache: make(map[string]map[string]string),
}
if err := p.RenderAll(tt.args.host, tt.args.dir); (err != nil) != tt.wantErr {
if err := p.RenderAll(tt.args.host, tt.args.dir, nil); (err != nil) != tt.wantErr {
t.Errorf("RenderAll() error = %v, wantErr %v", err, tt.wantErr)
}
})
+26 -27
View File
@@ -58,7 +58,7 @@ func (f *defaultRootfs) mountRootfs(cluster *v2.Cluster, ipList []string) error
target := constants.NewData(f.getClusterName(cluster)).RootFSPath()
ctx := context.Background()
eg, _ := errgroup.WithContext(ctx)
envProcessor := env.NewEnvProcessor(cluster, f.mounts)
envProcessor := env.NewEnvProcessor(cluster)
for _, mount := range f.mounts {
src := mount
eg.Go(func() error {
@@ -68,7 +68,8 @@ func (f *defaultRootfs) mountRootfs(cluster *v2.Cluster, ipList []string) error
}
// TODO: if we are planing to support rendering templates for each host,
// then move this rendering process before ssh.CopyDir and do it one by one.
err := renderTemplatesWithEnv(src.MountPoint, ipList, envProcessor)
envs := v2.MergeEnvWithBuiltinKeys(src.Env, src)
err := renderTemplatesWithEnv(src.MountPoint, ipList, envProcessor, envs)
if err != nil {
return fmt.Errorf("failed to render env: %w", err)
}
@@ -93,47 +94,45 @@ func (f *defaultRootfs) mountRootfs(cluster *v2.Cluster, ipList []string) error
notRegistryDirFilter := func(entry fs.DirEntry) bool { return !constants.IsRegistryDir(entry) }
copyFn := func(m v2.MountImage, targetHost, targetDir string) error {
logger.Debug("send mount image, target: %s, image: %s, type: %s", targetHost, m.ImageName, m.Type)
if err := ssh.CopyDir(sshClient, targetHost, m.MountPoint, targetDir, notRegistryDirFilter); err != nil {
logger.Error("error occur while sending mount image %s: %v", m.Name, err)
return err
}
return nil
}
for idx := range ipList {
ip := ipList[idx]
eg.Go(func() error {
egg, _ := errgroup.WithContext(ctx)
for idj := range f.mounts {
mount := f.mounts[idj]
egg.Go(func() error {
switch mount.Type {
case v2.RootfsImage, v2.PatchImage:
logger.Debug("send mount image, ip: %s, image name: %s, image type: %s", ip, mount.ImageName, mount.Type)
err := ssh.CopyDir(sshClient, ip, mount.MountPoint, target, notRegistryDirFilter)
if err != nil {
return fmt.Errorf("failed to copy %s %s: %v", mount.Type, mount.Name, err)
}
for i := range f.mounts {
if f.mounts[i].IsRootFs() || f.mounts[i].IsPatch() {
// contents in rootfs/patch type images cannot be replicated asynchronously
if err := copyFn(f.mounts[i], ip, target); err != nil {
return err
}
return nil
})
}
}
return egg.Wait()
return nil
})
}
if err := eg.Wait(); err != nil {
return err
}
endEg, _ := errgroup.WithContext(ctx)
eg, _ = errgroup.WithContext(ctx)
master0 := cluster.GetMaster0IPAndPort()
for idx := range f.mounts {
mountInfo := f.mounts[idx]
endEg.Go(func() error {
if mountInfo.Type == v2.AppImage {
logger.Debug("send app mount images, ip: %s, image name: %s, image type: %s", master0, mountInfo.ImageName, mountInfo.Type)
err := ssh.CopyDir(sshClient, master0, mountInfo.MountPoint, constants.GetAppWorkDir(cluster.Name, mountInfo.Name), notRegistryDirFilter)
if err != nil {
return fmt.Errorf("failed to copy %s %s: %v", mountInfo.Type, mountInfo.Name, err)
}
eg.Go(func() error {
if mountInfo.IsApplication() {
return copyFn(mountInfo, master0, constants.GetAppWorkDir(cluster.Name, mountInfo.Name))
}
return nil
})
}
return endEg.Wait()
return eg.Wait()
}
func (f *defaultRootfs) unmountRootfs(cluster *v2.Cluster, ipList []string) error {
@@ -152,7 +151,7 @@ func (f *defaultRootfs) unmountRootfs(cluster *v2.Cluster, ipList []string) erro
return eg.Wait()
}
func renderTemplatesWithEnv(mountDir string, ipList []string, p env.Interface) error {
func renderTemplatesWithEnv(mountDir string, ipList []string, p env.Interface, envs map[string]string) error {
var (
renderEtc = path.Join(mountDir, constants.EtcDirName)
renderScripts = path.Join(mountDir, constants.ScriptsDirName)
@@ -163,7 +162,7 @@ func renderTemplatesWithEnv(mountDir string, ipList []string, p env.Interface) e
for _, dir := range []string{renderEtc, renderScripts, renderManifests} {
logger.Debug("render env dir: %s", dir)
if file.IsExist(dir) {
err := p.RenderAll(ipList[0], dir)
err := p.RenderAll(ipList[0], dir, envs)
if err != nil {
return err
}
+64 -42
View File
@@ -15,10 +15,13 @@
package guest
import (
"context"
"fmt"
"path/filepath"
"strings"
"golang.org/x/sync/errgroup"
"github.com/labring/sealos/fork/golang/expansion"
"github.com/labring/sealos/pkg/constants"
"github.com/labring/sealos/pkg/env"
@@ -26,27 +29,22 @@ import (
"github.com/labring/sealos/pkg/ssh"
v2 "github.com/labring/sealos/pkg/types/v1beta1"
fileutil "github.com/labring/sealos/pkg/utils/file"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/utils/maps"
stringsutil "github.com/labring/sealos/pkg/utils/strings"
)
type Interface interface {
Apply(cluster *v2.Cluster, mounts []v2.MountImage) error
Apply(cluster *v2.Cluster, mounts []v2.MountImage, targetHosts []string) error
Delete(cluster *v2.Cluster) error
}
type Default struct {
}
type Default struct{}
func NewGuestManager() (Interface, error) {
return &Default{}, nil
}
func (d *Default) Apply(cluster *v2.Cluster, mounts []v2.MountImage) error {
envInterface := env.NewEnvProcessor(cluster, cluster.Status.Mounts)
envs := envInterface.WrapperEnv(cluster.GetMaster0IP()) //clusterfile
guestCMD := d.getGuestCmd(envs, cluster, mounts)
func (d *Default) Apply(cluster *v2.Cluster, mounts []v2.MountImage, targetHosts []string) error {
kubeConfig := filepath.Join(constants.GetHomeDir(), ".kube", "config")
if !fileutil.IsExist(kubeConfig) {
adminFile := constants.NewData(cluster.Name).AdminFile()
@@ -63,50 +61,74 @@ func (d *Default) Apply(cluster *v2.Cluster, mounts []v2.MountImage) error {
_ = fileutil.CleanFiles(kubeConfig)
}()
}
sshInterface := ssh.NewSSHByCluster(cluster, true)
for _, cmd := range guestCMD {
logger.Debug("exec guest command: %s", cmd)
if err := sshInterface.CmdAsync(cluster.GetMaster0IPAndPort(), envInterface.WrapperShell(cluster.GetMaster0IP(), cmd)); err != nil {
return err
envWrapper := env.NewEnvProcessor(cluster)
execer := ssh.NewSSHByCluster(cluster, true)
for i, m := range mounts {
switch {
case m.IsRootFs(), m.IsPatch():
eg, ctx := errgroup.WithContext(context.Background())
for j := range targetHosts {
node := targetHosts[j]
envs := envWrapper.Getenv(node)
cmds := formalizeImageCommands(cluster, i, m, envs)
eg.Go(func() error {
return execer.CmdAsyncWithContext(ctx, node,
stringsutil.RenderShellFromEnv(strings.Join(cmds, "; "), envs),
)
})
}
if err := eg.Wait(); err != nil {
return err
}
case m.IsApplication():
// on run on the first master
envs := envWrapper.Getenv(cluster.GetMaster0IP())
cmds := formalizeImageCommands(cluster, i, m, envs)
if err := execer.CmdAsync(cluster.GetMaster0IPAndPort(),
stringsutil.RenderShellFromEnv(strings.Join(cmds, "; "), envs),
); err != nil {
return err
}
}
}
logger.Debug("finish to exec guest commands: %v", guestCMD)
return nil
}
func (d *Default) getGuestCmd(envs map[string]string, cluster *v2.Cluster, mounts []v2.MountImage) []string {
command := make([]string, 0)
overrideCmd := cluster.Spec.Command
workCmd := func(applicationName, cmd string, t v2.ImageType) string {
if t == v2.RootfsImage {
return fmt.Sprintf(constants.CdAndExecCmd, constants.GetRootWorkDir(cluster.Name), cmd)
}
return fmt.Sprintf(constants.CdAndExecCmd, constants.GetAppWorkDir(cluster.Name, applicationName), cmd)
func formalizeWorkingCommand(clusterName string, imageName string, t v2.ImageType, cmd string) string {
if cmd == "" {
return ""
}
for idx, i := range mounts {
if i.Type != v2.AppImage && i.Type != v2.RootfsImage {
continue
}
mergeENV := maps.MergeMap(i.Env, envs)
mapping := expansion.MappingFuncFor(mergeENV)
for _, cmd := range i.Entrypoint {
command = append(command, workCmd(i.Name, expansion.Expand(cmd, mapping), i.Type))
}
switch t {
case v2.RootfsImage, v2.PatchImage:
return fmt.Sprintf(constants.CdAndExecCmd, constants.GetRootWorkDir(clusterName), cmd)
case v2.AppImage, "":
return fmt.Sprintf(constants.CdAndExecCmd, constants.GetAppWorkDir(clusterName, imageName), cmd)
}
return ""
}
// if --cmd is specified, only the CMD of the first MountImage will be overridden
if idx == 0 && len(overrideCmd) > 0 {
for _, cmd := range overrideCmd {
command = append(command, workCmd(i.Name, expansion.Expand(cmd, mapping), i.Type))
}
continue
}
func formalizeImageCommands(cluster *v2.Cluster, index int, m v2.MountImage, extraEnvs map[string]string) []string {
envs := maps.MergeMap(m.Env, extraEnvs)
envs = v2.MergeEnvWithBuiltinKeys(envs, m)
mapping := expansion.MappingFuncFor(envs)
for _, cmd := range i.Cmd {
command = append(command, workCmd(i.Name, expansion.Expand(cmd, mapping), i.Type))
cmds := make([]string, 0)
for i := range m.Entrypoint {
cmds = append(cmds, formalizeWorkingCommand(cluster.Name, m.Name, m.Type, expansion.Expand(m.Entrypoint[i], mapping)))
}
if index == 0 && len(cluster.Spec.Command) > 0 {
for i := range cluster.Spec.Command {
cmds = append(cmds, formalizeWorkingCommand(cluster.Name, m.Name, m.Type, expansion.Expand(cluster.Spec.Command[i], mapping)))
}
} else {
for i := range m.Cmd {
cmds = append(cmds, formalizeWorkingCommand(cluster.Name, m.Name, m.Type, expansion.Expand(m.Cmd[i], mapping)))
}
}
return command
return cmds
}
func (d Default) Delete(_ *v2.Cluster) error {
+4 -3
View File
@@ -127,9 +127,10 @@ func TestDefault_getGuestCmd(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Default{}
if got := d.getGuestCmd(tt.args.envs, tt.args.cluster, tt.args.mounts); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getGuestCmd() = %v, want %v", got, tt.want)
for i := range tt.args.mounts {
if got := formalizeImageCommands(tt.args.cluster, i, tt.args.mounts[i], tt.args.envs); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getGuestCmd() = %v, want %v", got, tt.want)
}
}
})
}
+8 -8
View File
@@ -21,12 +21,9 @@ import (
"fmt"
"strings"
"github.com/labring/sealos/pkg/client-go/kubernetes"
strings2 "github.com/labring/sealos/pkg/utils/strings"
"golang.org/x/sync/errgroup"
"github.com/labring/sealos/pkg/client-go/kubernetes"
"github.com/labring/sealos/pkg/constants"
"github.com/labring/sealos/pkg/env"
"github.com/labring/sealos/pkg/remote"
@@ -34,6 +31,8 @@ import (
"github.com/labring/sealos/pkg/types/v1beta1"
"github.com/labring/sealos/pkg/utils/iputils"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/utils/maps"
stringsutil "github.com/labring/sealos/pkg/utils/strings"
)
func (k *KubeadmRuntime) getKubeVersion() string {
@@ -101,8 +100,9 @@ func (k *KubeadmRuntime) getVIPFromImage() string {
if vip == "" {
vip = DefaultVIP
} else {
envs := k.getENVInterface().WrapperEnv(k.getMaster0IP())
vip = strings2.RenderTextFromEnv(vip, envs)
envsInRootFsImage := k.Cluster.GetRootfsImage().Env
envs := maps.MergeMap(envsInRootFsImage, k.getEnvInterface().Getenv(k.getMaster0IP()))
vip = stringsutil.RenderTextFromEnv(vip, envs)
}
logger.Debug("get vip is %s", vip)
return vip
@@ -193,8 +193,8 @@ func (k *KubeadmRuntime) getSSHInterface() ssh.Interface {
return k.ClusterClient
}
func (k *KubeadmRuntime) getENVInterface() env.Interface {
return env.NewEnvProcessor(k.Cluster, k.Cluster.Status.Mounts)
func (k *KubeadmRuntime) getEnvInterface() env.Interface {
return env.NewEnvProcessor(k.Cluster)
}
func (k *KubeadmRuntime) getRemoteInterface() remote.Interface {
+29 -3
View File
@@ -18,6 +18,8 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
"github.com/labring/sealos/pkg/version"
)
// +kubebuilder:object:root=true
@@ -78,11 +80,35 @@ type MountImage struct {
Entrypoint []string `json:"entrypoint,omitempty"`
}
func (img *MountImage) KubeVersion() string {
if img.Type != RootfsImage || img.Labels == nil {
func (m *MountImage) KubeVersion() string {
if m.Type != RootfsImage || m.Labels == nil {
return ""
}
return img.Labels[ImageKubeVersionKey]
return m.Labels[ImageKubeVersionKey]
}
func (m *MountImage) IsApplication() bool {
return m.Type == "" || m.Type == AppImage
}
func (m *MountImage) IsRootFs() bool {
return m.Type == RootfsImage
}
func (m *MountImage) IsPatch() bool {
return m.Type == PatchImage
}
func MergeEnvWithBuiltinKeys(src map[string]string, m MountImage) map[string]string {
out := make(map[string]string, len(src))
for k, v := range src {
out[k] = v
}
if m.IsRootFs() {
out[ImageKubeVersionEnvSysKey] = m.Labels[ImageKubeVersionKey]
out[ImageSealosVersionEnvSysKey] = version.Get().GitVersion
}
return out
}
type ClusterPhase string
+65
View File
@@ -0,0 +1,65 @@
/*
Copyright 2023 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 utils
import (
"fmt"
"gopkg.in/yaml.v2"
"github.com/labring/sealos/pkg/constants"
v2 "github.com/labring/sealos/pkg/types/v1beta1"
"github.com/labring/sealos/pkg/utils/exec"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/pkg/version"
)
func GetKubernetesVersion(cluster *v2.Cluster) *version.KubernetesVersion {
var cmd string
if cluster == nil {
cmd = "kubectl version -o yaml"
} else {
data := constants.NewData(cluster.Name)
cmd = fmt.Sprintf("kubectl version --kubeconfig %s -o yaml", data.AdminFile())
}
serverVersion, err := exec.RunBashCmd(cmd)
if err != nil || serverVersion == "" {
logger.Debug(err, "get kubernetes version failed")
return nil
}
var Unmarshaled version.KubernetesVersion
if err = yaml.Unmarshal([]byte(serverVersion), &Unmarshaled); err != nil {
logger.Debug(err, "unmarshal kubernetes version failed")
return nil
}
return &Unmarshaled
}
func GetCriRuntimeVersion() *version.CriRuntimeVersion {
cmd := "crictl version"
criRuntimeVersion, err := exec.RunBashCmd(cmd)
if err != nil || criRuntimeVersion == "" {
logger.Debug(err, "get cri runtime version failed")
return nil
}
var Unmarshaled version.CriRuntimeVersion
if err = yaml.Unmarshal([]byte(criRuntimeVersion), &Unmarshaled); err != nil {
logger.Debug(err, "unmarshal cri runtime version failed")
return nil
}
return &Unmarshaled
}
-46
View File
@@ -19,16 +19,6 @@ package version
import (
"fmt"
"runtime"
"github.com/labring/sealos/pkg/utils/logger"
"gopkg.in/yaml.v2"
"github.com/labring/sealos/pkg/utils/exec"
"github.com/labring/sealos/pkg/constants"
v2 "github.com/labring/sealos/pkg/types/v1beta1"
)
// Get returns the overall codebase version. It's for detecting
@@ -45,39 +35,3 @@ func Get() Info {
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
}
}
func GetKubernetesVersion(cluster *v2.Cluster) *KubernetesVersion {
var cmd string
if cluster == nil {
cmd = "kubectl version -o yaml"
} else {
data := constants.NewData(cluster.Name)
cmd = fmt.Sprintf("kubectl version --kubeconfig %s -o yaml", data.AdminFile())
}
serverVersion, err := exec.RunBashCmd(cmd)
if err != nil || serverVersion == "" {
logger.Debug(err, "get kubernetes version failed")
return nil
}
var Unmarshaled KubernetesVersion
if err = yaml.Unmarshal([]byte(serverVersion), &Unmarshaled); err != nil {
logger.Debug(err, "unmarshal kubernetes version failed")
return nil
}
return &Unmarshaled
}
func GetCriRuntimeVersion() *CriRuntimeVersion {
cmd := "crictl version"
criRuntimeVersion, err := exec.RunBashCmd(cmd)
if err != nil || criRuntimeVersion == "" {
logger.Debug(err, "get cri runtime version failed")
return nil
}
var Unmarshaled CriRuntimeVersion
if err = yaml.Unmarshal([]byte(criRuntimeVersion), &Unmarshaled); err != nil {
logger.Debug(err, "unmarshal cri runtime version failed")
return nil
}
return &Unmarshaled
}
+1 -1
View File
@@ -78,7 +78,7 @@ networking:
serviceSubnet: "100.55.0.0/16"
podSubnet: "10.160.0.0/12"
`
dFile := config.Dockerfile{
dFile := config.RootfsDockerfile{
KubeadmYaml: kubeadm,
BaseImage: "labring/kubernetes-docker:v1.25.0",
}
+1 -1
View File
@@ -78,7 +78,7 @@ networking:
serviceSubnet: "100.55.0.0/16"
podSubnet: "10.160.0.0/12"
`
dFile := config.Dockerfile{
dFile := config.RootfsDockerfile{
KubeadmYaml: kubeadm,
BaseImage: "labring/kubernetes:v1.25.0",
}
+1 -1
View File
@@ -43,7 +43,7 @@ var _ = Describe("E2E_sealos_filesystem_test", func() {
Context("sealos filesystem suit", func() {
BeforeEach(func() {
By("build rootfs")
dFile := config.Dockerfile{
dFile := config.RootfsDockerfile{
BaseImage: "labring/kubernetes:v1.25.0",
Copys: []string{"sealctl opt/"},
}
+1 -1
View File
@@ -38,7 +38,7 @@ var _ = Describe("E2E_sealos_images_buildrun_feature_test", func() {
It("images build image running cluster", func() {
By("write dockerfile")
_ = os.Setenv("SEALOS_REGISTRY_SYNC_EXPERIMENTAL", "true")
dFile := config.Dockerfile{
dFile := config.RootfsDockerfile{
Images: []string{"docker.io/altinity/clickhouse-operator:0.18.4", "docker.io/altinity/metrics-exporter:0.18.4"},
BaseImage: "labring/kubernetes:v1.25.0",
Copys: []string{"sealctl opt/"},
+1 -1
View File
@@ -35,7 +35,7 @@ var _ = Describe("E2E_sealos_images_buildrun_test", func() {
It("images build image running cluster", func() {
By("write dockerfile")
dFile := config.Dockerfile{
dFile := config.RootfsDockerfile{
Images: []string{"docker.io/altinity/clickhouse-operator:0.18.4", "docker.io/altinity/metrics-exporter:0.18.4"},
BaseImage: "labring/kubernetes:v1.25.0",
Copys: []string{"sealctl opt/"},
+1 -1
View File
@@ -60,7 +60,7 @@ kind: ClusterConfiguration
networking:
serviceSubnet: "100.55.0.0/16"
`
dFile := config.Dockerfile{
dFile := config.RootfsDockerfile{
KubeadmYaml: kubeadm,
BaseImage: "labring/kubernetes-docker:v1.25.0",
}
+85
View File
@@ -0,0 +1,85 @@
/*
Copyright 2023 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 e2e
import (
"fmt"
"path"
"github.com/labring/sealos/test/e2e/testhelper/config"
"github.com/labring/sealos/test/e2e/testhelper/utils"
"github.com/labring/sealos/test/e2e/suites/operators"
. "github.com/onsi/ginkgo/v2"
"github.com/labring/sealos/test/e2e/suites/checkers"
)
var _ = Describe("E2E_sealos_run_patchimage_test", func() {
var (
fakeClient *operators.FakeClient
err error
fakeCheckInterface checkers.FakeInterface
)
fakeClient = operators.NewFakeClient("")
Context("sealos run suit", func() {
AfterEach(func() {
err = fakeClient.Cluster.Reset()
utils.CheckErr(err, fmt.Sprintf("failed to reset cluster for earch cluster: %v", err))
})
It("sealos run patch image containerd", func() {
By("write dockerfile")
dFile := config.PatchDockerfile{
Images: []string{"nginx"},
Copys: []string{"sealctl opt/sealctl", "image-cri-shim cri/image-cri-shim"},
Cmds: []string{"systemctl stop image-cri-shim", "cp cri/image-cri-shim /usr/bin/image-cri-shim", "systemctl start image-cri-shim", "image-cri-shim -v"},
}
tmpdir, err := dFile.Write()
utils.CheckErr(err, fmt.Sprintf("failed to create dockerfile: %v", err))
By("copy sealctl to rootfs")
err = fakeClient.CmdInterface.Copy("/tmp/sealctl", path.Join(tmpdir, "sealctl"))
utils.CheckErr(err, fmt.Sprintf("failed to copy sealctl to rootfs: %v", err))
By("copy image-cri-shim to rootfs")
err = fakeClient.CmdInterface.Copy("/tmp/image-cri-shim", path.Join(tmpdir, "image-cri-shim"))
utils.CheckErr(err, fmt.Sprintf("failed to copy image-cri-shim to rootfs: %v", err))
By("build image")
err = fakeClient.Image.BuildImage("test-build-image:patch-upgrade", tmpdir, operators.BuildOptions{
MaxPullProcs: 5,
})
utils.CheckErr(err)
images := []string{"labring/kubernetes:v1.25.0", "labring/helm:v3.8.2"}
err = fakeClient.Image.PullImage(images...)
utils.CheckErr(err, fmt.Sprintf("failed to pull image: %v", err))
err = fakeClient.Cluster.Run(images...)
utils.CheckErr(err, fmt.Sprintf("failed to Run new cluster for single using tar: %v", err))
newImages := []string{"labring/kubernetes:v1.25.0", "labring/helm:v3.8.2"}
fakeCheckInterface, err = checkers.NewFakeGroupClient("default", &checkers.FakeOpts{Images: newImages})
utils.CheckErr(err, fmt.Sprintf("failed to get cluster interface: %v", err))
err = fakeCheckInterface.Verify()
utils.CheckErr(err, fmt.Sprintf("failed to verify cluster for single: %v", err))
patchImage := "test-build-image:patch-upgrade"
err = fakeClient.Cluster.Run(patchImage)
utils.CheckErr(err, fmt.Sprintf("failed to Run patch image for single using tar: %v", err))
})
})
})
+1 -1
View File
@@ -62,7 +62,7 @@ kind: ClusterConfiguration
networking:
serviceSubnet: "100.55.0.0/16"
`
dFile := config.Dockerfile{
dFile := config.RootfsDockerfile{
KubeadmYaml: kubeadm,
BaseImage: "labring/kubernetes:v1.25.0",
}
+90
View File
@@ -0,0 +1,90 @@
/*
Copyright 2023 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 config
import (
"bytes"
"fmt"
"os"
"path"
"github.com/labring/sealos/pkg/utils/logger"
"github.com/labring/sealos/test/e2e/testhelper/template"
"github.com/labring/sealos/test/e2e/testhelper/utils"
"github.com/pkg/errors"
)
const (
// ImageDockerfile = `FROM scratch
//COPY . .`
PatchfsTemplateDockerfile = `FROM scratch
MAINTAINER labring
LABEL sealos.io.type="patch"
{{- if .Copys }}
{{- range .Copys }}
COPY {{.}}
{{- end }}
{{- end }}
{{- if .Cmds }}
CMD [{{- range $index, $cmd := .Cmds }}{{if $index}}, {{end}}"{{ $cmd }}"{{- end }}]
{{- end }}`
)
type PatchDockerfile struct {
Images []string
dockerfileContent string
Copys []string
Cmds []string
}
func (d *PatchDockerfile) Write() (string, error) {
tmpdir, err := utils.MkTmpdir("")
if err != nil {
return "", errors.WithMessage(err, "create tmpdir failed")
}
if len(d.Images) != 0 {
if err := os.MkdirAll(path.Join(tmpdir, "images", "shim"), 0755); err != nil {
return "", errors.WithMessage(err, "create images dir failed")
}
for i, image := range d.Images {
if err := os.WriteFile(path.Join(tmpdir, "images", "shim", fmt.Sprintf("image%d", i)), []byte(image), 0644); err != nil {
return "", errors.WithMessage(err, "write shim image failed")
}
}
d.Copys = append(d.Copys, "registry registry")
}
t, _, err := template.TryParse(PatchfsTemplateDockerfile)
if err != nil {
return "", err
}
out := bytes.NewBuffer(nil)
if err = t.Execute(out, d); err != nil {
return "", err
}
d.dockerfileContent = out.String()
if d.dockerfileContent == "" {
return "", errors.New("dockerfile content is not set")
}
logger.Info("dockerfile content: %s", d.dockerfileContent)
if err := os.WriteFile(tmpdir+"/Dockerfile", []byte(d.dockerfileContent), 0644); err != nil {
return "", errors.WithMessage(err, "write RootfsDockerfile failed")
}
return tmpdir, nil
}
@@ -0,0 +1,59 @@
/*
Copyright 2023 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 config
import "testing"
func TestPatchDockerfile_Write(t *testing.T) {
type fields struct {
Images []string
dockerfileContent string
Copys []string
Cmds []string
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "default",
fields: fields{
Images: []string{"nginx"},
Copys: []string{"/tmp/sealctl /opt/"},
Cmds: []string{"bash upgrade.sh", "bash init.sh"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &PatchDockerfile{
Images: tt.fields.Images,
dockerfileContent: tt.fields.dockerfileContent,
Copys: tt.fields.Copys,
Cmds: tt.fields.Cmds,
}
got, err := d.Write()
if (err != nil) != tt.wantErr {
t.Errorf("Write() error = %v, wantErr %v", err, tt.wantErr)
return
}
t.Logf("out dir is %s", got)
})
}
}
@@ -31,7 +31,7 @@ import (
const (
// ImageDockerfile = `FROM scratch
//COPY . .`
TemplateDockerfile = `FROM {{ .BaseImage }}
RootfsTemplateDockerfile = `FROM {{ .BaseImage }}
MAINTAINER labring
{{- if .Copys }}
{{- range .Copys }}
@@ -40,7 +40,7 @@ COPY {{.}}
{{- end }}`
)
type Dockerfile struct {
type RootfsDockerfile struct {
Images []string
KubeadmYaml string
BaseImage string
@@ -48,7 +48,7 @@ type Dockerfile struct {
Copys []string
}
func (d *Dockerfile) Write() (string, error) {
func (d *RootfsDockerfile) Write() (string, error) {
tmpdir, err := utils.MkTmpdir("")
if err != nil {
return "", errors.WithMessage(err, "create tmpdir failed")
@@ -75,7 +75,7 @@ func (d *Dockerfile) Write() (string, error) {
d.Copys = append(d.Copys, "kubeadm.yml etc/")
}
t, _, err := template.TryParse(TemplateDockerfile)
t, _, err := template.TryParse(RootfsTemplateDockerfile)
if err != nil {
return "", err
}
@@ -88,7 +88,7 @@ func (d *Dockerfile) Write() (string, error) {
return "", errors.New("dockerfile content is not set")
}
if err := os.WriteFile(tmpdir+"/Dockerfile", []byte(d.dockerfileContent), 0644); err != nil {
return "", errors.WithMessage(err, "write Dockerfile failed")
return "", errors.WithMessage(err, "write RootfsDockerfile failed")
}
return tmpdir, nil
}
@@ -61,7 +61,7 @@ func TestDockerfile_Write(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Dockerfile{
d := &RootfsDockerfile{
Images: tt.fields.Images,
KubeadmYaml: tt.fields.KubeadmYaml,
BaseImage: tt.fields.BaseImage,