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