From d7f49531f84122ba759ab93d264be442ab8741a4 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Mon, 31 Oct 2016 00:48:49 +1100 Subject: [PATCH] gomtree: switch to using compare Switch the commandline to use the .Compare() API when checking specification files against the state of a tar archive or other archive. The main purpose is to completely remove the check.go code from being necessary (outside of a wrapper). Signed-off-by: Aleksa Sarai --- cmd/gomtree/main.go | 163 +++++++++++++++++++++++++++----------------- 1 file changed, 99 insertions(+), 64 deletions(-) diff --git a/cmd/gomtree/main.go b/cmd/gomtree/main.go index f69374d..8e7e941 100644 --- a/cmd/gomtree/main.go +++ b/cmd/gomtree/main.go @@ -29,30 +29,34 @@ var ( flVersion = flag.Bool("version", false, "display the version of this tool") ) -var formats = map[string]func(*mtree.Result) string{ +var formats = map[string]func([]mtree.InodeDelta) string{ // Outputs the errors in the BSD format. - "bsd": func(r *mtree.Result) string { + "bsd": func(d []mtree.InodeDelta) string { var buffer bytes.Buffer - for _, fail := range r.Failures { - fmt.Fprintln(&buffer, fail) + for _, delta := range d { + if delta.Type() == mtree.Modified { + fmt.Fprintln(&buffer, delta) + } } return buffer.String() }, // Outputs the full result struct in JSON. - "json": func(r *mtree.Result) string { + "json": func(d []mtree.InodeDelta) string { var buffer bytes.Buffer - if err := json.NewEncoder(&buffer).Encode(r); err != nil { + if err := json.NewEncoder(&buffer).Encode(d); err != nil { panic(err) } return buffer.String() }, // Outputs only the paths which failed to validate. - "path": func(r *mtree.Result) string { + "path": func(d []mtree.InodeDelta) string { var buffer bytes.Buffer - for _, fail := range r.Failures { - fmt.Fprintln(&buffer, fail.Path) + for _, delta := range d { + if delta.Type() == mtree.Modified { + fmt.Fprintln(&buffer, delta.Path()) + } } return buffer.String() }, @@ -66,6 +70,7 @@ func main() { } // so that defers cleanly exec + // TODO: Switch everything to being inside a function, to remove the need for isErr. var isErr bool defer func() { if isErr { @@ -104,9 +109,11 @@ func main() { } var ( + err error tmpKeywords []string currentKeywords []string ) + // -k if *flUseKeywords != "" { tmpKeywords = splitKeywordsArg(*flUseKeywords) @@ -143,8 +150,23 @@ func main() { currentKeywords = tmpKeywords } + // Check mutual exclusivity of keywords. + // TODO(cyphar): Abstract this inside keywords.go. + if inSlice("tar_time", currentKeywords) && inSlice("time", currentKeywords) { + log.Printf("tar_time and time are mutually exclusive keywords") + isErr = true + return + } + + // If we're doing a comparison, we always are comparing between a spec and + // state DH. If specDh is nil, we are generating a new one. + var ( + specDh *mtree.DirectoryHierarchy + stateDh *mtree.DirectoryHierarchy + specKeywords []string + ) + // -f - var dh *mtree.DirectoryHierarchy if *flFile != "" && !*flCreate { // load the hierarchy, if we're not creating a new spec fh, err := os.Open(*flFile) @@ -153,27 +175,30 @@ func main() { isErr = true return } - dh, err = mtree.ParseSpec(fh) + specDh, err = mtree.ParseSpec(fh) fh.Close() if err != nil { log.Println(err) isErr = true return } + + // We can't check against more fields than in the specKeywords list, so + // currentKeywords can only have a subset of specKeywords. + specKeywords = mtree.CollectUsedKeywords(specDh) } // -list-used if *flListUsedKeywords { - if *flFile == "" { + if specDh == nil { log.Println("no specification provided. please provide a validation manifest") - defer os.Exit(1) isErr = true return } - usedKeywords := mtree.CollectUsedKeywords(dh) + if *flResultFormat == "json" { // if they're asking for json, give it to them - data := map[string][]string{*flFile: usedKeywords} + data := map[string][]string{*flFile: specKeywords} buf, err := json.MarshalIndent(data, "", " ") if err != nil { defer os.Exit(1) @@ -183,7 +208,7 @@ func main() { fmt.Println(string(buf)) } else { fmt.Printf("Keywords used in [%s]:\n", *flFile) - for _, kw := range usedKeywords { + for _, kw := range specKeywords { fmt.Printf(" %s", kw) if _, ok := mtree.KeywordFuncs[kw]; !ok { fmt.Print(" (unsupported)") @@ -194,6 +219,36 @@ func main() { return } + if specKeywords != nil { + // If we didn't actually change the set of keywords, we can just use specKeywords. + if *flUseKeywords == "" && *flAddKeywords == "" { + currentKeywords = specKeywords + } + + for _, keyword := range currentKeywords { + // As always, time is a special case. + // TODO: Fix that. + if (keyword == "time" && inSlice("tar_time", specKeywords)) || (keyword == "tar_time" && inSlice("time", specKeywords)) { + continue + } + + if !inSlice(keyword, specKeywords) { + log.Printf("cannot verify keywords not in mtree specification: %s\n", keyword) + isErr = true + } + if isErr { + return + } + } + } + + // -p and -T are mutually exclusive + if *flPath != "" && *flTar != "" { + log.Println("options -T and -p are mutually exclusive") + isErr = true + return + } + // -p var rootPath = "." if *flPath != "" { @@ -201,7 +256,6 @@ func main() { } // -T - var tdh *mtree.DirectoryHierarchy if *flTar != "" { var input io.Reader if *flTar == "-" { @@ -229,7 +283,15 @@ func main() { return } var err error - tdh, err = ts.Hierarchy() + stateDh, err = ts.Hierarchy() + if err != nil { + log.Println(err) + isErr = true + return + } + } else { + // with a root directory + stateDh, err = mtree.Walk(rootPath, nil, currentKeywords) if err != nil { log.Println(err) isErr = true @@ -239,36 +301,36 @@ func main() { // -c if *flCreate { - // create a directory hierarchy - // with a tar stream - if tdh != nil { - tdh.WriteTo(os.Stdout) - } else { - // with a root directory - dh, err := mtree.Walk(rootPath, nil, currentKeywords) + fh := os.Stdout + if *flFile != "" { + fh, err = os.Create(*flFile) if err != nil { log.Println(err) isErr = true return } - dh.WriteTo(os.Stdout) - } - } else if tdh != nil || dh != nil { - var res *mtree.Result - var err error - // else this is a validation - if *flTar != "" { - res, err = mtree.TarCheck(tdh, dh, currentKeywords) - } else { - res, err = mtree.Check(rootPath, dh, currentKeywords) } + + // output stateDh + stateDh.WriteTo(fh) + return + } + + // This is a validation. + if specDh != nil && stateDh != nil { + var res []mtree.InodeDelta + + res, err = mtree.Compare(specDh, stateDh, currentKeywords) if err != nil { log.Println(err) isErr = true return } - if res != nil && len(res.Failures) > 0 { - defer os.Exit(1) + if res != nil { + if len(res) > 0 { + defer os.Exit(1) + } + out := formatFunc(res) if _, err := os.Stdout.Write([]byte(out)); err != nil { log.Println(err) @@ -276,36 +338,9 @@ func main() { return } } - if res != nil { - if len(res.Extra) > 0 { - defer os.Exit(1) - for _, extra := range res.Extra { - extrapath, err := extra.Path() - if err != nil { - log.Println(err) - isErr = true - return - } - fmt.Printf("%s extra\n", extrapath) - } - } - if len(res.Missing) > 0 { - defer os.Exit(1) - for _, missing := range res.Missing { - missingpath, err := missing.Path() - if err != nil { - log.Println(err) - isErr = true - return - } - fmt.Printf("%s missing\n", missingpath) - } - } - } } else { log.Println("neither validating or creating a manifest. Please provide additional arguments") isErr = true - defer os.Exit(1) return } }