Skip to content
This repository has been archived by the owner on Jan 5, 2024. It is now read-only.

Commit

Permalink
Generate imposters from swagger files
Browse files Browse the repository at this point in the history
Implement a new Generate command to generate imposters from swagger.

The command addresses many typical use cases but will need to be
extended further to cover specialist use cases.

Works towards friendsofgo#64.
  • Loading branch information
stoovon committed Oct 19, 2021
1 parent 39414f9 commit f662b8c
Show file tree
Hide file tree
Showing 304 changed files with 67,074 additions and 5 deletions.
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Killgrave is a simulator for HTTP-based APIs, in simple words a **Mock Server**,
<a href="https://www.buymeacoffee.com/friendsofgo" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: 100px !important;" ></a>
</p>

# Table of Content
# Table of Contents
- [Overview](#overview)
- [Concepts](#concepts)
* [Imposters](#imposters)
Expand All @@ -26,16 +26,17 @@ Killgrave is a simulator for HTTP-based APIs, in simple words a **Mock Server**,
* [Docker](#docker)
* [Other](#other)
- [Getting Started](#getting-started)
* [Using Killgrave by command line](#using-killgrave-by-command-line)
* [Using Killgrave from the command line](#using-killgrave-from-the-command-line)
* [Using Killgrave by config file](#using-killgrave-by-config-file)
* [Configure CORS](#configure-cors)
* [Prepare Killgrave for Proxy Mode](#prepare-killgrave-for-proxy-mode)
* [Create an Imposter](#create-an-imposter)
* [Preparing Killgrave for Proxy Mode](#preparing-killgrave-for-proxy-mode)
* [Creating an Imposter](#creating-an-imposter)
* [Imposters structure](#imposters-structure)
* [Create an Imposter using regex](#create-an-imposter-using-regex)
* [Using regex in imposters](#using-regex-in-imposters)
* [Create an imposter using JSON Schema](#create-an-imposter-using-json-schema)
* [Create an imposter with delay](#create-an-imposter-with-delay)
* [Create an imposter with dynamic responses](#create-an-imposter-with-dynamic-responses)
* [Generating imposters from a swagger file](#generating-imposters-from-a-swagger-file)
- [Contributing](#contributing)
- [License](#license)

Expand Down Expand Up @@ -560,6 +561,16 @@ In the following example, we have defined multiple imposters for the `POST /goph
]
````
### Generating imposters from a swagger file
For details on how to do this please see:
```sh
$ killgrave generate -h
```
## Contributing
[Contributions](CONTRIBUTING.md) are more than welcome, if you are interested please follow our guidelines to help you get started.
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/friendsofgo/killgrave
go 1.16

require (
github.com/getkin/kin-openapi v0.79.0
github.com/ghodss/yaml v1.0.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/radovskyb/watcher v1.0.7
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/getkin/kin-openapi v0.79.0 h1:YLZIgIhZLq9z5WFHHIK+oWORRfn6jjwr7qN0xak0xbE=
github.com/getkin/kin-openapi v0.79.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
Expand Down Expand Up @@ -59,6 +66,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
Expand Down Expand Up @@ -98,6 +108,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
Expand Down Expand Up @@ -151,6 +162,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
Expand Down
3 changes: 3 additions & 0 deletions internal/app/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"github.com/friendsofgo/killgrave/internal/app/cmd/generate"
"github.com/friendsofgo/killgrave/internal/app/cmd/http"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -31,6 +32,8 @@ func NewKillgraveCmd() *cobra.Command {
rootCmd.SetVersionTemplate("Killgrave version: {{.Version}}\n")

rootCmd.AddCommand(http.NewHTTPCmd())
rootCmd.AddCommand(generate.NewGenerate2Cmd())
rootCmd.AddCommand(generate.NewGenerate3Cmd())

return rootCmd
}
145 changes: 145 additions & 0 deletions internal/app/cmd/generate/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package generate

import (
"encoding/json"
"errors"
"fmt"
"github.com/friendsofgo/killgrave/internal/generator"
"github.com/friendsofgo/killgrave/internal/server/http"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"io/ioutil"
)

const (
_defaultImpostersPath = "impostersFile"
_defaultSwaggerFile = "swagger.yaml"
_defaultOutputYaml = false
)

var (
errGetDataFromImpostersFileFlag = errors.New("error trying to get data from imposters-file flag")
errGetDataFromSwaggerFileFlag = errors.New("error trying to get data from swagger-file flag")
errGetDataFromOutputYamlFlag = errors.New("error trying to get data from output-yaml flag")
)

type config struct {
impostersFile string
swaggerFile string
outputYAML bool
useV3 bool
}

// NewGenerate2Cmd returns cobra.Command to run generate sub command, this command will be used to generate impostersFile from swagger files
func NewGenerate2Cmd() *cobra.Command {
return newGenerateCmd(false)
}

// NewGenerate3Cmd returns cobra.Command to run generate3 sub command, this command will be used to generate impostersFile from swagger files
func NewGenerate3Cmd() *cobra.Command {
return newGenerateCmd(true)
}

// NewGenerateCmd returns cobra.Command to run generate sub command, this command will be used to generate impostersFile from swagger files
func newGenerateCmd(useV3 bool) *cobra.Command {
var cfg *config
var err error

preRunE := func(cmd *cobra.Command, args []string) error {
cfg, err = prepareConfig(cmd, useV3)
if err != nil {
return err
}
return nil
}

runE := func(cmd *cobra.Command, args []string) error {
return runGenerate(cfg)
}

var cmd *cobra.Command

if useV3 {
cmd = &cobra.Command{
Use: "swagger3-gen",
Short: "Generate impostersFile based on a swagger 3 (OpenAPI 3 Specification) file",
PreRunE: preRunE,
RunE: runE,
Args: cobra.NoArgs,
}
} else {
cmd = &cobra.Command{
Use: "swagger-gen",
Short: "Generate impostersFile based on a swagger (OpenAPI 2 Specification) file",
PreRunE: preRunE,
RunE: runE,
Args: cobra.NoArgs,
}
}

cmd.PersistentFlags().String("impostersFile", _defaultImpostersPath, "directory where your impostersFile are saved. Existing files will be silently overwritten if they have the same name")
cmd.PersistentFlags().String("swagger-file", _defaultSwaggerFile, "swagger file for use by swagger-gen")
cmd.PersistentFlags().Bool("output-yaml", _defaultOutputYaml, "true if YAML impostersFile should be generated; false if JSON impostersFile should be generated")

return cmd
}

func runGenerate(cfg *config) error {
g := generator.NewGenerator(cfg.swaggerFile)

swagger, err := ioutil.ReadFile(cfg.swaggerFile)
if err != nil {
return fmt.Errorf("%w: error trying to read swagger file: '%s'", err, cfg.swaggerFile)
}

var imposters *[]http.Imposter

imposters, err = g.GenerateSwagger(swagger, cfg.useV3)
if err != nil {
return fmt.Errorf("unable to generate imposters: %w", err)
}

var output []byte

if cfg.outputYAML {
output, err = yaml.Marshal(imposters)
} else {

output, err = json.Marshal(imposters)
}

if err != nil {
return fmt.Errorf("unable to marshal imposters: %w", err)
}

err = ioutil.WriteFile(cfg.impostersFile, output, 0644)
if err != nil {
return fmt.Errorf("%w: error trying to write to imposters file: '%s'", err, cfg.impostersFile)
}

return err
}

func prepareConfig(cmd *cobra.Command, useV3 bool) (cfg *config, err error) {
impostersFilePath, err := cmd.Flags().GetString("imposters-file")
if err != nil {
return nil, fmt.Errorf("%v: %w", err, errGetDataFromImpostersFileFlag)
}

swaggerFilePath, err := cmd.Flags().GetString("swagger-file")
if err != nil {
return nil, fmt.Errorf("%v: %w", err, errGetDataFromSwaggerFileFlag)
}

outputYAML, err := cmd.Flags().GetBool("output-yaml")
if err != nil {
return nil, fmt.Errorf("%v: %w", err, errGetDataFromOutputYamlFlag)
}

return &config{
impostersFile: impostersFilePath,
swaggerFile: swaggerFilePath,
outputYAML: outputYAML,
useV3: useV3,
}, nil
}
60 changes: 60 additions & 0 deletions internal/generator/generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package generator

import (
"fmt"
v2 "github.com/friendsofgo/killgrave/internal/generator/v2"
v3 "github.com/friendsofgo/killgrave/internal/generator/v3"
"github.com/friendsofgo/killgrave/internal/server/http"
"strings"
)

type Generator struct {
fileName string
}

type GenerationMode int

const (
YAML GenerationMode = iota
JSON
)

func NewGenerator(fileName string) *Generator {
return &Generator{
fileName: strings.ToLower(fileName),
}
}

func (g *Generator) generationMode() (*GenerationMode, error) {
var result GenerationMode

if strings.HasSuffix(g.fileName, ".yml") ||
strings.HasSuffix(g.fileName, ".yaml") {
result = YAML
return &result, nil
}

if strings.HasSuffix(g.fileName, ".json") {
result = JSON
return &result, nil
}

return nil, fmt.Errorf("unknown file extension for swagger file name: %v", g.fileName)
}

func (g *Generator) GenerateSwagger(swagger []byte, useV3 bool) (*[]http.Imposter, error) {
mode, err := g.generationMode()
if err != nil {
return nil, err
}

if useV3 {
return v3.GenerateSwagger(swagger)
} else {
if *mode == JSON {
return v2.GenerateSwaggerJSON(swagger)
} else {
return v2.GenerateSwaggerYAML(swagger)
}
}
}
Loading

0 comments on commit f662b8c

Please sign in to comment.