Skip to content

Commit

Permalink
Add a wrapper for command-line parameters using kingpin.Application
Browse files Browse the repository at this point in the history
Reorganize the lint command as a kingpin.Command (avoid using it as a top-level argument)
Modified the importer code to use boolean flags and moved it to its own package
  • Loading branch information
matiasinsaurralde authored and buger committed Jun 14, 2018
1 parent 90fab73 commit fd3af48
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 284 deletions.
91 changes: 91 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package cli

import (
"fmt"
"os"

kingpin "gopkg.in/alecthomas/kingpin.v2"

"github.com/TykTechnologies/tyk/cli/importer"
"github.com/TykTechnologies/tyk/cli/lint"
)

const (
appName = "tyk"
appDesc = "Tyk Gateway"
)

var (
// Conf specifies the configuration file path.
Conf *string
// Port specifies the listen port.
Port *string
// MemProfile enables memory profiling.
MemProfile *bool
// CPUProfile enables CPU profiling.
CPUProfile *bool
// BlockProfile enables block profiling.
BlockProfile *bool
// MutexProfile enables block profiling.
MutexProfile *bool
// HTTPProfile exposes a HTTP endpoint for accessing profiling data.
HTTPProfile *bool
// DebugMode sets the log level to debug mode.
DebugMode *bool
// LogInstrumentation outputs instrumentation data to stdout.
LogInstrumentation *bool

app *kingpin.Application
)

// Init sets all flags and subcommands.
func Init(version string, confPaths []string) {
app = kingpin.New(appName, appDesc)
app.HelpFlag.Short('h')
app.Version(version)

// Start/default command:
startCmd := app.Command("start", "Starts the Tyk Gateway")
Conf = startCmd.Flag("conf", "load a named configuration file").PlaceHolder("FILE").String()
Port = startCmd.Flag("port", "listen on PORT (overrides config file)").String()
MemProfile = startCmd.Flag("memprofile", "generate a memory profile").Bool()
CPUProfile = startCmd.Flag("cpuprofile", "generate a cpu profile").Bool()
BlockProfile = startCmd.Flag("blockprofile", "generate a block profile").Bool()
MutexProfile = startCmd.Flag("mutexprofile", "generate a mutex profile").Bool()
HTTPProfile = startCmd.Flag("httpprofile", "expose runtime profiling data via HTTP").Bool()
DebugMode = startCmd.Flag("debug", "enable debug mode").Bool()
LogInstrumentation = startCmd.Flag("log-intrumentation", "output intrumentation output to stdout").Bool()

startCmd.Action(func(ctx *kingpin.ParseContext) error {
return nil
})
startCmd.Default()

// Linter:
lintCmd := app.Command("lint", "Runs a linter on Tyk configuration file")
lintCmd.Action(func(c *kingpin.ParseContext) error {
path, lines, err := lint.Run(confPaths)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if len(lines) == 0 {
fmt.Printf("found no issues in %s\n", path)
return nil
}
fmt.Printf("issues found in %s:\n", path)
for _, line := range lines {
fmt.Println(line)
}
os.Exit(1)
return nil
})

// Import command:
importer.AddTo(app)
}

// Parse parses the command-line arguments.
func Parse() {
kingpin.MustParse(app.Parse(os.Args[1:]))
}
247 changes: 247 additions & 0 deletions cli/importer/importer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
package importer

import (
"encoding/json"
"errors"
"fmt"
"os"
"strings"

log "github.com/Sirupsen/logrus"

kingpin "gopkg.in/alecthomas/kingpin.v2"

"github.com/TykTechnologies/tyk/apidef"
"github.com/TykTechnologies/tyk/apidef/importer"
)

const (
cmdName = "import"
cmdDesc = "Imports a BluePrint/Swagger file"
)

var (
imp *Importer
errUnknownMode = errors.New("Unknown mode")
)

// Importer wraps the import functionality.
type Importer struct {
input *string
swaggerMode *bool
bluePrintMode *bool
createAPI *bool
orgID *string
upstreamTarget *string
asMock *bool
forAPI *string
asVersion *string
}

func init() {
imp = &Importer{}
}

// AddTo initializes an importer object.
func AddTo(app *kingpin.Application) {
cmd := app.Command(cmdName, cmdDesc)
imp.input = cmd.Arg("input file", "e.g. blueprint.json, swagger.json, etc.").String()
imp.swaggerMode = cmd.Flag("swagger", "Use Swagger mode").Bool()
imp.bluePrintMode = cmd.Flag("blueprint", "Use BluePrint mode").Bool()
imp.createAPI = cmd.Flag("create-api", "Creates a new API definition from the blueprint").Bool()
imp.orgID = cmd.Flag("org-id", "assign the API Definition to this org_id (required with create-api").String()
imp.upstreamTarget = cmd.Flag("upstream-target", "set the upstream target for the definition").PlaceHolder("URL").String()
imp.asMock = cmd.Flag("as-mock", "creates the API as a mock based on example fields").Bool()
imp.forAPI = cmd.Flag("for-api", "adds blueprint to existing API Definition as version").PlaceHolder("PATH").String()
imp.asVersion = cmd.Flag("as-version", "the version number to use when inserting").PlaceHolder("VERSION").String()
cmd.Action(imp.Import)
}

// Import performs the import process.
func (i *Importer) Import(ctx *kingpin.ParseContext) (err error) {
if *i.swaggerMode {
err = i.handleSwaggerMode()
if err != nil {
log.Fatal(err)
os.Exit(1)
}
} else if *i.bluePrintMode {
err = i.handleBluePrintMode()
if err != nil {
log.Fatal(err)
os.Exit(1)
}
} else {
log.Fatal(errUnknownMode)
os.Exit(1)
}
os.Exit(0)
return nil
}

func (i *Importer) handleBluePrintMode() error {
if !*i.createAPI {
// Different branch, here we need an API Definition to modify
if *i.forAPI == "" {
return fmt.Errorf("If adding to an API, the path to the definition must be listed")
}

if *i.asVersion == "" {
return fmt.Errorf("No version defined for this import operation, please set an import ID using the --as-version flag")
}

defFromFile, err := i.apiDefLoadFile(*i.forAPI)
if err != nil {
return fmt.Errorf("failed to load and decode file data for API Definition: %v", err)
}

bp, err := i.bluePrintLoadFile(*i.input)
if err != nil {
return fmt.Errorf("File load error: %v", err)
}
versionData, err := bp.ConvertIntoApiVersion(*i.asMock)
if err != nil {
return fmt.Errorf("onversion into API Def failed: %v", err)
}

if err := bp.InsertIntoAPIDefinitionAsVersion(versionData, defFromFile, *i.asVersion); err != nil {
return fmt.Errorf("Insertion failed: %v", err)
}

i.printDef(defFromFile)

}

if *i.upstreamTarget == "" && *i.orgID == "" {
return fmt.Errorf("No upstream target or org ID defined, these are both required")
}

// Create the API with the blueprint
bp, err := i.bluePrintLoadFile(*i.input)
if err != nil {
return fmt.Errorf("File load error: %v", err)
}

def, err := bp.ToAPIDefinition(*i.orgID, *i.upstreamTarget, *i.asMock)
if err != nil {
return fmt.Errorf("Failed to create API Definition from file")
}

i.printDef(def)
return nil
}

func (i *Importer) handleSwaggerMode() error {
if *i.createAPI {
if *i.upstreamTarget != "" && *i.orgID != "" {
// Create the API with the blueprint
s, err := i.swaggerLoadFile(*i.input)
if err != nil {
return fmt.Errorf("File load error: %v", err)
}

def, err := s.ToAPIDefinition(*i.orgID, *i.upstreamTarget, *i.asMock)
if err != nil {
return fmt.Errorf("Failed to create API Defintition from file")
}

i.printDef(def)
return nil
}

return fmt.Errorf("No upstream target or org ID defined, these are both required")

}

// Different branch, here we need an API Definition to modify
if *i.forAPI == "" {
return fmt.Errorf("If adding to an API, the path to the definition must be listed")
}

if *i.asVersion == "" {
return fmt.Errorf("No version defined for this import operation, please set an import ID using the --as-version flag")
}

defFromFile, err := i.apiDefLoadFile(*i.forAPI)
if err != nil {
return fmt.Errorf("failed to load and decode file data for API Definition: %v", err)
}

s, err := i.swaggerLoadFile(*i.input)
if err != nil {
return fmt.Errorf("File load error: %v", err)
}

versionData, err := s.ConvertIntoApiVersion(*i.asMock)
if err != nil {
return fmt.Errorf("Conversion into API Def failed: %v", err)
}

if err := s.InsertIntoAPIDefinitionAsVersion(versionData, defFromFile, *i.asVersion); err != nil {
return fmt.Errorf("Insertion failed: %v", err)
}

i.printDef(defFromFile)

return nil
}

func (i *Importer) printDef(def *apidef.APIDefinition) {
asJSON, err := json.MarshalIndent(def, "", " ")
if err != nil {
log.Error("Marshalling failed: ", err)
}

// The id attribute is for BSON only and breaks the parser if it's empty, cull it here.
fixed := strings.Replace(string(asJSON), ` "id": "",`, "", 1)
fmt.Println(fixed)
}

func (i *Importer) swaggerLoadFile(path string) (*importer.SwaggerAST, error) {
swagger, err := importer.GetImporterForSource(importer.SwaggerSource)
if err != nil {
return nil, err
}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()

if err := swagger.LoadFrom(f); err != nil {
return nil, err
}

return swagger.(*importer.SwaggerAST), nil
}

func (i *Importer) bluePrintLoadFile(path string) (*importer.BluePrintAST, error) {
blueprint, err := importer.GetImporterForSource(importer.ApiaryBluePrint)
if err != nil {
return nil, err
}

f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()

if err := blueprint.LoadFrom(f); err != nil {
return nil, err
}

return blueprint.(*importer.BluePrintAST), nil
}

func (i *Importer) apiDefLoadFile(path string) (*apidef.APIDefinition, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
def := &apidef.APIDefinition{}
if err := json.NewDecoder(f).Decode(&def); err != nil {
return nil, err
}
return def, nil
}
File renamed without changes.
2 changes: 1 addition & 1 deletion lint/lint_test.go → cli/lint/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
func TestMain(m *testing.M) {
// Use the root package, as that's where the directories and
// files required to run the gateway are.
os.Chdir("..")
os.Chdir("../..")
os.Exit(m.Run())
}

Expand Down
File renamed without changes.
Loading

0 comments on commit fd3af48

Please sign in to comment.