add http resource create method
Some checks failed
Lint / golangci-lint (push) Failing after 9m54s
Declarative Tests / test (push) Successful in 1m21s

This commit is contained in:
Matthew Rich 2024-04-10 12:38:12 -07:00
parent 01eaf00bb6
commit 11a55e27d0
5 changed files with 193 additions and 20 deletions

View File

@ -11,6 +11,9 @@ _ "errors"
"net/url" "net/url"
"net/http" "net/http"
_ "os" _ "os"
"encoding/json"
"strings"
"log/slog"
) )
func init() { func init() {
@ -20,19 +23,26 @@ func init() {
func HTTPFactory(u *url.URL) Resource { func HTTPFactory(u *url.URL) Resource {
h := NewHTTP() h := NewHTTP()
h.Endpoint = u.String()
return h return h
} }
type HTTPHeader struct {
Name string `yaml:"name" json:"name"`
Value string `yaml:"value" json"value"`
}
// Manage the state of an HTTP endpoint // Manage the state of an HTTP endpoint
type HTTP struct { type HTTP struct {
client *http.Client `yaml:"-" json:"-"`
Endpoint string `yaml:"endpoint" json:"endpoint"` Endpoint string `yaml:"endpoint" json:"endpoint"`
Headers []HTTPHeader `yaml:"headers,omitempty", json:"headers,omitempty"`
Body string `yaml:"body,omitempty" json:"body,omitempty"` Body string `yaml:"body,omitempty" json:"body,omitempty"`
State string `yaml:"state" json:"state"` State string `yaml:"state" json:"state"`
} }
func NewHTTP() *HTTP { func NewHTTP() *HTTP {
return &HTTP{} return &HTTP{ client: &http.Client{} }
} }
func (h *HTTP) URI() string { func (h *HTTP) URI() string {
@ -47,36 +57,77 @@ func (h *HTTP) SetURI(uri string) error {
return nil return nil
} }
func (h *HTTP) JSON() ([]byte, error) {
return json.Marshal(h)
}
func (h *HTTP) Validate() error { func (h *HTTP) Validate() error {
return fmt.Errorf("failed") s := NewSchema(h.Type())
jsonDoc, jsonErr := h.JSON()
if jsonErr == nil {
return s.Validate(string(jsonDoc))
}
return jsonErr
} }
func (h *HTTP) Apply() error { func (h *HTTP) Apply() error {
switch h.State { switch h.State {
case "absent": case "absent":
case "present": case "present":
} }
_,e := h.Read(context.Background())
return nil if e == nil {
h.State = "present"
}
return e
} }
func (h *HTTP) Load(r io.Reader) error { func (h *HTTP) Load(r io.Reader) error {
c := NewYAMLDecoder(r) c := NewYAMLDecoder(r)
return c.Decode(h) return c.Decode(h)
} }
func (h *HTTP) LoadDecl(yamlResourceDeclaration string) error { func (h *HTTP) LoadDecl(yamlResourceDeclaration string) error {
c := NewYAMLStringDecoder(yamlResourceDeclaration) slog.Info("LoadDecl()", "yaml", yamlResourceDeclaration)
return c.Decode(h) c := NewYAMLStringDecoder(yamlResourceDeclaration)
return c.Decode(h)
} }
func (h *HTTP) ResolveId(ctx context.Context) string { func (h *HTTP) ResolveId(ctx context.Context) string {
return h.Endpoint return h.Endpoint
} }
func (h *HTTP) Create() error {
body := strings.NewReader(h.Body)
req, reqErr := http.NewRequest("POST", h.Endpoint, body)
if reqErr != nil {
return reqErr
}
for _,header := range h.Headers {
req.Header.Add(header.Name, header.Value)
}
resp, err := h.client.Do(req)
defer resp.Body.Close()
if err != nil {
return err
}
return err
}
func (h *HTTP) Read(ctx context.Context) ([]byte, error) { func (h *HTTP) Read(ctx context.Context) ([]byte, error) {
resp, err := http.Get(h.Endpoint) req, reqErr := http.NewRequestWithContext(ctx, "GET", h.Endpoint, nil)
if reqErr != nil {
return nil, reqErr
}
slog.Info("HTTP.Read() ", "request", req, "err", reqErr)
if len(h.Headers) > 0 {
for _,header := range h.Headers {
req.Header.Add(header.Name, header.Value)
}
}
resp, err := h.client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,6 +141,5 @@ func (h *HTTP) Read(ctx context.Context) ([]byte, error) {
} }
func (h *HTTP) Type() string { func (h *HTTP) Type() string {
u, _ := url.Parse(h.Endpoint) return "http"
return u.Scheme
} }

View File

@ -3,23 +3,97 @@
package resource package resource
import ( import (
_ "context" "context"
_ "encoding/json" _ "encoding/json"
_ "fmt" "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
_ "gopkg.in/yaml.v3" _ "gopkg.in/yaml.v3"
_ "io" "io"
_ "log" _ "log"
_ "net/http" "net/http"
_ "net/http/httptest" "net/http/httptest"
_ "net/url" _ "net/url"
_ "os" _ "os"
_ "path/filepath" _ "path/filepath"
_ "strings" _ "strings"
"testing" "testing"
"regexp"
) )
func TestNewHTTPResource(t *testing.T) { func TestNewHTTPResource(t *testing.T) {
f := NewHTTP() h := NewHTTP()
assert.NotNil(t, f) assert.NotNil(t, h)
}
func TestHTTPDecode(t *testing.T) {
h := NewHTTP()
assert.NotNil(t, h)
decl:=`
endpoint: "https://example.foo"
body: |-
test body
`
assert.Nil(t, h.LoadDecl(decl))
assert.Equal(t, "test body", h.Body)
}
func TestHTTPRead(t *testing.T) {
h := NewHTTP()
assert.NotNil(t, h)
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
assert.Equal(t, req.URL.String(), "/resource/user/foo")
rw.Write([]byte(`
type: "user"
attributes:
name: "foo"
gecos: "foo user"
`))
}))
defer server.Close()
decl := fmt.Sprintf(`
endpoint: "%s/resource/user/foo"
`, server.URL)
assert.Nil(t, h.LoadDecl(decl))
_,e := h.Read(context.Background())
assert.Nil(t, e)
assert.Greater(t, len(h.Body), 0)
assert.Nil(t, h.Validate())
}
func TestHTTPCreate(t *testing.T) {
userdecl := `
type: "user"
attributes:
name: "foo"
gecos: "foo user"
`
h := NewHTTP()
assert.NotNil(t, h)
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
assert.Equal(t, req.URL.String(), "/resource/user")
body, err := io.ReadAll(req.Body)
assert.Nil(t, err)
assert.Equal(t, userdecl, string(body))
}))
defer server.Close()
re := regexp.MustCompile(`(?m)^(.*)$`)
decl := fmt.Sprintf(`
endpoint: "%s/resource/user"
headers:
- name: "content-type"
value: "application/yaml"
body: |
%s
`, server.URL, re.ReplaceAllString(userdecl, " $1"))
assert.Nil(t, h.LoadDecl(decl))
assert.Greater(t, len(h.Body), 0)
e := h.Create()
assert.Nil(t, e)
} }

View File

@ -12,6 +12,7 @@
"oneOf": [ "oneOf": [
{ "$ref": "package-declaration.jsonschema" }, { "$ref": "package-declaration.jsonschema" },
{ "$ref": "file-declaration.jsonschema" }, { "$ref": "file-declaration.jsonschema" },
{ "$ref": "http-declaration.jsonschema" },
{ "$ref": "user-declaration.jsonschema" }, { "$ref": "user-declaration.jsonschema" },
{ "$ref": "exec-declaration.jsonschema" }, { "$ref": "exec-declaration.jsonschema" },
{ "$ref": "network_route-declaration.jsonschema" } { "$ref": "network_route-declaration.jsonschema" }

View File

@ -0,0 +1,17 @@
{
"$id": "http-declaration.jsonschema",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "http-declaration",
"type": "object",
"required": [ "type", "attributes" ],
"properties": {
"type": {
"type": "string",
"description": "Resource type name.",
"enum": [ "http" ]
},
"attributes": {
"$ref": "http.jsonschema"
}
}
}

View File

@ -0,0 +1,31 @@
{
"$id": "http.jsonschema",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "http",
"type": "object",
"required": [ "endpoint" ],
"properties": {
"endpoint": {
"type": "string"
},
"headers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "HTTP header name"
},
"value": {
"type": "string",
"description": "HTTP header value"
}
}
}
},
"body": {
"type": "string"
}
}
}