github.com/jmrodri/operator-sdk@v0.5.0/commands/operator-sdk/cmd/generate/openapi.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package generate 16 17 import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "os/exec" 22 "path/filepath" 23 "strings" 24 25 genutil "github.com/operator-framework/operator-sdk/commands/operator-sdk/cmd/generate/internal" 26 "github.com/operator-framework/operator-sdk/internal/util/projutil" 27 "github.com/operator-framework/operator-sdk/pkg/scaffold" 28 "github.com/operator-framework/operator-sdk/pkg/scaffold/input" 29 30 "github.com/ghodss/yaml" 31 log "github.com/sirupsen/logrus" 32 "github.com/spf13/cobra" 33 apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 ) 36 37 var headerFile string 38 39 func NewGenerateOpenAPICmd() *cobra.Command { 40 openAPICmd := &cobra.Command{ 41 Use: "openapi", 42 Short: "Generates OpenAPI specs for API's", 43 Long: `generate openapi generates OpenAPI validation specs in Go from tagged types 44 in all pkg/apis/<group>/<version> directories. Go code is generated under 45 pkg/apis/<group>/<version>/zz_generated.openapi.go. CRD's are generated, or 46 updated if they exist for a particular group + version + kind, under 47 deploy/crds/<group>_<version>_<kind>_crd.yaml; OpenAPI V3 validation YAML 48 is generated as a 'validation' object. 49 50 Example: 51 $ operator-sdk generate openapi 52 $ tree pkg/apis 53 pkg/apis/ 54 └── app 55 └── v1alpha1 56 ├── zz_generated.openapi.go 57 $ tree deploy/crds 58 ├── deploy/crds/app_v1alpha1_appservice_cr.yaml 59 ├── deploy/crds/app_v1alpha1_appservice_crd.yaml 60 `, 61 RunE: openAPIFunc, 62 } 63 64 openAPICmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated files.") 65 66 return openAPICmd 67 } 68 69 func openAPIFunc(cmd *cobra.Command, args []string) error { 70 if len(args) != 0 { 71 return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) 72 } 73 74 return OpenAPIGen() 75 } 76 77 // OpenAPIGen generates OpenAPI validation specs for all CRD's in dirs. 78 func OpenAPIGen() error { 79 projutil.MustInProjectRoot() 80 81 absProjectPath := projutil.MustGetwd() 82 repoPkg := projutil.CheckAndGetProjectGoPkg() 83 srcDir := filepath.Join(absProjectPath, "vendor", "k8s.io", "kube-openapi") 84 binDir := filepath.Join(absProjectPath, scaffold.BuildBinDir) 85 86 if err := buildOpenAPIGenBinary(binDir, srcDir); err != nil { 87 return err 88 } 89 90 gvMap, err := genutil.ParseGroupVersions() 91 if err != nil { 92 return fmt.Errorf("failed to parse group versions: (%v)", err) 93 } 94 gvb := &strings.Builder{} 95 for g, vs := range gvMap { 96 gvb.WriteString(fmt.Sprintf("%s:%v, ", g, vs)) 97 } 98 99 log.Infof("Running OpenAPI code-generation for Custom Resource group versions: [%v]\n", gvb.String()) 100 101 apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) 102 fqApiStr := genutil.CreateFQApis(apisPkg, gvMap) 103 fqApis := strings.Split(fqApiStr, ",") 104 if err := openAPIGen(binDir, fqApis); err != nil { 105 return err 106 } 107 108 s := &scaffold.Scaffold{} 109 cfg := &input.Config{ 110 Repo: repoPkg, 111 AbsProjectPath: absProjectPath, 112 ProjectName: filepath.Base(absProjectPath), 113 } 114 crdMap, err := getCRDGVKMap() 115 if err != nil { 116 return err 117 } 118 for g, vs := range gvMap { 119 for _, v := range vs { 120 gvks := crdMap[filepath.Join(g, v)] 121 for _, gvk := range gvks { 122 r, err := scaffold.NewResource(filepath.Join(gvk.Group, gvk.Version), gvk.Kind) 123 if err != nil { 124 return err 125 } 126 err = s.Execute(cfg, 127 &scaffold.CRD{Resource: r, IsOperatorGo: projutil.IsOperatorGo()}, 128 ) 129 if err != nil { 130 return err 131 } 132 } 133 } 134 } 135 136 log.Info("Code-generation complete.") 137 return nil 138 } 139 140 func buildOpenAPIGenBinary(binDir, codegenSrcDir string) error { 141 genDirs := []string{"./cmd/openapi-gen"} 142 return genutil.BuildCodegenBinaries(genDirs, binDir, codegenSrcDir) 143 } 144 145 func openAPIGen(binDir string, fqApis []string) (err error) { 146 if headerFile == "" { 147 f, err := ioutil.TempFile(scaffold.BuildBinDir, "") 148 if err != nil { 149 return err 150 } 151 headerFile = f.Name() 152 defer func() { 153 if err = os.RemoveAll(headerFile); err != nil { 154 log.Error(err) 155 } 156 }() 157 } 158 cgPath := filepath.Join(binDir, "openapi-gen") 159 for _, fqApi := range fqApis { 160 args := []string{ 161 "--input-dirs", fqApi, 162 "--output-package", fqApi, 163 "--output-file-base", "zz_generated.openapi", 164 // openapi-gen requires a boilerplate file. Either use header or an 165 // empty file if header is empty. 166 "--go-header-file", headerFile, 167 } 168 cmd := exec.Command(cgPath, args...) 169 if projutil.IsGoVerbose() { 170 err = projutil.ExecCmd(cmd) 171 } else { 172 cmd.Stdout = ioutil.Discard 173 cmd.Stderr = ioutil.Discard 174 err = cmd.Run() 175 } 176 if err != nil { 177 return fmt.Errorf("failed to perform openapi code-generation: %v", err) 178 } 179 } 180 return nil 181 } 182 183 func getCRDGVKMap() (map[string][]metav1.GroupVersionKind, error) { 184 crdInfos, err := ioutil.ReadDir(scaffold.CRDsDir) 185 if err != nil { 186 return nil, err 187 } 188 crdMap := make(map[string][]metav1.GroupVersionKind) 189 for _, info := range crdInfos { 190 if filepath.Ext(info.Name()) == ".yaml" { 191 path := filepath.Join(scaffold.CRDsDir, info.Name()) 192 b, err := ioutil.ReadFile(path) 193 if err != nil { 194 return nil, err 195 } 196 crd := &apiextv1beta1.CustomResourceDefinition{} 197 if err := yaml.Unmarshal(b, crd); err != nil { 198 return nil, err 199 } 200 if crd.Kind != "CustomResourceDefinition" { 201 continue 202 } 203 gv := filepath.Join(strings.Split(info.Name(), "_")[:2]...) 204 crdMap[gv] = append(crdMap[gv], metav1.GroupVersionKind{ 205 Group: crd.Spec.Group, 206 Version: crd.Spec.Version, 207 Kind: crd.Spec.Names.Kind, 208 }) 209 } 210 } 211 return crdMap, nil 212 }