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  }