jx/internal/resource/service.go
Matthew Rich 8094d1c063
Some checks are pending
Lint / golangci-lint (push) Waiting to run
Declarative Tests / test (push) Waiting to run
Declarative Tests / build-fedora (push) Waiting to run
Declarative Tests / build-ubuntu-focal (push) Waiting to run
add build support to container_image resource; add more testing
2024-09-19 08:11:57 +00:00

279 lines
6.4 KiB
Go

// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
// Service resource
package resource
import (
"context"
"fmt"
_ "log/slog"
"net/url"
"path/filepath"
"io"
"gopkg.in/yaml.v3"
"gitea.rosskeen.house/rosskeen.house/machine"
"decl/internal/codec"
"decl/internal/data"
"encoding/json"
"strings"
)
const (
ServiceTypeName TypeName = "service"
)
type ServiceManagerType string
const (
ServiceManagerTypeSystemd ServiceManagerType = "systemd"
ServiceManagerTypeSysV ServiceManagerType = "sysv"
)
type Service struct {
*Common `yaml:",inline" json:",inline"`
stater machine.Stater `yaml:"-" json:"-"`
Name string `json:"name" yaml:"name"`
ServiceManagerType ServiceManagerType `json:"servicemanager,omitempty" yaml:"servicemanager,omitempty"`
CreateCommand *Command `yaml:"-" json:"-"`
ReadCommand *Command `yaml:"-" json:"-"`
UpdateCommand *Command `yaml:"-" json:"-"`
DeleteCommand *Command `yaml:"-" json:"-"`
config data.ConfigurationValueGetter
Resources data.ResourceMapper `yaml:"-" json:"-"`
}
func init() {
ResourceTypes.Register([]string{"service"}, func(u *url.URL) data.Resource {
s := NewService()
s.Name = filepath.Join(u.Hostname(), u.Path)
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD()
return s
})
}
func NewService() *Service {
return &Service{ ServiceManagerType: ServiceManagerTypeSystemd, Common: &Common{ resourceType: ServiceTypeName } }
}
func (s *Service) StateMachine() machine.Stater {
if s.stater == nil {
s.stater = ProcessMachine(s)
}
return s.stater
}
func (s *Service) Notify(m *machine.EventMessage) {
ctx := context.Background()
switch m.On {
case machine.ENTERSTATEEVENT:
switch m.Dest {
case "start_create":
if e := s.Create(ctx); e == nil {
if triggerErr := s.stater.Trigger("created"); triggerErr == nil {
return
}
}
s.Common.State = "absent"
case "created":
s.Common.State = "present"
case "running":
s.Common.State = "running"
}
case machine.EXITSTATEEVENT:
}
}
func (s *Service) URI() string {
return fmt.Sprintf("service://%s", s.Name)
}
func (s *Service) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if e == nil {
if resourceUri.Scheme == s.Type() {
s.Name = filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI())
} else {
e = fmt.Errorf("%w: %s is not a %s", ErrInvalidResourceURI, uri, s.Type())
}
}
return e
}
func (s *Service) UseConfig(config data.ConfigurationValueGetter) {
s.config = config
}
func (s *Service) JSON() ([]byte, error) {
return json.Marshal(s)
}
func (s *Service) Validate() error {
return nil
}
func (s *Service) SetResourceMapper(resources data.ResourceMapper) {
s.Resources = resources
}
func (s *Service) Clone() data.Resource {
news := &Service{
Common: s.Common,
Name: s.Name,
ServiceManagerType: s.ServiceManagerType,
}
news.CreateCommand, news.ReadCommand, news.UpdateCommand, news.DeleteCommand = s.ServiceManagerType.NewCRUD()
return news
}
func (s *Service) Apply() error {
return nil
}
func (s *Service) Load(docData []byte, f codec.Format) (err error) {
err = f.StringDecoder(string(docData)).Decode(s)
return
}
func (s *Service) LoadReader(r io.ReadCloser, f codec.Format) (err error) {
err = f.Decoder(r).Decode(s)
return
}
func (s *Service) LoadString(docData string, f codec.Format) (err error) {
err = f.StringDecoder(docData).Decode(s)
return
}
func (s *Service) LoadDecl(yamlResourceDeclaration string) error {
return s.LoadString(yamlResourceDeclaration, codec.FormatYaml)
}
func (s *Service) UnmarshalJSON(data []byte) error {
if unmarshalErr := json.Unmarshal(data, s); unmarshalErr != nil {
return unmarshalErr
}
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD()
return nil
}
func (s *Service) UnmarshalYAML(value *yaml.Node) error {
type decodeService Service
if unmarshalErr := value.Decode((*decodeService)(s)); unmarshalErr != nil {
return unmarshalErr
}
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD()
return nil
}
func (s *ServiceManagerType) NewCRUD() (create *Command, read *Command, update *Command, del *Command) {
switch *s {
case ServiceManagerTypeSystemd:
return NewSystemdCreateCommand(), NewSystemdReadCommand(), NewSystemdUpdateCommand(), NewSystemdDeleteCommand()
case ServiceManagerTypeSysV:
return NewSysVCreateCommand(), NewSysVReadCommand(), NewSysVUpdateCommand(), NewSysVDeleteCommand()
default:
}
return nil, nil, nil, nil
}
func (s *Service) Create(ctx context.Context) error {
return nil
}
func (s *Service) Read(ctx context.Context) ([]byte, error) {
return yaml.Marshal(s)
}
func (s *Service) Update(ctx context.Context) error {
return nil
}
func (s *Service) Delete(ctx context.Context) error {
return nil
}
func (s *Service) Type() string { return "service" }
func (s *Service) ResolveId(ctx context.Context) string {
return ""
}
func NewSystemdCreateCommand() *Command {
c := NewCommand()
c.Path = "systemctl"
c.Args = []CommandArg{
CommandArg("enable"),
CommandArg("{{ .Name }}"),
}
return c
}
func NewSystemdReadCommand() *Command {
c := NewCommand()
c.Path = "systemctl"
c.Args = []CommandArg{
CommandArg("show"),
CommandArg("{{ .Name }}"),
}
c.Extractor = func(out []byte, target any) error {
s := target.(*Service)
serviceStatus := strings.Split(string(out), "\n")
for _, statusLine := range(serviceStatus) {
if len(statusLine) > 1 {
statusKeyValue := strings.Split(statusLine, "=")
key := statusKeyValue[0]
value := strings.TrimSpace(strings.Join(statusKeyValue[1:], "="))
switch key {
case "Id":
case "ActiveState":
switch value {
case "active":
if stateCreatedErr := s.stater.Trigger("created"); stateCreatedErr != nil {
return stateCreatedErr
}
case "inactive":
}
case "SubState":
switch value {
case "running":
if stateRunningErr := s.stater.Trigger("running"); stateRunningErr != nil {
return stateRunningErr
}
case "dead":
}
}
}
}
return nil
}
return c
}
func NewSystemdUpdateCommand() *Command {
return nil
}
func NewSystemdDeleteCommand() *Command {
return nil
}
func NewSysVCreateCommand() *Command {
return nil
}
func NewSysVReadCommand() *Command {
return nil
}
func NewSysVUpdateCommand() *Command {
return nil
}
func NewSysVDeleteCommand() *Command {
return nil
}