diff --git a/Makefile b/Makefile index 097356f5..9a7785af 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ lint.fix: ## Lint and fix source code golangci-lint run --fix -v test: ## Run tests - IS_TEST="TRUE" bash -c 'go test -v ./... -coverprofile coverage.output' + go test -v ./... -coverprofile coverage.output fmt: ## Run `go fmt` on all files find -name '*.go' -exec gofmt -w -s '{}' ';' diff --git a/cmd/build.go b/cmd/build.go index a6ddcf80..1de162e9 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -25,6 +25,8 @@ import ( versn "github.com/radiofrance/dib/version" ) +const placeholderNonExistent = "non-existent" + func cmdBuild(cmd *cli.Cmd) { var opts buildOpts @@ -110,9 +112,15 @@ func doBuild(opts buildOpts) (*dag.DAG, error) { return nil, err } - previousVersion, diffs, err := versn.GetDiffSinceLastDockerVersionChange(workingDir, shell) + previousVersion, diffs, err := versn.GetDiffSinceLastDockerVersionChange( + workingDir, shell, gcrRegistry, path.Join(opts.buildPath, versn.DockerVersionFilename), + path.Join(opts.registryURL, opts.referentialImage)) if err != nil { - return nil, err + if errors.Is(err, versn.ErrNoPreviousBuild) { + previousVersion = placeholderNonExistent + } else { + return nil, err + } } if opts.forceRebuild { @@ -137,6 +145,13 @@ func doBuild(opts buildOpts) (*dag.DAG, error) { return nil, err } } + + // We retag the referential image to explicit this commit was build using dib + if err := DAG.Tagger.Tag(fmt.Sprintf("%s:%s", path.Join(opts.registryURL, opts.referentialImage), "latest"), + fmt.Sprintf("%s:%s", path.Join(opts.registryURL, opts.referentialImage), currentVersion)); err != nil { + return nil, err + } + logrus.Info("Build process completed") return DAG, nil } diff --git a/cmd/main.go b/cmd/main.go index 5d409072..fbddec36 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,10 +8,6 @@ import ( "github.com/sirupsen/logrus" ) -const ( - defaultRegistryURL = "eu.gcr.io/my-test-repository" -) - func main() { app := cli.App("dib", "Docker Image Builder helps building a complex image dependency graph") diff --git a/cmd/options.go b/cmd/options.go index efff7e17..ab31b198 100644 --- a/cmd/options.go +++ b/cmd/options.go @@ -4,15 +4,21 @@ import ( cli "github.com/jawher/mow.cli" ) +const ( + defaultRegistryURL = "eu.gcr.io/my-test-repository" + defaultReferentialImage = "dib-referential" +) + type buildOpts struct { - dryRun bool - forceRebuild bool - runTests bool - retagLatest bool - generateGraph bool - localOnly bool - buildPath string - registryURL string + dryRun bool + forceRebuild bool + runTests bool + retagLatest bool + generateGraph bool + localOnly bool + buildPath string + registryURL string + referentialImage string } func defaultOpts(opts *buildOpts, cmd *cli.Cmd) { @@ -27,4 +33,7 @@ be considered as the root directory for the hash generation and comparison` cmd.StringArgPtr(&opts.buildPath, "BUILD_PATH", "docker", desc) cmd.StringOptPtr(&opts.registryURL, "registry-url", defaultRegistryURL, "Docker registry URL where images are stored.") + cmd.StringOptPtr(&opts.referentialImage, "referential-image", defaultReferentialImage, "Name of an image on "+ + "the registry. This image will serve as a reference for checking build completion of previous dib runs. Tags will be"+ + "added to this image but it has no other purpose.") } diff --git a/init/Dockerfile b/init/Dockerfile new file mode 100644 index 00000000..3dc98043 --- /dev/null +++ b/init/Dockerfile @@ -0,0 +1,11 @@ +# This Dockerfile is only used to init your dib managed repository +# +# Within this repository, run the following commands +# +# $ docker build -t /dib-referential . +# $ docker push /dib-referential +# +# If you choose to use another name than `dib-referential`, you will need +# to set it in your command-line, with `--referential-image` +FROM scratch +LABEL name="dib-referential" diff --git a/version/git.go b/version/git.go index c136a306..ef450c54 100644 --- a/version/git.go +++ b/version/git.go @@ -1,26 +1,34 @@ package version import ( + "errors" "fmt" "path" "strings" + "github.com/go-git/go-git/v5/plumbing/object" + + "github.com/radiofrance/dib/types" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5" "github.com/radiofrance/dib/exec" ) +var ErrNoPreviousBuild = errors.New("no previous build was found in git history") + // GetDiffSinceLastDockerVersionChange computes the diff since last version. // The last version is the git revision hash of the .docker-version file. // It returns the hash of the compared revision, and the list of modified files. -func GetDiffSinceLastDockerVersionChange(repositoryPath string, exec exec.Executor) (string, []string, error) { +func GetDiffSinceLastDockerVersionChange(repositoryPath string, exec exec.Executor, + registry types.DockerRegistry, dockerVersionFile, referentialImage string) (string, []string, error) { repo, err := git.PlainOpen(repositoryPath) if err != nil { return "", nil, err } - lastChangedDockerVersionHash, err := getLastChangedDockerVersion(repo) + lastChangedDockerVersionHash, err := getLastChangedDockerVersion(repo, registry, dockerVersionFile, referentialImage) if err != nil { return "", nil, err } @@ -30,6 +38,11 @@ func GetDiffSinceLastDockerVersionChange(repositoryPath string, exec exec.Execut return "", nil, err } + if lastChangedDockerVersionHash == plumbing.ZeroHash { + // No previous build was found + return "", nil, nil + } + versions := fmt.Sprintf("%s..%s", head.Hash().String(), lastChangedDockerVersionHash.String()) out, err := exec.Execute("git", "diff", versions, "--name-only") if err != nil { @@ -43,16 +56,17 @@ func GetDiffSinceLastDockerVersionChange(repositoryPath string, exec exec.Execut fullPathDiffs = append(fullPathDiffs, path.Join(repositoryPath, filename)) } - dockerVersionContent, err := getDockerVersionContentForHash(repo, lastChangedDockerVersionHash) + dockerVersionContent, err := getDockerVersionContentForHash(repo, lastChangedDockerVersionHash, dockerVersionFile) if err != nil { return "", nil, fmt.Errorf("failed to get %s content for hash %s", - DockerVersionFilename, lastChangedDockerVersionHash.String()) + dockerVersionFile, lastChangedDockerVersionHash.String()) } return strings.TrimSuffix(dockerVersionContent, "\n"), fullPathDiffs, nil } -func getDockerVersionContentForHash(repo *git.Repository, lastChangedDockerVersionHash plumbing.Hash) (string, error) { +func getDockerVersionContentForHash(repo *git.Repository, lastChangedDockerVersionHash plumbing.Hash, + dockerVersionFile string) (string, error) { commitObject, err := repo.CommitObject(lastChangedDockerVersionHash) if err != nil { return "", err @@ -61,7 +75,7 @@ func getDockerVersionContentForHash(repo *git.Repository, lastChangedDockerVersi if err != nil { return "", err } - file, err := tree.File(DockerVersionFilename) + file, err := tree.File(dockerVersionFile) if err != nil { return "", err } @@ -72,23 +86,37 @@ func getDockerVersionContentForHash(repo *git.Repository, lastChangedDockerVersi return content, nil } -func getLastChangedDockerVersion(repository *git.Repository) (plumbing.Hash, error) { - filename := DockerVersionFilename +func getLastChangedDockerVersion(repository *git.Repository, registry types.DockerRegistry, + dockerVersionFile, referentialImage string) (plumbing.Hash, error) { commitLog, err := repository.Log(&git.LogOptions{ - FileName: &filename, + PathFilter: func(p string) bool { + return p == dockerVersionFile + }, }) if err != nil { return plumbing.Hash{}, err } - _, err = commitLog.Next() // We skip Next, which is the commit where it was last modified. We want the one before - if err != nil { - return plumbing.Hash{}, err + for { + commit, err := commitLog.Next() + if err != nil { + return plumbing.Hash{}, err + } + commitID := commit.ID() + dockerVersionContent, err := getDockerVersionContentForHash(repository, commitID, dockerVersionFile) + if err != nil { + if errors.Is(err, object.ErrFileNotFound) { + // We consider we went as far as we could to find a matching commit that was already built + return plumbing.Hash{}, ErrNoPreviousBuild + } + return plumbing.Hash{}, fmt.Errorf("failed to get %s content for hash %s", dockerVersionFile, commitID) + } + refExists, err := registry.RefExists(fmt.Sprintf("%s:%s", referentialImage, dockerVersionContent)) + if err != nil { + return plumbing.Hash{}, err + } + if refExists { + return commitID, nil + } } - commit, err := commitLog.Next() - if err != nil { - return plumbing.Hash{}, err - } - - return commit.ID(), nil }