Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
Merge pull request #136 from puppetlabs/gh-127/main/minimally_impleme…
Browse files Browse the repository at this point in the history
…nt_docker_validate

(GH-127) Minimally implement docker validate
  • Loading branch information
chelnak authored Feb 28, 2022
2 parents d2b6f75 + 3831bf1 commit d28ec41
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 31 deletions.
16 changes: 5 additions & 11 deletions cmd/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,23 +166,17 @@ func execute(cmd *cobra.Command, args []string) error {
return nil
}

// var additionalToolArgs []string
// if toolArgs != "" {
// additionalToolArgs, _ = shlex.Split(toolArgs)
// }

if selectedTool != "" {
// get the tool from the cache
// cachedTool, ok := prmApi.IsToolAvailable(selectedTool)
_, ok := prmApi.IsToolAvailable(selectedTool)
cachedTool, ok := prmApi.IsToolAvailable(selectedTool)
if !ok {
return fmt.Errorf("Tool %s not found in cache", selectedTool)
}
// execute!
// err := prmApi.Exec(cachedTool, additionalToolArgs)
// if err != nil {
// return err
// }
err := prmApi.Validate(cachedTool)
if err != nil {
return err
}
}
// Uncomment when implementing validate.yml
// else {
Expand Down
14 changes: 12 additions & 2 deletions internal/pkg/mock/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type MockBackend struct {
StatusMessageString string
ToolAvalible bool
ExecReturn string
ValidateReturn string
}

func (m *MockBackend) Status() prm.BackendStatus {
Expand All @@ -26,8 +27,17 @@ func (m *MockBackend) GetTool(tool *prm.Tool, prmConfig prm.Config) error {
}

// Implement when needed
func (m *MockBackend) Validate(tool *prm.Tool) (prm.ToolExitCode, error) {
return prm.FAILURE, nil
func (m *MockBackend) Validate(tool *prm.Tool, prmConfig prm.Config, paths prm.DirectoryPaths) (prm.ValidateExitCode, error) {
switch m.ExecReturn {
case "PASS":
return prm.VALIDATION_PASS, nil
case "FAIL":
return prm.VALIDATION_FAILED, errors.New("VALIDATION FAIL")
case "ERROR":
return prm.VALIDATION_ERROR, errors.New("DOCKER ERROR")
default:
return prm.VALIDATION_ERROR, errors.New("DOCKER FAIL")
}
}

func (m *MockBackend) Exec(tool *prm.Tool, args []string, prmConfig prm.Config, paths prm.DirectoryPaths) (prm.ToolExitCode, error) {
Expand Down
54 changes: 43 additions & 11 deletions internal/pkg/mock/docker.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package mock

import (
"bytes"
"context"
"fmt"
"io"
"strings"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/pkg/stdcopy"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/puppetlabs/prm/pkg/prm"
"io"
)

type DockerClient struct {
Platform string
Version string
ApiVersion string
ErrorString string
ImagesSlice []types.ImageSummary
Platform string
Version string
ApiVersion string
ErrorString string
ImagesSlice []types.ImageSummary
ExitCode int64
ExitErrorMsg string
}

type ReadClose struct{}
Expand Down Expand Up @@ -47,12 +49,39 @@ func (m *DockerClient) ContainerCreate(ctx context.Context, config *container.Co
return container.ContainerCreateCreatedBody{}, nil
}

func getSrcBuffer(stdOutBytes, stdErrBytes []byte) (buffer *bytes.Buffer, err error) {
buffer = new(bytes.Buffer)
dstOut := stdcopy.NewStdWriter(buffer, stdcopy.Stdout)
_, err = dstOut.Write(stdOutBytes)
if err != nil {
return
}
dstErr := stdcopy.NewStdWriter(buffer, stdcopy.Stderr)
_, err = dstErr.Write(stdErrBytes)
return
}

type ClosingBuffer struct {
*bytes.Buffer
}

func (cb *ClosingBuffer) Close() (err error) {
//we don't actually have to do anything here, since the buffer is just some data in memory
//and the error is initialized to no-error
return
}

func (m *DockerClient) ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
stdOutBytes := []byte("This is a test")
stdErrBytes := []byte("")
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
if err != nil {
return nil, err
}

mockReader := strings.NewReader("FAKE LOG MESSAGES!")
mockReadCloser := io.NopCloser(mockReader)
closingBuffer := &ClosingBuffer{buffer}

return mockReadCloser, nil
return closingBuffer, nil
}

func (m *DockerClient) ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error {
Expand All @@ -65,6 +94,9 @@ func (m *DockerClient) ContainerStart(ctx context.Context, containerID string, o

func (m *DockerClient) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) {
waitChan := make(chan container.ContainerWaitOKBody)
go func() {
waitChan <- container.ContainerWaitOKBody{StatusCode: m.ExitCode, Error: &container.ContainerWaitOKBodyError{Message: m.ExitErrorMsg}}
}()
errChan := make(chan error)
return waitChan, errChan
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/prm/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const (

type BackendI interface {
GetTool(tool *Tool, prmConfig Config) error
Validate(tool *Tool) (ToolExitCode, error)
Validate(tool *Tool, prmConfig Config, paths DirectoryPaths) (ValidateExitCode, error)
Exec(tool *Tool, args []string, prmConfig Config, paths DirectoryPaths) (ToolExitCode, error)
Status() BackendStatus
}
Expand Down
102 changes: 99 additions & 3 deletions pkg/prm/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,105 @@ func (d *Docker) ImageName(tool *Tool, prmConfig Config) string {
return imageName
}

func (*Docker) Validate(tool *Tool) (ToolExitCode, error) {
// TODO
return FAILURE, nil
func (d *Docker) Validate(tool *Tool, prmConfig Config, paths DirectoryPaths) (ValidateExitCode, error) {
// is Docker up and running?
status := d.Status()
if !status.IsAvailable {
log.Error().Msgf("Docker is not available")
return VALIDATION_ERROR, fmt.Errorf("%s", status.StatusMsg)
}

// clean up paths
codeDir, _ := filepath.Abs(paths.codeDir)
log.Info().Msgf("Code path: %s", codeDir)
cacheDir, _ := filepath.Abs(paths.cacheDir)
log.Info().Msgf("Cache path: %s", cacheDir)

// stand up a container
containerConf := container.Config{
Image: d.ImageName(tool, prmConfig),
Tty: false,
}

resp, err := d.Client.ContainerCreate(d.Context,
&containerConf,
&container.HostConfig{
Mounts: []mount.Mount{
{
Type: mount.TypeBind,
Source: codeDir,
Target: "/code",
},
{
Type: mount.TypeBind,
Source: cacheDir,
Target: "/cache",
},
},
}, nil, nil, "")

if err != nil {
return VALIDATION_ERROR, err
}
// the autoremove functionality is too aggressive
// it fires before we can get at the logs
defer func() {
err := d.Client.ContainerRemove(d.Context, resp.ID, types.ContainerRemoveOptions{
RemoveVolumes: true,
})
if err != nil {
log.Error().Msgf("Error removing container: %s", err)
}
}()

if err := d.Client.ContainerStart(d.Context, resp.ID, types.ContainerStartOptions{}); err != nil {
return VALIDATION_ERROR, err
}

isError := make(chan error)
toolExit := make(chan container.ContainerWaitOKBody)
// Move this wait into a goroutine
// when its finished it will return and post to the isDone channel
go func() {
statusCh, errCh := d.Client.ContainerWait(d.Context, resp.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
isError <- err
case status := <-statusCh:
toolExit <- status
}
}()

// parse out the containers logs while we wait for the container to finish
for {
out, err := d.Client.ContainerLogs(d.Context, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Tail: "all", Follow: true})
if err != nil {
return VALIDATION_ERROR, err
}

_, err = stdcopy.StdCopy(os.Stdout, os.Stderr, out)
if err != nil {
return VALIDATION_ERROR, err
}

select {
case err := <-isError:
return VALIDATION_ERROR, err
case exitValues := <-toolExit:
if exitValues.StatusCode == int64(tool.Cfg.Common.SuccessExitCode) {
return VALIDATION_PASS, nil
} else {
// If we have more details on why the tool failed, use that info
if exitValues.Error != nil {
err = fmt.Errorf("%s", exitValues.Error.Message)
} else {
// otherwise, just log the exit code
err = fmt.Errorf("Tool exited with code: %d", exitValues.StatusCode)
}
return VALIDATION_FAILED, err
}
}
}
}

func (d *Docker) Exec(tool *Tool, args []string, prmConfig Config, paths DirectoryPaths) (ToolExitCode, error) {
Expand Down
Loading

0 comments on commit d28ec41

Please sign in to comment.