From 980b4b232cf5f4f36c70a3fff2bf199bb0624e24 Mon Sep 17 00:00:00 2001 From: Greg Haskins Date: Mon, 15 May 2017 13:05:34 -0400 Subject: [PATCH] [FAB-3118] pull ccenv image at runtime The current code requires the CORE_CHAINCODE_BUILDER image to be pre-pulled down from dockerhub or chaincode building will fail. This patch adds the docker-client intelligence to check whether the image is present. If it finds that it is not present, it will try to pull the image from dockerhub. Change-Id: I95b6bcd0f1b1beb2edee7f0b542f90f5fb9eabc7 Signed-off-by: Greg Haskins --- core/chaincode/platforms/util/utils.go | 15 ++++++ core/chaincode/platforms/util/utils_test.go | 55 +++++++++++++++++++++ core/container/util/dockerutil.go | 4 +- core/container/util/dockerutil_test.go | 2 +- 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/core/chaincode/platforms/util/utils.go b/core/chaincode/platforms/util/utils.go index 9386310f589..6e3cf69697e 100644 --- a/core/chaincode/platforms/util/utils.go +++ b/core/chaincode/platforms/util/utils.go @@ -155,6 +155,21 @@ func DockerBuild(opts DockerBuildOptions) error { } } + logger.Debugf("Attempting build with image %s", opts.Image) + + //----------------------------------------------------------------------------------- + // Ensure the image exists locally, or pull it from a registry if it doesn't + //----------------------------------------------------------------------------------- + _, err = client.InspectImage(opts.Image) + if err != nil { + logger.Debugf("Image %s does not exist locally, attempt pull", opts.Image) + + err = client.PullImage(docker.PullImageOptions{Repository: opts.Image}, docker.AuthConfiguration{}) + if err != nil { + return fmt.Errorf("Failed to pull %s: %s", opts.Image, err) + } + } + //----------------------------------------------------------------------------------- // Create an ephemeral container, armed with our Env/Cmd //----------------------------------------------------------------------------------- diff --git a/core/chaincode/platforms/util/utils_test.go b/core/chaincode/platforms/util/utils_test.go index 676c3231048..df57a1bcbf0 100644 --- a/core/chaincode/platforms/util/utils_test.go +++ b/core/chaincode/platforms/util/utils_test.go @@ -23,7 +23,16 @@ import ( "testing" "time" + "archive/tar" + "fmt" + "io" + "os" + "strings" + "github.com/hyperledger/fabric/common/util" + "github.com/hyperledger/fabric/core/config" + cutil "github.com/hyperledger/fabric/core/container/util" + "github.com/spf13/viper" ) // TestHashContentChange changes a random byte in a content and checks for hash change @@ -187,3 +196,49 @@ func TestHashSameDir(t *testing.T) { t.Error("Hash should be same across multiple downloads") } } + +func TestDockerPull(t *testing.T) { + codepackage, output := io.Pipe() + go func() { + tw := tar.NewWriter(output) + + tw.Close() + output.Close() + }() + + binpackage := bytes.NewBuffer(nil) + + // Perform a nop operation within a fixed target. We choose 1.0.0-alpha2 because we know it's + // published and available. Ideally we could choose something that we know is both multi-arch + // and ok to delete prior to executing DockerBuild. This would ensure that we exercise the + // image pull logic. However, no suitable target exists that meets all the criteria. Therefore + // we settle on using a known released image. We don't know if the image is already + // downloaded per se, and we don't want to explicitly delete this particular image first since + // it could be in use legitimately elsewhere. Instead, we just know that this should always + // work and call that "close enough". + // + // Future considerations: publish a known dummy image that is multi-arch and free to randomly + // delete, and use that here instead. + err := DockerBuild(DockerBuildOptions{ + Image: cutil.ParseDockerfileTemplate("hyperledger/fabric-ccenv:$(ARCH)-1.0.0-alpha2"), + Cmd: "/bin/true", + InputStream: codepackage, + OutputStream: binpackage, + }) + if err != nil { + t.Errorf("Error during build: %s", err) + } +} + +func TestMain(m *testing.M) { + viper.SetConfigName("core") + viper.SetEnvPrefix("CORE") + config.AddDevConfigPath(nil) + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + if err := viper.ReadInConfig(); err != nil { + fmt.Printf("could not read config %s\n", err) + os.Exit(-1) + } + os.Exit(m.Run()) +} diff --git a/core/container/util/dockerutil.go b/core/container/util/dockerutil.go index f5c59508389..a668c34ed40 100644 --- a/core/container/util/dockerutil.go +++ b/core/container/util/dockerutil.go @@ -56,7 +56,7 @@ func getArch() string { } } -func parseDockerfileTemplate(template string) string { +func ParseDockerfileTemplate(template string) string { r := strings.NewReplacer( "$(ARCH)", getArch(), "$(PROJECT_VERSION)", metadata.Version, @@ -68,5 +68,5 @@ func parseDockerfileTemplate(template string) string { } func GetDockerfileFromConfig(path string) string { - return parseDockerfileTemplate(viper.GetString(path)) + return ParseDockerfileTemplate(viper.GetString(path)) } diff --git a/core/container/util/dockerutil_test.go b/core/container/util/dockerutil_test.go index b0fcfa53c2f..e8aac7f3a6a 100644 --- a/core/container/util/dockerutil_test.go +++ b/core/container/util/dockerutil_test.go @@ -26,7 +26,7 @@ import ( func TestUtil_DockerfileTemplateParser(t *testing.T) { expected := "FROM foo:" + getArch() + "-" + metadata.Version - actual := parseDockerfileTemplate("FROM foo:$(ARCH)-$(PROJECT_VERSION)") + actual := ParseDockerfileTemplate("FROM foo:$(ARCH)-$(PROJECT_VERSION)") assert.Equal(t, expected, actual, "Error parsing Dockerfile Template. Expected \"%s\", got \"%s\"", expected, actual) }