Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/pkg/hooks/0.1.0/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"

rspec "github.com/opencontainers/runtime-spec/specs-go"
current "go.podman.io/common/pkg/hooks/1.0.0"
current "go.podman.io/common/pkg/hooks/1.1.0"
)

// Version is the hook configuration version defined in this package.
Expand Down
2 changes: 1 addition & 1 deletion common/pkg/hooks/0.1.0/hook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (

rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert"
current "go.podman.io/common/pkg/hooks/1.0.0"
current "go.podman.io/common/pkg/hooks/1.1.0"
)

func TestGood(t *testing.T) {
Expand Down
75 changes: 3 additions & 72 deletions common/pkg/hooks/1.0.0/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,87 +3,18 @@ package hook

import (
"encoding/json"
"errors"
"fmt"
"regexp"

rspec "github.com/opencontainers/runtime-spec/specs-go"
"go.podman.io/storage/pkg/fileutils"
current "go.podman.io/common/pkg/hooks/1.1.0"
)

// Version is the hook configuration version defined in this package.
const Version = "1.0.0"

// Hook is the hook configuration structure.
type Hook struct {
Version string `json:"version"`
Hook rspec.Hook `json:"hook"`
When When `json:"when"`
Stages []string `json:"stages"`
}

// Read reads hook JSON bytes, verifies them, and returns the hook configuration.
func Read(content []byte) (hook *Hook, err error) {
func Read(content []byte) (hook *current.Hook, err error) {
if err = json.Unmarshal(content, &hook); err != nil {
return nil, err
}
hook.Version = current.Version
return hook, nil
}

// Validate performs load-time hook validation.
func (hook *Hook) Validate(extensionStages []string) (err error) {
if hook == nil {
return errors.New("nil hook")
}

if hook.Version != Version {
return fmt.Errorf("unexpected hook version %q (expecting %v)", hook.Version, Version)
}

if hook.Hook.Path == "" {
return errors.New("missing required property: hook.path")
}

if err := fileutils.Exists(hook.Hook.Path); err != nil {
return err
}

for key, value := range hook.When.Annotations {
if _, err = regexp.Compile(key); err != nil {
return fmt.Errorf("invalid annotation key %q: %w", key, err)
}
if _, err = regexp.Compile(value); err != nil {
return fmt.Errorf("invalid annotation value %q: %w", value, err)
}
}

for _, command := range hook.When.Commands {
if _, err = regexp.Compile(command); err != nil {
return fmt.Errorf("invalid command %q: %w", command, err)
}
}

if hook.Stages == nil {
return errors.New("missing required property: stages")
}

validStages := map[string]bool{
"createContainer": true,
"createRuntime": true,
"prestart": true,
"poststart": true,
"poststop": true,
"startContainer": true,
}
for _, stage := range extensionStages {
validStages[stage] = true
}

for _, stage := range hook.Stages {
if !validStages[stage] {
return fmt.Errorf("unknown stage %q", stage)
}
}

return nil
}
188 changes: 4 additions & 184 deletions common/pkg/hooks/1.0.0/hook_test.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
package hook

import (
"errors"
"os"
"path/filepath"
"runtime"
"testing"

rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert"
current "go.podman.io/common/pkg/hooks/1.1.0"
)

// path is the path to an example hook executable.
var path string

func TestGoodRead(t *testing.T) {
hook, err := Read([]byte("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"/a/b/c\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}"))
if err != nil {
t.Fatal(err)
}
always := true
assert.Equal(t, &Hook{
Version: Version,
assert.Equal(t, &current.Hook{
Version: current.Version,
Hook: rspec.Hook{
Path: "/a/b/c",
},
When: When{
When: current.When{
Always: &always,
},
Stages: []string{"prestart"},
Expand All @@ -39,177 +33,3 @@ func TestInvalidJSON(t *testing.T) {
}
assert.Regexp(t, "^unexpected end of JSON input$", err.Error())
}

func TestGoodValidate(t *testing.T) {
always := true
hook := &Hook{
Version: Version,
Hook: rspec.Hook{
Path: path,
},
When: When{
Always: &always,
},
Stages: []string{"prestart"},
}
err := hook.Validate([]string{})
if err != nil {
t.Fatal(err)
}
}

func TestNilValidation(t *testing.T) {
var hook *Hook
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^nil hook$", err.Error())
}

func TestWrongVersion(t *testing.T) {
hook := Hook{Version: "0.1.0"}
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^unexpected hook version \"0.1.0\" \\(expecting 1.0.0\\)$", err.Error())
}

func TestNoHookPath(t *testing.T) {
hook := Hook{
Version: "1.0.0",
Hook: rspec.Hook{},
}
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^missing required property: hook.path$", err.Error())
}

func TestUnknownHookPath(t *testing.T) {
hook := Hook{
Version: "1.0.0",
Hook: rspec.Hook{
Path: filepath.Join("does", "not", "exist"),
},
}
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^(faccessat|stat) does/not/exist: no such file or directory$", err.Error())
if !errors.Is(err, os.ErrNotExist) {
t.Fatal("opaque wrapping for not-exist errors")
}
}

func TestNoStages(t *testing.T) {
hook := Hook{
Version: "1.0.0",
Hook: rspec.Hook{
Path: path,
},
}
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^missing required property: stages$", err.Error())
}

func TestInvalidStage(t *testing.T) {
hook := Hook{
Version: "1.0.0",
Hook: rspec.Hook{
Path: path,
},
Stages: []string{"does-not-exist"},
}
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^unknown stage \"does-not-exist\"$", err.Error())
}

func TestExtensionStage(t *testing.T) {
hook := Hook{
Version: "1.0.0",
Hook: rspec.Hook{
Path: path,
},
Stages: []string{"prestart", "b"},
}
err := hook.Validate([]string{"a", "b", "c"})
if err != nil {
t.Fatal(err)
}
}

func TestInvalidAnnotationKey(t *testing.T) {
hook := Hook{
Version: "1.0.0",
Hook: rspec.Hook{
Path: path,
},
When: When{
Annotations: map[string]string{
"[": "a",
},
},
Stages: []string{"prestart"},
}
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^invalid annotation key \"\\[\": error parsing regexp: .*", err.Error())
}

func TestInvalidAnnotationValue(t *testing.T) {
hook := Hook{
Version: "1.0.0",
Hook: rspec.Hook{
Path: path,
},
When: When{
Annotations: map[string]string{
"a": "[",
},
},
Stages: []string{"prestart"},
}
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^invalid annotation value \"\\[\": error parsing regexp: .*", err.Error())
}

func TestInvalidCommand(t *testing.T) {
hook := Hook{
Version: "1.0.0",
Hook: rspec.Hook{
Path: path,
},
When: When{
Commands: []string{"["},
},
Stages: []string{"prestart"},
}
err := hook.Validate([]string{})
if err == nil {
t.Fatal("unexpected success")
}
assert.Regexp(t, "^invalid command \"\\[\": error parsing regexp: .*", err.Error())
}

func init() {
if runtime.GOOS != "windows" {
path = "/bin/sh"
} else {
panic("we need a reliable executable path on Windows")
}
}
Loading
Loading