// Copyright 2024 Matthew Rich . All rights reserved. package resource import ( "context" _ "errors" "fmt" "gopkg.in/yaml.v3" "io" "net/url" "net/http" _ "os" "encoding/json" "strings" "log/slog" "gitea.rosskeen.house/rosskeen.house/machine" ) func init() { ResourceTypes.Register("http", HTTPFactory) ResourceTypes.Register("https", HTTPFactory) } func HTTPFactory(u *url.URL) Resource { h := NewHTTP() h.Endpoint = u.String() return h } type HTTPHeader struct { Name string `yaml:"name" json:"name"` Value string `yaml:"value" json:"value"` } // Manage the state of an HTTP endpoint type HTTP struct { stater machine.Stater `yaml:"-" json:"-"` client *http.Client `yaml:"-" json:"-"` Endpoint string `yaml:"endpoint" json:"endpoint"` Headers []HTTPHeader `yaml:"headers,omitempty" json:"headers,omitempty"` Body string `yaml:"body,omitempty" json:"body,omitempty"` State string `yaml:"state,omitempty" json:"state,omitempty"` } func NewHTTP() *HTTP { return &HTTP{ client: &http.Client{} } } func (h *HTTP) Clone() Resource { return &HTTP { client: h.client, Endpoint: h.Endpoint, Headers: h.Headers, Body: h.Body, State: h.State, } } func (h *HTTP) StateMachine() machine.Stater { if h.stater == nil { h.stater = StorageMachine(h) } return h.stater } func (h *HTTP) Notify(m *machine.EventMessage) { ctx := context.Background() switch m.On { case machine.ENTERSTATEEVENT: switch m.Dest { case "start_create": if e := h.Create(ctx); e == nil { if triggerErr := h.stater.Trigger("created"); triggerErr == nil { return } } h.State = "absent" case "present": h.State = "present" } case machine.EXITSTATEEVENT: } } 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 } func (h *HTTP) JSON() ([]byte, error) { return json.Marshal(h) } func (h *HTTP) Validate() error { s := NewSchema(h.Type()) jsonDoc, jsonErr := h.JSON() if jsonErr == nil { return s.Validate(string(jsonDoc)) } return jsonErr } func (h *HTTP) Apply() error { switch h.State { case "absent": case "present": } _,e := h.Read(context.Background()) if e == nil { h.State = "present" } return e } func (h *HTTP) Load(r io.Reader) error { c := NewYAMLDecoder(r) return c.Decode(h) } func (h *HTTP) LoadDecl(yamlResourceDeclaration string) error { slog.Info("LoadDecl()", "yaml", yamlResourceDeclaration) c := NewYAMLStringDecoder(yamlResourceDeclaration) return c.Decode(h) } func (h *HTTP) ResolveId(ctx context.Context) string { return h.Endpoint } func (h *HTTP) Create(ctx context.Context) 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) if err != nil { return err } defer resp.Body.Close() return err } func (h *HTTP) Read(ctx context.Context) ([]byte, error) { 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 { return nil, err } defer resp.Body.Close() body, errReadBody := io.ReadAll(resp.Body) if errReadBody != nil { return nil, errReadBody } h.Body = string(body) return yaml.Marshal(h) } func (h *HTTP) Type() string { return "http" }