update resources to common uri handling
This commit is contained in:
parent
37ed8bfa83
commit
1117882ced
@ -13,9 +13,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UriSchemeValidator func(scheme string) bool
|
type UriSchemeValidator func(scheme string) bool
|
||||||
|
type UriNormalize func() error
|
||||||
|
|
||||||
type Common struct {
|
type Common struct {
|
||||||
SchemeCheck UriSchemeValidator `json:"-" yaml:"-"`
|
SchemeCheck UriSchemeValidator `json:"-" yaml:"-"`
|
||||||
|
NormalizePath UriNormalize `json:"-" yaml:"-"`
|
||||||
includeQueryParamsInURI bool `json:"-" yaml:"-"`
|
includeQueryParamsInURI bool `json:"-" yaml:"-"`
|
||||||
resourceType TypeName `json:"-" yaml:"-"`
|
resourceType TypeName `json:"-" yaml:"-"`
|
||||||
Uri folio.URI `json:"uri,omitempty" yaml:"uri,omitempty"`
|
Uri folio.URI `json:"uri,omitempty" yaml:"uri,omitempty"`
|
||||||
@ -34,6 +36,7 @@ type Common struct {
|
|||||||
func NewCommon(resourceType TypeName, includeQueryParams bool) *Common {
|
func NewCommon(resourceType TypeName, includeQueryParams bool) *Common {
|
||||||
c := &Common{ includeQueryParamsInURI: includeQueryParams, resourceType: resourceType }
|
c := &Common{ includeQueryParamsInURI: includeQueryParams, resourceType: resourceType }
|
||||||
c.SchemeCheck = c.IsValidResourceScheme
|
c.SchemeCheck = c.IsValidResourceScheme
|
||||||
|
c.NormalizePath = c.NormalizeFilePath
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ func (c *Common) ResolveId(ctx context.Context) string {
|
|||||||
return c.Path
|
return c.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Common) NormalizePath() error {
|
func (c *Common) NormalizeFilePath() error {
|
||||||
if c.config != nil {
|
if c.config != nil {
|
||||||
if prefixPath, configErr := c.config.GetValue("prefix"); configErr == nil {
|
if prefixPath, configErr := c.config.GetValue("prefix"); configErr == nil {
|
||||||
c.Path = filepath.Join(prefixPath.(string), c.Path)
|
c.Path = filepath.Join(prefixPath.(string), c.Path)
|
||||||
|
@ -118,13 +118,15 @@ func NewContainerImage(containerClientApi ContainerImageClient) *ContainerImage
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ContainerImage{
|
c := &ContainerImage{
|
||||||
Common: &Common{ includeQueryParamsInURI: true, resourceType: ContainerImageTypeName },
|
Common: NewCommon(ContainerImageTypeName, true),
|
||||||
apiClient: apiClient,
|
apiClient: apiClient,
|
||||||
InjectJX: true,
|
InjectJX: true,
|
||||||
PushImage: false,
|
PushImage: false,
|
||||||
ConverterTypes: folio.DocumentRegistry.ConverterTypes,
|
ConverterTypes: folio.DocumentRegistry.ConverterTypes,
|
||||||
}
|
}
|
||||||
|
c.Common.NormalizePath = c.NormalizePath
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) RegistryAuthConfig() (authConfig registry.AuthConfig, err error) {
|
func (c *ContainerImage) RegistryAuthConfig() (authConfig registry.AuthConfig, err error) {
|
||||||
@ -175,6 +177,10 @@ func (c *ContainerImage) RegistryAuth() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ContainerImage) NormalizePath() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ContainerImage) SetResourceMapper(resources data.ResourceMapper) {
|
func (c *ContainerImage) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
c.Resources = resources
|
c.Resources = resources
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainerNetwork(containerClientApi ContainerNetworkClient) *ContainerNetwork {
|
func NewContainerNetwork(containerClientApi ContainerNetworkClient) (cn *ContainerNetwork) {
|
||||||
var apiClient ContainerNetworkClient = containerClientApi
|
var apiClient ContainerNetworkClient = containerClientApi
|
||||||
if apiClient == nil {
|
if apiClient == nil {
|
||||||
var err error
|
var err error
|
||||||
@ -71,10 +71,16 @@ func NewContainerNetwork(containerClientApi ContainerNetworkClient) *ContainerNe
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ContainerNetwork{
|
cn = &ContainerNetwork{
|
||||||
Common: &Common{ includeQueryParamsInURI: true, resourceType: ContainerNetworkTypeName },
|
|
||||||
apiClient: apiClient,
|
apiClient: apiClient,
|
||||||
}
|
}
|
||||||
|
cn.Common = NewCommon(ContainerNetworkTypeName, true)
|
||||||
|
cn.Common.NormalizePath = cn.NormalizePath
|
||||||
|
return cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ContainerNetwork) NormalizePath() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) {
|
func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
|
@ -135,10 +135,17 @@ func NewNormalizedFile() *File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) ContentType() string {
|
func (f *File) ContentType() string {
|
||||||
if f.parsedURI.Scheme != "file" {
|
var ext strings.Builder
|
||||||
return f.parsedURI.Scheme
|
if f.parsedURI.Scheme != "file" {
|
||||||
}
|
return f.parsedURI.Scheme
|
||||||
return f.exttype
|
}
|
||||||
|
if f.fileext == "" {
|
||||||
|
return f.exttype
|
||||||
|
}
|
||||||
|
ext.WriteString(f.exttype)
|
||||||
|
ext.WriteRune('.')
|
||||||
|
ext.WriteString(f.fileext)
|
||||||
|
return ext.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) SetResourceMapper(resources data.ResourceMapper) {
|
func (f *File) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
@ -215,6 +222,17 @@ func (f *File) Notify(m *machine.EventMessage) {
|
|||||||
f.State = "absent"
|
f.State = "absent"
|
||||||
panic(e)
|
panic(e)
|
||||||
}
|
}
|
||||||
|
case "start_update":
|
||||||
|
if updateErr := f.Update(ctx); updateErr == nil {
|
||||||
|
if triggerErr := f.stater.Trigger("updated"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
f.State = "absent"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.State = "absent"
|
||||||
|
panic(updateErr)
|
||||||
|
}
|
||||||
case "start_delete":
|
case "start_delete":
|
||||||
if deleteErr := f.Delete(ctx); deleteErr == nil {
|
if deleteErr := f.Delete(ctx); deleteErr == nil {
|
||||||
if triggerErr := f.StateMachine().Trigger("deleted"); triggerErr == nil {
|
if triggerErr := f.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||||
|
@ -27,8 +27,9 @@ type decodeGroup Group
|
|||||||
type GroupType string
|
type GroupType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GroupTypeAddGroup = "addgroup"
|
GroupTypeName TypeName = "group"
|
||||||
GroupTypeGroupAdd = "groupadd"
|
GroupTypeAddGroup GroupType = "addgroup"
|
||||||
|
GroupTypeGroupAdd GroupType = "groupadd"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupportedGroupType error = errors.New("The GroupType is not supported on this system")
|
var ErrUnsupportedGroupType error = errors.New("The GroupType is not supported on this system")
|
||||||
@ -47,13 +48,17 @@ type Group struct {
|
|||||||
ReadCommand *command.Command `json:"-" yaml:"-"`
|
ReadCommand *command.Command `json:"-" yaml:"-"`
|
||||||
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
UpdateCommand *command.Command `json:"-" yaml:"-"`
|
||||||
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
DeleteCommand *command.Command `json:"-" yaml:"-"`
|
||||||
State string `json:"state,omitempty" yaml:"state,omitempty"`
|
//State string `json:"state,omitempty" yaml:"state,omitempty"`
|
||||||
config data.ConfigurationValueGetter
|
config data.ConfigurationValueGetter
|
||||||
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
Resources data.ResourceMapper `json:"-" yaml:"-"`
|
||||||
|
groupStatus *user.Group `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroup() *Group {
|
func NewGroup() (g *Group) {
|
||||||
return &Group{}
|
g = &Group{}
|
||||||
|
g.Common = NewCommon(GroupTypeName, true)
|
||||||
|
g.Common.NormalizePath = g.NormalizePath
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -82,15 +87,19 @@ func FindSystemGroupType() GroupType {
|
|||||||
return GroupTypeAddGroup
|
return GroupTypeAddGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Group) NormalizePath() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Group) SetResourceMapper(resources data.ResourceMapper) {
|
func (g *Group) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
g.Resources = resources
|
g.Resources = resources
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Clone() data.Resource {
|
func (g *Group) Clone() data.Resource {
|
||||||
newg := &Group {
|
newg := &Group {
|
||||||
|
Common: g.Common,
|
||||||
Name: g.Name,
|
Name: g.Name,
|
||||||
GID: g.GID,
|
GID: g.GID,
|
||||||
State: g.State,
|
|
||||||
GroupType: g.GroupType,
|
GroupType: g.GroupType,
|
||||||
}
|
}
|
||||||
newg.CreateCommand, newg.ReadCommand, newg.UpdateCommand, newg.DeleteCommand = g.GroupType.NewCRUD()
|
newg.CreateCommand, newg.ReadCommand, newg.UpdateCommand, newg.DeleteCommand = g.GroupType.NewCRUD()
|
||||||
@ -109,15 +118,53 @@ func (g *Group) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
|
case "start_stat":
|
||||||
|
if statErr := g.ReadStat(); statErr == nil {
|
||||||
|
if triggerErr := g.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := g.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "start_read":
|
||||||
|
if _,readErr := g.Read(ctx); readErr == nil {
|
||||||
|
if triggerErr := g.StateMachine().Trigger("state_read"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
panic(readErr)
|
||||||
|
}
|
||||||
case "start_create":
|
case "start_create":
|
||||||
if e := g.Create(ctx); e == nil {
|
if e := g.Create(ctx); e == nil {
|
||||||
if triggerErr := g.stater.Trigger("created"); triggerErr == nil {
|
if triggerErr := g.stater.Trigger("created"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.State = "absent"
|
g.Common.State = "absent"
|
||||||
case "present":
|
|
||||||
g.State = "present"
|
|
||||||
|
case "start_update":
|
||||||
|
if updateErr := g.Update(ctx); updateErr == nil {
|
||||||
|
if triggerErr := g.stater.Trigger("updated"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
panic(updateErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "absent":
|
||||||
|
g.Common.State = "absent"
|
||||||
|
case "present", "created", "read":
|
||||||
|
g.Common.State = "present"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
@ -153,7 +200,7 @@ func (g *Group) Validate() error {
|
|||||||
|
|
||||||
func (g *Group) Apply() error {
|
func (g *Group) Apply() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
switch g.State {
|
switch g.Common.State {
|
||||||
case "present":
|
case "present":
|
||||||
_, NoGroupExists := LookupGID(g.Name)
|
_, NoGroupExists := LookupGID(g.Name)
|
||||||
if NoGroupExists != nil {
|
if NoGroupExists != nil {
|
||||||
@ -198,10 +245,26 @@ func (g *Group) Create(ctx context.Context) (error) {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Group) ReadStat() (err error) {
|
||||||
|
if g.groupStatus == nil {
|
||||||
|
if g.groupStatus, err = user.LookupGroup(g.Name); err != nil {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(g.groupStatus.Gid) < 1 {
|
||||||
|
g.Common.State = "absent"
|
||||||
|
return ErrResourceStateAbsent
|
||||||
|
}
|
||||||
|
g.GID = g.groupStatus.Gid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func (g *Group) Read(ctx context.Context) ([]byte, error) {
|
func (g *Group) Read(ctx context.Context) ([]byte, error) {
|
||||||
exErr := g.ReadCommand.Extractor(nil, g)
|
exErr := g.ReadCommand.Extractor(nil, g)
|
||||||
if exErr != nil {
|
if exErr != nil {
|
||||||
g.State = "absent"
|
g.Common.State = "absent"
|
||||||
}
|
}
|
||||||
if yaml, yamlErr := yaml.Marshal(g); yamlErr != nil {
|
if yaml, yamlErr := yaml.Marshal(g); yamlErr != nil {
|
||||||
return yaml, yamlErr
|
return yaml, yamlErr
|
||||||
@ -210,8 +273,9 @@ func (g *Group) Read(ctx context.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Update(ctx context.Context) (error) {
|
func (g *Group) Update(ctx context.Context) (err error) {
|
||||||
return g.Create(ctx)
|
_, err = g.UpdateCommand.Execute(g)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Group) Delete(ctx context.Context) (error) {
|
func (g *Group) Delete(ctx context.Context) (error) {
|
||||||
@ -345,7 +409,7 @@ func NewGroupReadCommand() *command.Command {
|
|||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
g := target.(*Group)
|
g := target.(*Group)
|
||||||
g.State = "absent"
|
g.Common.State = "absent"
|
||||||
var readGroup *user.Group
|
var readGroup *user.Group
|
||||||
var e error
|
var e error
|
||||||
if g.Name != "" {
|
if g.Name != "" {
|
||||||
@ -360,7 +424,7 @@ func NewGroupReadCommand() *command.Command {
|
|||||||
g.Name = readGroup.Name
|
g.Name = readGroup.Name
|
||||||
g.GID = readGroup.Gid
|
g.GID = readGroup.Gid
|
||||||
if g.GID != "" {
|
if g.GID != "" {
|
||||||
g.State = "present"
|
g.Common.State = "present"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
@ -369,7 +433,17 @@ func NewGroupReadCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewGroupUpdateCommand() *command.Command {
|
func NewGroupUpdateCommand() *command.Command {
|
||||||
return nil
|
c := command.NewCommand()
|
||||||
|
c.Path = "addgroup"
|
||||||
|
c.FailOnError = false
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("{{ if .GID }}-g {{ .GID }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ .Name }}"),
|
||||||
|
}
|
||||||
|
c.Extractor = func(out []byte, target any) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroupDelDeleteCommand() *command.Command {
|
func NewGroupDelDeleteCommand() *command.Command {
|
||||||
@ -416,7 +490,7 @@ func NewReadGroupsCommand() *command.Command {
|
|||||||
g := (*Groups)[lineIndex]
|
g := (*Groups)[lineIndex]
|
||||||
g.Name = groupRecord[0]
|
g.Name = groupRecord[0]
|
||||||
g.GID = groupRecord[2]
|
g.GID = groupRecord[2]
|
||||||
g.State = "present"
|
g.Common.State = "present"
|
||||||
g.GroupType = SystemGroupType
|
g.GroupType = SystemGroupType
|
||||||
lineIndex++
|
lineIndex++
|
||||||
}
|
}
|
||||||
|
@ -102,17 +102,26 @@ type HTTP struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP() *HTTP {
|
func NewHTTP() *HTTP {
|
||||||
h := &HTTP{ client: &http.Client{}, Common: &Common{ includeQueryParamsInURI: true, resourceType: HTTPTypeName, SchemeCheck: func(scheme string) bool {
|
h := &HTTP{ client: &http.Client{} }
|
||||||
switch scheme {
|
h.Common = NewCommon(HTTPTypeName, true)
|
||||||
case "http", "https":
|
h.Common.SchemeCheck = h.SchemeCheck
|
||||||
return true
|
h.Common.NormalizePath = h.NormalizePath
|
||||||
}
|
|
||||||
return false
|
|
||||||
} } }
|
|
||||||
slog.Info("NewHTTP()", "http", h)
|
slog.Info("NewHTTP()", "http", h)
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HTTP) SchemeCheck(scheme string) bool {
|
||||||
|
switch scheme {
|
||||||
|
case "http", "https":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTP) NormalizePath() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HTTP) SetResourceMapper(resources data.ResourceMapper) {
|
func (h *HTTP) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
h.Resources = resources
|
h.Resources = resources
|
||||||
}
|
}
|
||||||
@ -266,11 +275,13 @@ func (h *HTTP) ContentSourceRefStat() (info fs.FileInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTP) ReadStat() (err error) {
|
func (h *HTTP) ReadStat() (err error) {
|
||||||
|
|
||||||
if h.reader == nil {
|
if h.reader == nil {
|
||||||
if err = h.OpenGetter(); err != nil {
|
if err = h.OpenGetter(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var info fs.FileInfo
|
var info fs.FileInfo
|
||||||
info, err = h.reader.Stat()
|
info, err = h.reader.Stat()
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ func init() {
|
|||||||
} else {
|
} else {
|
||||||
i.ResourceType = IptableTypeRule
|
i.ResourceType = IptableTypeRule
|
||||||
id, _ := strconv.ParseUint(fields[1], 10, 32)
|
id, _ := strconv.ParseUint(fields[1], 10, 32)
|
||||||
i.Id = uint(id)
|
i.SetId(uint(id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
||||||
@ -72,9 +72,9 @@ var IptableNumber = regexp.MustCompile(`^[0-9]+$`)
|
|||||||
type IptableChain string
|
type IptableChain string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IptableChainInput = "INPUT"
|
IptableChainInput = "INPUT"
|
||||||
IptableChainOutput = "OUTPUT"
|
IptableChainOutput = "OUTPUT"
|
||||||
IptableChainForward = "FORWARD"
|
IptableChainForward = "FORWARD"
|
||||||
IptableChainPreRouting = "PREROUTING"
|
IptableChainPreRouting = "PREROUTING"
|
||||||
IptableChainPostRouting = "POSTROUTING"
|
IptableChainPostRouting = "POSTROUTING"
|
||||||
)
|
)
|
||||||
@ -101,6 +101,8 @@ type ExtensionFlag struct {
|
|||||||
Value string `json:"value" yaml:"value"`
|
Value string `json:"value" yaml:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TargetFlag ExtensionFlag
|
||||||
|
|
||||||
type IptablePort uint16
|
type IptablePort uint16
|
||||||
|
|
||||||
type IptableType string
|
type IptableType string
|
||||||
@ -110,6 +112,8 @@ const (
|
|||||||
IptableTypeChain = "chain"
|
IptableTypeChain = "chain"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type IptableRule string
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidIptableName error = errors.New("The IptableName is not a valid table")
|
ErrInvalidIptableName error = errors.New("The IptableName is not a valid table")
|
||||||
)
|
)
|
||||||
@ -117,32 +121,33 @@ var (
|
|||||||
// Manage the state of iptables rules
|
// Manage the state of iptables rules
|
||||||
// iptable://filter/INPUT/0
|
// iptable://filter/INPUT/0
|
||||||
type Iptable struct {
|
type Iptable struct {
|
||||||
*Common `json:",inline" yaml:",inline"`
|
*Common `json:",inline" yaml:",inline"`
|
||||||
stater machine.Stater `json:"-" yaml:"-"`
|
stater machine.Stater `json:"-" yaml:"-"`
|
||||||
parsedURI *url.URL `json:"-" yaml:"-"`
|
Id uint `json:"id,omitempty" yaml:"id,omitempty"`
|
||||||
Id uint `json:"id,omitempty" yaml:"id,omitempty"`
|
Table IptableName `json:"table" yaml:"table"`
|
||||||
Table IptableName `json:"table" yaml:"table"`
|
Chain IptableChain `json:"chain" yaml:"chain"`
|
||||||
Chain IptableChain `json:"chain" yaml:"chain"`
|
Destination IptableCIDR `json:"destination,omitempty" yaml:"destination,omitempty"`
|
||||||
Destination IptableCIDR `json:"destination,omitempty" yaml:"destination,omitempty"`
|
Source IptableCIDR `json:"source,omitempty" yaml:"source,omitempty"`
|
||||||
Source IptableCIDR `json:"source,omitempty" yaml:"source,omitempty"`
|
Dport IptablePort `json:"dport,omitempty" yaml:"dport,omitempty"`
|
||||||
Dport IptablePort `json:"dport,omitempty" yaml:"dport,omitempty"`
|
Sport IptablePort `json:"sport,omitempty" yaml:"sport,omitempty"`
|
||||||
Sport IptablePort `json:"sport,omitempty" yaml:"sport,omitempty"`
|
In string `json:"in,omitempty" yaml:"in,omitempty"`
|
||||||
In string `json:"in,omitempty" yaml:"in,omitempty"`
|
Out string `json:"out,omitempty" yaml:"out,omitempty"`
|
||||||
Out string `json:"out,omitempty" yaml:"out,omitempty"`
|
Match []string `json:"match,omitempty" yaml:"match,omitempty"`
|
||||||
Match []string `json:"match,omitempty" yaml:"match,omitempty"`
|
Flags []ExtensionFlag `json:"extension_flags,omitempty" yaml:"extension_flags,omitempty"`
|
||||||
Flags []ExtensionFlag `json:"extension_flags,omitempty" yaml:"extension_flags,omitempty"`
|
Proto IptableProto `json:"proto,omitempty" yaml:"proto,omitempty"`
|
||||||
Proto IptableProto `json:"proto,omitempty" yaml:"proto,omitempty"`
|
Jump string `json:"jump,omitempty" yaml:"jump,omitempty"`
|
||||||
Jump string `json:"jump,omitempty" yaml:"jump,omitempty"`
|
TargetFlags []TargetFlag `json:"target_flags,omitempty" yaml:"target_flags,omitempty"`
|
||||||
ChainLength uint `json:"-" yaml:"-"`
|
ChainLength uint `json:"-" yaml:"-"`
|
||||||
|
|
||||||
ResourceType IptableType `json:"resourcetype,omitempty" yaml:"resourcetype,omitempty"`
|
ResourceType IptableType `json:"resourcetype,omitempty" yaml:"resourcetype,omitempty"`
|
||||||
CreateCommand *command.Command `yaml:"-" json:"-"`
|
CreateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
ReadCommand *command.Command `yaml:"-" json:"-"`
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
ReadChainCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||||
|
|
||||||
config data.ConfigurationValueGetter
|
config data.ConfigurationValueGetter
|
||||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -155,10 +160,13 @@ func (n IptableName) Validate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIptable() *Iptable {
|
func NewIptable() (i *Iptable) {
|
||||||
i := &Iptable{ ResourceType: IptableTypeRule, Common: &Common{ resourceType: IptableTypeName } }
|
i = &Iptable{ ResourceType: IptableTypeRule }
|
||||||
|
i.Common = NewCommon(IptableTypeName, false)
|
||||||
|
i.Common.NormalizePath = i.NormalizePath
|
||||||
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
||||||
return i
|
i.ReadChainCommand = NewIptableChainReadCommand()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) {
|
func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
@ -195,51 +203,126 @@ func (i *Iptable) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
case "start_create":
|
case "start_stat":
|
||||||
if e := i.Create(ctx); e == nil {
|
if statErr := i.ReadStat(ctx); statErr == nil {
|
||||||
if triggerErr := i.stater.Trigger("created"); triggerErr == nil {
|
if triggerErr := i.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := i.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i.Common.State = "absent"
|
case "start_read":
|
||||||
case "present":
|
if _,readErr := i.Read(ctx); readErr == nil {
|
||||||
|
if triggerErr := i.stater.Trigger("state_read"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(readErr)
|
||||||
|
}
|
||||||
|
case "start_create":
|
||||||
|
if createErr := i.Create(ctx); createErr == nil {
|
||||||
|
if triggerErr := i.stater.Trigger("created"); triggerErr == nil {
|
||||||
|
slog.Info("ContainerImage.Notify()", "created", i, "error", triggerErr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
slog.Info("ContainerImage.Notify()", "created", i, "error", triggerErr)
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(createErr)
|
||||||
|
}
|
||||||
|
case "start_update":
|
||||||
|
if createErr := i.Update(ctx); createErr == nil {
|
||||||
|
if triggerErr := i.stater.Trigger("updated"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i.Common.State = "absent"
|
||||||
|
panic(createErr)
|
||||||
|
}
|
||||||
|
case "start_delete":
|
||||||
|
if deleteErr := i.Delete(ctx); deleteErr == nil {
|
||||||
|
if triggerErr := i.stater.Trigger("deleted"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(deleteErr)
|
||||||
|
}
|
||||||
|
case "present", "created", "read":
|
||||||
i.Common.State = "present"
|
i.Common.State = "present"
|
||||||
|
case "absent":
|
||||||
|
i.Common.State = "absent"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the chain ID and update the mapped URI
|
||||||
|
func (i *Iptable) SetId(id uint) {
|
||||||
|
if i.Id != id {
|
||||||
|
uri := i.URI()
|
||||||
|
i.Id = id
|
||||||
|
decl, ok := i.Resources.Get(uri)
|
||||||
|
if ok {
|
||||||
|
i.Resources.Delete(uri)
|
||||||
|
}
|
||||||
|
i.Resources.Set(i.URI(), decl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Iptable) URI() string {
|
func (i *Iptable) URI() string {
|
||||||
return fmt.Sprintf("iptable://%s/%s/%d", i.Table, i.Chain, i.Id)
|
return fmt.Sprintf("iptable://%s/%s/%d", i.Table, i.Chain, i.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) SetURI(uri string) (err error) {
|
|
||||||
i.parsedURI, err = url.Parse(uri)
|
func (i *Iptable) SetParsedURI(uri *url.URL) (err error) {
|
||||||
if err == nil {
|
if err = i.Common.SetParsedURI(uri); err == nil {
|
||||||
fields := strings.FieldsFunc(i.parsedURI.Path, func(c rune) bool { return c == '/' })
|
i.setFieldsFromPath()
|
||||||
fieldsLen := len(fields)
|
|
||||||
if i.parsedURI.Scheme == "iptable" && fieldsLen > 0 {
|
|
||||||
i.Table = IptableName(i.parsedURI.Hostname())
|
|
||||||
if err = i.Table.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.Chain = IptableChain(fields[0])
|
|
||||||
if fieldsLen < 2 {
|
|
||||||
i.ResourceType = IptableTypeChain
|
|
||||||
} else {
|
|
||||||
i.ResourceType = IptableTypeRule
|
|
||||||
id, _ := strconv.ParseUint(fields[1], 10, 32)
|
|
||||||
i.Id = uint(id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, uri)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) UseConfig(config data.ConfigurationValueGetter) {
|
func (i *Iptable) NormalizePath() error {
|
||||||
i.config = config
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) setFieldsFromPath() (err error) {
|
||||||
|
fields := strings.FieldsFunc(i.Common.Path, func(c rune) bool { return c == '/' })
|
||||||
|
fieldsLen := len(fields)
|
||||||
|
if fieldsLen > 0 {
|
||||||
|
i.Table = IptableName(fields[0])
|
||||||
|
if err = i.Table.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Chain = IptableChain(fields[1])
|
||||||
|
if fieldsLen < 3 {
|
||||||
|
i.ResourceType = IptableTypeChain
|
||||||
|
} else {
|
||||||
|
i.ResourceType = IptableTypeRule
|
||||||
|
id, _ := strconv.ParseUint(fields[1], 10, 32)
|
||||||
|
i.Id = uint(id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, i.Common.Uri)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) SetURI(uri string) (err error) {
|
||||||
|
if err = i.Common.SetURI(uri); err == nil {
|
||||||
|
err = i.setFieldsFromPath()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Validate() error {
|
func (i *Iptable) Validate() error {
|
||||||
@ -316,6 +399,46 @@ func (i *Iptable) ResolveId(ctx context.Context) string {
|
|||||||
return fmt.Sprintf("%d", i.Id)
|
return fmt.Sprintf("%d", i.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *ExtensionFlag) Match(name string, value string) bool {
|
||||||
|
start := 0
|
||||||
|
if name[1] == '-' {
|
||||||
|
start = 2
|
||||||
|
} else if name[0] == '-' {
|
||||||
|
start = 1
|
||||||
|
}
|
||||||
|
return f.Name == name[start:] && f.Value == value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) HasExtensionFlag(name string) bool {
|
||||||
|
optName := strings.Trim(name, "-")
|
||||||
|
for _, ext := range i.Flags {
|
||||||
|
if ext.Name == optName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) HasTargetFlag(name string) bool {
|
||||||
|
optName := strings.Trim(name, "-")
|
||||||
|
for _, ext := range i.TargetFlags {
|
||||||
|
if ext.Name == optName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TargetFlag) Match(name string, value string) bool {
|
||||||
|
start := 0
|
||||||
|
if name[1] == '-' {
|
||||||
|
start = 2
|
||||||
|
} else if name[0] == '-' {
|
||||||
|
start = 1
|
||||||
|
}
|
||||||
|
return f.Name == name[start:] && f.Value == value
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Iptable) SetFlagValue(opt, value string) bool {
|
func (i *Iptable) SetFlagValue(opt, value string) bool {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "-i":
|
case "-i":
|
||||||
@ -353,10 +476,14 @@ func (i *Iptable) SetFlagValue(opt, value string) bool {
|
|||||||
i.Sport = IptablePort(port)
|
i.Sport = IptablePort(port)
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
if opt[0] == '-' {
|
if opt[0] == '-' {
|
||||||
i.Flags = append(i.Flags, ExtensionFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(value)})
|
if len(i.Jump) > 0 {
|
||||||
|
i.TargetFlags = append(i.TargetFlags, TargetFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(value)})
|
||||||
|
} else {
|
||||||
|
i.Flags = append(i.Flags, ExtensionFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(value)})
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -382,9 +509,15 @@ func (i *Iptable) GetFlagValue(opt string) any {
|
|||||||
case "--sport":
|
case "--sport":
|
||||||
return strconv.Itoa(int(i.Sport))
|
return strconv.Itoa(int(i.Sport))
|
||||||
default:
|
default:
|
||||||
if opt[0] == '-' {
|
if opt[0] == '-' {
|
||||||
return i.Flags
|
slog.Info("Iptable.GetFlagValue()", "opt", opt, "iptable", i)
|
||||||
}
|
if i.HasExtensionFlag(opt) {
|
||||||
|
return i.Flags
|
||||||
|
}
|
||||||
|
if i.HasTargetFlag(opt) {
|
||||||
|
return i.TargetFlags
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -407,9 +540,11 @@ func (i *Iptable) SetRule(flags []string) (assigned bool) {
|
|||||||
|
|
||||||
func (i *Iptable) MatchRule(flags []string) (match bool) {
|
func (i *Iptable) MatchRule(flags []string) (match bool) {
|
||||||
match = true
|
match = true
|
||||||
|
next:
|
||||||
for index, flag := range flags {
|
for index, flag := range flags {
|
||||||
if flag[0] == '-' {
|
if flag[0] == '-' {
|
||||||
value := flags[index + 1]
|
value := flags[index + 1]
|
||||||
|
slog.Info("Iptable.MatchRule()", "flag", flag, "value", value)
|
||||||
switch v := i.GetFlagValue(flag).(type) {
|
switch v := i.GetFlagValue(flag).(type) {
|
||||||
case []string:
|
case []string:
|
||||||
for _,element := range v {
|
for _,element := range v {
|
||||||
@ -417,48 +552,57 @@ func (i *Iptable) MatchRule(flags []string) (match bool) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
case []ExtensionFlag:
|
case []ExtensionFlag:
|
||||||
for _,element := range v {
|
for _,element := range v {
|
||||||
if element.Name == flag && element.Value == value {
|
if element.Match(flag, value) {
|
||||||
continue
|
continue next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
|
match = false
|
||||||
|
case []TargetFlag:
|
||||||
|
for _,element := range v {
|
||||||
|
if element.Match(flag, value) {
|
||||||
|
continue next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
case IptableCIDR:
|
case IptableCIDR:
|
||||||
if v == IptableCIDR(value) {
|
if v == IptableCIDR(value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
case IptableName:
|
case IptableName:
|
||||||
if v == IptableName(value) {
|
if v == IptableName(value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
case IptableChain:
|
case IptableChain:
|
||||||
if v == IptableChain(value) {
|
if v == IptableChain(value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
default:
|
default:
|
||||||
if v.(string) == value {
|
if v.(string) == value {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v)
|
||||||
match = false
|
match = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slog.Info("Iptable.MatchRule()", "flags", flags, "match", match)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) ReadChainLength() error {
|
func (i *Iptable) ReadChainLength() error {
|
||||||
c := command.NewCommand()
|
output,err := i.ReadChainCommand.Execute(i)
|
||||||
c.Path = "iptables"
|
|
||||||
c.Args = []command.CommandArg{
|
|
||||||
command.CommandArg("-S"),
|
|
||||||
command.CommandArg("{{ .Chain }}"),
|
|
||||||
}
|
|
||||||
output,err := c.Execute(i)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
linesCount := strings.Count(string(output), "\n")
|
linesCount := strings.Count(string(output), "\n")
|
||||||
if linesCount > 0 {
|
if linesCount > 0 {
|
||||||
@ -470,13 +614,16 @@ func (i *Iptable) ReadChainLength() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Create(ctx context.Context) error {
|
func (i *Iptable) Create(ctx context.Context) (err error) {
|
||||||
|
slog.Info("Iptable.Create()", "iptable", i)
|
||||||
if i.Id > 0 {
|
if i.Id > 0 {
|
||||||
if lenErr := i.ReadChainLength(); lenErr != nil {
|
if lenErr := i.ReadChainLength(); lenErr != nil {
|
||||||
return lenErr
|
return lenErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := i.CreateCommand.Execute(i)
|
|
||||||
|
_, err = i.CreateCommand.Execute(i)
|
||||||
|
slog.Info("Iptable.Create()", "err", err, "iptable", i, "createcommand", i.CreateCommand)
|
||||||
//slog.Info("IptableChain Create()", "err", err, "errstr", err.Error(), "iptable", i, "createcommand", i.CreateCommand)
|
//slog.Info("IptableChain Create()", "err", err, "errstr", err.Error(), "iptable", i, "createcommand", i.CreateCommand)
|
||||||
// TODO add Command status/error handler rather than using the read extractor
|
// TODO add Command status/error handler rather than using the read extractor
|
||||||
if i.CreateCommand.Extractor != nil {
|
if i.CreateCommand.Extractor != nil {
|
||||||
@ -484,7 +631,20 @@ func (i *Iptable) Create(ctx context.Context) error {
|
|||||||
return i.CreateCommand.Extractor([]byte(err.Error()), i)
|
return i.CreateCommand.Extractor([]byte(err.Error()), i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Iptable) ReadStat(ctx context.Context) (err error) {
|
||||||
|
if i.ReadCommand.Exists() {
|
||||||
|
var out []byte
|
||||||
|
if out, err = i.ReadCommand.Execute(i); err == nil {
|
||||||
|
err = i.ReadCommand.Extractor(out, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i.Id == 0 {
|
||||||
|
return ErrResourceStateAbsent
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Read(ctx context.Context) ([]byte, error) {
|
func (i *Iptable) Read(ctx context.Context) ([]byte, error) {
|
||||||
@ -503,8 +663,15 @@ func (i *Iptable) Update(ctx context.Context) error {
|
|||||||
return i.Create(ctx)
|
return i.Create(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Delete(ctx context.Context) error {
|
func (i *Iptable) Delete(ctx context.Context) (err error) {
|
||||||
return nil
|
if i.Id < 1 {
|
||||||
|
return fmt.Errorf("Failed to find rule to delete")
|
||||||
|
}
|
||||||
|
var out []byte
|
||||||
|
if out, err = i.DeleteCommand.Execute(i); err == nil {
|
||||||
|
err = i.DeleteCommand.Extractor(out, i)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iptable) Type() string { return "iptable" }
|
func (i *Iptable) Type() string { return "iptable" }
|
||||||
@ -552,9 +719,7 @@ func NewIptableCreateCommand() *command.Command {
|
|||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
command.CommandArg("-t"),
|
command.CommandArg("-t"),
|
||||||
command.CommandArg("{{ .Table }}"),
|
command.CommandArg("{{ .Table }}"),
|
||||||
command.CommandArg("{{ if le .Id .ChainLength }}-R{{ else }}-A{{ end }}"),
|
command.CommandArg("{{ if and (le .Id .ChainLength) (gt .Id 0) }}-R {{ .Chain }} {{ .Id }}{{ else }}-A {{ .Chain }}{{ end }}"),
|
||||||
command.CommandArg("{{ .Chain }}"),
|
|
||||||
command.CommandArg("{{ if le .Id .ChainLength }}{{ .Id }}{{ end }}"),
|
|
||||||
command.CommandArg("{{ if .In }}-i {{ .In }}{{ else if .Out }}-o {{ .Out }}{{ end }}"),
|
command.CommandArg("{{ if .In }}-i {{ .In }}{{ else if .Out }}-o {{ .Out }}{{ end }}"),
|
||||||
command.CommandArg("{{ range .Match }}-m {{ . }} {{- end }}"),
|
command.CommandArg("{{ range .Match }}-m {{ . }} {{- end }}"),
|
||||||
command.CommandArg("{{ if .Source }}-s {{ .Source }}{{ end }}"),
|
command.CommandArg("{{ if .Source }}-s {{ .Source }}{{ end }}"),
|
||||||
@ -562,8 +727,9 @@ func NewIptableCreateCommand() *command.Command {
|
|||||||
command.CommandArg("{{ if .Destination }}-d {{ .Destination }}{{ end }}"),
|
command.CommandArg("{{ if .Destination }}-d {{ .Destination }}{{ end }}"),
|
||||||
command.CommandArg("{{ if .Dport }}--dport {{ .Dport }}{{ end }}"),
|
command.CommandArg("{{ if .Dport }}--dport {{ .Dport }}{{ end }}"),
|
||||||
command.CommandArg("{{ if .Proto }}-p {{ .Proto }}{{ end }}"),
|
command.CommandArg("{{ if .Proto }}-p {{ .Proto }}{{ end }}"),
|
||||||
command.CommandArg("{{ range .Flags }} --{{ .Name }} {{ .Value }} {{ end }}"),
|
command.CommandArg("{{ range .Flags -}} --{{ .Name }} {{ .Value }} {{- end }}"),
|
||||||
command.CommandArg("{{ if .Jump }}-j {{ .Jump }}{{ end }}"),
|
command.CommandArg("{{ if .Jump }}-j {{ .Jump }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ range .TargetFlags -}} --{{ .Name }} {{ .Value }}{{- end }}"),
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@ -584,7 +750,7 @@ func IptableExtractRule(lineNumber uint, ruleLine string, target *Iptable) (stat
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if target.MatchRule(flags) {
|
if target.MatchRule(flags) {
|
||||||
target.Id = lineNumber
|
target.SetId(lineNumber)
|
||||||
state = "present"
|
state = "present"
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@ -661,7 +827,7 @@ func NewIptableReadChainCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
for lineIndex, line := range lines[1:] {
|
for lineIndex, line := range lines[1:] {
|
||||||
i := (*IptableChainRules)[lineIndex]
|
i := (*IptableChainRules)[lineIndex]
|
||||||
i.Id = uint(lineIndex + 1)
|
i.SetId(uint(lineIndex + 1))
|
||||||
ruleFields := strings.Split(strings.TrimSpace(line), " ")
|
ruleFields := strings.Split(strings.TrimSpace(line), " ")
|
||||||
if ruleFields[0] == "-A" {
|
if ruleFields[0] == "-A" {
|
||||||
flags := ruleFields[2:]
|
flags := ruleFields[2:]
|
||||||
@ -682,7 +848,16 @@ func NewIptableUpdateCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewIptableDeleteCommand() *command.Command {
|
func NewIptableDeleteCommand() *command.Command {
|
||||||
return nil
|
c := command.NewCommand()
|
||||||
|
c.Path = "iptables"
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("-t"),
|
||||||
|
command.CommandArg("{{ .Table }}"),
|
||||||
|
command.CommandArg("-D"),
|
||||||
|
command.CommandArg("{{ .Chain }}"),
|
||||||
|
command.CommandArg("{{ .Id }}"),
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIptableChainCreateCommand() *command.Command {
|
func NewIptableChainCreateCommand() *command.Command {
|
||||||
@ -766,7 +941,7 @@ func RuleExtractorMatchFlags(out []byte, target any) (err error) {
|
|||||||
slog.Info("RuleExtractorMatchFlags()", "flags", flags, "ipt", ipt)
|
slog.Info("RuleExtractorMatchFlags()", "flags", flags, "ipt", ipt)
|
||||||
err = nil
|
err = nil
|
||||||
ipt.Common.State = "present"
|
ipt.Common.State = "present"
|
||||||
ipt.Id = uint(linesIndex)
|
ipt.SetId(uint(linesIndex))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -838,6 +1013,14 @@ func NewIptableChainUpdateCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewIptableChainDeleteCommand() *command.Command {
|
func NewIptableChainDeleteCommand() *command.Command {
|
||||||
return nil
|
c := command.NewCommand()
|
||||||
|
c.Path = "iptables"
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("-t"),
|
||||||
|
command.CommandArg("{{ .Table }}"),
|
||||||
|
command.CommandArg("-X"),
|
||||||
|
command.CommandArg("{{ .Chain }}"),
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ _ "syscall"
|
|||||||
"testing"
|
"testing"
|
||||||
_ "time"
|
_ "time"
|
||||||
"decl/internal/command"
|
"decl/internal/command"
|
||||||
|
"decl/internal/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewIptableResource(t *testing.T) {
|
func TestNewIptableResource(t *testing.T) {
|
||||||
@ -77,10 +78,59 @@ func TestReadIptable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateIptable(t *testing.T) {
|
func TestCreateIptable(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
testRule := NewIptable()
|
testRule := NewIptable()
|
||||||
assert.NotNil(t, testRule)
|
assert.NotNil(t, testRule)
|
||||||
|
|
||||||
|
declarationAttributes := `
|
||||||
|
table: "filter"
|
||||||
|
id: 5
|
||||||
|
chain: "INPUT"
|
||||||
|
source: "192.168.0.0/24"
|
||||||
|
destination: "192.168.0.1"
|
||||||
|
jump: "ACCEPT"
|
||||||
|
state: present
|
||||||
|
`
|
||||||
|
|
||||||
|
m := &MockCommand{
|
||||||
|
Executor: func(value any) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
Extractor: func(output []byte, target any) error {
|
||||||
|
testRule.Table = "filter"
|
||||||
|
testRule.Id = 3
|
||||||
|
testRule.Chain = "INPUT"
|
||||||
|
testRule.In = "eth0"
|
||||||
|
testRule.Source = "192.168.0.0/24"
|
||||||
|
testRule.State = "present"
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockReadChain := &MockCommand{
|
||||||
|
Executor: func(value any) ([]byte, error) {
|
||||||
|
return []byte(`
|
||||||
|
-P INPUT ACCEPT
|
||||||
|
-A INPUT -j LIBVIRT_INP
|
||||||
|
`), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
e := testRule.LoadDecl(declarationAttributes)
|
||||||
|
assert.Nil(t, e)
|
||||||
|
|
||||||
|
testRule.ReadChainCommand = (*command.Command)(mockReadChain)
|
||||||
|
testRule.ReadCommand = (*command.Command)(m)
|
||||||
|
testRule.CreateCommand = (*command.Command)(m)
|
||||||
|
|
||||||
|
assert.Nil(t, testRule.Create(ctx))
|
||||||
|
|
||||||
|
assert.Equal(t, uint(2), testRule.ChainLength)
|
||||||
|
_, err := testRule.Read(ctx)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
//assert.Equal(t, uint(3), testRule.ChainLength)
|
||||||
|
assert.Equal(t, uint(3), testRule.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIptableSetFlagValue(t *testing.T) {
|
func TestIptableSetFlagValue(t *testing.T) {
|
||||||
@ -117,6 +167,7 @@ func TestIptableRuleExtractorById(t *testing.T) {
|
|||||||
|
|
||||||
func TestIptableRuleExtractorByFlags(t *testing.T) {
|
func TestIptableRuleExtractorByFlags(t *testing.T) {
|
||||||
ipt := NewIptable()
|
ipt := NewIptable()
|
||||||
|
ipt.Resources = data.NewResourceMapper()
|
||||||
assert.NotNil(t, ipt)
|
assert.NotNil(t, ipt)
|
||||||
ipt.Table = IptableName("filter")
|
ipt.Table = IptableName("filter")
|
||||||
ipt.Chain = IptableChain("FOO")
|
ipt.Chain = IptableChain("FOO")
|
||||||
|
@ -136,11 +136,17 @@ type NetworkRoute struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworkRoute() *NetworkRoute {
|
func NewNetworkRoute() *NetworkRoute {
|
||||||
n := &NetworkRoute{Rtid: NetworkRouteTableMain, Common: &Common{ resourceType: NetworkRouteTypeName } }
|
n := &NetworkRoute{Rtid: NetworkRouteTableMain}
|
||||||
|
n.Common = NewCommon(NetworkRouteTypeName, false)
|
||||||
|
n.Common.NormalizePath = n.NormalizePath
|
||||||
n.CreateCommand, n.ReadCommand, n.UpdateCommand, n.DeleteCommand = n.NewCRUD()
|
n.CreateCommand, n.ReadCommand, n.UpdateCommand, n.DeleteCommand = n.NewCRUD()
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NetworkRoute) NormalizePath() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) {
|
func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
n.Resources = resources
|
n.Resources = resources
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,16 @@ func (k *PKI) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
|
case "start_stat":
|
||||||
|
if statErr := k.ReadStat(); statErr == nil {
|
||||||
|
if triggerErr := k.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := k.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
case "start_read":
|
case "start_read":
|
||||||
if _,readErr := k.Read(ctx); readErr == nil {
|
if _,readErr := k.Read(ctx); readErr == nil {
|
||||||
if triggerErr := k.StateMachine().Trigger("state_read"); triggerErr == nil {
|
if triggerErr := k.StateMachine().Trigger("state_read"); triggerErr == nil {
|
||||||
@ -228,6 +238,27 @@ func (k *PKI) LoadDecl(yamlResourceDeclaration string) error {
|
|||||||
return k.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
return k.LoadString(yamlResourceDeclaration, codec.FormatYaml)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *PKI) ReadStat() (err error) {
|
||||||
|
var resourcesErr []string
|
||||||
|
if ! k.PrivateKeyRef.Exists() {
|
||||||
|
resourcesErr = append(resourcesErr, string(k.PrivateKeyRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! k.PublicKeyRef.Exists() {
|
||||||
|
resourcesErr = append(resourcesErr, string(k.PublicKeyRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! k.CertificateRef.Exists() {
|
||||||
|
resourcesErr = append(resourcesErr, string(k.CertificateRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resourcesErr) > 0 {
|
||||||
|
err = fmt.Errorf("PKI resources missing: %s", strings.Join(resourcesErr, ","))
|
||||||
|
// k.State = "absent"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (k *PKI) ResolveId(ctx context.Context) string {
|
func (k *PKI) ResolveId(ctx context.Context) string {
|
||||||
return string(k.PrivateKeyRef)
|
return string(k.PrivateKeyRef)
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,9 @@ func (rm TestResourceMapper) Has(key string) (ok bool) {
|
|||||||
func (rm TestResourceMapper) Set(key string, value data.Declaration) {
|
func (rm TestResourceMapper) Set(key string, value data.Declaration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rm TestResourceMapper) Delete(key string) {
|
||||||
|
}
|
||||||
|
|
||||||
type StringContentReadWriter func() (any, error)
|
type StringContentReadWriter func() (any, error)
|
||||||
|
|
||||||
func (s StringContentReadWriter) ContentWriterStream() (*transport.Writer, error) {
|
func (s StringContentReadWriter) ContentWriterStream() (*transport.Writer, error) {
|
||||||
|
@ -6,7 +6,7 @@ package resource
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "log/slog"
|
"log/slog"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"io"
|
"io"
|
||||||
@ -14,8 +14,14 @@ _ "log/slog"
|
|||||||
"gitea.rosskeen.house/rosskeen.house/machine"
|
"gitea.rosskeen.house/rosskeen.house/machine"
|
||||||
"decl/internal/codec"
|
"decl/internal/codec"
|
||||||
"decl/internal/data"
|
"decl/internal/data"
|
||||||
|
"decl/internal/command"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnsupportedServiceManagerType error = errors.New("Unsupported service manager")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -29,16 +35,28 @@ const (
|
|||||||
ServiceManagerTypeSysV ServiceManagerType = "sysv"
|
ServiceManagerTypeSysV ServiceManagerType = "sysv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SysVStatusRunning int = 0
|
||||||
|
SysVStatusStopped int = 1
|
||||||
|
SysVStatusUnknown int = 2
|
||||||
|
SysVStatusMissing int = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SupportedServiceManagerTypes []ServiceManagerType = []ServiceManagerType{ServiceManagerTypeSystemd, ServiceManagerTypeSysV}
|
||||||
|
SystemServiceManagerType ServiceManagerType = FindSystemServiceManagerType()
|
||||||
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
*Common `yaml:",inline" json:",inline"`
|
*Common `yaml:",inline" json:",inline"`
|
||||||
stater machine.Stater `yaml:"-" json:"-"`
|
stater machine.Stater `yaml:"-" json:"-"`
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
ServiceManagerType ServiceManagerType `json:"servicemanager,omitempty" yaml:"servicemanager,omitempty"`
|
ServiceManagerType ServiceManagerType `json:"servicemanager,omitempty" yaml:"servicemanager,omitempty"`
|
||||||
|
|
||||||
CreateCommand *Command `yaml:"-" json:"-"`
|
CreateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
ReadCommand *Command `yaml:"-" json:"-"`
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
||||||
UpdateCommand *Command `yaml:"-" json:"-"`
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
||||||
DeleteCommand *Command `yaml:"-" json:"-"`
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
||||||
|
|
||||||
config data.ConfigurationValueGetter
|
config data.ConfigurationValueGetter
|
||||||
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
Resources data.ResourceMapper `yaml:"-" json:"-"`
|
||||||
@ -53,8 +71,25 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService() *Service {
|
func FindSystemServiceManagerType() ServiceManagerType {
|
||||||
return &Service{ ServiceManagerType: ServiceManagerTypeSystemd, Common: &Common{ resourceType: ServiceTypeName } }
|
for _, servicemanagerType := range SupportedServiceManagerTypes {
|
||||||
|
if c := servicemanagerType.NewReadCommand(); c != nil && c.Exists() {
|
||||||
|
return servicemanagerType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ServiceManagerTypeSystemd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService() (s *Service) {
|
||||||
|
s = &Service{ ServiceManagerType: SystemServiceManagerType }
|
||||||
|
s.Common = NewCommon(ServiceTypeName, false)
|
||||||
|
s.Common.NormalizePath = s.NormalizePath
|
||||||
|
s.CreateCommand, s.ReadCommand, s.UpdateCommand, s.DeleteCommand = s.ServiceManagerType.NewCRUD()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) NormalizePath() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) StateMachine() machine.Stater {
|
func (s *Service) StateMachine() machine.Stater {
|
||||||
@ -69,6 +104,16 @@ func (s *Service) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
|
case "start_stat":
|
||||||
|
if statErr := s.ReadStat(); statErr == nil {
|
||||||
|
if triggerErr := s.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := s.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
case "start_create":
|
case "start_create":
|
||||||
if e := s.Create(ctx); e == nil {
|
if e := s.Create(ctx); e == nil {
|
||||||
if triggerErr := s.stater.Trigger("created"); triggerErr == nil {
|
if triggerErr := s.stater.Trigger("created"); triggerErr == nil {
|
||||||
@ -76,10 +121,24 @@ func (s *Service) Notify(m *machine.EventMessage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.Common.State = "absent"
|
s.Common.State = "absent"
|
||||||
case "created":
|
case "start_read":
|
||||||
|
if _,readErr := s.Read(ctx); readErr == nil {
|
||||||
|
if triggerErr := s.stater.Trigger("state_read"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
s.Common.State = "absent"
|
||||||
|
panic(triggerErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.Common.State = "absent"
|
||||||
|
panic(readErr)
|
||||||
|
}
|
||||||
|
case "present", "created", "read":
|
||||||
s.Common.State = "present"
|
s.Common.State = "present"
|
||||||
case "running":
|
case "running":
|
||||||
s.Common.State = "running"
|
s.Common.State = "running"
|
||||||
|
case "absent":
|
||||||
|
s.Common.State = "absent"
|
||||||
}
|
}
|
||||||
case machine.EXITSTATEEVENT:
|
case machine.EXITSTATEEVENT:
|
||||||
}
|
}
|
||||||
@ -89,20 +148,27 @@ func (s *Service) URI() string {
|
|||||||
return fmt.Sprintf("service://%s", s.Name)
|
return fmt.Sprintf("service://%s", s.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SetURI(uri string) error {
|
func (s *Service) SetParsedURI(uri *url.URL) (err error) {
|
||||||
resourceUri, e := url.Parse(uri)
|
if err = s.Common.SetParsedURI(uri); err == nil {
|
||||||
if e == nil {
|
err = s.setFieldsFromPath()
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) UseConfig(config data.ConfigurationValueGetter) {
|
func (s *Service) setFieldsFromPath() (err error) {
|
||||||
s.config = config
|
if len(s.Common.Path) > 0 {
|
||||||
|
s.Name = s.Common.Path
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, s.Common.Uri)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SetURI(uri string) (err error) {
|
||||||
|
if err = s.Common.SetURI(uri); err == nil {
|
||||||
|
err = s.setFieldsFromPath()
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) JSON() ([]byte, error) {
|
func (s *Service) JSON() ([]byte, error) {
|
||||||
@ -167,7 +233,7 @@ func (s *Service) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServiceManagerType) NewCRUD() (create *Command, read *Command, update *Command, del *Command) {
|
func (s *ServiceManagerType) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
|
||||||
switch *s {
|
switch *s {
|
||||||
case ServiceManagerTypeSystemd:
|
case ServiceManagerTypeSystemd:
|
||||||
return NewSystemdCreateCommand(), NewSystemdReadCommand(), NewSystemdUpdateCommand(), NewSystemdDeleteCommand()
|
return NewSystemdCreateCommand(), NewSystemdReadCommand(), NewSystemdUpdateCommand(), NewSystemdDeleteCommand()
|
||||||
@ -178,14 +244,52 @@ func (s *ServiceManagerType) NewCRUD() (create *Command, read *Command, update *
|
|||||||
return nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServiceManagerType) NewReadCommand() (read *command.Command) {
|
||||||
func (s *Service) Create(ctx context.Context) error {
|
switch *s {
|
||||||
|
case ServiceManagerTypeSystemd:
|
||||||
|
return NewSystemdReadCommand()
|
||||||
|
case ServiceManagerTypeSysV:
|
||||||
|
return NewSysVReadCommand()
|
||||||
|
default:
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Read(ctx context.Context) ([]byte, error) {
|
func (s *Service) Create(ctx context.Context) (err error) {
|
||||||
|
var out []byte
|
||||||
|
out, err = s.CreateCommand.Execute(s)
|
||||||
|
slog.Info("Service.Create()", "out", out, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return yaml.Marshal(s)
|
func (s *Service) ReadStat() (err error) {
|
||||||
|
if s.ReadCommand.Exists() {
|
||||||
|
_, err = s.ReadCommand.Execute(s)
|
||||||
|
} else {
|
||||||
|
err = ErrUnsupportedServiceManagerType
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Read(ctx context.Context) (resourceYaml []byte, err error) {
|
||||||
|
if s.ReadCommand.Exists() {
|
||||||
|
var out []byte
|
||||||
|
out, err = s.ReadCommand.Execute(s)
|
||||||
|
if err == nil {
|
||||||
|
err = s.ReadCommand.Extractor(out, s)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%w - %w", ErrResourceStateAbsent, err)
|
||||||
|
}
|
||||||
|
slog.Info("Service.Read()", "service", s, "error", err)
|
||||||
|
} else {
|
||||||
|
err = ErrUnsupportedServiceManagerType
|
||||||
|
}
|
||||||
|
var yamlErr error
|
||||||
|
resourceYaml, yamlErr = yaml.Marshal(s)
|
||||||
|
if err == nil {
|
||||||
|
err = yamlErr
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Update(ctx context.Context) error {
|
func (s *Service) Update(ctx context.Context) error {
|
||||||
@ -202,22 +306,22 @@ func (s *Service) ResolveId(ctx context.Context) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemdCreateCommand() *Command {
|
func NewSystemdCreateCommand() *command.Command {
|
||||||
c := NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "systemctl"
|
c.Path = "systemctl"
|
||||||
c.Args = []CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
CommandArg("enable"),
|
command.CommandArg("enable"),
|
||||||
CommandArg("{{ .Name }}"),
|
command.CommandArg("{{ .Name }}"),
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemdReadCommand() *Command {
|
func NewSystemdReadCommand() *command.Command {
|
||||||
c := NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "systemctl"
|
c.Path = "systemctl"
|
||||||
c.Args = []CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
CommandArg("show"),
|
command.CommandArg("show"),
|
||||||
CommandArg("{{ .Name }}"),
|
command.CommandArg("{{ .Name }}"),
|
||||||
}
|
}
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
s := target.(*Service)
|
s := target.(*Service)
|
||||||
@ -253,26 +357,63 @@ func NewSystemdReadCommand() *Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemdUpdateCommand() *Command {
|
func NewSystemdUpdateCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemdDeleteCommand() *Command {
|
func NewSystemdDeleteCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysVCreateCommand() *Command {
|
func NewSysVCreateCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysVReadCommand() *Command {
|
func NewSysVReadCommand() *command.Command {
|
||||||
|
c := command.NewCommand()
|
||||||
|
c.Path = "service"
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("status"),
|
||||||
|
command.CommandArg("{{ .Name }}"),
|
||||||
|
}
|
||||||
|
c.Extractor = func(out []byte, target any) (err error) {
|
||||||
|
s := target.(*Service)
|
||||||
|
/*
|
||||||
|
serviceStatus := strings.Split(string(out), "\n")
|
||||||
|
for _, statusLine := range(serviceStatus) {
|
||||||
|
if len(statusLine) > 1 {
|
||||||
|
statusFields := strings.Fields(statusLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if len(out) < 1 {
|
||||||
|
if err = s.stater.Trigger("notexist"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch c.ExitCode {
|
||||||
|
case SysVStatusRunning:
|
||||||
|
if err = s.stater.Trigger("running"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case SysVStatusStopped:
|
||||||
|
if err = s.stater.Trigger("created"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case SysVStatusUnknown, SysVStatusMissing:
|
||||||
|
if err = s.stater.Trigger("notexist"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysVUpdateCommand() *Command {
|
func NewSysVUpdateCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSysVDeleteCommand() *Command {
|
func NewSysVDeleteCommand() *command.Command {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ package resource
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "decl/tests/mocks"
|
_ "decl/tests/mocks"
|
||||||
|
"decl/internal/command"
|
||||||
_ "fmt"
|
_ "fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
@ -22,15 +23,28 @@ func TestUriServiceResource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadServiceResource(t *testing.T) {
|
func TestReadServiceResource(t *testing.T) {
|
||||||
|
|
||||||
|
s := NewService()
|
||||||
|
s.Name = "ssh"
|
||||||
|
|
||||||
yamlResult := `
|
yamlResult := `
|
||||||
name: "ssh"
|
name: "ssh"
|
||||||
servicemanager: "systemd"
|
servicemanager: "systemd"
|
||||||
state: "present"
|
state: "present"
|
||||||
`
|
`
|
||||||
c := NewService()
|
m := &MockCommand{
|
||||||
c.Name = "ssh"
|
CommandExists: func() error { return nil },
|
||||||
c.State = "present"
|
Executor: func(value any) ([]byte, error) {
|
||||||
yamlData, err := c.Read(context.Background())
|
return nil, nil
|
||||||
|
},
|
||||||
|
Extractor: func(output []byte, target any) error {
|
||||||
|
s.Common.State = "present"
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ReadCommand = (*command.Command)(m)
|
||||||
|
yamlData, err := s.Read(context.Background())
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.YAMLEq(t, yamlResult, string(yamlData))
|
assert.YAMLEq(t, yamlResult, string(yamlData))
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,15 @@ type UserType string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
UserTypeName TypeName = "user"
|
UserTypeName TypeName = "user"
|
||||||
UserTypeAddUser UserType = "adduser"
|
UserTypeBusyBox UserType = "busybox"
|
||||||
UserTypeUserAdd UserType = "useradd"
|
UserTypeShadow UserType = "shadow"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnsupportedUserType error = errors.New("The UserType is not supported on this system")
|
var ErrUnsupportedUserType error = errors.New("The UserType is not supported on this system")
|
||||||
var ErrInvalidUserType error = errors.New("invalid UserType value")
|
var ErrInvalidUserType error = errors.New("invalid UserType value")
|
||||||
|
|
||||||
|
var SupportedUserTypes []UserType = []UserType{UserTypeShadow, UserTypeBusyBox}
|
||||||
|
|
||||||
var SystemUserType UserType = FindSystemUserType()
|
var SystemUserType UserType = FindSystemUserType()
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
@ -44,11 +46,15 @@ type User struct {
|
|||||||
UID string `json:"uid,omitempty" yaml:"uid,omitempty"`
|
UID string `json:"uid,omitempty" yaml:"uid,omitempty"`
|
||||||
Group string `json:"group,omitempty" yaml:"group,omitempty"`
|
Group string `json:"group,omitempty" yaml:"group,omitempty"`
|
||||||
Groups []string `json:"groups,omitempty" yaml:"groups,omitempty"`
|
Groups []string `json:"groups,omitempty" yaml:"groups,omitempty"`
|
||||||
|
AppendGroups bool `json:"appendgroups,omitempty" yaml:"appendgroups,omitempty"`
|
||||||
Gecos string `json:"gecos,omitempty" yaml:"gecos,omitempty"`
|
Gecos string `json:"gecos,omitempty" yaml:"gecos,omitempty"`
|
||||||
Home string `json:"home" yaml:"home"`
|
Home string `json:"home" yaml:"home"`
|
||||||
CreateHome bool `json:"createhome,omitempty" yaml:"createhome,omitempty"`
|
CreateHome bool `json:"createhome,omitempty" yaml:"createhome,omitempty"`
|
||||||
Shell string `json:"shell,omitempty" yaml:"shell,omitempty"`
|
Shell string `json:"shell,omitempty" yaml:"shell,omitempty"`
|
||||||
UserType UserType `json:"-" yaml:"-"`
|
UserType UserType `json:"type,omitempty" yaml:"type,omitempty"`
|
||||||
|
|
||||||
|
userStatus *user.User `json:"-" yaml:"-"`
|
||||||
|
groupStatus *user.Group `json:"-" yaml:"-"`
|
||||||
|
|
||||||
CreateCommand *command.Command `json:"-" yaml:"-"`
|
CreateCommand *command.Command `json:"-" yaml:"-"`
|
||||||
ReadCommand *command.Command `json:"-" yaml:"-"`
|
ReadCommand *command.Command `json:"-" yaml:"-"`
|
||||||
@ -59,7 +65,10 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewUser() *User {
|
func NewUser() *User {
|
||||||
return &User{ CreateHome: true, Common: &Common{ resourceType: UserTypeName } }
|
u := &User{ CreateHome: true, AppendGroups: true, UserType: SystemUserType }
|
||||||
|
u.Common = NewCommon(UserTypeName, false)
|
||||||
|
u.Common.NormalizePath = u.NormalizePath
|
||||||
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -67,25 +76,23 @@ func init() {
|
|||||||
user := NewUser()
|
user := NewUser()
|
||||||
user.Name = u.Hostname()
|
user.Name = u.Hostname()
|
||||||
user.UID = LookupUIDString(u.Hostname())
|
user.UID = LookupUIDString(u.Hostname())
|
||||||
if _, addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
|
|
||||||
user.UserType = UserTypeAddUser
|
|
||||||
}
|
|
||||||
if _, pathErr := exec.LookPath("useradd"); pathErr == nil {
|
|
||||||
user.UserType = UserTypeUserAdd
|
|
||||||
}
|
|
||||||
user.CreateCommand, user.ReadCommand, user.UpdateCommand, user.DeleteCommand = user.UserType.NewCRUD()
|
user.CreateCommand, user.ReadCommand, user.UpdateCommand, user.DeleteCommand = user.UserType.NewCRUD()
|
||||||
return user
|
return user
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindSystemUserType() UserType {
|
func FindSystemUserType() UserType {
|
||||||
for _, userType := range []UserType{UserTypeAddUser, UserTypeUserAdd} {
|
for _, userType := range SupportedUserTypes {
|
||||||
c := userType.NewCreateCommand()
|
c := userType.NewCreateCommand()
|
||||||
if c.Exists() {
|
if c.Exists() {
|
||||||
return userType
|
return userType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return UserTypeAddUser
|
return UserTypeShadow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) NormalizePath() error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) SetResourceMapper(resources data.ResourceMapper) {
|
func (u *User) SetResourceMapper(resources data.ResourceMapper) {
|
||||||
@ -121,6 +128,16 @@ func (u *User) Notify(m *machine.EventMessage) {
|
|||||||
switch m.On {
|
switch m.On {
|
||||||
case machine.ENTERSTATEEVENT:
|
case machine.ENTERSTATEEVENT:
|
||||||
switch m.Dest {
|
switch m.Dest {
|
||||||
|
case "start_stat":
|
||||||
|
if statErr := u.ReadStat(); statErr == nil {
|
||||||
|
if triggerErr := u.StateMachine().Trigger("exists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if triggerErr := u.StateMachine().Trigger("notexists"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
case "start_read":
|
case "start_read":
|
||||||
if _,readErr := u.Read(ctx); readErr == nil {
|
if _,readErr := u.Read(ctx); readErr == nil {
|
||||||
if triggerErr := u.StateMachine().Trigger("state_read"); triggerErr == nil {
|
if triggerErr := u.StateMachine().Trigger("state_read"); triggerErr == nil {
|
||||||
@ -133,6 +150,17 @@ func (u *User) Notify(m *machine.EventMessage) {
|
|||||||
u.Common.State = "absent"
|
u.Common.State = "absent"
|
||||||
panic(readErr)
|
panic(readErr)
|
||||||
}
|
}
|
||||||
|
case "start_update":
|
||||||
|
if updateErr := u.Update(ctx); updateErr == nil {
|
||||||
|
if triggerErr := u.stater.Trigger("updated"); triggerErr == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
u.Common.State = "absent"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
u.Common.State = "absent"
|
||||||
|
panic(updateErr)
|
||||||
|
}
|
||||||
case "start_delete":
|
case "start_delete":
|
||||||
if deleteErr := u.Delete(ctx); deleteErr == nil {
|
if deleteErr := u.Delete(ctx); deleteErr == nil {
|
||||||
if triggerErr := u.StateMachine().Trigger("deleted"); triggerErr == nil {
|
if triggerErr := u.StateMachine().Trigger("deleted"); triggerErr == nil {
|
||||||
@ -183,7 +211,7 @@ func (u *User) UseConfig(config data.ConfigurationValueGetter) {
|
|||||||
|
|
||||||
func (u *User) ResolveId(ctx context.Context) string {
|
func (u *User) ResolveId(ctx context.Context) string {
|
||||||
if u.config != nil {
|
if u.config != nil {
|
||||||
if configUser, configUserErr := u.config.GetValue("user"); configUserErr == nil && u.Name == "" {
|
if configUser, configUserErr := u.config.GetValue("user"); configUserErr == nil && u.Name == "self" {
|
||||||
u.Name = configUser.(string)
|
u.Name = configUser.(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,20 +269,74 @@ func (u *User) Create(ctx context.Context) (error) {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) ReadStat() (err error) {
|
||||||
|
if u.userStatus == nil {
|
||||||
|
if u.userStatus, err = user.Lookup(u.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(u.userStatus.Uid) < 1 {
|
||||||
|
return ErrResourceStateAbsent
|
||||||
|
}
|
||||||
|
u.UID = u.userStatus.Uid
|
||||||
|
if len(u.Group) > 1 {
|
||||||
|
if u.groupStatus == nil {
|
||||||
|
if u.groupStatus, err = user.LookupGroup(u.Group); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(u.groupStatus.Gid) < 1 {
|
||||||
|
return ErrResourceStateAbsent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) Read(ctx context.Context) ([]byte, error) {
|
func (u *User) Read(ctx context.Context) ([]byte, error) {
|
||||||
exErr := u.ReadCommand.Extractor(nil, u)
|
exErr := u.ReadCommand.Extractor(nil, u)
|
||||||
if exErr != nil {
|
if exErr != nil {
|
||||||
u.Common.State = "absent"
|
u.Common.State = "absent"
|
||||||
}
|
}
|
||||||
if yaml, yamlErr := yaml.Marshal(u); yamlErr != nil {
|
_ = u.ReadGroups()
|
||||||
return yaml, yamlErr
|
if yamlDoc, yamlErr := yaml.Marshal(u); yamlErr != nil {
|
||||||
|
return yamlDoc, yamlErr
|
||||||
} else {
|
} else {
|
||||||
return yaml, exErr
|
return yamlDoc, exErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) ReadGroups() (err error) {
|
||||||
|
knownSecondaryGroups := make(map[string]bool)
|
||||||
|
for _, secondaryGroupName := range u.Groups {
|
||||||
|
knownSecondaryGroups[secondaryGroupName] = true
|
||||||
|
}
|
||||||
|
if u.ReadStat() == nil {
|
||||||
|
if groups, groupsErr := u.userStatus.GroupIds(); groupsErr == nil {
|
||||||
|
for _, secondaryGroup := range groups {
|
||||||
|
if readGroup, groupErr := user.LookupGroupId(secondaryGroup); groupErr == nil {
|
||||||
|
if ! knownSecondaryGroups[readGroup.Name] {
|
||||||
|
u.Groups = append(u.Groups, readGroup.Name)
|
||||||
|
knownSecondaryGroups[readGroup.Name] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = groupErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = groupsErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) Update(ctx context.Context) (error) {
|
func (u *User) Update(ctx context.Context) (error) {
|
||||||
return u.Create(ctx)
|
_, err := u.UpdateCommand.Execute(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_,e := u.Read(ctx)
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Delete(ctx context.Context) (error) {
|
func (u *User) Delete(ctx context.Context) (error) {
|
||||||
@ -284,38 +366,38 @@ func (u *User) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
|
|
||||||
func (u *UserType) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
|
func (u *UserType) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) {
|
||||||
switch *u {
|
switch *u {
|
||||||
case UserTypeUserAdd:
|
case UserTypeShadow:
|
||||||
return NewUserAddCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewUserDelDeleteCommand()
|
return NewUserShadowCreateCommand(), NewUserReadCommand(), NewUserShadowUpdateCommand(), NewUserShadowDeleteCommand()
|
||||||
case UserTypeAddUser:
|
case UserTypeBusyBox:
|
||||||
return NewAddUserCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewDelUserDeleteCommand()
|
return NewUserBusyBoxCreateCommand(), NewUserReadCommand(), NewUserBusyBoxUpdateCommand(), NewUserBusyBoxDeleteCommand()
|
||||||
default:
|
default:
|
||||||
if _, addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
|
if _, addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
|
||||||
*u = UserTypeAddUser
|
*u = UserTypeBusyBox
|
||||||
return NewAddUserCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewDelUserDeleteCommand()
|
return NewUserBusyBoxCreateCommand(), NewUserReadCommand(), NewUserBusyBoxUpdateCommand(), NewUserBusyBoxDeleteCommand()
|
||||||
}
|
}
|
||||||
if _, pathErr := exec.LookPath("useradd"); pathErr == nil {
|
if _, pathErr := exec.LookPath("useradd"); pathErr == nil {
|
||||||
*u = UserTypeUserAdd
|
*u = UserTypeShadow
|
||||||
return NewUserAddCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewUserDelDeleteCommand()
|
return NewUserShadowCreateCommand(), NewUserReadCommand(), NewUserShadowUpdateCommand(), NewUserShadowDeleteCommand()
|
||||||
}
|
}
|
||||||
return NewUserAddCreateCommand(), NewUserReadCommand(), NewUserUpdateCommand(), NewUserDelDeleteCommand()
|
return NewUserShadowCreateCommand(), NewUserReadCommand(), NewUserShadowUpdateCommand(), NewUserShadowDeleteCommand()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UserType) NewReadCommand() (read *command.Command) {
|
||||||
|
return NewUserReadCommand()
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UserType) NewCreateCommand() (create *command.Command) {
|
func (u *UserType) NewCreateCommand() (create *command.Command) {
|
||||||
switch *u {
|
switch *u {
|
||||||
case UserTypeUserAdd:
|
case UserTypeShadow:
|
||||||
return NewUserAddCreateCommand()
|
return NewUserShadowCreateCommand()
|
||||||
case UserTypeAddUser:
|
case UserTypeBusyBox:
|
||||||
return NewAddUserCreateCommand()
|
return NewUserBusyBoxCreateCommand()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserType) NewReadCommand() (*command.Command) {
|
|
||||||
return NewUserReadCommand()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UserType) NewReadUsersCommand() (*command.Command) {
|
func (p *UserType) NewReadUsersCommand() (*command.Command) {
|
||||||
return NewReadUsersCommand()
|
return NewReadUsersCommand()
|
||||||
}
|
}
|
||||||
@ -323,7 +405,7 @@ func (p *UserType) NewReadUsersCommand() (*command.Command) {
|
|||||||
|
|
||||||
func (u *UserType) UnmarshalValue(value string) error {
|
func (u *UserType) UnmarshalValue(value string) error {
|
||||||
switch value {
|
switch value {
|
||||||
case string(UserTypeUserAdd), string(UserTypeAddUser):
|
case string(UserTypeShadow), string(UserTypeBusyBox):
|
||||||
*u = UserType(value)
|
*u = UserType(value)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
@ -390,7 +472,7 @@ func NewReadUsersCommand() *command.Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserAddCreateCommand() *command.Command {
|
func NewUserShadowCreateCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "useradd"
|
c.Path = "useradd"
|
||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
@ -404,19 +486,11 @@ func NewUserAddCreateCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
return nil
|
return nil
|
||||||
/*
|
|
||||||
for _,line := range strings.Split(string(out), "\n") {
|
|
||||||
if line == "iptables: Chain already exists." {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf(string(out))
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAddUserCreateCommand() *command.Command {
|
func NewUserBusyBoxCreateCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "adduser"
|
c.Path = "adduser"
|
||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
@ -430,14 +504,6 @@ func NewAddUserCreateCommand() *command.Command {
|
|||||||
}
|
}
|
||||||
c.Extractor = func(out []byte, target any) error {
|
c.Extractor = func(out []byte, target any) error {
|
||||||
return nil
|
return nil
|
||||||
/*
|
|
||||||
for _,line := range strings.Split(string(out), "\n") {
|
|
||||||
if line == "iptables: Chain already exists." {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf(string(out))
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@ -476,11 +542,40 @@ func NewUserReadCommand() *command.Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserUpdateCommand() *command.Command {
|
func NewUserBusyBoxUpdateCommand() *command.Command {
|
||||||
return nil
|
c := command.NewCommand()
|
||||||
|
c.Path = "xargs"
|
||||||
|
c.StdinAvailable = true
|
||||||
|
c.Input = command.CommandInput("{{ if .Groups }}{{ range .Groups }}{{ . }}\n{{ end }}{{ end }}")
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("adduser"),
|
||||||
|
command.CommandArg("{{ .Name }}"),
|
||||||
|
}
|
||||||
|
c.Extractor = func(out []byte, target any) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserDelDeleteCommand() *command.Command {
|
func NewUserShadowUpdateCommand() *command.Command {
|
||||||
|
c := command.NewCommand()
|
||||||
|
c.Path = "usermod"
|
||||||
|
c.Args = []command.CommandArg{
|
||||||
|
command.CommandArg("{{ if .UID }}-u {{ .UID }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ if .Gecos }}-c {{ .Gecos }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ if .Group }}-g {{ .groupStatus.Gid }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ if .Home }}-d {{ .Home }}{{ if .CreateHome }} -m{{- end }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ if .Groups }}-G {{- range $i, $g := .Groups -}}{{ if $i }}, {{- end }}{{ . }}{{- end }}{{ if .AppendGroups }} -a{{- end }}{{- end }}"),
|
||||||
|
command.CommandArg("{{ if .Shell }}-s {{ .Shell }}{{ end }}"),
|
||||||
|
command.CommandArg("{{ .Name }}"),
|
||||||
|
}
|
||||||
|
c.Extractor = func(out []byte, target any) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserShadowDeleteCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "userdel"
|
c.Path = "userdel"
|
||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
@ -492,7 +587,7 @@ func NewUserDelDeleteCommand() *command.Command {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDelUserDeleteCommand() *command.Command {
|
func NewUserBusyBoxDeleteCommand() *command.Command {
|
||||||
c := command.NewCommand()
|
c := command.NewCommand()
|
||||||
c.Path = "deluser"
|
c.Path = "deluser"
|
||||||
c.Args = []command.CommandArg{
|
c.Args = []command.CommandArg{
|
||||||
|
@ -81,7 +81,7 @@ func TestCreateUser(t *testing.T) {
|
|||||||
|
|
||||||
func TestSystemUser(t *testing.T) {
|
func TestSystemUser(t *testing.T) {
|
||||||
u := NewUser()
|
u := NewUser()
|
||||||
|
u.Name = "self"
|
||||||
u.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
u.UseConfig(MockConfigValueGetter(func(key string) (any, error) {
|
||||||
switch key {
|
switch key {
|
||||||
case "user":
|
case "user":
|
||||||
@ -97,3 +97,22 @@ func TestSystemUser(t *testing.T) {
|
|||||||
u.ResolveId(context.Background())
|
u.ResolveId(context.Background())
|
||||||
assert.Equal(t, "bar", u.Name)
|
assert.Equal(t, "bar", u.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserSecondaryGroups(t *testing.T) {
|
||||||
|
groupCounts := make(map[string]int)
|
||||||
|
u := NewUser()
|
||||||
|
u.Name = "root"
|
||||||
|
u.ResolveId(context.Background())
|
||||||
|
u.Groups = []string{
|
||||||
|
"root",
|
||||||
|
"wheel",
|
||||||
|
}
|
||||||
|
u.ReadGroups()
|
||||||
|
|
||||||
|
for _, groupName := range u.Groups {
|
||||||
|
groupCounts[groupName]++
|
||||||
|
}
|
||||||
|
assert.Greater(t, len(u.Groups), 2)
|
||||||
|
assert.Equal(t, 1, groupCounts["root"])
|
||||||
|
assert.Equal(t, 1, groupCounts["wheel"])
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user