osbuild: add support for org.osbuild.chown stage
Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
parent
2e54557cd4
commit
0bd0ce9fc1
2 changed files with 342 additions and 0 deletions
98
internal/osbuild/chown_stage.go
Normal file
98
internal/osbuild/chown_stage.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package osbuild
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
// should be "^\\/(?!\\.\\.)((?!\\/\\.\\.\\/).)+$" but Go doesn't support lookaheads
|
||||
// therefore we have to instead check for the invalid cases, which is much simpler
|
||||
chownStageInvalidPathRegex = `((^|\/)[.]{2}(\/|$))|^([^/].*)*$`
|
||||
chownStageUsernameRegex = `^[A-Za-z0-9_.][A-Za-z0-9_.-]{0,31}$`
|
||||
chownStageGroupnameRegex = `^[A-Za-z0-9_][A-Za-z0-9_-]{0,31}$`
|
||||
)
|
||||
|
||||
type ChownStageOptions struct {
|
||||
Items map[string]ChownStagePathOptions `json:"items"`
|
||||
}
|
||||
|
||||
func (ChownStageOptions) isStageOptions() {}
|
||||
|
||||
func (o *ChownStageOptions) validate() error {
|
||||
for path, options := range o.Items {
|
||||
invalidPathRegex := regexp.MustCompile(chownStageInvalidPathRegex)
|
||||
if invalidPathRegex.FindAllString(path, -1) != nil {
|
||||
return fmt.Errorf("chown path %q matches invalid path pattern (%s)", path, invalidPathRegex.String())
|
||||
}
|
||||
|
||||
if err := options.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ChownStagePathOptions struct {
|
||||
// User can be either a string (user name), an int64 (UID) or nil
|
||||
User interface{} `json:"user,omitempty"`
|
||||
// Group can be either a string (grou pname), an int64 (GID) or nil
|
||||
Group interface{} `json:"group,omitempty"`
|
||||
|
||||
Recursive bool `json:"recursive,omitempty"`
|
||||
}
|
||||
|
||||
// validate checks that the options values conform to the schema
|
||||
func (o *ChownStagePathOptions) validate() error {
|
||||
switch user := o.User.(type) {
|
||||
case string:
|
||||
usernameRegex := regexp.MustCompile(chownStageUsernameRegex)
|
||||
if !usernameRegex.MatchString(user) {
|
||||
return fmt.Errorf("chown user name %q doesn't conform to schema (%s)", user, usernameRegex.String())
|
||||
}
|
||||
case int64:
|
||||
if user < 0 {
|
||||
return fmt.Errorf("chown user id %d is negative", user)
|
||||
}
|
||||
case nil:
|
||||
// user is not set
|
||||
default:
|
||||
return fmt.Errorf("chown user must be either a string nor an int64, got %T", user)
|
||||
}
|
||||
|
||||
switch group := o.Group.(type) {
|
||||
case string:
|
||||
groupnameRegex := regexp.MustCompile(chownStageGroupnameRegex)
|
||||
if !groupnameRegex.MatchString(group) {
|
||||
return fmt.Errorf("chown group name %q doesn't conform to schema (%s)", group, groupnameRegex.String())
|
||||
}
|
||||
case int64:
|
||||
if group < 0 {
|
||||
return fmt.Errorf("chown group id %d is negative", group)
|
||||
}
|
||||
case nil:
|
||||
// group is not set
|
||||
default:
|
||||
return fmt.Errorf("chown group must be either a string nor an int64, got %T", group)
|
||||
}
|
||||
|
||||
// check that at least one of user or group is set
|
||||
if o.User == nil && o.Group == nil {
|
||||
return fmt.Errorf("chown user and group are both not set")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewChownStage creates a new org.osbuild.chown stage
|
||||
func NewChownStage(options *ChownStageOptions) *Stage {
|
||||
if err := options.validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Stage{
|
||||
Type: "org.osbuild.chown",
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
244
internal/osbuild/chown_stage_test.go
Normal file
244
internal/osbuild/chown_stage_test.go
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
package osbuild
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewChownStage(t *testing.T) {
|
||||
stageOptions := &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/etc/foobar": {
|
||||
User: "root",
|
||||
Group: int64(12345),
|
||||
Recursive: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedStage := &Stage{
|
||||
Type: "org.osbuild.chown",
|
||||
Options: stageOptions,
|
||||
}
|
||||
actualStage := NewChownStage(stageOptions)
|
||||
assert.Equal(t, expectedStage, actualStage)
|
||||
}
|
||||
|
||||
func TestChownStageOptionsValidate(t *testing.T) {
|
||||
validPathOptions := ChownStagePathOptions{
|
||||
User: "root",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
options *ChownStageOptions
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
name: "empty-options",
|
||||
options: &ChownStageOptions{},
|
||||
},
|
||||
{
|
||||
name: "no-items",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid-item-path-1",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"": validPathOptions,
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-item-path-2",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"foobar": validPathOptions,
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-item-path-3",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/../foobar": validPathOptions,
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-item-path-4",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/etc/../foobar": validPathOptions,
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-item-path-5",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/etc/..": validPathOptions,
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-item-path-6",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"../etc/foo/../bar": validPathOptions,
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "valid-item-path-1",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/etc/foobar": validPathOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-item-path-2",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/etc/foo/bar/baz": validPathOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-item-path-3",
|
||||
options: &ChownStageOptions{
|
||||
Items: map[string]ChownStagePathOptions{
|
||||
"/etc": validPathOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.options.validate()
|
||||
if tc.err {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChownStagePathOptionsValidate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
options ChownStagePathOptions
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
name: "empty-options",
|
||||
options: ChownStagePathOptions{},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-user-string-1",
|
||||
options: ChownStagePathOptions{
|
||||
User: "",
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-user-string-2",
|
||||
options: ChownStagePathOptions{
|
||||
User: "r@@t",
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-user-id",
|
||||
options: ChownStagePathOptions{
|
||||
User: int64(-1),
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "valid-user-string",
|
||||
options: ChownStagePathOptions{
|
||||
User: "root",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-user-id",
|
||||
options: ChownStagePathOptions{
|
||||
User: int64(0),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid-group-string-1",
|
||||
options: ChownStagePathOptions{
|
||||
Group: "",
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-group-string-2",
|
||||
options: ChownStagePathOptions{
|
||||
Group: "r@@t",
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "invalid-group-id",
|
||||
options: ChownStagePathOptions{
|
||||
Group: int64(-1),
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "valid-group-string",
|
||||
options: ChownStagePathOptions{
|
||||
Group: "root",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-group-id",
|
||||
options: ChownStagePathOptions{
|
||||
Group: int64(0),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-both-1",
|
||||
options: ChownStagePathOptions{
|
||||
User: "root",
|
||||
Group: int64(12345),
|
||||
Recursive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-both-2",
|
||||
options: ChownStagePathOptions{
|
||||
User: int64(12345),
|
||||
Group: "root",
|
||||
Recursive: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.options.validate()
|
||||
if tc.err {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue