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