Skip to content

Commit

Permalink
Merge pull request #234 from asteris-llc/feature/user-group
Browse files Browse the repository at this point in the history
Add user group support
  • Loading branch information
BrianHicks authored Sep 13, 2016
2 parents d73211b + dfd5a89 commit 69167c0
Show file tree
Hide file tree
Showing 7 changed files with 887 additions and 0 deletions.
1 change: 1 addition & 0 deletions load/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
_ "github.com/asteris-llc/converge/resource/docker/image"
_ "github.com/asteris-llc/converge/resource/file/content"
_ "github.com/asteris-llc/converge/resource/file/mode"
_ "github.com/asteris-llc/converge/resource/group"
_ "github.com/asteris-llc/converge/resource/module"
_ "github.com/asteris-llc/converge/resource/param"
_ "github.com/asteris-llc/converge/resource/shell"
Expand Down
166 changes: 166 additions & 0 deletions resource/group/group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package group

import (
"fmt"
"os/user"

"github.com/asteris-llc/converge/resource"
)

// State type for Group
type State string

const (
// StatePresent indicates the group should be present
StatePresent State = "present"

// StateAbsent indicates the group should be absent
StateAbsent State = "absent"
)

// Group manages user groups
type Group struct {
GID string
Name string
State State
system SystemUtils
}

// SystemUtils provides system utilities for group
type SystemUtils interface {
AddGroup(string, string) error
DelGroup(string) error
LookupGroup(string) (*user.Group, error)
LookupGroupID(string) (*user.Group, error)
}

// NewGroup constructs and returns a new Group
func NewGroup(system SystemUtils) *Group {
return &Group{
system: system,
}
}

// Check if a user group exists
func (g *Group) Check(resource.Renderer) (resource.TaskStatus, error) {
// lookup the group by name and lookup the group by gid
// the lookup returns an error if the group is not found
groupByName, nameErr := g.system.LookupGroup(g.Name)
groupByGid, gidErr := g.system.LookupGroupID(g.GID)

status := &resource.Status{}

switch g.State {
case StatePresent:
_, nameNotFound := nameErr.(user.UnknownGroupError)
_, gidNotFound := gidErr.(user.UnknownGroupIdError)

switch {
case nameNotFound && gidNotFound:
status.WarningLevel = resource.StatusWillChange
status.Output = append(status.Output, "group name and gid do not exist")
status.AddDifference("group", string(StateAbsent), fmt.Sprintf("group %s with gid %s", g.Name, g.GID), "")
status.WillChange = true
case nameNotFound:
status.WarningLevel = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group gid %s already exists", g.GID))
case gidNotFound:
status.WarningLevel = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group %s already exists", g.Name))
case groupByName.Name != groupByGid.Name || groupByName.Gid != groupByGid.Gid:
status.WarningLevel = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group %s and gid %s belong to different groups", g.Name, g.GID))
case groupByName != nil && groupByGid != nil && *groupByName == *groupByGid:
status.WarningLevel = resource.StatusNoChange
}
case StateAbsent:
_, nameNotFound := nameErr.(user.UnknownGroupError)
_, gidNotFound := gidErr.(user.UnknownGroupIdError)

switch {
case nameNotFound && gidNotFound:
status.WarningLevel = resource.StatusNoChange
status.Output = append(status.Output, "group name and gid do not exist")
case nameNotFound:
status.WarningLevel = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group %s does not exist", g.Name))
case gidNotFound:
status.WarningLevel = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group gid %s does not exist", g.GID))
case groupByName.Name != groupByGid.Name || groupByName.Gid != groupByGid.Gid:
status.WarningLevel = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group %s and gid %s belong to different groups", g.Name, g.GID))
case groupByName != nil && groupByGid != nil && *groupByName == *groupByGid:
status.WarningLevel = resource.StatusWillChange
status.WillChange = true
status.AddDifference("group", fmt.Sprintf("group %s with gid %s", g.Name, g.GID), string(StateAbsent), "")
}
default:
return nil, fmt.Errorf("group: unrecognized state %v", g.State)
}

return status, nil
}

// Apply changes for group
func (g *Group) Apply(resource.Renderer) (resource.TaskStatus, error) {
// lookup the group by name and lookup the group by gid
// the lookup returns an error if the group is not found
groupByName, nameErr := g.system.LookupGroup(g.Name)
groupByGid, gidErr := g.system.LookupGroupID(g.GID)

status := &resource.Status{}

switch g.State {
case StatePresent:
_, nameNotFound := nameErr.(user.UnknownGroupError)
_, gidNotFound := gidErr.(user.UnknownGroupIdError)

switch {
case nameNotFound && gidNotFound:
err := g.system.AddGroup(g.Name, g.GID)
if err != nil {
status.WarningLevel = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("error adding group %s with gid %s", g.Name, g.GID))
return status, err
}
status.Output = append(status.Output, fmt.Sprintf("added group %s with gid %s", g.Name, g.GID))
default:
status.WarningLevel = resource.StatusFatal
return status, fmt.Errorf("will not attempt add: group %s with gid %s", g.Name, g.GID)
}
case StateAbsent:
switch {
case groupByName != nil && groupByGid != nil && *groupByName == *groupByGid:
err := g.system.DelGroup(g.Name)
if err != nil {
status.WarningLevel = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("error deleting group %s with gid %s", g.Name, g.GID))
return status, err
}
status.Output = append(status.Output, fmt.Sprintf("deleted group %s with gid %s", g.Name, g.GID))
default:
status.WarningLevel = resource.StatusFatal
return status, fmt.Errorf("will not attempt delete: group %s with gid %s", g.Name, g.GID)
}
default:
status.WarningLevel = resource.StatusFatal
return status, fmt.Errorf("group: unrecognized state %s", g.State)
}

return status, nil
}
58 changes: 58 additions & 0 deletions resource/group/group_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build linux

package group

import (
"fmt"
"os/exec"
"os/user"
)

// System implements SystemUtils
type System struct{}

// AddGroup adds a group
func (s *System) AddGroup(groupName, groupID string) error {
cmd := exec.Command("groupadd", groupName, "-g", groupID)
err := cmd.Run()
if err != nil {
return fmt.Errorf("groupadd: %s", err)
}
return nil
}

// DelGroup deletes a group
func (s *System) DelGroup(groupName string) error {
cmd := exec.Command("groupdel", groupName)
err := cmd.Run()
if err != nil {
return fmt.Errorf("groupdel: %s", err)
}
return nil
}

// LookupGroup looks up a group by name
// If the group cannot be found an error is returned
func (s *System) LookupGroup(groupName string) (*user.Group, error) {
return user.LookupGroup(groupName)
}

// LookupGroupID looks up a group by gid
// If the group cannot be found an error is returned
func (s *System) LookupGroupID(groupID string) (*user.Group, error) {
return user.LookupGroupId(groupID)
}
Loading

0 comments on commit 69167c0

Please sign in to comment.