2024-04-03 16:54:50 +00:00
|
|
|
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
|
|
|
|
|
|
|
|
package resource
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-04-05 17:22:17 +00:00
|
|
|
_ "errors"
|
2024-04-03 16:54:50 +00:00
|
|
|
"fmt"
|
|
|
|
"gopkg.in/yaml.v3"
|
2024-04-05 17:22:17 +00:00
|
|
|
"io"
|
2024-04-03 16:54:50 +00:00
|
|
|
"net/url"
|
2024-04-05 17:22:17 +00:00
|
|
|
"net/http"
|
|
|
|
_ "os"
|
2024-04-10 19:38:12 +00:00
|
|
|
"encoding/json"
|
|
|
|
"strings"
|
|
|
|
"log/slog"
|
2024-05-06 00:48:54 +00:00
|
|
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
2024-05-14 18:26:05 +00:00
|
|
|
"decl/internal/codec"
|
2024-04-03 16:54:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2024-07-01 21:54:18 +00:00
|
|
|
ResourceTypes.Register([]string{"http", "https"}, HTTPFactory)
|
2024-04-03 16:54:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func HTTPFactory(u *url.URL) Resource {
|
|
|
|
h := NewHTTP()
|
2024-04-10 19:38:12 +00:00
|
|
|
h.Endpoint = u.String()
|
2024-04-03 16:54:50 +00:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2024-04-10 19:38:12 +00:00
|
|
|
type HTTPHeader struct {
|
|
|
|
Name string `yaml:"name" json:"name"`
|
2024-04-10 20:27:34 +00:00
|
|
|
Value string `yaml:"value" json:"value"`
|
2024-04-10 19:38:12 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 16:54:50 +00:00
|
|
|
// Manage the state of an HTTP endpoint
|
|
|
|
type HTTP struct {
|
2024-05-09 07:39:45 +00:00
|
|
|
stater machine.Stater `yaml:"-" json:"-"`
|
2024-04-10 19:38:12 +00:00
|
|
|
client *http.Client `yaml:"-" json:"-"`
|
2024-04-09 19:30:05 +00:00
|
|
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
2024-04-10 20:27:34 +00:00
|
|
|
Headers []HTTPHeader `yaml:"headers,omitempty" json:"headers,omitempty"`
|
2024-04-09 19:30:05 +00:00
|
|
|
Body string `yaml:"body,omitempty" json:"body,omitempty"`
|
2024-07-01 21:54:18 +00:00
|
|
|
Status string `yaml:"status,omitempty" json:"status,omitempty"`
|
|
|
|
StatusCode int `yaml:"statuscode,omitempty" json:"statuscode,omitempty"`
|
2024-05-06 00:48:54 +00:00
|
|
|
State string `yaml:"state,omitempty" json:"state,omitempty"`
|
2024-07-01 21:54:18 +00:00
|
|
|
config ConfigurationValueGetter
|
2024-04-03 16:54:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewHTTP() *HTTP {
|
2024-04-10 19:38:12 +00:00
|
|
|
return &HTTP{ client: &http.Client{} }
|
2024-04-03 16:54:50 +00:00
|
|
|
}
|
|
|
|
|
2024-04-19 07:52:10 +00:00
|
|
|
func (h *HTTP) Clone() Resource {
|
|
|
|
return &HTTP {
|
|
|
|
client: h.client,
|
|
|
|
Endpoint: h.Endpoint,
|
|
|
|
Headers: h.Headers,
|
|
|
|
Body: h.Body,
|
|
|
|
State: h.State,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-06 00:48:54 +00:00
|
|
|
func (h *HTTP) StateMachine() machine.Stater {
|
2024-05-09 07:39:45 +00:00
|
|
|
if h.stater == nil {
|
|
|
|
h.stater = StorageMachine(h)
|
|
|
|
}
|
|
|
|
return h.stater
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HTTP) Notify(m *machine.EventMessage) {
|
|
|
|
ctx := context.Background()
|
2024-07-01 21:54:18 +00:00
|
|
|
slog.Info("Notify()", "http", h, "m", m)
|
2024-05-09 07:39:45 +00:00
|
|
|
switch m.On {
|
|
|
|
case machine.ENTERSTATEEVENT:
|
|
|
|
switch m.Dest {
|
2024-07-01 21:54:18 +00:00
|
|
|
case "start_read":
|
|
|
|
if _,readErr := h.Read(ctx); readErr == nil {
|
|
|
|
if triggerErr := h.StateMachine().Trigger("state_read"); triggerErr == nil {
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
h.State = "absent"
|
|
|
|
panic(triggerErr)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
h.State = "absent"
|
|
|
|
panic(readErr)
|
|
|
|
}
|
2024-05-09 07:39:45 +00:00
|
|
|
case "start_create":
|
2024-05-13 17:13:20 +00:00
|
|
|
if e := h.Create(ctx); e == nil {
|
|
|
|
if triggerErr := h.stater.Trigger("created"); triggerErr == nil {
|
|
|
|
return
|
2024-05-13 05:41:12 +00:00
|
|
|
}
|
2024-05-09 07:39:45 +00:00
|
|
|
}
|
2024-05-13 17:13:20 +00:00
|
|
|
h.State = "absent"
|
2024-07-01 21:54:18 +00:00
|
|
|
case "start_delete":
|
|
|
|
if deleteErr := h.Delete(ctx); deleteErr == nil {
|
|
|
|
if triggerErr := h.StateMachine().Trigger("deleted"); triggerErr == nil {
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
h.State = "present"
|
|
|
|
panic(triggerErr)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
h.State = "present"
|
|
|
|
panic(deleteErr)
|
|
|
|
}
|
|
|
|
case "absent":
|
|
|
|
h.State = "absent"
|
|
|
|
case "present", "created", "read":
|
2024-05-09 07:39:45 +00:00
|
|
|
h.State = "present"
|
|
|
|
}
|
|
|
|
case machine.EXITSTATEEVENT:
|
|
|
|
}
|
2024-05-06 00:48:54 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 16:54:50 +00:00
|
|
|
func (h *HTTP) URI() string {
|
|
|
|
return h.Endpoint
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HTTP) SetURI(uri string) error {
|
|
|
|
if _, e := url.Parse(uri); e != nil {
|
|
|
|
return fmt.Errorf("%w: %s is not a file", ErrInvalidResourceURI, uri)
|
|
|
|
}
|
|
|
|
h.Endpoint = uri
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-01 21:54:18 +00:00
|
|
|
func (h *HTTP) UseConfig(config ConfigurationValueGetter) {
|
|
|
|
h.config = config
|
|
|
|
}
|
|
|
|
|
2024-04-10 19:38:12 +00:00
|
|
|
func (h *HTTP) JSON() ([]byte, error) {
|
|
|
|
return json.Marshal(h)
|
|
|
|
}
|
|
|
|
|
2024-04-09 19:30:05 +00:00
|
|
|
func (h *HTTP) Validate() error {
|
2024-04-10 19:38:12 +00:00
|
|
|
s := NewSchema(h.Type())
|
|
|
|
jsonDoc, jsonErr := h.JSON()
|
|
|
|
if jsonErr == nil {
|
|
|
|
return s.Validate(string(jsonDoc))
|
|
|
|
}
|
|
|
|
return jsonErr
|
2024-04-09 19:30:05 +00:00
|
|
|
}
|
|
|
|
|
2024-04-03 16:54:50 +00:00
|
|
|
func (h *HTTP) Apply() error {
|
|
|
|
switch h.State {
|
|
|
|
case "absent":
|
|
|
|
case "present":
|
|
|
|
}
|
2024-04-10 19:38:12 +00:00
|
|
|
_,e := h.Read(context.Background())
|
|
|
|
if e == nil {
|
|
|
|
h.State = "present"
|
|
|
|
}
|
|
|
|
return e
|
2024-04-03 16:54:50 +00:00
|
|
|
}
|
|
|
|
|
2024-04-05 17:22:17 +00:00
|
|
|
func (h *HTTP) Load(r io.Reader) error {
|
2024-05-14 18:26:05 +00:00
|
|
|
return codec.NewYAMLDecoder(r).Decode(h)
|
2024-04-05 17:22:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HTTP) LoadDecl(yamlResourceDeclaration string) error {
|
2024-04-10 19:38:12 +00:00
|
|
|
slog.Info("LoadDecl()", "yaml", yamlResourceDeclaration)
|
2024-05-14 18:26:05 +00:00
|
|
|
return codec.NewYAMLStringDecoder(yamlResourceDeclaration).Decode(h)
|
2024-04-03 16:54:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HTTP) ResolveId(ctx context.Context) string {
|
|
|
|
return h.Endpoint
|
|
|
|
}
|
|
|
|
|
2024-05-09 07:39:45 +00:00
|
|
|
func (h *HTTP) Create(ctx context.Context) error {
|
2024-04-10 19:38:12 +00:00
|
|
|
body := strings.NewReader(h.Body)
|
|
|
|
req, reqErr := http.NewRequest("POST", h.Endpoint, body)
|
|
|
|
if reqErr != nil {
|
|
|
|
return reqErr
|
|
|
|
}
|
2024-07-01 21:54:18 +00:00
|
|
|
|
|
|
|
if tokenErr := h.ReadAuthorizationTokenFromConfig(req); tokenErr != nil {
|
|
|
|
slog.Error("ReadAuthorizationTokenFromConfig()", "error", tokenErr)
|
|
|
|
}
|
|
|
|
|
2024-04-10 19:38:12 +00:00
|
|
|
for _,header := range h.Headers {
|
|
|
|
req.Header.Add(header.Name, header.Value)
|
|
|
|
}
|
2024-07-01 21:54:18 +00:00
|
|
|
|
2024-04-10 19:38:12 +00:00
|
|
|
resp, err := h.client.Do(req)
|
2024-07-01 21:54:18 +00:00
|
|
|
h.Status = resp.Status
|
|
|
|
h.StatusCode = resp.StatusCode
|
2024-04-10 19:38:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-04-10 20:27:34 +00:00
|
|
|
defer resp.Body.Close()
|
2024-04-10 19:38:12 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-07-01 21:54:18 +00:00
|
|
|
func (h *HTTP) ReadAuthorizationTokenFromConfig(req *http.Request) error {
|
|
|
|
if h.config != nil {
|
|
|
|
token, tokenErr := h.config.GetValue("authorization_token")
|
|
|
|
if tokenErr == nil {
|
|
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
|
|
}
|
|
|
|
slog.Info("ReadAuthorizationTokenFromConfig()", "error", tokenErr)
|
|
|
|
return tokenErr
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-03 16:54:50 +00:00
|
|
|
func (h *HTTP) Read(ctx context.Context) ([]byte, error) {
|
2024-04-10 19:38:12 +00:00
|
|
|
req, reqErr := http.NewRequestWithContext(ctx, "GET", h.Endpoint, nil)
|
|
|
|
if reqErr != nil {
|
|
|
|
return nil, reqErr
|
|
|
|
}
|
|
|
|
slog.Info("HTTP.Read() ", "request", req, "err", reqErr)
|
|
|
|
|
2024-07-01 21:54:18 +00:00
|
|
|
tokenErr := h.ReadAuthorizationTokenFromConfig(req)
|
|
|
|
if tokenErr != nil {
|
|
|
|
slog.Error("ReadAuthorizationTokenFromConfig()", "error", tokenErr)
|
|
|
|
}
|
|
|
|
|
2024-04-10 19:38:12 +00:00
|
|
|
if len(h.Headers) > 0 {
|
|
|
|
for _,header := range h.Headers {
|
|
|
|
req.Header.Add(header.Name, header.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := h.client.Do(req)
|
2024-07-01 21:54:18 +00:00
|
|
|
slog.Info("Http.Read()", "response", resp, "error", err)
|
|
|
|
h.Status = resp.Status
|
|
|
|
h.StatusCode = resp.StatusCode
|
2024-04-05 17:22:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
body, errReadBody := io.ReadAll(resp.Body)
|
|
|
|
if errReadBody != nil {
|
|
|
|
return nil, errReadBody
|
|
|
|
}
|
|
|
|
h.Body = string(body)
|
2024-04-03 16:54:50 +00:00
|
|
|
return yaml.Marshal(h)
|
|
|
|
}
|
|
|
|
|
2024-07-01 21:54:18 +00:00
|
|
|
func (h *HTTP) Delete(ctx context.Context) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-03 16:54:50 +00:00
|
|
|
func (h *HTTP) Type() string {
|
2024-04-10 19:38:12 +00:00
|
|
|
return "http"
|
2024-04-03 16:54:50 +00:00
|
|
|
}
|