Skip to content

Commit

Permalink
Merge pull request #104 from spinup-host/viggy/gh-103-image-not-exist…
Browse files Browse the repository at this point in the history
…-issue
  • Loading branch information
viggy28 authored Mar 28, 2022
2 parents 54a63fb + 1b6f663 commit 6cd5851
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 4 deletions.
2 changes: 2 additions & 0 deletions api/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func CreateService(w http.ResponseWriter, req *http.Request) {
image := s.Architecture + "/" + s.Db.Type + ":" + strconv.Itoa(int(s.Version.Maj))
if s.Version.Min > 0 {
image += "." + strconv.Itoa(int(s.Version.Min))
} else {
image += ".0"
}
dockerClient, err := dockerservice.NewDocker()
if err != nil {
Expand Down
72 changes: 68 additions & 4 deletions internal/dockerservice/dockerservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
Expand Down Expand Up @@ -96,19 +98,81 @@ func (d Docker) LastContainerID(ctx context.Context) (string, error) {
}

func (c *Container) Start(d Docker) (container.ContainerCreateCreatedBody, error) {
exists, err := imageExistsLocally(context.Background(), d, c.Config.Image)
if err != nil {
return container.ContainerCreateCreatedBody{}, fmt.Errorf("error checking whether the image exists locally %w", err)
}
if !exists {
log.Printf("INFO: docker image %s doesn't exist on the host \n", c.Config.Image)
if err := pullImageFromDockerRegistry(d, c.Config.Image); err != nil {
return container.ContainerCreateCreatedBody{}, fmt.Errorf("unable to pull image from docker registry %w", err)
}
}
body, err := d.Cli.ContainerCreate(c.Ctx, &c.Config, &c.HostConfig, &c.NetworkConfig, nil, c.Name)
if err != nil {
log.Println("error creating container")
return container.ContainerCreateCreatedBody{}, err
return container.ContainerCreateCreatedBody{}, fmt.Errorf("unable to create container with image %s", c.Config.Image)
}
err = d.Cli.ContainerStart(c.Ctx, body.ID, types.ContainerStartOptions{})
if err != nil {
log.Println("error starting container")
return container.ContainerCreateCreatedBody{}, err
return container.ContainerCreateCreatedBody{}, fmt.Errorf("unable to start container for image %s", c.Config.Image)
}
return body, nil
}

// imageExistsLocally returns a boolean indicating if an image with the
// requested name exists in the local docker image store
func imageExistsLocally(ctx context.Context, d Docker, imageName string) (bool, error) {

filters := filters.NewArgs()
filters.Add("reference", imageName)

imageListOptions := types.ImageListOptions{
Filters: filters,
}

images, err := d.Cli.ImageList(ctx, imageListOptions)
if err != nil {
return false, err
}

if len(images) > 0 {

for _, v := range images {
_, _, err := d.Cli.ImageInspectWithRaw(ctx, v.ID)
if err != nil {
return false, err
}
return true, nil

}
return false, nil
}

return false, nil
}

func pullImageFromDockerRegistry(d Docker, image string) error {
rc, err := d.Cli.ImagePull(context.Background(), image, types.ImagePullOptions{
// Platform: "linux/amd64",
})
if err != nil {
return fmt.Errorf("unable to pull docker image %s %w", image, err)
}
defer rc.Close()
_, err = ioutil.ReadAll(rc)
if err != nil {
return fmt.Errorf("unable to download docker image %s %w", image, err)
}
return nil
}

func removeDockerImage(d Docker, image string) error {
_, err := d.Cli.ImageRemove(context.Background(), image, types.ImageRemoveOptions{
Force: true,
})
return err
}

// ExecCommand executes a given bash command through execConfig and displays the output in stdout and stderr
// This function doesn't return an error for the failure of the command itself
func (c Container) ExecCommand(ctx context.Context, d Docker, execConfig types.ExecConfig) (types.IDResponse, error) {
Expand Down
91 changes: 91 additions & 0 deletions internal/dockerservice/dockerservice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package dockerservice

import (
"context"
"errors"
"fmt"
"log"
"os"
"strings"
"testing"
)

var docker Docker

func TestMain(m *testing.M) {
fmt.Println("INFO: setting up things for testing dockerservice package")
var err error
docker, err = NewDocker()
if err != nil {
log.Fatalf("couldn't create a new docker instance")
}
//TODO: (viggy) we should create some customer spinup images for testing purpose instead of using docker registry postgres images
imagesToRemove := []string{"postgres:14-alpine", "postgres:13-alpine"}
removeImageHelper(imagesToRemove)
exitVal := m.Run()
removeImageHelper(imagesToRemove)
os.Exit(exitVal)
}

func removeImageHelper(imagesToRemove []string) {
for _, imageToRemove := range imagesToRemove {
err := removeDockerImage(docker, imageToRemove)
if err != nil {
log.Printf("INFO: error removing docker image %s %v \n", imageToRemove, err)
}
}
}

func Test_imageExistsLocally(t *testing.T) {
data := []struct {
name string
image string
pullImageFromDockerRegistry bool
expected bool
}{
{"image exist", "postgres:14-alpine", true, true},
{"image doesnot exist", "imageDoesnotExist:notag", false, false},
}
for _, d := range data {
t.Run(d.name, func(t *testing.T) {
if d.pullImageFromDockerRegistry {
log.Println("INFO: pulling docker image from docker registry:", d.image)
// INFO: not sure what's the best way to make sure an image exists locally. Hence pulling it before testing imageExistsLocally.
// Perhaps we could move this to TestMain() which means we need to define a type for struct - not sure its that the right way to do
// postgres:9.6-alpine image will be pulled since its fairly small. It could be any image.
if err := pullImageFromDockerRegistry(docker, d.image); err != nil {
t.Errorf("error setting up imageExistsLocally() for test data %+v", d)
}
}
actual, err := imageExistsLocally(context.Background(), docker, d.image)
if err != nil {
t.Errorf("error testing imageExistsLocally() for test data %+v", d)

}
if actual != d.expected {
t.Errorf("incorrect result: actual %t , expected %t", actual, d.expected)
}
})
}
}

func Test_pullImageFromDockerRegistry(t *testing.T) {
data := []struct {
name string
image string
expected error
}{
{"image exist", "postgres:13-alpine", nil},
{"image doesnot exist", "imageDoesnotExistInRegistry:notag", errors.New("unable to pull docker image")},
}
for _, d := range data {
t.Run(d.name, func(t *testing.T) {
actual := pullImageFromDockerRegistry(docker, d.image)
if actual != d.expected {
if !strings.Contains(actual.Error(), d.expected.Error()) {
t.Errorf("incorrect result: actual %t , expected %t", actual, d.expected)
}
}
})
}
}

0 comments on commit 6cd5851

Please sign in to comment.