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 #104 from puppetlabs/gh-85/main/Add_always_build_flag
Browse files Browse the repository at this point in the history
(GH-85) Add `alwaysBuild` flag and functionality
  • Loading branch information
michaeltlombardi authored Jan 18, 2022
2 parents e18f4ac + 4e007da commit ad8de00
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 11 deletions.
16 changes: 11 additions & 5 deletions cmd/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ var (
format string
selectedTool string
// selectedToolInfo string
listTools bool
prmApi *prm.Prm
toolArgs string
listTools bool
prmApi *prm.Prm
toolArgs string
alwaysBuild bool
)

func CreateCommand(parent *prm.Prm) *cobra.Command {
Expand Down Expand Up @@ -72,6 +73,10 @@ func CreateCommand(parent *prm.Prm) *cobra.Command {
err = viper.BindPFlag("toolArgs", tmp.Flags().Lookup("toolArgs"))
cobra.CheckErr(err)

tmp.Flags().BoolVarP(&alwaysBuild, "alwaysBuild", "a", false, "Rebuild the docker image for each tool execution, even if it already exists")
err = viper.BindPFlag("alwaysBuild", tmp.Flags().Lookup("alwaysBuild"))
cobra.CheckErr(err)

return tmp
}

Expand All @@ -82,9 +87,9 @@ func preExecute(cmd *cobra.Command, args []string) error {

switch prmApi.RunningConfig.Backend {
case prm.DOCKER:
prmApi.Backend = &prm.Docker{AFS: prmApi.AFS, IOFS: prmApi.IOFS}
prmApi.Backend = &prm.Docker{AFS: prmApi.AFS, IOFS: prmApi.IOFS, AlwaysBuild: alwaysBuild}
default:
prmApi.Backend = &prm.Docker{AFS: prmApi.AFS, IOFS: prmApi.IOFS}
prmApi.Backend = &prm.Docker{AFS: prmApi.AFS, IOFS: prmApi.IOFS, AlwaysBuild: alwaysBuild}
}

// handle the default cachepath
Expand Down Expand Up @@ -188,6 +193,7 @@ func execute(cmd *cobra.Command, args []string) error {
if !ok {
return fmt.Errorf("Tool %s not found in cache", tool)
}

err := prmApi.Exec(cachedTool, tool.Args)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/json-iterator/go v1.1.12
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.4.3
github.com/moby/sys/mount v0.3.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/olekukonko/tablewriter v0.0.5
Expand Down
26 changes: 24 additions & 2 deletions internal/pkg/mock/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,25 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/puppetlabs/prm/pkg/prm"
)

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

type ReadClose struct{}

func (re *ReadClose) Read(r []byte) (n int, err error) {
return 0, nil
}

func (re *ReadClose) Close() error {
return nil
}

func (m *DockerClient) ServerVersion(ctx context.Context) (types.Version, error) {
Expand Down Expand Up @@ -58,9 +70,19 @@ func (m *DockerClient) ContainerWait(ctx context.Context, containerID string, co
}

func (m *DockerClient) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
return types.ImageBuildResponse{}, nil
return types.ImageBuildResponse{Body: &ReadClose{}}, nil
}

func (m *DockerClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
return []types.ImageSummary{}, nil
return m.ImagesSlice, nil
}

func (m *DockerClient) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
return []types.ImageDeleteResponseItem{{Deleted: "test_id"}}, nil
}

func (m *DockerClient) ImageName(tool *prm.Tool, prmConfig prm.Config) string {
// build up a name based on the tool and puppet version
imageName := fmt.Sprintf("pdk:puppet-%s_%s-%s_%s", prmConfig.PuppetVersion.String(), tool.Cfg.Plugin.Author, tool.Cfg.Plugin.Id, tool.Cfg.Plugin.Version)
return imageName
}
32 changes: 28 additions & 4 deletions pkg/prm/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Docker struct {
ContextCancel func()
AFS *afero.Afero
IOFS *afero.IOFS
AlwaysBuild bool
}

type DockerClientI interface {
Expand All @@ -42,6 +43,7 @@ type DockerClientI interface {
ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error)
ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error)
ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
ServerVersion(context.Context) (types.Version, error)
}

Expand All @@ -64,16 +66,33 @@ func (d *Docker) GetTool(tool *Tool, prmConfig Config) error {
return err
}

foundImage := ""
for _, image := range list {
for _, tag := range image.RepoTags {
if tag == toolImageName {
log.Info().Msgf("Found image: %s", image.ID)
return nil
if !d.AlwaysBuild {
return nil
}
foundImage = image.ID
break
}
}
if foundImage != "" {
break
}
}

log.Info().Msg("Creating new image. Please wait...")
if d.AlwaysBuild && foundImage != "" {
log.Info().Msg("Rebuilding image. Please wait...")
_, err = d.Client.ImageRemove(d.Context, foundImage, types.ImageRemoveOptions{Force: true})
if err != nil {
log.Error().Msgf("Error removing docker image: %v", err)
return err
}
} else {
log.Info().Msg("Creating new image. Please wait...")
}

// No image found with that configuration
// we must create it
Expand Down Expand Up @@ -122,7 +141,12 @@ func (d *Docker) GetTool(tool *Tool, prmConfig Config) error {
return err
}

defer imageBuildResponse.Body.Close()
defer func() {
err = imageBuildResponse.Body.Close()
if err != nil {
log.Error().Msg(err.Error())
}
}()

// Parse the output from Docker, cleaning up where possible
scanner := bufio.NewScanner(imageBuildResponse.Body)
Expand Down Expand Up @@ -300,7 +324,7 @@ func (d *Docker) Exec(tool *Tool, args []string, prmConfig Config, paths Directo

// 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: "2", Follow: true})
out, err := d.Client.ContainerLogs(d.Context, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Tail: "all", Follow: true})
if err != nil {
return FAILURE, err
}
Expand Down
73 changes: 73 additions & 0 deletions pkg/prm/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import (
"reflect"
"testing"

"github.com/Masterminds/semver"
"github.com/docker/docker/api/types"
"github.com/mitchellh/mapstructure"
"github.com/puppetlabs/prm/internal/pkg/mock"
"github.com/puppetlabs/prm/pkg/prm"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
)

func TestDocker_Status(t *testing.T) {
Expand Down Expand Up @@ -59,3 +64,71 @@ func TestDocker_Status(t *testing.T) {
})
}
}

func TestDocker_GetTool(t *testing.T) {
type ToolInfo struct {
id string
author string
version string
}
tests := []struct {
name string
mockClient mock.DockerClient
tool prm.Tool
config prm.Config
toolInfo ToolInfo
errorMsg string
alwaysBuild bool
}{
{
name: "Image not found and create new image",
config: prm.Config{PuppetVersion: &semver.Version{}},
},
{
name: "Image found and alwaysBuild set to false",
toolInfo: ToolInfo{id: "test", author: "user", version: "0.1.0"},
mockClient: mock.DockerClient{
ImagesSlice: []types.ImageSummary{
{
RepoTags: []string{"pdk:puppet-0.0.0_user-test_0.1.0"},
ID: "foo",
},
},
},
config: prm.Config{PuppetVersion: &semver.Version{}},
},
{
name: "Image found and alwaysBuild set to true",
toolInfo: ToolInfo{id: "test", author: "user", version: "0.1.0"},
mockClient: mock.DockerClient{
ImagesSlice: []types.ImageSummary{
{
RepoTags: []string{"pdk:puppet-0.0.0_user-test_0.1.0"},
ID: "foo",
},
},
},
alwaysBuild: true,
config: prm.Config{PuppetVersion: &semver.Version{}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Map fields to the Plugin config which are then squashed into its struct
toolinfo := map[string]interface{}{
"id": tt.toolInfo.id,
"author": tt.toolInfo.author,
"version": tt.toolInfo.version,
}
_ = mapstructure.Decode(toolinfo, &tt.tool.Cfg.Plugin)

fs := afero.NewMemMapFs()
afs := &afero.Afero{Fs: fs}
d := &prm.Docker{Client: &tt.mockClient, AFS: afs, AlwaysBuild: tt.alwaysBuild}
err := d.GetTool(&tt.tool, tt.config)
if err != nil {
assert.Contains(t, err.Error(), tt.errorMsg)
}
})
}
}

0 comments on commit ad8de00

Please sign in to comment.