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 }