github.com/jenkins-x/jx-api@v0.0.24/cmd/codegen/generator/client_set.go (about)

     1  package generator
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/format"
     8  	"go/parser"
     9  	"go/token"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"github.com/jenkins-x/jx-logging/pkg/log"
    16  
    17  	"github.com/jenkins-x/jx-api/cmd/codegen/util"
    18  	"github.com/pkg/errors"
    19  	"golang.org/x/tools/go/ast/astutil"
    20  )
    21  
    22  const (
    23  	basePath       = "k8s.io/code-generator/cmd"
    24  	defaultVersion = "kubernetes-1.11.3"
    25  )
    26  
    27  var (
    28  	// AllGenerators is a list of all the generators provide by kubernetes code-generator
    29  	allGenerators = map[string]string{
    30  		"clientset": "client-gen",
    31  		"defaulter": "defaulter-gen",
    32  		"listers":   "lister-gen",
    33  		"informers": "informer-gen",
    34  		"deepcopy":  "deepcopy-gen",
    35  	}
    36  )
    37  
    38  // InstallCodeGenerators installs client-gen from the kubernetes-incubator/reference-docs repository.
    39  func InstallCodeGenerators(version string, gopath string) error {
    40  	if version == "" {
    41  		version = defaultVersion
    42  	}
    43  	path := fmt.Sprintf("%s/...", basePath)
    44  	log.Logger().Infof("installing %s version %s into %s", path, version, gopath)
    45  	err := util.GoGet(path, version, gopath, true, false, false)
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	return nil
    51  }
    52  
    53  // GenerateClient runs the generators specified. It looks at the specified groupsWithVersions in inputPackage and generates to outputPackage (
    54  //// relative to the module outputBase). A boilerplateFile is written to the top of any generated files.
    55  func GenerateClient(generators []string, groupsWithVersions []string, inputPackage string, outputPackage string,
    56  	relativePackage string, outputBase string, boilerplateFile string, gopath string, semVer string) error {
    57  	for _, generatorName := range generators {
    58  		if generator, ok := allGenerators[generatorName]; ok {
    59  			switch generatorName {
    60  			case "clientset":
    61  				err := generateWithOutputPackage(generator,
    62  					generatorName,
    63  					groupsWithVersions,
    64  					inputPackage,
    65  					outputPackage,
    66  					outputBase,
    67  					boilerplateFile,
    68  					gopath,
    69  					"--clientset-name",
    70  					"versioned")
    71  				if err != nil {
    72  					return err
    73  				}
    74  				if semVer != "" {
    75  					oldPkg := filepath.Join(outputPackage, generatorName)
    76  					csDir := filepath.Join(outputBase, oldPkg)
    77  					svPkg := strings.ReplaceAll(oldPkg, fmt.Sprintf("/%s", relativePackage), fmt.Sprintf("/%s/%s", semVer, relativePackage))
    78  					err = fixClientImportsForSemVer(csDir, oldPkg, svPkg)
    79  					if err != nil {
    80  						return errors.Wrapf(err, "updating clientset imports for semver")
    81  					}
    82  				}
    83  			case "deepcopy":
    84  				err := defaultGenerate(generator,
    85  					generatorName,
    86  					groupsWithVersions,
    87  					inputPackage,
    88  					outputPackage,
    89  					outputBase,
    90  					boilerplateFile,
    91  					gopath,
    92  					"-O",
    93  					"zz_generated.deepcopy",
    94  					"--bounding-dirs",
    95  					inputPackage)
    96  				if err != nil {
    97  					return err
    98  				}
    99  				// If we have a semver, copy the semver's pkg directory contents to the normal pkg and delete the semver directory
   100  				if semVer != "" {
   101  					wd, err := os.Getwd()
   102  					if err != nil {
   103  						return errors.Wrapf(err, "getting working directory")
   104  					}
   105  					pkgs, err := packagesForGroupsWithVersions(inputPackage, groupsWithVersions)
   106  					if err != nil {
   107  						return err
   108  					}
   109  					for _, p := range pkgs {
   110  						svFile := filepath.Join(outputBase, inputPackage, p, "zz_generated.deepcopy.go")
   111  						pkgFile := strings.ReplaceAll(svFile, fmt.Sprintf("/%s/", semVer), "/")
   112  						exists, err := util.FileExists(svFile)
   113  						if err != nil {
   114  							return errors.Wrapf(err, "checking if file %s exists", svFile)
   115  						}
   116  						if exists {
   117  							err = util.CopyFile(svFile, pkgFile)
   118  							if err != nil {
   119  								return errors.Wrapf(err, "copying %s to %s", svFile, pkgFile)
   120  							}
   121  						}
   122  					}
   123  					err = os.RemoveAll(filepath.Join(wd, semVer))
   124  					if err != nil {
   125  						return err
   126  					}
   127  				}
   128  			case "informers":
   129  				basePkg := outputPackage
   130  				if semVer != "" {
   131  					basePkg = strings.ReplaceAll(basePkg, fmt.Sprintf("/%s", relativePackage), fmt.Sprintf("/%s/%s", semVer, relativePackage))
   132  				}
   133  				err := generateWithOutputPackage(generator,
   134  					generatorName,
   135  					groupsWithVersions,
   136  					inputPackage,
   137  					outputPackage,
   138  					outputBase,
   139  					boilerplateFile,
   140  					gopath,
   141  					"--versioned-clientset-package",
   142  					fmt.Sprintf("%s/clientset/versioned", basePkg),
   143  					"--listers-package",
   144  					fmt.Sprintf("%s/listers", basePkg))
   145  				if err != nil {
   146  					return err
   147  				}
   148  				if semVer != "" {
   149  					oldPkg := filepath.Join(outputPackage, generatorName)
   150  					infDir := filepath.Join(outputBase, oldPkg)
   151  					svPkg := strings.ReplaceAll(oldPkg, fmt.Sprintf("/%s", relativePackage), fmt.Sprintf("/%s/%s", semVer, relativePackage))
   152  					err = fixClientImportsForSemVer(infDir, oldPkg, svPkg)
   153  					if err != nil {
   154  						return errors.Wrapf(err, "updating informer imports for semver")
   155  					}
   156  				}
   157  			default:
   158  				err := generateWithOutputPackage(generator, generatorName, groupsWithVersions, inputPackage,
   159  					outputPackage, outputBase, boilerplateFile, gopath)
   160  				if err != nil {
   161  					return err
   162  				}
   163  			}
   164  		} else {
   165  			return errors.Errorf("no generator %s available", generatorName)
   166  		}
   167  	}
   168  	return nil
   169  }
   170  
   171  // GetBoilerplateFile is responsible for canonicalizing the name of the boilerplate file
   172  func GetBoilerplateFile(fileName string) (string, error) {
   173  	if fileName != "" {
   174  		if _, err := os.Stat(fileName); err != nil && !os.IsNotExist(err) {
   175  			return "", errors.Wrapf(err, "error reading boilerplate file %s", fileName)
   176  		} else if err == nil {
   177  			abs, err := filepath.Abs(fileName)
   178  			if err == nil {
   179  				fileName = abs
   180  			} else {
   181  				log.Logger().Errorf("error converting %s to absolute path so leaving as is %v", fileName, err)
   182  			}
   183  		}
   184  	}
   185  	return fileName, nil
   186  }
   187  
   188  func generateWithOutputPackage(generator string, name string, groupsWithVersions []string,
   189  	inputPackage string, outputPackage string, outputBase string, boilerplateFile string, gopath string, args ...string) error {
   190  	args = append(args, "--output-package", fmt.Sprintf("%s/%s", outputPackage, name))
   191  	return defaultGenerate(generator, name, groupsWithVersions, inputPackage, outputPackage, outputBase,
   192  		boilerplateFile, gopath, args...)
   193  }
   194  
   195  func fixClientImportsForSemVer(clientDir string, oldPackage string, semVerPackage string) error {
   196  	return filepath.Walk(clientDir, func(path string, info os.FileInfo, err error) error {
   197  		if strings.HasSuffix(path, ".go") {
   198  			fs := token.NewFileSet()
   199  			f, err := parser.ParseFile(fs, path, nil, parser.ParseComments)
   200  			if err != nil {
   201  				return errors.Wrapf(err, "parsing %s", path)
   202  			}
   203  			importsToReplace := make(map[string]string)
   204  
   205  			for _, imp := range f.Imports {
   206  				existingImp := strings.Replace(imp.Path.Value, `"`, ``, 2)
   207  				if strings.HasPrefix(existingImp, oldPackage) {
   208  					importsToReplace[existingImp] = strings.ReplaceAll(existingImp, oldPackage, semVerPackage)
   209  				}
   210  			}
   211  			if len(importsToReplace) > 0 {
   212  				for oldPkg, newPkg := range importsToReplace {
   213  					astutil.RewriteImport(fs, f, oldPkg, newPkg)
   214  				}
   215  				// Sort the imports
   216  				ast.SortImports(fs, f)
   217  				var buf bytes.Buffer
   218  				err = format.Node(&buf, fs, f)
   219  				if err != nil {
   220  					return errors.Wrapf(err, "convert AST to []byte for %s", path)
   221  				}
   222  				err = ioutil.WriteFile(path, buf.Bytes(), 0600)
   223  				if err != nil {
   224  					return errors.Wrapf(err, "writing %s", path)
   225  				}
   226  			}
   227  		}
   228  		return nil
   229  	})
   230  }