diff --git a/.gitignore b/.gitignore index 9860123..3f6d5ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /tmp/ +/korrel8rcli _covdata/ -_korrel8rcli -bin diff --git a/Makefile b/Makefile index 980a9de..daa85d7 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,15 @@ all: lint test build -VERSION=0.0.3 +VERSION=0.0.4 include .bingo/Variables.mk VERSION_TXT=pkg/build/version.txt SWAGGER_SPEC=swagger.json SWAGGER_CLIENT=pkg/swagger -KORREL8RCLI=./korrel8rcli -lint: $(SWAGGER_CLIENT) $(GOLANGCI_LINT) +lint: $(VERSION_TXT) $(SWAGGER_CLIENT) $(GOLANGCI_LINT) go mod tidy $(GOLANGCI_LINT) run ./... @if grep -q github.com/korrel8r/korrel8r go.mod; then \ @@ -18,16 +17,18 @@ lint: $(SWAGGER_CLIENT) $(GOLANGCI_LINT) exit 1; \ fi -build: $(KORREL8RCLI) -$(KORREL8RCLI): $(VERSION_TXT) $(SWAGGER_CLIENT) - go build -o $@ ./cmd/korrel8rcli +build: lint + go build ./cmd/korrel8rcli + +install: lint + go install ./cmd/korrel8rcli test: go test -cover -race ./... go tool covdata percent -i pkg/cmd/_covdata clean: - rm -rfv $(SWAGGER_CLIENT) $(SWAGGER_SPEC) $(KORREL8RCLI) + rm -rfv $(SWAGGER_CLIENT) $(SWAGGER_SPEC) git clean -dfx run: diff --git a/go.mod b/go.mod index 5d3a7a7..6fb9a1e 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-openapi/validate v0.24.0 github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect diff --git a/pkg/build/version.txt b/pkg/build/version.txt index bcab45a..81340c7 100644 --- a/pkg/build/version.txt +++ b/pkg/build/version.txt @@ -1 +1 @@ -0.0.3 +0.0.4 diff --git a/pkg/cmd/main_test.go b/pkg/cmd/main_test.go index 5a77cd5..af2de26 100644 --- a/pkg/cmd/main_test.go +++ b/pkg/cmd/main_test.go @@ -23,19 +23,19 @@ func Test_domains(t *testing.T) { require.NoError(t, err) var domains []*models.Domain - require.NoError(t, yaml.Unmarshal([]byte(out), &domains), out) + require.NoError(t, yaml.Unmarshal([]byte(out), &domains), out, string(out)) var names []string for _, d := range domains { names = append(names, d.Name) } - require.ElementsMatch(t, []string{"k8s", "alert", "log", "metric", "netflow", "mock"}, names) + require.ElementsMatch(t, []string{"k8s", "alert", "log", "metric", "netflow", "mock", "trace"}, names) } func Test_bad_parameters(t *testing.T) { u := korrel8rServer(t) out, err := korrel8rcli(t, "objects", "-u", u.String(), "this-is-not-a-query") require.EqualError(t, err, "exit status 1: stderr: invalid query string: this-is-not-a-query\n") - require.Equal(t, "", out) + require.Equal(t, "", out) } var buildOnce sync.Once @@ -65,7 +65,7 @@ func korrel8rcli(t *testing.T, args ...string) (out string, err error) { } // Start a korrel8r server, will shut down at end of test. -func korrel8r(t *testing.T, args ...string) *url.URL { +func korrel8r(t *testing.T) *url.URL { t.Helper() l, err := net.Listen("tcp", ":0") require.NoError(t, err) diff --git a/pkg/cmd/operations.go b/pkg/cmd/operations.go index 5186c7a..b40204a 100644 --- a/pkg/cmd/operations.go +++ b/pkg/cmd/operations.go @@ -3,6 +3,7 @@ package cmd import ( + "errors" "os" "github.com/korrel8r/client/pkg/swagger/client/operations" @@ -35,14 +36,13 @@ var domainsCmd = &cobra.Command{ c := newClient() ok, err := c.Operations.GetDomains(&operations.GetDomainsParams{}) check(err) - p := NewPrinter(output.String(), os.Stdout) - for _, v := range ok.Payload { - p(v) - } + NewPrinter(output.String(), os.Stdout)(ok.Payload) }, } -func init() { rootCmd.AddCommand(domainsCmd) } +func init() { + rootCmd.AddCommand(domainsCmd) +} var ( objectsCmd = &cobra.Command{ @@ -53,10 +53,7 @@ var ( c := newClient() ok, err := c.Operations.GetObjects(&operations.GetObjectsParams{Query: args[0]}) check(err) - p := NewPrinter(output.String(), os.Stdout) - for _, v := range ok.Payload { - p(v) - } + NewPrinter(output.String(), os.Stdout)(ok.Payload) }, } ) @@ -114,3 +111,30 @@ func init() { neighboursCmd.Flags().Int64Var(&depth, "depth", 2, "Depth of neighbourhood search.") commonFlags(goalsCmd) } + +var ( + configVerbose *int64 + configCmd = &cobra.Command{ + Use: "config", + Short: "Change configuration settings on the server", + Run: func(cmd *cobra.Command, args []string) { + config := &operations.PutConfigParams{} + changes := false + if cmd.Flags().Changed("set-verbose") { + changes = true + config.Verbose = configVerbose + } + if !changes { + check(errors.New("No changes requested")) + } + c := newClient() + _, err := c.Operations.PutConfig(config) + check(err) + }, + } +) + +func init() { + configVerbose = configCmd.Flags().Int64("set-verbose", 0, "Set verbose level for logging") + rootCmd.AddCommand(configCmd) +} diff --git a/pkg/swagger/client/operations/operations_client.go b/pkg/swagger/client/operations/operations_client.go index ee73dee..f495ffd 100644 --- a/pkg/swagger/client/operations/operations_client.go +++ b/pkg/swagger/client/operations/operations_client.go @@ -66,6 +66,8 @@ type ClientService interface { PostListsGoals(params *PostListsGoalsParams, opts ...ClientOption) (*PostListsGoalsOK, error) + PutConfig(params *PutConfigParams, opts ...ClientOption) (*PutConfigOK, error) + SetTransport(transport runtime.ClientTransport) } @@ -291,6 +293,43 @@ func (a *Client) PostListsGoals(params *PostListsGoalsParams, opts ...ClientOpti return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +PutConfig sets verbose level for logging on a running server +*/ +func (a *Client) PutConfig(params *PutConfigParams, opts ...ClientOption) (*PutConfigOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewPutConfigParams() + } + op := &runtime.ClientOperation{ + ID: "PutConfig", + Method: "PUT", + PathPattern: "/config", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &PutConfigReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*PutConfigOK) + if ok { + return success, nil + } + // unexpected success response + unexpectedSuccess := result.(*PutConfigDefault) + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + // SetTransport changes the transport on the client func (a *Client) SetTransport(transport runtime.ClientTransport) { a.transport = transport diff --git a/pkg/swagger/client/operations/put_config_parameters.go b/pkg/swagger/client/operations/put_config_parameters.go new file mode 100644 index 0000000..b008e8b --- /dev/null +++ b/pkg/swagger/client/operations/put_config_parameters.go @@ -0,0 +1,164 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewPutConfigParams creates a new PutConfigParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPutConfigParams() *PutConfigParams { + return &PutConfigParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPutConfigParamsWithTimeout creates a new PutConfigParams object +// with the ability to set a timeout on a request. +func NewPutConfigParamsWithTimeout(timeout time.Duration) *PutConfigParams { + return &PutConfigParams{ + timeout: timeout, + } +} + +// NewPutConfigParamsWithContext creates a new PutConfigParams object +// with the ability to set a context for a request. +func NewPutConfigParamsWithContext(ctx context.Context) *PutConfigParams { + return &PutConfigParams{ + Context: ctx, + } +} + +// NewPutConfigParamsWithHTTPClient creates a new PutConfigParams object +// with the ability to set a custom HTTPClient for a request. +func NewPutConfigParamsWithHTTPClient(client *http.Client) *PutConfigParams { + return &PutConfigParams{ + HTTPClient: client, + } +} + +/* +PutConfigParams contains all the parameters to send to the API endpoint + + for the put config operation. + + Typically these are written to a http.Request. +*/ +type PutConfigParams struct { + + /* Verbose. + + verbose setting for logging + */ + Verbose *int64 + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the put config params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutConfigParams) WithDefaults() *PutConfigParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the put config params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PutConfigParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the put config params +func (o *PutConfigParams) WithTimeout(timeout time.Duration) *PutConfigParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the put config params +func (o *PutConfigParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the put config params +func (o *PutConfigParams) WithContext(ctx context.Context) *PutConfigParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the put config params +func (o *PutConfigParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the put config params +func (o *PutConfigParams) WithHTTPClient(client *http.Client) *PutConfigParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the put config params +func (o *PutConfigParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithVerbose adds the verbose to the put config params +func (o *PutConfigParams) WithVerbose(verbose *int64) *PutConfigParams { + o.SetVerbose(verbose) + return o +} + +// SetVerbose adds the verbose to the put config params +func (o *PutConfigParams) SetVerbose(verbose *int64) { + o.Verbose = verbose +} + +// WriteToRequest writes these params to a swagger request +func (o *PutConfigParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if o.Verbose != nil { + + // query param verbose + var qrVerbose int64 + + if o.Verbose != nil { + qrVerbose = *o.Verbose + } + qVerbose := swag.FormatInt64(qrVerbose) + if qVerbose != "" { + + if err := r.SetQueryParam("verbose", qVerbose); err != nil { + return err + } + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/swagger/client/operations/put_config_responses.go b/pkg/swagger/client/operations/put_config_responses.go new file mode 100644 index 0000000..8245478 --- /dev/null +++ b/pkg/swagger/client/operations/put_config_responses.go @@ -0,0 +1,169 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" +) + +// PutConfigReader is a Reader for the PutConfig structure. +type PutConfigReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PutConfigReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewPutConfigOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewPutConfigDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPutConfigOK creates a PutConfigOK with default headers values +func NewPutConfigOK() *PutConfigOK { + return &PutConfigOK{} +} + +/* +PutConfigOK describes a response with status code 200, with default header values. + +OK +*/ +type PutConfigOK struct { +} + +// IsSuccess returns true when this put config o k response has a 2xx status code +func (o *PutConfigOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this put config o k response has a 3xx status code +func (o *PutConfigOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this put config o k response has a 4xx status code +func (o *PutConfigOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this put config o k response has a 5xx status code +func (o *PutConfigOK) IsServerError() bool { + return false +} + +// IsCode returns true when this put config o k response a status code equal to that given +func (o *PutConfigOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the put config o k response +func (o *PutConfigOK) Code() int { + return 200 +} + +func (o *PutConfigOK) Error() string { + return fmt.Sprintf("[PUT /config][%d] putConfigOK", 200) +} + +func (o *PutConfigOK) String() string { + return fmt.Sprintf("[PUT /config][%d] putConfigOK", 200) +} + +func (o *PutConfigOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPutConfigDefault creates a PutConfigDefault with default headers values +func NewPutConfigDefault(code int) *PutConfigDefault { + return &PutConfigDefault{ + _statusCode: code, + } +} + +/* +PutConfigDefault describes a response with status code -1, with default header values. + +PutConfigDefault put config default +*/ +type PutConfigDefault struct { + _statusCode int + + Payload interface{} +} + +// IsSuccess returns true when this put config default response has a 2xx status code +func (o *PutConfigDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this put config default response has a 3xx status code +func (o *PutConfigDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this put config default response has a 4xx status code +func (o *PutConfigDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this put config default response has a 5xx status code +func (o *PutConfigDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this put config default response a status code equal to that given +func (o *PutConfigDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the put config default response +func (o *PutConfigDefault) Code() int { + return o._statusCode +} + +func (o *PutConfigDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /config][%d] PutConfig default %s", o._statusCode, payload) +} + +func (o *PutConfigDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[PUT /config][%d] PutConfig default %s", o._statusCode, payload) +} + +func (o *PutConfigDefault) GetPayload() interface{} { + return o.Payload +} + +func (o *PutConfigDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/pkg/swagger/models/constraint.go b/pkg/swagger/models/constraint.go index 8665b2c..1243012 100644 --- a/pkg/swagger/models/constraint.go +++ b/pkg/swagger/models/constraint.go @@ -8,8 +8,10 @@ package models import ( "context" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" + "github.com/go-openapi/validate" ) // Constraint Constraint constrains the objects that will be included in search results. @@ -17,18 +19,60 @@ import ( // swagger:model Constraint type Constraint struct { - // End of time interval to include. - End string `json:"end,omitempty"` + // End of time interval, quoted RFC 3339 format. + // Format: date-time + End strfmt.DateTime `json:"end,omitempty"` - // Limit number of objects returned per query. + // Limit number of objects returned per query, <=0 means no limit. Limit int64 `json:"limit,omitempty"` - // Start of time interval to include. - Start string `json:"start,omitempty"` + // Start of time interval, quoted RFC 3339 format. + // Format: date-time + Start strfmt.DateTime `json:"start,omitempty"` + + // Timeout per request, h/m/s/ms/ns format + Timeout string `json:"timeout,omitempty"` } // Validate validates this constraint func (m *Constraint) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateEnd(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStart(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Constraint) validateEnd(formats strfmt.Registry) error { + if swag.IsZero(m.End) { // not required + return nil + } + + if err := validate.FormatOf("end", "body", "date-time", m.End.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *Constraint) validateStart(formats strfmt.Registry) error { + if swag.IsZero(m.Start) { // not required + return nil + } + + if err := validate.FormatOf("start", "body", "date-time", m.Start.String(), formats); err != nil { + return err + } + return nil } diff --git a/pkg/swagger/models/node.go b/pkg/swagger/models/node.go index 91bc4a5..3c57f9d 100644 --- a/pkg/swagger/models/node.go +++ b/pkg/swagger/models/node.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/swag" ) -// Node node +// Node Node in the result graph, contains results for a single class. // // swagger:model Node type Node struct { diff --git a/pkg/swagger/models/rule.go b/pkg/swagger/models/rule.go index fc85497..f9848d0 100644 --- a/pkg/swagger/models/rule.go +++ b/pkg/swagger/models/rule.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/swag" ) -// Rule rule +// Rule Rule is a correlation rule with a list of queries and results counts found during navigation. // // swagger:model Rule type Rule struct { diff --git a/swagger.json b/swagger.json index 9c9d2e4..6780414 100644 --- a/swagger.json +++ b/swagger.json @@ -1,11 +1,14 @@ { - "schemes": ["https","http"], "consumes": [ "application/json" ], "produces": [ "application/json" ], + "schemes": [ + "https", + "http" + ], "swagger": "2.0", "info": { "description": "REST API for the Korrel8r correlation engine.", @@ -23,6 +26,30 @@ "host": "localhost:8080", "basePath": "/api/v1alpha1", "paths": { + "/config": { + "put": { + "summary": "Set verbose level for logging on a running server.", + "parameters": [ + { + "type": "integer", + "description": "verbose setting for logging", + "name": "verbose", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK" + }, + "default": { + "description": "", + "schema": { + "type": "object" + } + } + } + } + }, "/domains": { "get": { "summary": "Get name, configuration and status for each domain.", @@ -223,15 +250,21 @@ "type": "object", "properties": { "end": { - "description": "End of time interval to include.", - "type": "string" + "description": "End of time interval, quoted RFC 3339 format.", + "type": "string", + "format": "date-time" }, "limit": { - "description": "Limit number of objects returned per query.", + "description": "Limit number of objects returned per query, \u003c=0 means no limit.", "type": "integer" }, "start": { - "description": "Start of time interval to include.", + "description": "Start of time interval, quoted RFC 3339 format.", + "type": "string", + "format": "date-time" + }, + "timeout": { + "description": "Timeout per request, h/m/s/ms/ns format", "type": "string" } } @@ -327,6 +360,7 @@ } }, "Node": { + "description": "Node in the result graph, contains results for a single class.", "type": "object", "properties": { "class": { @@ -362,6 +396,7 @@ } }, "Rule": { + "description": "Rule is a correlation rule with a list of queries and results counts found during navigation.", "type": "object", "properties": { "name": { @@ -409,4 +444,4 @@ } } } -} \ No newline at end of file +}