kops/upup/pkg/fi/nodeup/nodetasks/bindmount.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
}