1//go:build codegen 2// +build codegen 3 4// Command aws-gen-gocli parses a JSON description of an AWS API and generates a 5// Go file containing a client for the API. 6// 7// aws-gen-gocli apis/s3/2006-03-03/api-2.json 8package main 9 10import ( 11 "flag" 12 "fmt" 13 "io/ioutil" 14 "os" 15 "path/filepath" 16 "runtime/debug" 17 "strings" 18 "sync" 19 20 "github.com/aws/aws-sdk-go/private/model/api" 21 "github.com/aws/aws-sdk-go/private/util" 22) 23 24func usage() { 25 fmt.Fprintln(os.Stderr, `Usage: api-gen <options> [model path | file path] 26Loads API models from file and generates SDK clients from the models. 27 28The model path arguments can be globs, or paths to individual files. The 29utiliity requires that the API model files follow the following pattern: 30 31<root>/<servicename>/<api-version>/<model json files> 32 33e.g: 34 35./models/apis/s3/2006-03-01/*.json 36 37Flags:`) 38 flag.PrintDefaults() 39} 40 41// Generates service api, examples, and interface from api json definition files. 42// 43// Flags: 44// -path alternative service path to write generated files to for each service. 45// 46// Env: 47// SERVICES comma separated list of services to generate. 48func main() { 49 var svcPath, svcImportPath string 50 flag.StringVar(&svcPath, "path", "service", 51 "The `path` to generate service clients in to.", 52 ) 53 flag.StringVar(&svcImportPath, "svc-import-path", 54 api.SDKImportRoot+"/service", 55 "The Go `import path` to generate client to be under.", 56 ) 57 var ignoreUnsupportedAPIs bool 58 flag.BoolVar(&ignoreUnsupportedAPIs, "ignore-unsupported-apis", 59 true, 60 "Ignores API models that use unsupported features", 61 ) 62 flag.Usage = usage 63 flag.Parse() 64 65 if len(os.Getenv("AWS_SDK_CODEGEN_DEBUG")) != 0 { 66 api.LogDebug(os.Stdout) 67 } 68 69 // Make sure all paths are based on platform's pathing not Unix 70 globs := flag.Args() 71 for i, g := range globs { 72 globs[i] = filepath.FromSlash(g) 73 } 74 svcPath = filepath.FromSlash(svcPath) 75 76 modelPaths, err := api.ExpandModelGlobPath(globs...) 77 if err != nil { 78 fmt.Fprintln(os.Stderr, "failed to glob file pattern", err) 79 os.Exit(1) 80 } 81 modelPaths, _ = api.TrimModelServiceVersions(modelPaths) 82 83 loader := api.Loader{ 84 BaseImport: svcImportPath, 85 IgnoreUnsupportedAPIs: ignoreUnsupportedAPIs, 86 } 87 88 apis, err := loader.Load(modelPaths) 89 if err != nil { 90 fmt.Fprintln(os.Stderr, "failed to load API models", err) 91 os.Exit(1) 92 } 93 if len(apis) == 0 { 94 fmt.Fprintf(os.Stderr, "expected to load models, but found none") 95 os.Exit(1) 96 } 97 98 if v := os.Getenv("SERVICES"); len(v) != 0 { 99 svcs := strings.Split(v, ",") 100 for pkgName, a := range apis { 101 var found bool 102 for _, include := range svcs { 103 if a.PackageName() == include { 104 found = true 105 break 106 } 107 } 108 if !found { 109 delete(apis, pkgName) 110 } 111 } 112 } 113 114 var wg sync.WaitGroup 115 servicePaths := map[string]struct{}{} 116 for _, a := range apis { 117 if _, ok := excludeServices[a.PackageName()]; ok { 118 continue 119 } 120 121 // Create the output path for the model. 122 pkgDir := filepath.Join(svcPath, a.PackageName()) 123 os.MkdirAll(filepath.Join(pkgDir, a.InterfacePackageName()), 0775) 124 125 if _, ok := servicePaths[pkgDir]; ok { 126 fmt.Fprintf(os.Stderr, 127 "attempted to generate a client into %s twice. Second model package, %v\n", 128 pkgDir, a.PackageName()) 129 os.Exit(1) 130 } 131 servicePaths[pkgDir] = struct{}{} 132 133 g := &generateInfo{ 134 API: a, 135 PackageDir: pkgDir, 136 } 137 138 wg.Add(1) 139 go func() { 140 defer wg.Done() 141 writeServiceFiles(g, pkgDir) 142 }() 143 } 144 145 wg.Wait() 146} 147 148type generateInfo struct { 149 *api.API 150 PackageDir string 151} 152 153var excludeServices = map[string]struct{}{ 154 "importexport": {}, 155} 156 157func writeServiceFiles(g *generateInfo, pkgDir string) { 158 defer func() { 159 if r := recover(); r != nil { 160 fmt.Fprintf(os.Stderr, "Error generating %s\n%s\n%s\n", 161 pkgDir, r, debug.Stack()) 162 os.Exit(1) 163 } 164 }() 165 166 fmt.Printf("Generating %s (%s)...\n", 167 g.API.PackageName(), g.API.Metadata.APIVersion) 168 169 // write files for service client and API 170 Must(writeServiceDocFile(g)) 171 Must(writeAPIFile(g)) 172 Must(writeServiceFile(g)) 173 Must(writeInterfaceFile(g)) 174 Must(writeWaitersFile(g)) 175 Must(writeAPIErrorsFile(g)) 176 Must(writeExamplesFile(g)) 177 178 if g.API.HasEventStream { 179 Must(writeAPIEventStreamTestFile(g)) 180 } 181 182 if g.API.PackageName() == "s3" { 183 Must(writeS3ManagerUploadInputFile(g)) 184 } 185 186 if len(g.API.SmokeTests.TestCases) > 0 { 187 Must(writeAPISmokeTestsFile(g)) 188 } 189} 190 191// Must will panic if the error passed in is not nil. 192func Must(err error) { 193 if err != nil { 194 panic(err) 195 } 196} 197 198const codeLayout = `// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT. 199 200%s 201package %s 202 203%s 204` 205 206func writeGoFile(file string, layout string, args ...interface{}) error { 207 return ioutil.WriteFile(file, []byte(util.GoFmt(fmt.Sprintf(layout, args...))), 0664) 208} 209 210// writeServiceDocFile generates the documentation for service package. 211func writeServiceDocFile(g *generateInfo) error { 212 return writeGoFile(filepath.Join(g.PackageDir, "doc.go"), 213 codeLayout, 214 strings.TrimSpace(g.API.ServicePackageDoc()), 215 g.API.PackageName(), 216 "", 217 ) 218} 219 220// writeExamplesFile writes out the service example file. 221func writeExamplesFile(g *generateInfo) error { 222 code := g.API.ExamplesGoCode() 223 if len(code) > 0 { 224 return writeGoFile(filepath.Join(g.PackageDir, "examples_test.go"), 225 codeLayout, 226 "", 227 g.API.PackageName()+"_test", 228 code, 229 ) 230 } 231 return nil 232} 233 234// writeServiceFile writes out the service initialization file. 235func writeServiceFile(g *generateInfo) error { 236 return writeGoFile(filepath.Join(g.PackageDir, "service.go"), 237 codeLayout, 238 "", 239 g.API.PackageName(), 240 g.API.ServiceGoCode(), 241 ) 242} 243 244// writeInterfaceFile writes out the service interface file. 245func writeInterfaceFile(g *generateInfo) error { 246 const pkgDoc = ` 247// Package %s provides an interface to enable mocking the %s service client 248// for testing your code. 249// 250// It is important to note that this interface will have breaking changes 251// when the service model is updated and adds new API operations, paginators, 252// and waiters.` 253 return writeGoFile(filepath.Join(g.PackageDir, g.API.InterfacePackageName(), "interface.go"), 254 codeLayout, 255 fmt.Sprintf(pkgDoc, g.API.InterfacePackageName(), g.API.Metadata.ServiceFullName), 256 g.API.InterfacePackageName(), 257 g.API.InterfaceGoCode(), 258 ) 259} 260 261func writeWaitersFile(g *generateInfo) error { 262 if len(g.API.Waiters) == 0 { 263 return nil 264 } 265 266 return writeGoFile(filepath.Join(g.PackageDir, "waiters.go"), 267 codeLayout, 268 "", 269 g.API.PackageName(), 270 g.API.WaitersGoCode(), 271 ) 272} 273 274// writeAPIFile writes out the service API file. 275func writeAPIFile(g *generateInfo) error { 276 return writeGoFile(filepath.Join(g.PackageDir, "api.go"), 277 codeLayout, 278 "", 279 g.API.PackageName(), 280 g.API.APIGoCode(), 281 ) 282} 283 284// writeAPIErrorsFile writes out the service API errors file. 285func writeAPIErrorsFile(g *generateInfo) error { 286 return writeGoFile(filepath.Join(g.PackageDir, "errors.go"), 287 codeLayout, 288 "", 289 g.API.PackageName(), 290 g.API.APIErrorsGoCode(), 291 ) 292} 293 294func writeAPIEventStreamTestFile(g *generateInfo) error { 295 return writeGoFile(filepath.Join(g.PackageDir, "eventstream_test.go"), 296 codeLayout, 297 "//go:build go1.16\n// +build go1.16\n", 298 g.API.PackageName(), 299 g.API.APIEventStreamTestGoCode(), 300 ) 301} 302 303func writeS3ManagerUploadInputFile(g *generateInfo) error { 304 return writeGoFile(filepath.Join(g.PackageDir, "s3manager", "upload_input.go"), 305 codeLayout, 306 "", 307 "s3manager", 308 api.S3ManagerUploadInputGoCode(g.API), 309 ) 310} 311 312func writeAPISmokeTestsFile(g *generateInfo) error { 313 return writeGoFile(filepath.Join(g.PackageDir, "integ_test.go"), 314 codeLayout, 315 "//go:build go1.16 && integration\n// +build go1.16,integration\n", 316 g.API.PackageName()+"_test", 317 g.API.APISmokeTestsGoCode(), 318 ) 319} 320