diff --git a/cmd/build.go b/cmd/build.go index 3de085f9..a6ddcf80 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -1,6 +1,9 @@ package main import ( + "errors" + "fmt" + "os" "path" "github.com/radiofrance/dib/docker" @@ -36,25 +39,41 @@ func cmdBuild(cmd *cli.Cmd) { cmd.Action = func() { preflight.RunPreflightChecks([]string{"docker"}) + DAG, err := doBuild(opts) if err != nil { logrus.Fatalf("Build failed: %v", err) } if opts.generateGraph { - if err := graphviz.GenerateGraph(DAG, opts.outputDir); err != nil { + workingDir, err := getWorkingDir() + if err != nil { + logrus.Fatalf("failed to get current working directory: %v", err) + } + if err := graphviz.GenerateGraph(DAG, workingDir); err != nil { logrus.Fatalf("Generating graph failed: %v", err) } } } } +func getWorkingDir() (string, error) { + currentDir, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("failed to get current working directory: %w", err) + } + return currentDir, nil +} + func doBuild(opts buildOpts) (*dag.DAG, error) { + workingDir, err := getWorkingDir() + if err != nil { + return nil, fmt.Errorf("failed to get current working directory: %w", err) + } shell := &exec.ShellExecutor{ - Dir: opts.inputDir, + Dir: workingDir, } - var err error gcrRegistry, err := registry.NewRegistry(opts.registryURL, opts.dryRun) if err != nil { return nil, err @@ -74,19 +93,24 @@ func doBuild(opts buildOpts) (*dag.DAG, error) { DAG.Tagger = gcrRegistry } - buildPath := path.Join(opts.inputDir, opts.buildDir) + buildPath := path.Join(workingDir, opts.buildPath) logrus.Infof("Building images in directory \"%s\"", buildPath) logrus.Debug("Generate DAG") DAG.GenerateDAG(buildPath, opts.registryURL) logrus.Debug("Generate DAG -- Done") - currentVersion, err := versn.CheckDockerVersionIntegrity(opts.inputDir, opts.buildDir) + dockerDir, err := findDockerRootDir(workingDir, opts.buildPath) + if err != nil { + return nil, err + } + + currentVersion, err := versn.CheckDockerVersionIntegrity(path.Join(workingDir, dockerDir)) if err != nil { return nil, err } - previousVersion, diffs, err := versn.GetDiffSinceLastDockerVersionChange(opts.inputDir, shell) + previousVersion, diffs, err := versn.GetDiffSinceLastDockerVersionChange(workingDir, shell) if err != nil { return nil, err } @@ -116,3 +140,23 @@ func doBuild(opts buildOpts) (*dag.DAG, error) { logrus.Info("Build process completed") return DAG, nil } + +// findDockerRootDir iterates over the buildPath to find the first matching directory containing +// a .docker-version file. We consider this directory as the root docker directory containing all the dockerfiles. +func findDockerRootDir(workingDir, buildPath string) (string, error) { + searchPath := buildPath + for { + if _, err := os.Stat(path.Join(workingDir, searchPath, versn.DockerVersionFilename)); err == nil { + return searchPath, nil + } else if !errors.Is(err, os.ErrNotExist) { + return "", err + } + + dir, _ := path.Split(buildPath) + if dir == "" { + return "", fmt.Errorf("searching for docker root dir failed, no directory in %s "+ + "contains a %s file", buildPath, versn.DockerVersionFilename) + } + searchPath = dir + } +} diff --git a/cmd/graph.go b/cmd/graph.go index 75fdaa22..b04b3887 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -3,6 +3,8 @@ package main import ( "log" + "github.com/sirupsen/logrus" + cli "github.com/jawher/mow.cli" "github.com/radiofrance/dib/graphviz" "github.com/radiofrance/dib/preflight" @@ -21,7 +23,11 @@ func cmdGraph(cmd *cli.Cmd) { if err != nil { log.Fatal(err) } - if err := graphviz.GenerateGraph(DAG, opts.outputDir); err != nil { + workingDir, err := getWorkingDir() + if err != nil { + logrus.Fatalf("failed to get current working directory: %v", err) + } + if err := graphviz.GenerateGraph(DAG, workingDir); err != nil { log.Fatal(err) } } diff --git a/cmd/options.go b/cmd/options.go index bf9a6ee7..efff7e17 100644 --- a/cmd/options.go +++ b/cmd/options.go @@ -1,9 +1,6 @@ package main import ( - "log" - "os" - cli "github.com/jawher/mow.cli" ) @@ -14,29 +11,20 @@ type buildOpts struct { retagLatest bool generateGraph bool localOnly bool - buildDir string - inputDir string - outputDir string + buildPath string registryURL string } func defaultOpts(opts *buildOpts, cmd *cli.Cmd) { - pwd, err := os.Getwd() - if err != nil { - log.Fatal(err) - } + cmd.Spec = "[OPTIONS] [BUILD_PATH]" - //nolint:lll - desc := `Path to the directory containing the Dockerfiles, relative to the input directory specified by --input. -All Dockerfiles within this directory will be recursively found and added to the build graph. -You can provide any subdirectory if you want to focus on a reduced set of images, as long as it has at least one Dockerfile in it.` + desc := `Path to the directory you want to build All Dockerfiles within this directory will be recursively +found and added to the build graph. You can provide any subdirectory if you want to focus on a reduced set of images, +as long as it has at least one Dockerfile in it. - cmd.Spec = "[OPTIONS] [BUILD_PATH]" - cmd.StringArgPtr(&opts.buildDir, "BUILD_PATH", "docker", desc) +It is also expected that one of the director in this path contains a .docker-version file. This directory will +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.outputDir, "o output", pwd, - "Output directory where .dot and .png files will be generated") - cmd.StringOptPtr(&opts.inputDir, "i input", pwd, - "Root directory where docker directory and .dockerversion files are stored") } diff --git a/dag/dag.go b/dag/dag.go index 94d60d24..c4d6928a 100644 --- a/dag/dag.go +++ b/dag/dag.go @@ -29,23 +29,23 @@ func (dag *DAG) GenerateDAG(buildPath string, registryPrefix string) { return err } if dockerfile.IsDockerfile(filePath) { - dockerfile, err := dockerfile.ParseDockerfile(filePath) + dckfile, err := dockerfile.ParseDockerfile(filePath) if err != nil { return err } - skipBuild, hasSkipLabel := dockerfile.Labels["skipbuild"] + skipBuild, hasSkipLabel := dckfile.Labels["skipbuild"] if hasSkipLabel && skipBuild == "true" { return nil } - imageShortName, hasSkipLabel := dockerfile.Labels["name"] + imageShortName, hasSkipLabel := dckfile.Labels["name"] if !hasSkipLabel { return fmt.Errorf("missing label \"image\" in Dockerfile at path \"%s\"", filePath) } img := &Image{ Name: fmt.Sprintf("%s/%s", registryPrefix, imageShortName), ShortName: imageShortName, - Dockerfile: dockerfile, + Dockerfile: dckfile, RebuildCond: sync.NewCond(&sync.Mutex{}), Builder: dag.Builder, Registry: dag.Registry, @@ -53,7 +53,7 @@ func (dag *DAG) GenerateDAG(buildPath string, registryPrefix string) { Tagger: dag.Tagger, } - allParents[img.Name] = dockerfile.From + allParents[img.Name] = dckfile.From cache[img.Name] = img } return nil diff --git a/version/git.go b/version/git.go index 4d941c53..c136a306 100644 --- a/version/git.go +++ b/version/git.go @@ -46,10 +46,10 @@ func GetDiffSinceLastDockerVersionChange(repositoryPath string, exec exec.Execut dockerVersionContent, err := getDockerVersionContentForHash(repo, lastChangedDockerVersionHash) if err != nil { return "", nil, fmt.Errorf("failed to get %s content for hash %s", - dockerVersionFilename, lastChangedDockerVersionHash.String()) + DockerVersionFilename, lastChangedDockerVersionHash.String()) } - return dockerVersionContent, fullPathDiffs, nil + return strings.TrimSuffix(dockerVersionContent, "\n"), fullPathDiffs, nil } func getDockerVersionContentForHash(repo *git.Repository, lastChangedDockerVersionHash plumbing.Hash) (string, error) { @@ -61,7 +61,7 @@ func getDockerVersionContentForHash(repo *git.Repository, lastChangedDockerVersi if err != nil { return "", err } - file, err := tree.File(dockerVersionFilename) + file, err := tree.File(DockerVersionFilename) if err != nil { return "", err } @@ -73,7 +73,7 @@ func getDockerVersionContentForHash(repo *git.Repository, lastChangedDockerVersi } func getLastChangedDockerVersion(repository *git.Repository) (plumbing.Hash, error) { - filename := dockerVersionFilename + filename := DockerVersionFilename commitLog, err := repository.Log(&git.LogOptions{ FileName: &filename, }) diff --git a/version/hash.go b/version/hash.go index 0c3a43af..5f8d2a84 100644 --- a/version/hash.go +++ b/version/hash.go @@ -25,6 +25,10 @@ func humanReadableHashFn(files []string, open func(string) (io.ReadCloser, error if strings.Contains(file, "\n") { return "", errors.New("dirhash: filenames with newlines are not supported") } + if file == DockerVersionFilename { + // We ignore the hash file itself in the hash process + continue + } readCloser, err := open(file) if err != nil { return "", err diff --git a/version/version.go b/version/version.go index 1210ab5a..03c7607e 100644 --- a/version/version.go +++ b/version/version.go @@ -7,18 +7,18 @@ import ( "strings" ) -const dockerVersionFilename = ".docker-version" +const DockerVersionFilename = ".docker-version" // CheckDockerVersionIntegrity verifies the consistency of the version hash // contained in the .docker-version file against the revision hash from git. // It returns the version if the verification is successful. -func CheckDockerVersionIntegrity(rootPath, buildPath string) (string, error) { - fileVersion, err := getDockerVersionFromFile(path.Join(rootPath, dockerVersionFilename)) +func CheckDockerVersionIntegrity(buildPath string) (string, error) { + fileVersion, err := getDockerVersionFromFile(path.Join(buildPath, DockerVersionFilename)) if err != nil { return "", err } - dockerVersionHash, err := GetDockerVersionHash(path.Join(rootPath, buildPath)) + dockerVersionHash, err := GetDockerVersionHash(buildPath) if err != nil { return "", fmt.Errorf("could not obtain docker-version hash: %w", err) }