1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
package gnostic_plugin_v1
import (
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/any"
openapiv2 "github.com/googleapis/gnostic/OpenAPIv2"
openapiv3 "github.com/googleapis/gnostic/OpenAPIv3"
discovery "github.com/googleapis/gnostic/discovery"
surface "github.com/googleapis/gnostic/surface"
)
// Environment contains the environment of a plugin call.
type Environment struct {
Request *Request // plugin request object
Response *Response // response message
Invocation string // string representation of call
RunningAsPlugin bool // true if app is being run as a plugin
}
// NewEnvironment creates a plugin context from arguments and standard input.
func NewEnvironment() (env *Environment, err error) {
env = &Environment{
Invocation: os.Args[0],
Response: &Response{},
}
input := flag.String("input", "", "API description (in binary protocol buffer form)")
output := flag.String("output", "-", "Output file or directory")
plugin := flag.Bool("plugin", false, "Run as a gnostic plugin (other flags are ignored).")
flag.Parse()
env.RunningAsPlugin = *plugin
programName := path.Base(os.Args[0])
if (*input == "") && !*plugin {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "\n")
fmt.Fprintf(os.Stderr, programName+" is a gnostic plugin.\n")
fmt.Fprintf(os.Stderr, `
When it is run from gnostic, the -plugin option is specified and gnostic
writes a binary request to stdin and waits for a binary response on stdout.
This program can also be run standalone using the other flags listed below.
When the -plugin option is specified, these flags are ignored.`)
fmt.Fprintf(os.Stderr, "\n\nUsage:\n")
flag.PrintDefaults()
}
flag.Usage()
os.Exit(0)
}
if env.RunningAsPlugin {
// Handle invocation as a plugin.
// Read the plugin input.
pluginData, err := ioutil.ReadAll(os.Stdin)
env.RespondAndExitIfError(err)
if len(pluginData) == 0 {
env.RespondAndExitIfError(fmt.Errorf("no input data"))
}
// Deserialize the request from the input.
request := &Request{}
err = proto.Unmarshal(pluginData, request)
env.RespondAndExitIfError(err)
// Collect parameters passed to the plugin.
parameters := request.Parameters
for _, parameter := range parameters {
env.Invocation += " " + parameter.Name + "=" + parameter.Value
}
// Log the invocation.
//log.Printf("Running plugin %s", env.Invocation)
env.Request = request
} else {
// Handle invocation from the command line.
// Read the input document.
apiData, err := ioutil.ReadFile(*input)
if len(apiData) == 0 {
env.RespondAndExitIfError(fmt.Errorf("no input data"))
}
env.Request = &Request{}
env.Request.OutputPath = *output
env.Request.SourceName = path.Base(*input)
// First try to unmarshal OpenAPI v2.
documentv2 := &openapiv2.Document{}
err = proto.Unmarshal(apiData, documentv2)
if err == nil {
env.Request.AddModel("openapi.v2.Document", documentv2)
// include experimental API surface model
surfaceModel, err := surface.NewModelFromOpenAPI2(documentv2)
if err != nil {
env.Request.AddModel("surface.v1.Model", surfaceModel)
}
return env, err
}
// If that failed, ignore deserialization errors and try to unmarshal OpenAPI v3.
documentv3 := &openapiv3.Document{}
err = proto.Unmarshal(apiData, documentv3)
if err == nil {
env.Request.AddModel("openapi.v3.Document", documentv3)
// include experimental API surface model
surfaceModel, err := surface.NewModelFromOpenAPI3(documentv3)
if err != nil {
env.Request.AddModel("surface.v1.Model", surfaceModel)
}
return env, err
}
// If that failed, ignore deserialization errors and try to unmarshal a Discovery document.
discoveryDocument := &discovery.Document{}
err = proto.Unmarshal(apiData, discoveryDocument)
if err == nil {
env.Request.AddModel("discovery.v1.Document", discoveryDocument)
return env, err
}
// If we get here, we don't know what we got
err = errors.New("Unrecognized format for input")
return env, err
}
return env, err
}
// RespondAndExitIfError checks an error and if it is non-nil, records it and serializes and returns the response and then exits.
func (env *Environment) RespondAndExitIfError(err error) {
if err != nil {
env.Response.Errors = append(env.Response.Errors, err.Error())
env.RespondAndExit()
}
}
// RespondAndExit serializes and returns the plugin response and then exits.
func (env *Environment) RespondAndExit() {
if env.RunningAsPlugin {
responseBytes, _ := proto.Marshal(env.Response)
os.Stdout.Write(responseBytes)
} else {
err := HandleResponse(env.Response, env.Request.OutputPath)
if err != nil {
log.Printf("%s", err.Error())
}
}
os.Exit(0)
}
func HandleResponse(response *Response, outputLocation string) error {
if response.Errors != nil {
return fmt.Errorf("Plugin error: %+v", response.Errors)
}
// Write files to the specified directory.
var writer io.Writer
switch {
case outputLocation == "!":
// Write nothing.
case outputLocation == "-":
writer = os.Stdout
for _, file := range response.Files {
writer.Write([]byte("\n\n" + file.Name + " -------------------- \n"))
writer.Write(file.Data)
}
case isFile(outputLocation):
return fmt.Errorf("unable to overwrite %s", outputLocation)
default: // write files into a directory named by outputLocation
if !isDirectory(outputLocation) {
os.Mkdir(outputLocation, 0755)
}
for _, file := range response.Files {
p := outputLocation + "/" + file.Name
dir := path.Dir(p)
os.MkdirAll(dir, 0755)
f, _ := os.Create(p)
defer f.Close()
f.Write(file.Data)
}
}
return nil
}
func (request *Request) AddModel(modelType string, model proto.Message) error {
modelBytes, err := proto.Marshal(model)
request.Models = append(request.Models, &any.Any{TypeUrl: modelType, Value: modelBytes})
return err
}
func isFile(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
return false
}
return !fileInfo.IsDir()
}
func isDirectory(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
return false
}
return fileInfo.IsDir()
}
|