Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / sigs.k8s.io / controller-tools / pkg / crd / generator / generator.go
1 /*
2 Copyright 2018 The Kubernetes Authors.
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 generator
18
19 import (
20         "fmt"
21         "log"
22         "os"
23         "path"
24         "strings"
25
26         "github.com/ghodss/yaml"
27         "github.com/spf13/afero"
28         extensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
29         "k8s.io/gengo/args"
30         "k8s.io/gengo/types"
31         crdutil "sigs.k8s.io/controller-tools/pkg/crd/util"
32         "sigs.k8s.io/controller-tools/pkg/internal/codegen"
33         "sigs.k8s.io/controller-tools/pkg/internal/codegen/parse"
34         "sigs.k8s.io/controller-tools/pkg/util"
35 )
36
37 // Generator generates CRD manifests from API resource definitions defined in Go source files.
38 type Generator struct {
39         RootPath          string
40         OutputDir         string
41         Repo              string
42         Domain            string
43         Namespace         string
44         SkipMapValidation bool
45
46         // OutFs is filesystem to be used for writing out the result
47         OutFs afero.Fs
48
49         // apisPkg is the absolute Go pkg name for current project's 'pkg/apis' pkg.
50         // This is needed to determine if a Type belongs to the project or it is a referred Type.
51         apisPkg string
52
53         // APIsPath and APIsPkg allow customized generation for Go types existing under directories other than pkg/apis
54         APIsPath string
55         APIsPkg  string
56 }
57
58 // ValidateAndInitFields validate and init generator fields.
59 func (c *Generator) ValidateAndInitFields() error {
60         var err error
61
62         if c.OutFs == nil {
63                 c.OutFs = afero.NewOsFs()
64         }
65
66         if len(c.RootPath) == 0 {
67                 // Take current path as root path if not specified.
68                 c.RootPath, err = os.Getwd()
69                 if err != nil {
70                         return err
71                 }
72         }
73
74         // Validate PROJECT file if Domain or Repo are not set manually
75         if len(c.Domain) == 0 || len(c.Repo) == 0 {
76                 if !crdutil.PathHasProjectFile(c.RootPath) {
77                         return fmt.Errorf("PROJECT file missing in dir %s", c.RootPath)
78                 }
79         }
80
81         if len(c.Repo) == 0 {
82                 c.Repo = crdutil.GetRepoFromProject(c.RootPath)
83         }
84
85         // If Domain is not explicitly specified,
86         // try to search for PROJECT file as a basis.
87         if len(c.Domain) == 0 {
88                 c.Domain = crdutil.GetDomainFromProject(c.RootPath)
89         }
90
91         err = c.setAPIsPkg()
92         if err != nil {
93                 return err
94         }
95
96         // Init output directory
97         if c.OutputDir == "" {
98                 c.OutputDir = path.Join(c.RootPath, "config/crds")
99         }
100
101         return nil
102 }
103
104 // Do manages CRD generation.
105 func (c *Generator) Do() error {
106         arguments := args.Default()
107         b, err := arguments.NewBuilder()
108         if err != nil {
109                 return fmt.Errorf("failed making a parser: %v", err)
110         }
111
112         // Switch working directory to root path.
113         wd, err := os.Getwd()
114         if err != nil {
115                 return err
116         }
117         if err := os.Chdir(c.RootPath); err != nil {
118                 return fmt.Errorf("failed switching working dir: %v", err)
119         }
120         defer func() {
121                 if err := os.Chdir(wd); err != nil {
122                         log.Fatalf("Failed to switch back to original working dir: %v", err)
123                 }
124         }()
125
126         if err := b.AddDirRecursive(fmt.Sprintf("%s/%s", c.Repo, c.APIsPath)); err != nil {
127                 return fmt.Errorf("failed making a parser: %v", err)
128         }
129
130         ctx, err := parse.NewContext(b)
131         if err != nil {
132                 return fmt.Errorf("failed making a context: %v", err)
133         }
134
135         arguments.CustomArgs = &parse.Options{SkipMapValidation: c.SkipMapValidation}
136
137         // TODO: find an elegant way to fulfill the domain in APIs.
138         p := parse.NewAPIs(ctx, arguments, c.Domain, c.apisPkg)
139         crds := c.getCrds(p)
140
141         return c.writeCRDs(crds)
142 }
143
144 func (c *Generator) writeCRDs(crds map[string][]byte) error {
145         // Ensure output dir exists.
146         if err := c.OutFs.MkdirAll(c.OutputDir, os.FileMode(0700)); err != nil {
147                 return err
148         }
149
150         for file, crd := range crds {
151                 outFile := path.Join(c.OutputDir, file)
152                 if err := (&util.FileWriter{Fs: c.OutFs}).WriteFile(outFile, crd); err != nil {
153                         return err
154                 }
155         }
156         return nil
157 }
158
159 func getCRDFileName(resource *codegen.APIResource) string {
160         elems := []string{resource.Group, resource.Version, strings.ToLower(resource.Kind)}
161         return strings.Join(elems, "_") + ".yaml"
162 }
163
164 func (c *Generator) getCrds(p *parse.APIs) map[string][]byte {
165         crds := map[string]extensionsv1beta1.CustomResourceDefinition{}
166         for _, g := range p.APIs.Groups {
167                 for _, v := range g.Versions {
168                         for _, r := range v.Resources {
169                                 crd := r.CRD
170                                 // ignore types which do not belong to this project
171                                 if !c.belongsToAPIsPkg(r.Type) {
172                                         continue
173                                 }
174                                 if len(c.Namespace) > 0 {
175                                         crd.Namespace = c.Namespace
176                                 }
177                                 fileName := getCRDFileName(r)
178                                 crds[fileName] = crd
179                         }
180                 }
181         }
182
183         result := map[string][]byte{}
184         for file, crd := range crds {
185                 b, err := yaml.Marshal(crd)
186                 if err != nil {
187                         log.Fatalf("Error: %v", err)
188                 }
189                 result[file] = b
190         }
191
192         return result
193 }
194
195 // belongsToAPIsPkg returns true if type t is defined under pkg/apis pkg of
196 // current project.
197 func (c *Generator) belongsToAPIsPkg(t *types.Type) bool {
198         return strings.HasPrefix(t.Name.Package, c.apisPkg)
199 }
200
201 func (c *Generator) setAPIsPkg() error {
202         if c.APIsPath == "" {
203                 c.APIsPath = "pkg/apis"
204         }
205
206         c.apisPkg = c.APIsPkg
207         if c.apisPkg == "" {
208                 // Validate apis directory exists under working path
209                 apisPath := path.Join(c.RootPath, c.APIsPath)
210                 if _, err := os.Stat(apisPath); err != nil {
211                         return fmt.Errorf("error validating apis path %s: %v", apisPath, err)
212                 }
213
214                 c.apisPkg = path.Join(c.Repo, c.APIsPath)
215         }
216         return nil
217 }