github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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  	"github.com/SagerNet/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  	b, err := json.Marshal(s)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	var stdout, stderr bytes.Buffer
    77  	cmd := exec.Cmd{
    78  		Path:   h.Path,
    79  		Args:   h.Args,
    80  		Env:    h.Env,
    81  		Stdin:  bytes.NewReader(b),
    82  		Stdout: &stdout,
    83  		Stderr: &stderr,
    84  	}
    85  	if err := cmd.Start(); err != nil {
    86  		return err
    87  	}
    88  
    89  	c := make(chan error, 1)
    90  	go func() {
    91  		c <- cmd.Wait()
    92  	}()
    93  
    94  	var timer <-chan time.Time
    95  	if h.Timeout != nil {
    96  		timer = time.After(time.Duration(*h.Timeout) * time.Second)
    97  	}
    98  	select {
    99  	case err := <-c:
   100  		if err != nil {
   101  			return fmt.Errorf("failure executing hook %q, err: %v\nstdout: %s\nstderr: %s", h.Path, err, stdout.String(), stderr.String())
   102  		}
   103  	case <-timer:
   104  		cmd.Process.Kill()
   105  		cmd.Wait()
   106  		return fmt.Errorf("timeout executing hook %q\nstdout: %s\nstderr: %s", h.Path, stdout.String(), stderr.String())
   107  	}
   108  
   109  	log.Debugf("Execute hook %q success!", h.Path)
   110  	return nil
   111  }