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 flag.Usage = usage 57 flag.Parse() 58 59 if len(os.Getenv("AWS_SDK_CODEGEN_DEBUG")) != 0 { 60 api.LogDebug(os.Stdout) 61 } 62 63 // Make sure all paths are based on platform's pathing not Unix 64 globs := flag.Args() 65 for i, g := range globs { 66 globs[i] = filepath.FromSlash(g) 67 } 68 svcPath = filepath.FromSlash(svcPath) 69 70 modelPaths, err := api.ExpandModelGlobPath(globs...) 71 if err != nil { 72 fmt.Fprintln(os.Stderr, "failed to glob file pattern", err) 73 os.Exit(1) 74 } 75 modelPaths, _ = api.TrimModelServiceVersions(modelPaths) 76 77 apis, err := api.LoadAPIs(modelPaths, svcImportPath) 78 if err != nil { 79 fmt.Fprintln(os.Stderr, "failed to load API models", err) 80 os.Exit(1) 81 } 82 if len(apis) == 0 { 83 fmt.Fprintf(os.Stderr, "expected to load models, but found none") 84 os.Exit(1) 85 } 86 87 if v := os.Getenv("SERVICES"); len(v) != 0 { 88 svcs := strings.Split(v, ",") 89 for pkgName, a := range apis { 90 var found bool 91 for _, include := range svcs { 92 if a.PackageName() == include { 93 found = true 94 break 95 } 96 } 97 if !found { 98 delete(apis, pkgName) 99 } 100 } 101 } 102 103 var wg sync.WaitGroup 104 servicePaths := map[string]struct{}{} 105 for _, a := range apis { 106 if _, ok := excludeServices[a.PackageName()]; ok { 107 continue 108 } 109 110 // Create the output path for the model. 111 pkgDir := filepath.Join(svcPath, a.PackageName()) 112 os.MkdirAll(filepath.Join(pkgDir, a.InterfacePackageName()), 0775) 113 114 if _, ok := servicePaths[pkgDir]; ok { 115 fmt.Fprintf(os.Stderr, 116 "attempted to generate a client into %s twice. Second model package, %v\n", 117 pkgDir, a.PackageName()) 118 os.Exit(1) 119 } 120 servicePaths[pkgDir] = struct{}{} 121 122 g := &generateInfo{ 123 API: a, 124 PackageDir: pkgDir, 125 } 126 127 wg.Add(1) 128 go func() { 129 defer wg.Done() 130 writeServiceFiles(g, pkgDir) 131 }() 132 } 133 134 wg.Wait() 135} 136 137type generateInfo struct { 138 *api.API 139 PackageDir string 140} 141 142var excludeServices = map[string]struct{}{ 143 "importexport": {}, 144} 145 146func writeServiceFiles(g *generateInfo, pkgDir string) { 147 defer func() { 148 if r := recover(); r != nil { 149 fmt.Fprintf(os.Stderr, "Error generating %s\n%s\n%s\n", 150 pkgDir, r, debug.Stack()) 151 os.Exit(1) 152 } 153 }() 154 155 fmt.Printf("Generating %s (%s)...\n", 156 g.API.PackageName(), g.API.Metadata.APIVersion) 157 158 // write files for service client and API 159 Must(writeServiceDocFile(g)) 160 Must(writeAPIFile(g)) 161 Must(writeServiceFile(g)) 162 Must(writeInterfaceFile(g)) 163 Must(writeWaitersFile(g)) 164 Must(writeAPIErrorsFile(g)) 165 Must(writeExamplesFile(g)) 166 167 if g.API.HasEventStream { 168 Must(writeAPIEventStreamTestFile(g)) 169 } 170 171 if g.API.PackageName() == "s3" { 172 Must(writeS3ManagerUploadInputFile(g)) 173 } 174 175 if len(g.API.SmokeTests.TestCases) > 0 { 176 Must(writeAPISmokeTestsFile(g)) 177 } 178} 179 180// Must will panic if the error passed in is not nil. 181func Must(err error) { 182 if err != nil { 183 panic(err) 184 } 185} 186 187const codeLayout = `// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT. 188 189%s 190package %s 191 192%s 193` 194 195func writeGoFile(file string, layout string, args ...interface{}) error { 196 return ioutil.WriteFile(file, []byte(util.GoFmt(fmt.Sprintf(layout, args...))), 0664) 197} 198 199// writeServiceDocFile generates the documentation for service package. 200func writeServiceDocFile(g *generateInfo) error { 201 return writeGoFile(filepath.Join(g.PackageDir, "doc.go"), 202 codeLayout, 203 strings.TrimSpace(g.API.ServicePackageDoc()), 204 g.API.PackageName(), 205 "", 206 ) 207} 208 209// writeExamplesFile writes out the service example file. 210func writeExamplesFile(g *generateInfo) error { 211 code := g.API.ExamplesGoCode() 212 if len(code) > 0 { 213 return writeGoFile(filepath.Join(g.PackageDir, "examples_test.go"), 214 codeLayout, 215 "", 216 g.API.PackageName()+"_test", 217 code, 218 ) 219 } 220 return nil 221} 222 223// writeServiceFile writes out the service initialization file. 224func writeServiceFile(g *generateInfo) error { 225 return writeGoFile(filepath.Join(g.PackageDir, "service.go"), 226 codeLayout, 227 "", 228 g.API.PackageName(), 229 g.API.ServiceGoCode(), 230 ) 231} 232 233// writeInterfaceFile writes out the service interface file. 234func writeInterfaceFile(g *generateInfo) error { 235 const pkgDoc = ` 236// Package %s provides an interface to enable mocking the %s service client 237// for testing your code. 238// 239// It is important to note that this interface will have breaking changes 240// when the service model is updated and adds new API operations, paginators, 241// and waiters.` 242 return writeGoFile(filepath.Join(g.PackageDir, g.API.InterfacePackageName(), "interface.go"), 243 codeLayout, 244 fmt.Sprintf(pkgDoc, g.API.InterfacePackageName(), g.API.Metadata.ServiceFullName), 245 g.API.InterfacePackageName(), 246 g.API.InterfaceGoCode(), 247 ) 248} 249 250func writeWaitersFile(g *generateInfo) error { 251 if len(g.API.Waiters) == 0 { 252 return nil 253 } 254 255 return writeGoFile(filepath.Join(g.PackageDir, "waiters.go"), 256 codeLayout, 257 "", 258 g.API.PackageName(), 259 g.API.WaitersGoCode(), 260 ) 261} 262 263// writeAPIFile writes out the service API file. 264func writeAPIFile(g *generateInfo) error { 265 return writeGoFile(filepath.Join(g.PackageDir, "api.go"), 266 codeLayout, 267 "", 268 g.API.PackageName(), 269 g.API.APIGoCode(), 270 ) 271} 272 273// writeAPIErrorsFile writes out the service API errors file. 274func writeAPIErrorsFile(g *generateInfo) error { 275 return writeGoFile(filepath.Join(g.PackageDir, "errors.go"), 276 codeLayout, 277 "", 278 g.API.PackageName(), 279 g.API.APIErrorsGoCode(), 280 ) 281} 282 283func writeAPIEventStreamTestFile(g *generateInfo) error { 284 return writeGoFile(filepath.Join(g.PackageDir, "eventstream_test.go"), 285 codeLayout, 286 "// +build go1.6\n", 287 g.API.PackageName(), 288 g.API.APIEventStreamTestGoCode(), 289 ) 290} 291 292func writeS3ManagerUploadInputFile(g *generateInfo) error { 293 return writeGoFile(filepath.Join(g.PackageDir, "s3manager", "upload_input.go"), 294 codeLayout, 295 "", 296 "s3manager", 297 api.S3ManagerUploadInputGoCode(g.API), 298 ) 299} 300 301func writeAPISmokeTestsFile(g *generateInfo) error { 302 return writeGoFile(filepath.Join(g.PackageDir, "integ_test.go"), 303 codeLayout, 304 "// +build go1.10,integration\n", 305 g.API.PackageName()+"_test", 306 g.API.APISmokeTestsGoCode(), 307 ) 308} 309