tools/dockerversioning/scripts/dockerfiles/main.go (224 lines of code) (raw):

/* Command line tool for updating Dockerfiles based on versions.yaml. */ package main import ( "bytes" "flag" "io/ioutil" "log" "os" "path" "path/filepath" "reflect" "strings" "text/template" "github.com/GoogleCloudPlatform/click-to-deploy/tools/dockerversioning/versions" ) type indentFormat string // indent replaces leading spaces in the input string with the // value of the indentFormat object. func (f indentFormat) indent(s string) string { temp := strings.Split(s, "\n") str := "" for index, line := range temp { if index > 0 { str = str + "\n" } trimmed := strings.TrimLeft(line, " ") diff := len(line) - len(trimmed) prefix := strings.Repeat(string(f), diff) str = str + prefix + trimmed } return str } const keyServersRetryTemplate = `found='' && \ for server in \ pool.sks-keyservers.net \ na.pool.sks-keyservers.net \ eu.pool.sks-keyservers.net \ oc.pool.sks-keyservers.net \ ha.pool.sks-keyservers.net \ hkp://p80.pool.sks-keyservers.net:80 \ hkp://keyserver.ubuntu.com:80 \ pgp.mit.edu \ ; do \ {{ . }} \ && found=yes && break; \ done; \ test -n "$found"` func funcKeyServersRetryLoop(indentSequence string, cmd string) string { f := indentFormat(indentSequence) tmpl, err := template.New("retryTemplate").Parse(f.indent(keyServersRetryTemplate)) check(err) var result bytes.Buffer tmpl.Execute(&result, cmd) return funcIndent(indentSequence, string(result.Bytes())) } func funcIndent(leading string, s string) string { temp := strings.Split(s, "\n") str := "" for index, line := range temp { if index > 0 { str = str + "\n" + leading } str = str + line } return str } func renderDockerfile(version versions.Version, tmpl template.Template) []byte { var result bytes.Buffer tmpl.Execute(&result, version) return result.Bytes() } func writeDockerfile(version versions.Version, data []byte, createDir bool) { path := filepath.Join(version.Dir, "Dockerfile") // Delete first to make sure file is created with the right mode. deleteIfFileExists(path) // Create nested directory structure if needed. if createDir { os.MkdirAll(version.Dir, os.ModePerm) } err := ioutil.WriteFile(path, data, 0644) check(err) } func findFilesToCopy(templateDir string, callback func(path string, fileInfo os.FileInfo)) { filepath.Walk(templateDir, func(path string, info os.FileInfo, err error) error { check(err) if strings.HasSuffix(info.Name(), ".template") || info.IsDir() { return nil } path, err = filepath.Rel(templateDir, path) check(err) callback(path, info) return nil }) } func copyFiles(version versions.Version, templateDir string, createDir bool) { findFilesToCopy(templateDir, func(filePath string, fileInfo os.FileInfo) { data, err := ioutil.ReadFile(filepath.Join(templateDir, filePath)) check(err) target := filepath.Join(version.Dir, filePath) // Delete first to make sure file is created with the right mode. deleteIfFileExists(target) // Create nested directory structure if needed. if createDir { os.MkdirAll(path.Dir(target), os.ModePerm) } err = ioutil.WriteFile(target, data, fileInfo.Mode()) check(err) }) } func deleteIfFileExists(path string) { if fileInfo, err := os.Stat(path); err != nil { if !os.IsNotExist(err) { log.Fatalf("File %s exists but cannot be stat'ed", path) } } else { if fileInfo.IsDir() { log.Fatalf("%s is unexpectedly a directory", path) } err = os.Remove(path) check(err) } } func verifyDockerfiles(version versions.Version, templateDir string, tmpl template.Template) (failureCount int) { foundDockerfile := make(map[string]bool) failureCount = 0 warningCount := 0 data := renderDockerfile(version, tmpl) path := filepath.Join(version.Dir, "Dockerfile") dockerfile, err := ioutil.ReadFile(path) check(err) foundDockerfile[path] = true if string(dockerfile) == string(data) { log.Printf("%s: OK", path) } else { failureCount++ log.Printf("%s: FAILED", path) } err = filepath.Walk(templateDir, func(path string, info os.FileInfo, err error) error { check(err) if info.Name() == "Dockerfile" && !info.IsDir() && !foundDockerfile[path] { warningCount++ log.Printf("%s: UNIDENTIFIED (warning)", path) } return nil }) check(err) if failureCount == 0 && warningCount > 0 { log.Print("Dockerfile verification completed: PASSED (with warnings)") } else if failureCount == 0 { log.Print("Dockerfile verification completed: PASSED") } else { log.Print("Dockerfile verification completed: FAILED") } return } func verifyCopiedFiles(version versions.Version, templateDir string) (failureCount int) { failureCount = 0 findFilesToCopy(templateDir, func(path string, sourceFileInfo os.FileInfo) { failureCount++ source := filepath.Join(templateDir, path) target := filepath.Join(version.Dir, path) targetFileInfo, err := os.Stat(target) if err != nil { log.Printf("%s is expected but cannot be stat'ed", target) log.Printf("Please, check accessability of %s", source) return } // Check mode for owner only. sourcePerm := os.FileMode(sourceFileInfo.Mode().Perm() & 0700) targetPerm := os.FileMode(targetFileInfo.Mode().Perm() & 0700) if sourcePerm != targetPerm { log.Printf("%s has wrong file mode %v, expected %v", target, targetPerm, sourcePerm) return } expected, err := ioutil.ReadFile(source) check(err) actual, err := ioutil.ReadFile(filepath.Join(version.Dir, path)) if err != nil { log.Printf("%s is expected but cannot be read", target) return } if !reflect.DeepEqual(expected, actual) { log.Printf("%s content is different from its template", target) return } log.Printf("%s: OK", target) failureCount-- }) if failureCount == 0 { log.Print("Copied files verification completed: PASSED") } else { log.Print("Copied files verification completed: FAILED") } return } func check(e error) { if e != nil { panic(e) } } func main() { defaultTemplateDirPtr := flag.String("template_dir", "templates", "Path to directory containing Dockerfile.template and any other files to copy over") verifyPtr := flag.Bool("verify_only", false, "Verify dockerfiles") createDirPtr := flag.Bool("create_directories", false, "Create new directories") failureCount := 0 flag.Parse() var spec versions.Spec spec = versions.LoadVersions("versions.yaml") for _, version := range spec.Versions { // Ignore version without Dir for possibility to use builder images if version.Dir == "" { continue } // templatePath - path to Dockerfile.template templatePath := filepath.Join(*defaultTemplateDirPtr, version.TemplateSubDir, "Dockerfile.template") templateData, err := ioutil.ReadFile(templatePath) templateString := string(templateData) check(err) tmpl, err := template. New("dockerfileTemplate"). Funcs(template.FuncMap{"KeyServersRetryLoop": funcKeyServersRetryLoop}). Parse(templateString) check(err) if *verifyPtr { failureCount += verifyDockerfiles(version, filepath.Join(*defaultTemplateDirPtr, version.TemplateSubDir), *tmpl) failureCount += verifyCopiedFiles(version, filepath.Join(*defaultTemplateDirPtr, version.TemplateSubDir)) } else { data := renderDockerfile(version, *tmpl) writeDockerfile(version, data, *createDirPtr) // if version.TemplateSubDir is empty then we default to 'templates' folder copyFiles(version, filepath.Join(*defaultTemplateDirPtr, version.TemplateSubDir), *createDirPtr) } } os.Exit(failureCount) }