Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRAFT feat(domains): api domain options #752

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/pkg/common/schemas/validation.json
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,45 @@
"url": {
"type": "string",
"format": "uri"
},
"method": {
"type": "string",
"enum": ["Get", "Head", "Post", "PostForm"],
"default": "Get"
},
"body": {
"type": "string"
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "string"}
},
"options": {
"$ref": "#/definitions/api-options"
}
}
},
"required": ["name", "url"]
},
"options": {
"$ref": "#/definitions/api-options"
}
},
"required": ["requests"]
},
"api-options": {
"type": "object",
"properties": {
"timeout": {
"type": "integer"
},
"proxy": {
"type": "string"
},
"headers": {
"type": "array",
"items": {
"type": "string"
}
}
}
Expand Down
32 changes: 27 additions & 5 deletions src/pkg/domains/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,44 @@

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/defenseunicorns/lula/src/types"
)

func MakeRequests(Requests []Request) (types.DomainResources, error) {
func (a ApiDomain) makeRequests(_ context.Context) (types.DomainResources, error) {
collection := make(map[string]interface{}, 0)

for _, request := range Requests {
transport := &http.Transport{}
client := &http.Client{Transport: transport}
var defaultOpts *ApiOpts
if a.Spec.Options == nil {
defaultOpts = new(ApiOpts)
} else {
defaultOpts = a.Spec.Options
}

// configure the default HTTP client using any top-level Options. Individual requests with overrides will get bespoke clients.
transport := &http.Transport{}
if defaultOpts.Proxy != "" {
proxy, err := url.Parse(a.Spec.Options.Proxy)
if err != nil {
return nil, fmt.Errorf("error parsing proxy url: %s", err)
}
transport.Proxy = http.ProxyURL(proxy)
}

defaultClient := &http.Client{Transport: transport}
if defaultOpts.Timeout != 0 {

Check failure on line 37 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / test

invalid operation: defaultOpts.Timeout != 0 (mismatched types string and untyped int)

Check failure on line 37 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / lint

invalid operation: defaultOpts.Timeout != 0 (mismatched types string and untyped int)

Check failure on line 37 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / lint

invalid operation: defaultOpts.Timeout != 0 (mismatched types string and untyped int)

Check failure on line 37 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / lint

invalid operation: defaultOpts.Timeout != 0 (mismatched types string and untyped int)

Check failure on line 37 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / lint

invalid operation: defaultOpts.Timeout != 0 (mismatched types string and untyped int)

Check failure on line 37 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / Scan CVEs (go)

invalid operation: defaultOpts.Timeout != 0 (mismatched types string and untyped int)
defaultClient.Timeout = time.Duration(defaultOpts.Timeout) * time.Second

Check failure on line 38 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / test

cannot convert defaultOpts.Timeout (variable of type string) to type time.Duration

Check failure on line 38 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / lint

cannot convert defaultOpts.Timeout (variable of type string) to type time.Duration (typecheck)

Check failure on line 38 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / lint

cannot convert defaultOpts.Timeout (variable of type string) to type time.Duration) (typecheck)

Check failure on line 38 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / lint

cannot convert defaultOpts.Timeout (variable of type string) to type time.Duration) (typecheck)

Check failure on line 38 in src/pkg/domains/api/api.go

View workflow job for this annotation

GitHub Actions / Scan CVEs (go)

cannot convert defaultOpts.Timeout (variable of type string) to type time.Duration
}

resp, err := client.Get(request.URL)
for _, request := range a.Spec.Requests {
resp, err := defaultClient.Get(request.URL)
if err != nil {
return nil, err
}
Expand Down
29 changes: 29 additions & 0 deletions src/pkg/domains/api/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package api

import (
"errors"
"fmt"
)

func validateSpec(spec *ApiSpec) error {
if spec == nil {
return errors.New("spec is required")
}
if len(spec.Requests) == 0 {
return errors.New("some requests must be specified")
}
for _, request := range spec.Requests {
if request.Name == "" {
return errors.New("request name cannot be empty")
}
if request.URL == "" {
return errors.New("request url cannot be empty")
}
if request.Method != "" {
if request.Method != "Get" && request.Method != "Head" && request.Method != "Post" && request.Method != "PostForm" {
return fmt.Errorf("unsupported method: %s", request.Method)
}
}
}
return nil
}
41 changes: 22 additions & 19 deletions src/pkg/domains/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package api

import (
"context"
"fmt"

"github.com/defenseunicorns/lula/src/types"
)
Expand All @@ -15,29 +14,18 @@ type ApiDomain struct {

func CreateApiDomain(spec *ApiSpec) (types.Domain, error) {
// Check validity of spec
if spec == nil {
return nil, fmt.Errorf("spec is nil")
}

if len(spec.Requests) == 0 {
return nil, fmt.Errorf("some requests must be specified")
}
for _, request := range spec.Requests {
if request.Name == "" {
return nil, fmt.Errorf("request name cannot be empty")
}
if request.URL == "" {
return nil, fmt.Errorf("request url cannot be empty")
}
err := validateSpec(spec)
if err != nil {
return nil, err
}

return ApiDomain{
Spec: spec,
}, nil
}

func (a ApiDomain) GetResources(_ context.Context) (types.DomainResources, error) {
return MakeRequests(a.Spec.Requests)
func (a ApiDomain) GetResources(ctx context.Context) (types.DomainResources, error) {
return a.makeRequests(ctx)
}

func (a ApiDomain) IsExecutable() bool {
Expand All @@ -48,10 +36,25 @@ func (a ApiDomain) IsExecutable() bool {
// ApiSpec contains a list of API requests
type ApiSpec struct {
Requests []Request `mapstructure:"requests" json:"requests" yaml:"requests"`
// Opts will be applied to all requests, except those which have their own specified ApiOpts
Options *ApiOpts `mapstructure:"options" json:"options,omitempty" yaml:"options,omitempty"`
}

// Request is a single API request
type Request struct {
Name string `json:"name" yaml:"name"`
URL string `json:"url" yaml:"url"`
Name string `json:"name" yaml:"name"`
URL string `json:"url" yaml:"url"`
Method string `json:"method,omitempty" yaml:"method,omitempty"`
IsExecutable bool `json:"executable,omitempty" yaml:"executable,omitempty"`
Body string `json:"body,omitempty" yaml:"body,omitempty"`
Params map[string]string `json:"parameters,omitempty" yaml:"parameters,omitempty"`
brandtkeller marked this conversation as resolved.
Show resolved Hide resolved
// ApiOpts specific to this request
Options *ApiOpts `json:"options,omitempty" yaml:"options,omitempty"`
}

type ApiOpts struct {
// Timeout in seconds
Timeout string `json:"timeout,omitempty" yaml:"timeout,omitempty"`
Proxy string `json:"proxy,omitempty" yaml:"proxy,omitempty"`
Headers []string `json:"headers,omitempty" yaml:"headers,omitempty"`
}
Loading