mirror of https://github.com/kubernetes/kops.git
251 lines
6.6 KiB
Go
251 lines
6.6 KiB
Go
/*
|
|
Copyright 2017 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 nodetasks
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/kops/upup/pkg/fi"
|
|
"k8s.io/kops/upup/pkg/fi/nodeup/local"
|
|
)
|
|
|
|
// BindMount performs bind mounts
|
|
type BindMount struct {
|
|
Source string `json:"source"`
|
|
Mountpoint string `json:"mountpoint"`
|
|
Options []string `json:"options,omitempty"`
|
|
Recursive bool `json:"recursive"`
|
|
}
|
|
|
|
var _ fi.NodeupTask = &BindMount{}
|
|
|
|
func (s *BindMount) String() string {
|
|
return fmt.Sprintf("BindMount: %s->%s", s.Source, s.Mountpoint)
|
|
}
|
|
|
|
var _ CreatesDir = &BindMount{}
|
|
|
|
// Dir implements CreatesDir::Dir
|
|
func (e *BindMount) Dir() string {
|
|
return e.Mountpoint
|
|
}
|
|
|
|
var _ fi.HasName = &Archive{}
|
|
|
|
func (e *BindMount) GetName() *string {
|
|
return fi.PtrTo("BindMount-" + e.Mountpoint)
|
|
}
|
|
|
|
var _ fi.NodeupHasDependencies = &BindMount{}
|
|
|
|
// GetDependencies implements HasDependencies::GetDependencies
|
|
func (e *BindMount) GetDependencies(tasks map[string]fi.NodeupTask) []fi.NodeupTask {
|
|
var deps []fi.NodeupTask
|
|
|
|
// Requires parent directories to be created
|
|
deps = append(deps, findCreatesDirParents(e.Mountpoint, tasks)...)
|
|
for _, v := range findCreatesDirMatching(e.Mountpoint, tasks) {
|
|
if v != e && findTaskInSlice(deps, v) == -1 {
|
|
deps = append(deps, v)
|
|
}
|
|
}
|
|
|
|
// Requires source to be created
|
|
for _, v := range findCreatesDirParents(e.Source, tasks) {
|
|
if findTaskInSlice(deps, v) == -1 {
|
|
deps = append(deps, v)
|
|
}
|
|
}
|
|
for _, v := range findCreatesDirMatching(e.Source, tasks) {
|
|
if v != e && findTaskInSlice(deps, v) == -1 {
|
|
deps = append(deps, v)
|
|
}
|
|
}
|
|
|
|
return deps
|
|
}
|
|
|
|
func findTaskInSlice(tasks []fi.NodeupTask, task fi.NodeupTask) int {
|
|
for i, t := range tasks {
|
|
if t == task {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (e *BindMount) Find(c *fi.NodeupContext) (*BindMount, error) {
|
|
mounts, err := os.ReadFile("/proc/self/mountinfo")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading /proc/self/mountinfo: %v", err)
|
|
}
|
|
for _, line := range strings.Split(string(mounts), "\n") {
|
|
// See `man mount_namespaces` and `man proc`
|
|
// 534 458 8:1 /var/lib/kubelet /home/kubernetes/containerized_mounter/rootfs/var/lib/kubelet rw,nosuid,nodev,noexec,relatime shared:19 - ext4 /dev/sda1 rw,commit=30,data=ordered
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
tokens := strings.Fields(line)
|
|
if len(tokens) < 8 {
|
|
klog.V(4).Infof("ignoring mountinfo line: %q", line)
|
|
}
|
|
|
|
mountpoint := tokens[4]
|
|
if strings.TrimSuffix(mountpoint, "/") != strings.TrimSuffix(e.Mountpoint, "/") {
|
|
continue
|
|
}
|
|
|
|
fstype := tokens[len(tokens)-3]
|
|
source := tokens[3]
|
|
switch fstype {
|
|
// Some special cases
|
|
case "devtmpfs":
|
|
source = "/dev"
|
|
case "proc":
|
|
source = "/proc"
|
|
}
|
|
if e.Source == "/etc/resolv.conf" {
|
|
// /etc/resolv.conf is a symlink on ContainerOS, and "magically" gets transformed to /systemd/resolve/resolv.conf
|
|
// Special case this very odd case!
|
|
if source == "/systemd/resolve/resolv.conf" || source == "/run/systemd/resolve/resolv.conf" {
|
|
source = e.Source // force match
|
|
}
|
|
}
|
|
if strings.TrimSuffix(source, "/") != strings.TrimSuffix(e.Source, "/") {
|
|
continue
|
|
}
|
|
|
|
klog.V(8).Infof("candidate mount: %v", line)
|
|
|
|
mountOptions := sets.NewString(strings.Split(tokens[5], ",")...)
|
|
// exec is inferred from a lack of noexec
|
|
if !mountOptions.Has("noexec") {
|
|
mountOptions.Insert("exec")
|
|
}
|
|
|
|
// optional fields: zero or more fields of the form "tag[:value]"
|
|
for _, token := range tokens[6:] {
|
|
if token == "-" {
|
|
// the end of the optional fields is marked by a single hyphen.
|
|
break
|
|
}
|
|
if strings.HasPrefix(token, "shared:") {
|
|
mountOptions.Insert("rshared")
|
|
}
|
|
}
|
|
|
|
if !mountOptions.HasAll(e.Options...) {
|
|
klog.V(2).Infof("options mismatch on mount %v", line)
|
|
continue
|
|
}
|
|
|
|
klog.V(2).Infof("found matching mount %v", line)
|
|
a := &BindMount{
|
|
Source: e.Source,
|
|
Mountpoint: e.Mountpoint,
|
|
Options: e.Options,
|
|
Recursive: e.Recursive, // TODO: Validate
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (e *BindMount) Run(c *fi.NodeupContext) error {
|
|
return fi.NodeupDefaultDeltaRunMethod(e, c)
|
|
}
|
|
|
|
func (s *BindMount) CheckChanges(a, e, changes *BindMount) error {
|
|
return nil
|
|
}
|
|
|
|
// Executor allows execution of a command; it makes for testing of commands
|
|
type Executor interface {
|
|
CombinedOutput(args []string) ([]byte, error)
|
|
}
|
|
|
|
func (_ *BindMount) RenderLocal(t *local.LocalTarget, a, e, changes *BindMount) error {
|
|
return e.execute(t)
|
|
}
|
|
|
|
func (e *BindMount) execute(t Executor) error {
|
|
var simpleOptions []string
|
|
var makeOptions []string
|
|
var remountOptions []string
|
|
for _, option := range e.Options {
|
|
switch option {
|
|
case "ro":
|
|
simpleOptions = append(simpleOptions, option)
|
|
|
|
case "rshared":
|
|
makeOptions = append(makeOptions, "--make-rshared")
|
|
|
|
case "exec", "noexec", "suid", "nosuid", "dev", "nodev":
|
|
remountOptions = append(remountOptions, option)
|
|
|
|
default:
|
|
return fmt.Errorf("unknown option: %q", option)
|
|
}
|
|
}
|
|
|
|
{
|
|
args := []string{"mount"}
|
|
if e.Recursive {
|
|
args = append(args, "--rbind")
|
|
} else {
|
|
args = append(args, "--bind")
|
|
}
|
|
if len(simpleOptions) != 0 {
|
|
args = append(args, "-o", strings.Join(simpleOptions, ","))
|
|
}
|
|
args = append(args, e.Source, e.Mountpoint)
|
|
|
|
klog.Infof("running mount command %s", args)
|
|
if output, err := t.CombinedOutput(args); err != nil {
|
|
return fmt.Errorf("error doing mount %q: %v: %s", strings.Join(args, " "), err, string(output))
|
|
}
|
|
}
|
|
|
|
if len(remountOptions) != 0 {
|
|
args := []string{"mount", "-o", "remount," + strings.Join(remountOptions, ","), e.Mountpoint}
|
|
|
|
klog.Infof("running mount command %s", args)
|
|
if output, err := t.CombinedOutput(args); err != nil {
|
|
return fmt.Errorf("error doing mount options %q: %v: %s", strings.Join(args, " "), err, string(output))
|
|
}
|
|
}
|
|
|
|
if len(makeOptions) != 0 {
|
|
args := []string{"mount"}
|
|
args = append(args, makeOptions...)
|
|
args = append(args, e.Mountpoint)
|
|
|
|
klog.Infof("running mount command %s", args)
|
|
if output, err := t.CombinedOutput(args); err != nil {
|
|
return fmt.Errorf("error doing mount operation %q: %v: %s", strings.Join(args, " "), err, string(output))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|