github.com/docker/import-restrictions@v0.0.0-20200820154456-7e04b6301b4d/main.go (about) 1 /* 2 Copyright 2020 Docker, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "strings" 27 28 "github.com/hashicorp/go-multierror" 29 "github.com/pkg/errors" 30 "github.com/urfave/cli/v2" 31 "gopkg.in/yaml.v2" 32 ) 33 34 type goPackage struct { 35 ImportPath string `yaml:",omitempty"` 36 Deps []string `yaml:",omitempty"` 37 } 38 39 type importRestrictions struct { 40 Path string `yaml:"path,omitempty"` 41 ForbiddenImports []string `yaml:"forbiddenImports,omitempty"` 42 } 43 44 func main() { 45 app := cli.App{ 46 Name: "import-restrictions", 47 Usage: "Restrict imports in your go project", 48 ArgsUsage: "config-file", 49 HideHelpCommand: true, 50 Flags: []cli.Flag{ 51 &cli.StringFlag{ 52 Name: "configuration", 53 Aliases: []string{"c"}, 54 Value: "import-restrictions.yaml", 55 }, 56 }, 57 Action: func(clix *cli.Context) error { 58 return run(clix.String("configuration")) 59 }, 60 } 61 62 if err := app.Run(os.Args); err != nil { 63 fmt.Fprintln(os.Stderr, err) 64 os.Exit(1) 65 } 66 } 67 68 func run(configFile string) error { 69 cfg, err := loadConfig(configFile) 70 if err != nil { 71 return err 72 } 73 74 var importErrors *multierror.Error 75 76 for _, dir := range cfg { 77 dirImports, err := getDirDeps(dir.Path) 78 if err != nil { 79 return err 80 } 81 82 for _, dirImport := range dirImports { 83 for _, dependency := range dirImport.Deps { 84 if stringSliceContains(dir.ForbiddenImports, dependency) { 85 importErrors = multierror.Append(importErrors, fmt.Errorf("Forbidden import %q in package %s", dependency, dirImport.ImportPath)) 86 } 87 } 88 } 89 } 90 91 if importErrors != nil { 92 importErrors.ErrorFormat = formatErrors 93 } 94 95 return importErrors.ErrorOrNil() 96 } 97 98 func formatErrors(errs []error) string { 99 messages := make([]string, len(errs)) 100 for i, err := range errs { 101 messages[i] = "* " + err.Error() 102 } 103 return strings.Join(messages, "\n") 104 } 105 106 func loadConfig(cfg string) ([]importRestrictions, error) { 107 config, err := ioutil.ReadFile(cfg) 108 if err != nil { 109 return nil, err 110 } 111 112 var ir []importRestrictions 113 if err := yaml.Unmarshal(config, &ir); err != nil { 114 return nil, err 115 } 116 117 return ir, nil 118 } 119 120 func getDirDeps(dir string) ([]goPackage, error) { 121 cmd := exec.Command("go", "list", "-json", fmt.Sprintf("%s...", dir)) 122 stdout, err := cmd.CombinedOutput() 123 if err != nil { 124 return nil, errors.Wrap(errors.Wrap(err, string(stdout)), "go list") 125 } 126 127 dec := json.NewDecoder(bytes.NewReader(stdout)) 128 var packages []goPackage 129 for dec.More() { 130 var pkg goPackage 131 if err := dec.Decode(&pkg); err != nil { 132 return nil, err 133 } 134 packages = append(packages, pkg) 135 } 136 137 return packages, nil 138 } 139 140 func stringSliceContains(haystack []string, needle string) bool { 141 for _, s := range haystack { 142 if s == needle { 143 return true 144 } 145 } 146 return false 147 }