-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #234 from asteris-llc/feature/user-group
Add user group support
- Loading branch information
Showing
7 changed files
with
887 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.