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