github.com/opencontainers/runtime-tools@v0.9.0/validation/hooks_stdin/hooks_stdin.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"time"
    12  
    13  	multierror "github.com/hashicorp/go-multierror"
    14  	tap "github.com/mndrix/tap-go"
    15  	rspecs "github.com/opencontainers/runtime-spec/specs-go"
    16  	"github.com/opencontainers/runtime-tools/specerror"
    17  	"github.com/opencontainers/runtime-tools/validation/util"
    18  	uuid "github.com/satori/go.uuid"
    19  )
    20  
    21  func stdinStateCheck(outputDir, hookName string, expectedState rspecs.State) (errs *multierror.Error) {
    22  	var state rspecs.State
    23  	data, err := ioutil.ReadFile(filepath.Join(outputDir, hookName))
    24  	if err != nil {
    25  		errs = multierror.Append(errs, err)
    26  		return
    27  	}
    28  	err = json.Unmarshal(data, &state)
    29  	if err != nil {
    30  		errs = multierror.Append(errs, err)
    31  		return
    32  	}
    33  
    34  	if state.ID != expectedState.ID {
    35  		err = fmt.Errorf("wrong container ID %q in the stdin of %s hook, expected %q", state.ID, hookName, expectedState.ID)
    36  		errs = multierror.Append(errs, err)
    37  	}
    38  
    39  	if state.Bundle != expectedState.Bundle {
    40  		err = fmt.Errorf("wrong bundle directory %q in the stdin of %s hook, expected %q", state.Bundle, hookName, expectedState.Bundle)
    41  		errs = multierror.Append(errs, err)
    42  	}
    43  
    44  	if hookName != "poststop" && state.Pid != expectedState.Pid {
    45  		err = fmt.Errorf("wrong container process ID %q in the stdin of %s hook, expected %q", state.Version, hookName, expectedState.Version)
    46  		errs = multierror.Append(errs, err)
    47  	}
    48  
    49  	if !reflect.DeepEqual(state.Annotations, expectedState.Annotations) {
    50  		err = fmt.Errorf("wrong annotations \"%v\" in the stdin of %s hook, expected \"%v\"", state.Annotations, hookName, expectedState.Annotations)
    51  		errs = multierror.Append(errs, err)
    52  	}
    53  
    54  	switch hookName {
    55  	case "prestart":
    56  		if state.Status != "created" {
    57  			err = fmt.Errorf("wrong status %q in the stdin of %s hook, expected %q", state.Status, hookName, "created")
    58  			errs = multierror.Append(errs, err)
    59  		}
    60  	case "poststart":
    61  		if state.Status != "running" {
    62  			err = fmt.Errorf("wrong status %q in the stdin of %s hook, expected %q", state.Status, hookName, "running")
    63  			errs = multierror.Append(errs, err)
    64  		}
    65  	case "poststop":
    66  		if state.Status == "" {
    67  			err = fmt.Errorf("status in the stdin of %s hook should not be empty", hookName)
    68  			errs = multierror.Append(errs, err)
    69  		}
    70  	default:
    71  		err = fmt.Errorf("internal error, unexpected hook name %q", hookName)
    72  		errs = multierror.Append(errs, err)
    73  	}
    74  
    75  	return
    76  }
    77  
    78  func main() {
    79  	t := tap.New()
    80  	t.Header(0)
    81  
    82  	bundleDir, err := util.PrepareBundle()
    83  	if err != nil {
    84  		util.Fatal(err)
    85  	}
    86  	containerID := uuid.NewV4().String()
    87  	defer os.RemoveAll(bundleDir)
    88  
    89  	var containerPid int
    90  
    91  	annotationKey := "org.opencontainers.runtime-tools"
    92  	annotationValue := "hook stdin test"
    93  	g, err := util.GetDefaultGenerator()
    94  	if err != nil {
    95  		util.Fatal(err)
    96  	}
    97  	outputDir := filepath.Join(bundleDir, g.Spec().Root.Path)
    98  	timeout := 1
    99  	g.AddAnnotation(annotationKey, annotationValue)
   100  	g.AddPreStartHook(rspecs.Hook{
   101  		Path: filepath.Join(bundleDir, g.Spec().Root.Path, "/bin/sh"),
   102  		Args: []string{
   103  			"sh", "-c", fmt.Sprintf("cat > %s", filepath.Join(outputDir, "prestart")),
   104  		},
   105  		Timeout: &timeout,
   106  	})
   107  	g.AddPostStartHook(rspecs.Hook{
   108  		Path: filepath.Join(bundleDir, g.Spec().Root.Path, "/bin/sh"),
   109  		Args: []string{
   110  			"sh", "-c", fmt.Sprintf("cat > %s", filepath.Join(outputDir, "poststart")),
   111  		},
   112  		Timeout: &timeout,
   113  	})
   114  	g.AddPostStopHook(rspecs.Hook{
   115  		Path: filepath.Join(bundleDir, g.Spec().Root.Path, "/bin/sh"),
   116  		Args: []string{
   117  			"sh", "-c", fmt.Sprintf("cat > %s", filepath.Join(outputDir, "poststop")),
   118  		},
   119  		Timeout: &timeout,
   120  	})
   121  	g.SetProcessArgs([]string{"true"})
   122  	config := util.LifecycleConfig{
   123  		BundleDir: bundleDir,
   124  		Config:    g,
   125  		Actions:   util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete,
   126  		PreCreate: func(r *util.Runtime) error {
   127  			r.SetID(containerID)
   128  			return nil
   129  		},
   130  		PreDelete: func(r *util.Runtime) error {
   131  			state, err := r.State()
   132  			if err != nil {
   133  				return err
   134  			}
   135  			containerPid = state.Pid
   136  			util.WaitingForStatus(*r, util.LifecycleStatusStopped, time.Second*10, time.Second)
   137  			return nil
   138  		},
   139  	}
   140  
   141  	err = util.RuntimeLifecycleValidate(config)
   142  	if err != nil {
   143  		t.Fail(err.Error())
   144  	}
   145  
   146  	expectedState := rspecs.State{
   147  		Pid:         containerPid,
   148  		ID:          containerID,
   149  		Bundle:      bundleDir,
   150  		Annotations: map[string]string{annotationKey: annotationValue},
   151  	}
   152  	for _, file := range []string{"prestart", "poststart", "poststop"} {
   153  		errs := stdinStateCheck(outputDir, file, expectedState)
   154  		var newError error
   155  		if errs == nil {
   156  			newError = errors.New("")
   157  		} else {
   158  			newError = errors.New(errs.Error())
   159  		}
   160  		util.SpecErrorOK(t, errs.ErrorOrNil() == nil, specerror.NewError(specerror.PosixHooksStateToStdin, fmt.Errorf("the state of the container MUST be passed to %q hook over stdin", file), rspecs.Version), newError)
   161  	}
   162  
   163  	t.AutoPlan()
   164  }