diff --git a/pkg/devspace/build/build.go b/pkg/devspace/build/build.go index 45ab51ba72..2a6121690e 100644 --- a/pkg/devspace/build/build.go +++ b/pkg/devspace/build/build.go @@ -11,7 +11,6 @@ import ( "github.com/loft-sh/devspace/pkg/devspace/build/types" "github.com/loft-sh/devspace/pkg/devspace/config/constants" devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context" - "github.com/loft-sh/devspace/pkg/devspace/kubectl" "github.com/loft-sh/devspace/pkg/util/stringutil" "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" @@ -89,12 +88,8 @@ func (c *controller) Build(ctx devspacecontext.Context, images []string, options // Determine if we need to use the local registry to build any images. kubeClient := ctx.KubeClient() - isKindContext := kubeClient != nil && kubectl.GetKindContext(kubeClient.CurrentContext()) != "" - useKindLoad := !registry.IsLocalRegistryEnabled(conf) && isKindContext var localRegistry *registry.LocalRegistry - if !options.SkipPush && - !useKindLoad && - (registry.IsLocalRegistryEnabled(conf) || registry.IsLocalRegistryFallback(conf)) { + if registry.UseLocalRegistry(kubeClient, conf, options.SkipPush) { ctx := ctx.WithLogger(ctx.Log().WithPrefix("local-registry: ")) for key, imageConf := range conf.Images { imageName := imageConf.Image @@ -106,10 +101,7 @@ func (c *controller) Build(ctx devspacecontext.Context, images []string, options // Determine whether the local registry is required / enabled isLocalReqistryRequired := !registry.HasPushPermission(imageConf) - useMinikubeDocker := registry.UseMinikubeDocker(ctx, imageConf) - if useMinikubeDocker { - ctx.Log().Warnf("Using Minikube for image %s, skipping local registry", imageConf.Image) - } else if isLocalReqistryRequired { + if isLocalReqistryRequired { // Not able to deploy a local registry if kubeClient == nil { return fmt.Errorf("unable to push image %s and a valid kube context is not available", imageConf.Image) diff --git a/pkg/devspace/build/builder/buildkit/buildkit.go b/pkg/devspace/build/builder/buildkit/buildkit.go index 2927db3fe8..771ded36cf 100644 --- a/pkg/devspace/build/builder/buildkit/buildkit.go +++ b/pkg/devspace/build/builder/buildkit/buildkit.go @@ -139,13 +139,13 @@ func (b *Builder) BuildImage(ctx devspacecontext.Context, contextPath, dockerfil // We skip pushing when using a local registry imageCache, _ := ctx.Config().LocalCache().GetImageCache(b.helper.ImageConfigName) - if !usingLocalKubernetes && imageCache.IsLocalRegistryImage() { - b.skipPush = true + if usingLocalKubernetes && imageCache.IsLocalRegistryImage() { + b.skipPush = false } // Should we use the minikube docker daemon? useMinikubeDocker := false - if ctx.KubeClient() != nil && ctx.KubeClient().CurrentContext() == "minikube" && (buildKitConfig.PreferMinikube == nil || *buildKitConfig.PreferMinikube) { + if ctx.KubeClient() != nil && kubectl.IsMinikubeKubernetes(ctx.KubeClient().CurrentContext()) && (buildKitConfig.PreferMinikube == nil || *buildKitConfig.PreferMinikube) { useMinikubeDocker = true } @@ -235,7 +235,13 @@ func buildWithCLI(ctx context.Context, dir string, environ expand.Environ, conte // Push image to local registry for _, tag := range options.Tags { log.Info("The push refers to repository [" + tag + "]") - err := registry.CopyImageToRemote(ctx, tag, writer) + preferMinikube := imageConf.PreferMinikube == nil || *imageConf.PreferMinikube + client, err := dockerpkg.NewClientWithMinikube(ctx, kubeClient.CurrentContext(), preferMinikube, log) + if err != nil { + return errors.Wrap(err, "new docker client") + } + + err = registry.CopyImageToRemote(ctx, client, tag, writer) if err != nil { return errors.Errorf("error during local registry image push: %v", err) } diff --git a/pkg/devspace/build/builder/docker/docker.go b/pkg/devspace/build/builder/docker/docker.go index 0f5508b6ed..0da1f32370 100644 --- a/pkg/devspace/build/builder/docker/docker.go +++ b/pkg/devspace/build/builder/docker/docker.go @@ -212,7 +212,7 @@ func (b *Builder) BuildImage(ctx devspacecontext.Context, contextPath, dockerfil // Push image to local registry for _, tag := range buildOptions.Tags { ctx.Log().Info("The push refers to repository [" + tag + "]") - err := registry.CopyImageToRemote(ctx.Context(), tag, writer) + err := registry.CopyImageToRemote(ctx.Context(), b.client, tag, writer) if err != nil { return errors.Errorf("error during local registry image push: %v", err) } diff --git a/pkg/devspace/build/registry/util.go b/pkg/devspace/build/registry/util.go index 86bf873c3e..1d5d52dc95 100644 --- a/pkg/devspace/build/registry/util.go +++ b/pkg/devspace/build/registry/util.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "strings" "github.com/docker/docker/pkg/jsonmessage" "github.com/google/go-containerregistry/pkg/authn" @@ -14,7 +15,8 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" - devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context" + dockerclient "github.com/loft-sh/devspace/pkg/devspace/docker" + "github.com/loft-sh/devspace/pkg/devspace/kubectl" corev1 "k8s.io/api/core/v1" ) @@ -29,7 +31,7 @@ func HasPushPermission(image *latest.Image) bool { } func IsLocalRegistryFallback(config *latest.Config) bool { - return config.LocalRegistry == nil || (config.LocalRegistry != nil || config.LocalRegistry.Enabled == nil) + return config.LocalRegistry == nil || (config.LocalRegistry != nil && config.LocalRegistry.Enabled == nil) } func IsLocalRegistryEnabled(config *latest.Config) bool { @@ -67,13 +69,13 @@ func IsImageAvailableRemotely(ctx context.Context, imageName string) (bool, erro return image != nil, nil } -func CopyImageToRemote(ctx context.Context, imageName string, writer io.Writer) error { +func CopyImageToRemote(ctx context.Context, client dockerclient.Client, imageName string, writer io.Writer) error { ref, err := name.ParseReference(imageName) if err != nil { return err } - image, err := daemon.Image(ref, daemon.WithContext(ctx)) + image, err := daemon.Image(ref, daemon.WithContext(ctx), daemon.WithClient(client.DockerAPIClient())) if err != nil { return err } @@ -118,17 +120,26 @@ func CopyImageToRemote(ctx context.Context, imageName string, writer io.Writer) return <-errChan } -func UseMinikubeDocker(ctx devspacecontext.Context, image *latest.Image) bool { - // preferMinikube := false - var preferMinikube *bool +func UseLocalRegistry(client kubectl.Client, config *latest.Config, skipPush bool) bool { + if skipPush { + return false + } - if image.Docker != nil { - preferMinikube = image.Docker.PreferMinikube + if client == nil { + return false } - if image.BuildKit != nil { - preferMinikube = image.BuildKit.PreferMinikube + if !IsLocalRegistryFallback(config) { + return IsLocalRegistryEnabled(config) } - return ctx.KubeClient() != nil && ctx.KubeClient().CurrentContext() == "minikube" && (preferMinikube == nil || *preferMinikube) + context := client.CurrentContext() + + // Determine if this is a vcluster + isVClusterContext := strings.HasPrefix(context, "vcluster_") + + // Determine if this is a local kubernetes cluster + isLocalKubernetes := kubectl.IsLocalKubernetes(context) + + return !isLocalKubernetes && !(isVClusterContext && isLocalKubernetes) } diff --git a/pkg/devspace/build/registry/util_test.go b/pkg/devspace/build/registry/util_test.go new file mode 100644 index 0000000000..88c8b237f8 --- /dev/null +++ b/pkg/devspace/build/registry/util_test.go @@ -0,0 +1,262 @@ +package registry + +import ( + "testing" + + "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" + "github.com/loft-sh/devspace/pkg/devspace/kubectl" + kubectltesting "github.com/loft-sh/devspace/pkg/devspace/kubectl/testing" + "github.com/loft-sh/devspace/pkg/util/ptr" + "gotest.tools/assert" +) + +type useLocalRegistryTestCase struct { + name string + client kubectl.Client + config *latest.Config + skipPush bool + expected bool +} + +func TestUseLocalRegistry(t *testing.T) { + testCases := []useLocalRegistryTestCase{ + { + name: "KinD Cluster", + client: &kubectltesting.Client{ + Context: "kind-kind", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: false, + }, + { + name: "KinD Cluster Local Registry Enabled", + client: &kubectltesting.Client{ + Context: "kind-kind", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: ptr.Bool(true), + }, + }, + expected: true, + }, + { + name: "KinD Cluster Local Registry Enabled skip push", + client: &kubectltesting.Client{ + Context: "kind-kind", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: ptr.Bool(true), + }, + }, + skipPush: true, + expected: false, + }, + { + name: "KinD Cluster Local Registry Fallback", + client: &kubectltesting.Client{ + Context: "kind-kind", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: nil, + }, + }, + expected: false, + }, + { + name: "VCluster with KinD Cluster", + client: &kubectltesting.Client{ + Context: "vcluster_devspace-kind_vcluster-devspace-kind_kind-kind", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: false, + }, + { + name: "Docker Desktop Cluster", + client: &kubectltesting.Client{ + Context: "docker-desktop", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: false, + }, + { + name: "Docker Desktop Cluster Local Registry Enabled", + client: &kubectltesting.Client{ + Context: "docker-desktop", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: ptr.Bool(true), + }, + }, + expected: true, + }, + { + name: "Docker Desktop Cluster Local Registry Enabled skip push", + client: &kubectltesting.Client{ + Context: "docker-desktop", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: ptr.Bool(true), + }, + }, + skipPush: true, + expected: false, + }, + { + name: "Docker Desktop Cluster Local Registry Fallback", + client: &kubectltesting.Client{ + Context: "docker-desktop", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: nil, + }, + }, + expected: false, + }, + { + name: "VCluster with Docker Desktop Cluster", + client: &kubectltesting.Client{ + Context: "vcluster_devspacehelper_deploy-example_docker-desktop", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: false, + }, + { + name: "Minikube Cluster", + client: &kubectltesting.Client{ + Context: "minikube", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: false, + }, + { + name: "Minikube Cluster Local Registry Enabled", + client: &kubectltesting.Client{ + Context: "minikube", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: ptr.Bool(true), + }, + }, + expected: true, + }, + { + name: "Minikube Cluster Local Registry Enabled skip push", + client: &kubectltesting.Client{ + Context: "minikube", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: ptr.Bool(true), + }, + }, + skipPush: true, + expected: false, + }, + { + name: "Minikube Cluster Local Registry Fallback", + client: &kubectltesting.Client{ + Context: "minikube", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: nil, + }, + }, + expected: false, + }, + { + name: "VCluster with Minikube Cluster", + client: &kubectltesting.Client{ + Context: "vcluster_devspace-minikube_vcluster-devspace-minikube_minikube", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: false, + }, + { + name: "Remote Cluster", + client: &kubectltesting.Client{ + Context: "arn:aws:eks:us-west-2:1234567890:cluster/remote-eks", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: true, + }, + { + name: "Remote Cluster skip push", + client: &kubectltesting.Client{ + Context: "arn:aws:eks:us-west-2:1234567890:cluster/remote-eks", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + skipPush: true, + expected: false, + }, + { + name: "Remote Cluster Local Registry Disabled", + client: &kubectltesting.Client{ + Context: "arn:aws:eks:us-west-2:1234567890:cluster/remote-eks", + }, + config: &latest.Config{ + LocalRegistry: &latest.LocalRegistryConfig{ + Enabled: ptr.Bool(false), + }, + }, + expected: false, + }, + { + name: "VCluster with Remote Cluster", + client: &kubectltesting.Client{ + Context: "vcluster_vcluster-eks_vcluster-vcluster-eks_arn:aws:eks:us-west-2:1234567890:cluster/remote-eks", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: true, + }, + { + name: "VCluster with Remote Cluster skip push", + client: &kubectltesting.Client{ + Context: "vcluster_vcluster-eks_vcluster-vcluster-eks_arn:aws:eks:us-west-2:1234567890:cluster/remote-eks", + }, + config: &latest.Config{ + LocalRegistry: nil, + }, + skipPush: true, + expected: false, + }, + { + name: "Nil KubeClient", + client: nil, + config: &latest.Config{ + LocalRegistry: nil, + }, + expected: false, + }, + } + + for _, testCase := range testCases { + actual := UseLocalRegistry(testCase.client, testCase.config, testCase.skipPush) + assert.Equal(t, actual, testCase.expected, "Unexpected result in test case %s", testCase.name) + } +} diff --git a/pkg/devspace/docker/client.go b/pkg/devspace/docker/client.go index ea00ff4a70..d857b63878 100644 --- a/pkg/devspace/docker/client.go +++ b/pkg/devspace/docker/client.go @@ -2,15 +2,17 @@ package docker import ( "context" - "github.com/loft-sh/loft-util/pkg/command" "io" - "mvdan.cc/sh/v3/expand" "net/http" "os" "os/exec" "path/filepath" "strings" + "github.com/loft-sh/loft-util/pkg/command" + "mvdan.cc/sh/v3/expand" + + "github.com/loft-sh/devspace/pkg/devspace/kubectl" "github.com/loft-sh/devspace/pkg/util/log" dockertypes "github.com/docker/docker/api/types" @@ -107,7 +109,7 @@ func newDockerClientFromEnvironment() (Client, error) { } func newDockerClientFromMinikube(ctx context.Context, currentKubeContext string) (Client, error) { - if currentKubeContext != "minikube" { + if !kubectl.IsMinikubeKubernetes(currentKubeContext) { return nil, errNotMinikube } diff --git a/pkg/devspace/kubectl/util.go b/pkg/devspace/kubectl/util.go index 67a89d635a..f55e4e9dfc 100644 --- a/pkg/devspace/kubectl/util.go +++ b/pkg/devspace/kubectl/util.go @@ -3,6 +3,10 @@ package kubectl import ( "context" "fmt" + "net" + "net/http" + "strings" + "github.com/loft-sh/devspace/pkg/devspace/kubectl/portforward" "github.com/loft-sh/devspace/pkg/util/log" "github.com/pkg/errors" @@ -11,14 +15,13 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/transport/spdy" - "net" - "net/http" - "strings" ) -const minikubeContext = "minikube" -const dockerDesktopContext = "docker-desktop" -const dockerForDesktopContext = "docker-for-desktop" +const ( + minikubeContext = "minikube" + dockerDesktopContext = "docker-desktop" + dockerForDesktopContext = "docker-for-desktop" +) // WaitStatus are the status to wait var WaitStatus = []string{ @@ -206,7 +209,23 @@ func IsLocalKubernetes(context string) bool { context == dockerDesktopContext || context == dockerForDesktopContext { return true - } else if strings.HasPrefix(context, "vcluster_") && (strings.HasSuffix(context, minikubeContext) || strings.HasSuffix(context, dockerDesktopContext) || strings.HasSuffix(context, dockerForDesktopContext)) { + } else if strings.HasPrefix(context, "vcluster_") && + (strings.HasSuffix(context, minikubeContext) || + strings.HasSuffix(context, dockerDesktopContext) || + strings.HasSuffix(context, dockerForDesktopContext) || + strings.Contains(context, "kind-")) { + return true + } + + return false +} + +func IsMinikubeKubernetes(context string) bool { + if context == minikubeContext { + return true + } + + if strings.HasPrefix(context, "vcluster_") && strings.HasSuffix(context, minikubeContext) { return true }