diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 871ff7b22b7..412b5970607 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -1463,8 +1463,6 @@ func validateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath } if len(mnt.MountPath) == 0 { allErrs = append(allErrs, field.Required(idxPath.Child("mountPath"), "")) - } else if strings.Contains(mnt.MountPath, ":") { - allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must not contain ':'")) } if mountpoints.Has(mnt.MountPath) { allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique")) diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 5630526b7be..1c270bd529b 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -2611,6 +2611,7 @@ func TestValidateVolumeMounts(t *testing.T) { {Name: "abc-123", MountPath: "/bab", SubPath: "baz"}, {Name: "abc-123", MountPath: "/bac", SubPath: ".baz"}, {Name: "abc-123", MountPath: "/bad", SubPath: "..baz"}, + {Name: "abc", MountPath: "c:/foo/bar"}, } if errs := validateVolumeMounts(successCase, volumes, field.NewPath("field")); len(errs) != 0 { t.Errorf("expected success: %v", errs) @@ -2620,7 +2621,6 @@ func TestValidateVolumeMounts(t *testing.T) { "empty name": {{Name: "", MountPath: "/foo"}}, "name not found": {{Name: "", MountPath: "/foo"}}, "empty mountpath": {{Name: "abc", MountPath: ""}}, - "colon mountpath": {{Name: "abc", MountPath: "foo:bar"}}, "mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}}, "absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}}, "subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}}, diff --git a/pkg/apis/componentconfig/v1alpha1/defaults.go b/pkg/apis/componentconfig/v1alpha1/defaults.go index 8e98d8d0cd1..7ef2c59e776 100644 --- a/pkg/apis/componentconfig/v1alpha1/defaults.go +++ b/pkg/apis/componentconfig/v1alpha1/defaults.go @@ -222,7 +222,7 @@ func SetDefaults_KubeletConfiguration(obj *KubeletConfiguration) { if obj.DockerExecHandlerName == "" { obj.DockerExecHandlerName = "native" } - if obj.DockerEndpoint == "" { + if obj.DockerEndpoint == "" && runtime.GOOS != "windows" { obj.DockerEndpoint = "unix:///var/run/docker.sock" } if obj.EventBurst == 0 { diff --git a/pkg/kubelet/cadvisor/cadvisor_unsupported.go b/pkg/kubelet/cadvisor/cadvisor_unsupported.go index fc3b3166870..dc7b9fbd4d6 100644 --- a/pkg/kubelet/cadvisor/cadvisor_unsupported.go +++ b/pkg/kubelet/cadvisor/cadvisor_unsupported.go @@ -1,4 +1,4 @@ -// +build !cgo !linux +// +build !linux,!windows /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/kubelet/cadvisor/cadvisor_windows.go b/pkg/kubelet/cadvisor/cadvisor_windows.go new file mode 100644 index 00000000000..a970f096d58 --- /dev/null +++ b/pkg/kubelet/cadvisor/cadvisor_windows.go @@ -0,0 +1,75 @@ +// +build windows + +/* +Copyright 2015 The Kubernetes 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 cadvisor + +import ( + "github.com/google/cadvisor/events" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" +) + +type cadvisorClient struct { +} + +var _ Interface = new(cadvisorClient) + +// New creates a cAdvisor and exports its API on the specified port if port > 0. +func New(port uint, runtime string, rootPath string) (Interface, error) { + return &cadvisorClient{}, nil +} + +func (cu *cadvisorClient) Start() error { + return nil +} + +func (cu *cadvisorClient) DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) { + return cadvisorapi.ContainerInfo{}, nil +} + +func (cu *cadvisorClient) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + return &cadvisorapi.ContainerInfo{}, nil +} + +func (cu *cadvisorClient) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) { + return make(map[string]cadvisorapiv2.ContainerInfo), nil +} + +func (cu *cadvisorClient) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) { + return nil, nil +} + +func (cu *cadvisorClient) MachineInfo() (*cadvisorapi.MachineInfo, error) { + return &cadvisorapi.MachineInfo{}, nil +} + +func (cu *cadvisorClient) VersionInfo() (*cadvisorapi.VersionInfo, error) { + return &cadvisorapi.VersionInfo{}, nil +} + +func (cu *cadvisorClient) ImagesFsInfo() (cadvisorapiv2.FsInfo, error) { + return cadvisorapiv2.FsInfo{}, nil +} + +func (cu *cadvisorClient) RootFsInfo() (cadvisorapiv2.FsInfo, error) { + return cadvisorapiv2.FsInfo{}, nil +} + +func (cu *cadvisorClient) WatchEvents(request *events.Request) (*events.EventChannel, error) { + return &events.EventChannel{}, nil +} diff --git a/pkg/kubelet/cm/container_manager_unsupported.go b/pkg/kubelet/cm/container_manager_unsupported.go index 4118a998f8c..0db75bd2ada 100644 --- a/pkg/kubelet/cm/container_manager_unsupported.go +++ b/pkg/kubelet/cm/container_manager_unsupported.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!windows /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/container_manager_unsupported_test.go b/pkg/kubelet/cm/container_manager_unsupported_test.go index 421ecd19253..11d70260e9d 100644 --- a/pkg/kubelet/cm/container_manager_unsupported_test.go +++ b/pkg/kubelet/cm/container_manager_unsupported_test.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!windows /* Copyright 2015 The Kubernetes Authors. diff --git a/pkg/kubelet/cm/container_manager_windows.go b/pkg/kubelet/cm/container_manager_windows.go new file mode 100644 index 00000000000..be257e8a474 --- /dev/null +++ b/pkg/kubelet/cm/container_manager_windows.go @@ -0,0 +1,42 @@ +// +build windows + +/* +Copyright 2015 The Kubernetes 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 cm + +import ( + "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/util/mount" +) + +type containerManagerImpl struct { + containerManagerStub +} + +var _ ContainerManager = &containerManagerImpl{} + +func (cm *containerManagerImpl) Start(_ *api.Node) error { + glog.V(2).Infof("Starting Windows stub container manager") + return nil +} + +func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.Interface, nodeConfig NodeConfig) (ContainerManager, error) { + return &containerManagerImpl{}, nil +} diff --git a/pkg/kubelet/dockertools/BUILD b/pkg/kubelet/dockertools/BUILD index 998f74a80b2..5a1d3fbd9c5 100644 --- a/pkg/kubelet/dockertools/BUILD +++ b/pkg/kubelet/dockertools/BUILD @@ -17,6 +17,7 @@ go_library( "convert.go", "docker.go", "docker_manager.go", + "docker_manager_linux.go", "exec.go", "fake_docker_client.go", "fake_manager.go", diff --git a/pkg/kubelet/dockertools/docker_manager.go b/pkg/kubelet/dockertools/docker_manager.go index 50ac79c280c..7739f75608a 100644 --- a/pkg/kubelet/dockertools/docker_manager.go +++ b/pkg/kubelet/dockertools/docker_manager.go @@ -28,6 +28,7 @@ import ( "os/exec" "path" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -58,7 +59,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/kubelet/util/cache" "k8s.io/kubernetes/pkg/kubelet/util/format" - "k8s.io/kubernetes/pkg/runtime" + kruntime "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/security/apparmor" "k8s.io/kubernetes/pkg/securitycontext" kubetypes "k8s.io/kubernetes/pkg/types" @@ -360,16 +361,7 @@ var ( // that the container passed is the infrastructure container of a pod and the responsibility // of the caller to ensure that the correct container is passed. func (dm *DockerManager) determineContainerIP(podNamespace, podName string, container *dockertypes.ContainerJSON) (string, error) { - result := "" - - if container.NetworkSettings != nil { - result = container.NetworkSettings.IPAddress - - // Fall back to IPv6 address if no IPv4 address is present - if result == "" { - result = container.NetworkSettings.GlobalIPv6Address - } - } + result := getContainerIP(container) networkMode := getDockerNetworkMode(container) isHostNetwork := networkMode == namespaceModeHost @@ -450,7 +442,7 @@ func (dm *DockerManager) inspectContainer(id string, podName, podNamespace strin // Container that are running, restarting and paused status.State = kubecontainer.ContainerStateRunning status.StartedAt = startedAt - if containerName == PodInfraContainerName { + if containerProvidesPodIP(dockerName) { ip, err = dm.determineContainerIP(podNamespace, podName, iResult) // Kubelet doesn't handle the network error scenario if err != nil { @@ -628,7 +620,7 @@ func (dm *DockerManager) runContainer( // TODO: This is kind of hacky, we should really just encode the bits we need. // TODO: This is hacky because the Kubelet should be parameterized to encode a specific version // and needs to be able to migrate this whenever we deprecate v1. Should be a member of DockerManager. - if data, err := runtime.Encode(api.Codecs.LegacyCodec(unversioned.GroupVersion{Group: api.GroupName, Version: "v1"}), pod); err == nil { + if data, err := kruntime.Encode(api.Codecs.LegacyCodec(unversioned.GroupVersion{Group: api.GroupName, Version: "v1"}), pod); err == nil { labels[kubernetesPodLabel] = string(data) } else { glog.Errorf("Failed to encode pod: %s for prestop hook", pod.Name) @@ -705,6 +697,12 @@ func (dm *DockerManager) runContainer( SecurityOpt: fmtSecurityOpts, } + // There is no /etc/resolv.conf in Windows, DNS and DNSSearch options would have to be passed to Docker runtime instead + if runtime.GOOS == "windows" { + hc.DNS = opts.DNS + hc.DNSSearch = opts.DNSSearch + } + // Set sysctls if requested if container.Name == PodInfraContainerName { sysctls, unsafeSysctls, err := api.SysctlsFromPodAnnotations(pod.Annotations) @@ -1154,23 +1152,6 @@ func (dm *DockerManager) fmtDockerOpts(opts []dockerOpt) ([]string, error) { return fmtOpts, nil } -func (dm *DockerManager) getSecurityOpts(pod *api.Pod, ctrName string) ([]dockerOpt, error) { - var securityOpts []dockerOpt - if seccompOpts, err := dm.getSeccompOpts(pod, ctrName); err != nil { - return nil, err - } else { - securityOpts = append(securityOpts, seccompOpts...) - } - - if appArmorOpts, err := dm.getAppArmorOpts(pod, ctrName); err != nil { - return nil, err - } else { - securityOpts = append(securityOpts, appArmorOpts...) - } - - return securityOpts, nil -} - type dockerOpt struct { // The key-value pair passed to docker. key, value string @@ -1654,7 +1635,7 @@ func containerAndPodFromLabels(inspect *dockertypes.ContainerJSON) (pod *api.Pod // the pod data may not be set if body, found := labels[kubernetesPodLabel]; found { pod = &api.Pod{} - if err = runtime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(body), pod); err == nil { + if err = kruntime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(body), pod); err == nil { name := labels[types.KubernetesContainerNameLabel] for ix := range pod.Spec.Containers { if pod.Spec.Containers[ix].Name == name { @@ -2363,7 +2344,14 @@ func (dm *DockerManager) tryContainerStart(container *api.Container, pod *api.Po restartCount = containerStatus.RestartCount + 1 } - _, err = dm.runContainerInPod(pod, container, namespaceMode, namespaceMode, pidMode, podIP, restartCount) + // Allow override of networking mode for specific platforms (e.g. Windows) + netMode := getNetworkingMode() + if netMode == "" { + // If not overriden, use the namespace mode + netMode = namespaceMode + } + + _, err = dm.runContainerInPod(pod, container, netMode, namespaceMode, pidMode, podIP, restartCount) if err != nil { // TODO(bburns) : Perhaps blacklist a container after N failures? return kubecontainer.ErrRunContainer, err.Error() @@ -2660,7 +2648,7 @@ func (dm *DockerManager) GetPodStatus(uid kubetypes.UID, name, namespace string) } } containerStatuses = append(containerStatuses, result) - if ip != "" { + if containerProvidesPodIP(dockerName) && ip != "" { podStatus.IP = ip } } diff --git a/pkg/kubelet/dockertools/docker_manager_linux.go b/pkg/kubelet/dockertools/docker_manager_linux.go new file mode 100644 index 00000000000..2a520468578 --- /dev/null +++ b/pkg/kubelet/dockertools/docker_manager_linux.go @@ -0,0 +1,63 @@ +// +build linux + +/* +Copyright 2015 The Kubernetes 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 dockertools + +import ( + dockertypes "github.com/docker/engine-api/types" + "k8s.io/kubernetes/pkg/api" +) + +func getContainerIP(container *dockertypes.ContainerJSON) string { + result := "" + if container.NetworkSettings != nil { + result = container.NetworkSettings.IPAddress + + // Fall back to IPv6 address if no IPv4 address is present + if result == "" { + result = container.NetworkSettings.GlobalIPv6Address + } + } + return result +} + +// We don't want to override the networking mode on Linux. +func getNetworkingMode() string { return "" } + +// Returns true if the container name matches the infrastructure's container name +func containerProvidesPodIP(name *KubeletContainerName) bool { + return name.ContainerName == PodInfraContainerName +} + +// Returns Seccomp and AppArmor Security options +func (dm *DockerManager) getSecurityOpts(pod *api.Pod, ctrName string) ([]dockerOpt, error) { + var securityOpts []dockerOpt + if seccompOpts, err := dm.getSeccompOpts(pod, ctrName); err != nil { + return nil, err + } else { + securityOpts = append(securityOpts, seccompOpts...) + } + + if appArmorOpts, err := dm.getAppArmorOpts(pod, ctrName); err != nil { + return nil, err + } else { + securityOpts = append(securityOpts, appArmorOpts...) + } + + return securityOpts, nil +} diff --git a/pkg/kubelet/dockertools/docker_manager_unsupported.go b/pkg/kubelet/dockertools/docker_manager_unsupported.go new file mode 100644 index 00000000000..8182784a161 --- /dev/null +++ b/pkg/kubelet/dockertools/docker_manager_unsupported.go @@ -0,0 +1,42 @@ +// +build !linux,!windows + +/* +Copyright 2015 The Kubernetes 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 dockertools + +import ( + "k8s.io/kubernetes/pkg/api" + + dockertypes "github.com/docker/engine-api/types" +) + +func getContainerIP(container *dockertypes.ContainerJSON) string { + return "" +} + +func getNetworkingMode() string { + return "" +} + +func containerProvidesPodIP(name *KubeletContainerName) bool { + return false +} + +// Returns nil as both Seccomp and AppArmor security options are not valid on Windows +func (dm *DockerManager) getSecurityOpts(pod *api.Pod, ctrName string) ([]dockerOpt, error) { + return nil, nil +} diff --git a/pkg/kubelet/dockertools/docker_manager_windows.go b/pkg/kubelet/dockertools/docker_manager_windows.go new file mode 100644 index 00000000000..cb32cc7aa72 --- /dev/null +++ b/pkg/kubelet/dockertools/docker_manager_windows.go @@ -0,0 +1,58 @@ +// +build windows + +/* +Copyright 2015 The Kubernetes 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 dockertools + +import ( + "os" + + "k8s.io/kubernetes/pkg/api" + + dockertypes "github.com/docker/engine-api/types" +) + +func getContainerIP(container *dockertypes.ContainerJSON) string { + if container.NetworkSettings != nil { + for _, network := range container.NetworkSettings.Networks { + if network.IPAddress != "" { + return network.IPAddress + } + } + } + return "" +} + +func getNetworkingMode() string { + // Allow override via env variable. Otherwise, use a default "kubenet" network + netMode := os.Getenv("CONTAINER_NETWORK") + if netMode == "" { + netMode = "kubenet" + } + return netMode +} + +// Infrastructure containers are not supported on Windows. For this reason, we +// make sure to not grab the infra container's IP for the pod. +func containerProvidesPodIP(name *KubeletContainerName) bool { + return name.ContainerName != PodInfraContainerName +} + +// Returns nil as both Seccomp and AppArmor security options are not valid on Windows +func (dm *DockerManager) getSecurityOpts(pod *api.Pod, ctrName string) ([]dockerOpt, error) { + return nil, nil +} diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index 14f648518c4..917962364cf 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -26,6 +26,7 @@ import ( "os" "path" "path/filepath" + "runtime" "sort" "strings" @@ -100,7 +101,8 @@ func makeMounts(pod *api.Pod, podDir string, container *api.Container, hostName, // - container is not an infrastructure(pause) container // - container is not already mounting on /etc/hosts // When the pause container is being created, its IP is still unknown. Hence, PodIP will not have been set. - mountEtcHostsFile := (pod.Spec.SecurityContext == nil || !pod.Spec.SecurityContext.HostNetwork) && len(podIP) > 0 + // OS is not Windows + mountEtcHostsFile := (pod.Spec.SecurityContext == nil || !pod.Spec.SecurityContext.HostNetwork) && len(podIP) > 0 && runtime.GOOS != "windows" glog.V(3).Infof("container: %v/%v/%v podIP: %q creating hosts mount: %v", pod.Namespace, pod.Name, container.Name, podIP, mountEtcHostsFile) mounts := []kubecontainer.Mount{} for _, mount := range container.VolumeMounts { @@ -126,9 +128,21 @@ func makeMounts(pod *api.Pod, podDir string, container *api.Container, hostName, if mount.SubPath != "" { hostPath = filepath.Join(hostPath, mount.SubPath) } + + // Docker Volume Mounts fail on Windows if it is not of the form C:/ + containerPath := mount.MountPath + if runtime.GOOS == "windows" { + if strings.HasPrefix(hostPath, "/") && !strings.Contains(hostPath, ":") { + hostPath = "c:" + hostPath + } + if strings.HasPrefix(containerPath, "/") && !strings.Contains(containerPath, ":") { + containerPath = "c:" + containerPath + } + } + mounts = append(mounts, kubecontainer.Mount{ Name: mount.Name, - ContainerPath: mount.MountPath, + ContainerPath: containerPath, HostPath: hostPath, ReadOnly: mount.ReadOnly, SELinuxRelabel: relabelVolume, @@ -284,7 +298,9 @@ func (kl *Kubelet) GenerateRunContainerOptions(pod *api.Pod, container *api.Cont return nil, err } - if len(container.TerminationMessagePath) != 0 { + // Disabling adding TerminationMessagePath on Windows as these files would be mounted as docker volume and + // Docker for Windows has a bug where only directories can be mounted + if len(container.TerminationMessagePath) != 0 && runtime.GOOS != "windows" { p := kl.getPodContainerDir(pod.UID, container.Name) if err := os.MkdirAll(p, 0750); err != nil { glog.Errorf("Error on creating %q: %v", p, err) diff --git a/pkg/volume/secret/secret.go b/pkg/volume/secret/secret.go index 116d519bce6..cbb7b39d756 100644 --- a/pkg/volume/secret/secret.go +++ b/pkg/volume/secret/secret.go @@ -18,6 +18,8 @@ package secret import ( "fmt" + "path/filepath" + "runtime" "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" @@ -157,7 +159,12 @@ func (sv *secretVolume) GetAttributes() volume.Attributes { } } func (b *secretVolumeMounter) SetUp(fsGroup *int64) error { - return b.SetUpAt(b.GetPath(), fsGroup) + // Update each Slash "/" character for Windows with seperator character + dir := b.GetPath() + if runtime.GOOS == "windows" { + dir = filepath.FromSlash(dir) + } + return b.SetUpAt(dir, fsGroup) } func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error { @@ -269,7 +276,12 @@ type secretVolumeUnmounter struct { var _ volume.Unmounter = &secretVolumeUnmounter{} func (c *secretVolumeUnmounter) TearDown() error { - return c.TearDownAt(c.GetPath()) + // Update each Slash "/" character for Windows with seperator character + dir := c.GetPath() + if runtime.GOOS == "windows" { + dir = filepath.FromSlash(dir) + } + return c.TearDownAt(dir) } func (c *secretVolumeUnmounter) TearDownAt(dir string) error { diff --git a/pkg/volume/util/atomic_writer.go b/pkg/volume/util/atomic_writer.go index 8e2dad20eab..8b84710fc31 100644 --- a/pkg/volume/util/atomic_writer.go +++ b/pkg/volume/util/atomic_writer.go @@ -23,6 +23,7 @@ import ( "os" "path" "path/filepath" + "runtime" "strings" "time" @@ -183,7 +184,14 @@ func (w *AtomicWriter) Write(payload map[string]FileProjection) error { } // (9) - if err = os.Rename(newDataDirPath, dataDirPath); err != nil { + if runtime.GOOS == "windows" { + os.Remove(dataDirPath) + err = os.Symlink(tsDirName, dataDirPath) + os.Remove(newDataDirPath) + } else { + err = os.Rename(newDataDirPath, dataDirPath) + } + if err != nil { os.Remove(newDataDirPath) os.RemoveAll(tsDir) glog.Errorf("%s: error renaming symbolic link for data directory %s: %v", w.logContext, newDataDirPath, err) @@ -303,7 +311,11 @@ func (w *AtomicWriter) pathsToRemove(payload map[string]FileProjection) (sets.St } relativePath := strings.TrimPrefix(path, w.targetDir) - relativePath = strings.TrimPrefix(relativePath, "/") + if runtime.GOOS == "windows" { + relativePath = strings.TrimPrefix(relativePath, "\\") + } else { + relativePath = strings.TrimPrefix(relativePath, "/") + } if strings.HasPrefix(relativePath, "..") { return nil } diff --git a/pkg/volume/volume.go b/pkg/volume/volume.go index 6431a6119c7..c4968f6a68e 100644 --- a/pkg/volume/volume.go +++ b/pkg/volume/volume.go @@ -17,11 +17,14 @@ limitations under the License. package volume import ( + "io" "io/ioutil" "os" - "path" + filepath "path/filepath" + "runtime" "time" + "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/types" @@ -223,13 +226,94 @@ func (err deletedVolumeInUseError) Error() string { } func RenameDirectory(oldPath, newName string) (string, error) { - newPath, err := ioutil.TempDir(path.Dir(oldPath), newName) + newPath, err := ioutil.TempDir(filepath.Dir(oldPath), newName) if err != nil { return "", err } + + // os.Rename call fails on windows (https://github.com/golang/go/issues/14527) + // Replacing with copyFolder to the newPath and deleting the oldPath directory + if runtime.GOOS == "windows" { + err = copyFolder(oldPath, newPath) + if err != nil { + glog.Errorf("Error copying folder from: %s to: %s with error: %v", oldPath, newPath, err) + return "", err + } + os.RemoveAll(oldPath) + return newPath, nil + } + err = os.Rename(oldPath, newPath) if err != nil { return "", err } return newPath, nil } + +func copyFolder(source string, dest string) (err error) { + fi, err := os.Lstat(source) + if err != nil { + glog.Errorf("Error getting stats for %s. %v", source, err) + return err + } + + err = os.MkdirAll(dest, fi.Mode()) + if err != nil { + glog.Errorf("Unable to create %s directory %v", dest, err) + } + + directory, _ := os.Open(source) + + defer directory.Close() + + objects, err := directory.Readdir(-1) + + for _, obj := range objects { + if obj.Mode()&os.ModeSymlink != 0 { + continue + } + + sourcefilepointer := source + "\\" + obj.Name() + destinationfilepointer := dest + "\\" + obj.Name() + + if obj.IsDir() { + err = copyFolder(sourcefilepointer, destinationfilepointer) + if err != nil { + return err + } + } else { + err = copyFile(sourcefilepointer, destinationfilepointer) + if err != nil { + return err + } + } + + } + return +} + +func copyFile(source string, dest string) (err error) { + sourcefile, err := os.Open(source) + if err != nil { + return err + } + + defer sourcefile.Close() + + destfile, err := os.Create(dest) + if err != nil { + return err + } + + defer destfile.Close() + + _, err = io.Copy(destfile, sourcefile) + if err == nil { + sourceinfo, err := os.Stat(source) + if err != nil { + err = os.Chmod(dest, sourceinfo.Mode()) + } + + } + return +}