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