gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/container/hook.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package container
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"fmt"
    21  	"os/exec"
    22  	"path/filepath"
    23  	"strings"
    24  	"time"
    25  
    26  	specs "github.com/opencontainers/runtime-spec/specs-go"
    27  	"gvisor.dev/gvisor/pkg/log"
    28  )
    29  
    30  // This file implements hooks as defined in OCI spec:
    31  // https://github.com/opencontainers/runtime-spec/blob/master/config.md#toc22
    32  //
    33  // "hooks":{
    34  // 		"prestart":[{
    35  // 			"path":"/usr/bin/dockerd",
    36  // 			"args":[
    37  // 				"libnetwork-setkey", "arg2",
    38  // 			]
    39  // 		}]
    40  // },
    41  
    42  // executeHooksBestEffort executes hooks and logs warning in case they fail.
    43  // Runs all hooks, always.
    44  func executeHooksBestEffort(hooks []specs.Hook, s specs.State) {
    45  	for _, h := range hooks {
    46  		if err := executeHook(h, s); err != nil {
    47  			log.Warningf("Failure to execute hook %+v, err: %v", h, err)
    48  		}
    49  	}
    50  }
    51  
    52  // executeHooks executes hooks until the first one fails or they all execute.
    53  func executeHooks(hooks []specs.Hook, s specs.State) error {
    54  	for _, h := range hooks {
    55  		if err := executeHook(h, s); err != nil {
    56  			return err
    57  		}
    58  	}
    59  	return nil
    60  }
    61  
    62  func executeHook(h specs.Hook, s specs.State) error {
    63  	log.Debugf("Executing hook %+v, state: %+v", h, s)
    64  
    65  	if strings.TrimSpace(h.Path) == "" {
    66  		return fmt.Errorf("empty path for hook")
    67  	}
    68  	if !filepath.IsAbs(h.Path) {
    69  		return fmt.Errorf("path for hook is not absolute: %q", h.Path)
    70  	}
    71  
    72  	// Don't invoke nvidia-container-runtime-hook at prestart, which may be
    73  	// configured by e.g. Docker's --gpus flag, since
    74  	// nvidia-container-runtime-hook doesn't understand gVisor's bifurcation
    75  	// between sentry and application filesystems.
    76  	if strings.HasSuffix(h.Path, "/nvidia-container-runtime-hook") {
    77  		log.Infof("Skipping nvidia-container-runtime-hook")
    78  		return nil
    79  	}
    80  
    81  	b, err := json.Marshal(s)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	var stdout, stderr bytes.Buffer
    86  	cmd := exec.Cmd{
    87  		Path:   h.Path,
    88  		Args:   h.Args,
    89  		Env:    h.Env,
    90  		Stdin:  bytes.NewReader(b),
    91  		Stdout: &stdout,
    92  		Stderr: &stderr,
    93  	}
    94  	if err := cmd.Start(); err != nil {
    95  		return err
    96  	}
    97  
    98  	c := make(chan error, 1)
    99  	go func() {
   100  		c <- cmd.Wait()
   101  	}()
   102  
   103  	var timer <-chan time.Time
   104  	if h.Timeout != nil {
   105  		timer = time.After(time.Duration(*h.Timeout) * time.Second)
   106  	}
   107  	select {
   108  	case err := <-c:
   109  		if err != nil {
   110  			return fmt.Errorf("failure executing hook %q, err: %v\nstdout: %s\nstderr: %s", h.Path, err, stdout.String(), stderr.String())
   111  		}
   112  	case <-timer:
   113  		_ = cmd.Process.Kill()
   114  		_ = cmd.Wait()
   115  		return fmt.Errorf("timeout executing hook %q\nstdout: %s\nstderr: %s", h.Path, stdout.String(), stderr.String())
   116  	}
   117  
   118  	log.Debugf("Execute hook %q success!", h.Path)
   119  	return nil
   120  }