Skip to content

Commit

Permalink
Merge branch 'master' into interrupt
Browse files Browse the repository at this point in the history
  • Loading branch information
acabarbaye authored Oct 17, 2023
2 parents e71d971 + ccf97fb commit 9262a39
Show file tree
Hide file tree
Showing 18 changed files with 467 additions and 31 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ beta releases are not included in this history.

[//]: # (begin_release_notes)

"1.49.0" (2023-10-17)
=====================

Features
--------

- :sparkles: `[environment]` Expose utilities for parsing and finding environment variables (#20231013160208)
- :sparkles: `[collection]` Added `AllNotEmpty` and `AnyEmpty` to check the content of string slices (#20231016164922)
- :sparkles: `[collection]` Added `FindInSlice` to extend `Find` search capabilities (#20231016173631)
- :sparkles: `[platform]` Added a way to determine if a user has admin rights (i.e. root or superuser on Linux and administrator on Windows) (#20231016175953)
- :sparkles: `[command]` Added a way to prepend commands (using `Prepend()`) to command wrappers in order to chain command wrappers easily (#20231016204910)
- :sparkles: `[platform]` Added `WithPrivileges` so that commands are elevated with privileges depending on the permissions of the current user (#20231016234712)


Bugfixes
--------

- :bug: `[environment]` Fix environment variables parsing when an entry is incorrect (#20231013160247)


"1.48.0" (2023-10-13)
=====================

Expand Down
1 change: 0 additions & 1 deletion changes/20231013160208.feature

This file was deleted.

1 change: 0 additions & 1 deletion changes/20231013160247.bugfix

This file was deleted.

61 changes: 54 additions & 7 deletions utils/collection/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,78 @@
*/
package collection

import "strings"

// Find looks for an element in a slice. If found it will
// return its index and true; otherwise it will return -1 and false.
func Find(slice *[]string, val string) (int, bool) {
for i, item := range *slice {
if item == val {
return i, true
if slice == nil {
return -1, false
}
return FindInSlice(true, *slice, val)
}

// FindInSlice finds if any values val are present in the slice and if so returns the first index.
// if strict, it check of an exact match; otherwise discard whitespaces and case.
func FindInSlice(strict bool, slice []string, val ...string) (int, bool) {
if len(val) == 0 || len(slice) == 0 {
return -1, false
}

inSlice := make(map[string]int, len(slice))
for i := range slice {
item := slice[i]
if !strict {
item = strings.ToLower(strings.TrimSpace(item))
}
if _, ok := inSlice[item]; !ok {
inSlice[item] = i
}
}

for i := range val {
item := val[i]
if !strict {
item = strings.ToLower(strings.TrimSpace(item))
}
if idx, ok := inSlice[item]; ok {
return idx, true
}
}

return -1, false
}

// Any returns true if there is at least one element of the slice which is true.
func Any(slice []bool) bool {
for _, v := range slice {
if v {
for i := range slice {
if slice[i] {
return true
}
}
return false
}

// AnyEmpty returns whether there is one entry in the slice which is empty.
// If strict, then whitespaces are considered as empty strings
func AnyEmpty(strict bool, slice []string) bool {
_, found := FindInSlice(!strict, slice, "")
return found
}

// All returns true if all items of the slice are true.
func All(slice []bool) bool {
for _, v := range slice {
if !v {
for i := range slice {
if !slice[i] {
return false
}
}
return true
}

// AllNotEmpty returns whether all elements of the slice are not empty.
// If strict, then whitespaces are considered as empty strings
func AllNotEmpty(strict bool, slice []string) bool {
_, found := FindInSlice(!strict, slice, "")
return !found
}
59 changes: 58 additions & 1 deletion utils/collection/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ package collection
import (
"testing"

"github.com/bxcodec/faker/v3"
"github.com/stretchr/testify/assert"
)

func TestFind(t *testing.T) {
index, found := Find(&[]string{"A", "b", "c"}, "D")
index, found := Find(nil, "D")
assert.False(t, found)
assert.Equal(t, -1, index)
index, found = Find(&[]string{"A", "b", "c"}, "D")
assert.False(t, found)
assert.Equal(t, -1, index)

Expand All @@ -20,6 +24,39 @@ func TestFind(t *testing.T) {
assert.Equal(t, 2, index)
}

func TestFindInSlice(t *testing.T) {
index, found := FindInSlice(true, nil, "D")
assert.False(t, found)
assert.Equal(t, -1, index)
index, found = FindInSlice(true, []string{"A", "b", "c"})
assert.False(t, found)
assert.Equal(t, -1, index)
index, found = FindInSlice(true, []string{"A", "b", "c"}, "D")
assert.False(t, found)
assert.Equal(t, -1, index)
index, found = FindInSlice(true, []string{"A", "b", "c"}, "D", "e", "f", "g", "H")
assert.False(t, found)
assert.Equal(t, -1, index)
index, found = FindInSlice(false, []string{"A", "b", "c"}, "D")
assert.False(t, found)
assert.Equal(t, -1, index)
index, found = FindInSlice(false, []string{"A", "b", "c"}, "D", "e", "f", "g", "H")
assert.False(t, found)
assert.Equal(t, -1, index)
index, found = FindInSlice(true, []string{"A", "B", "b", "c"}, "b")
assert.True(t, found)
assert.Equal(t, 2, index)
index, found = FindInSlice(false, []string{"A", "B", "b", "c"}, "b")
assert.True(t, found)
assert.Equal(t, 1, index)
index, found = FindInSlice(true, []string{"A", "B", "b", "c"}, "b", "D", "e", "f", "g", "H")
assert.True(t, found)
assert.Equal(t, 2, index)
index, found = FindInSlice(false, []string{"A", "B", "b", "c"}, "b", "D", "e", "f", "g", "H")
assert.True(t, found)
assert.Equal(t, 1, index)
}

func TestAny(t *testing.T) {
assert.True(t, Any([]bool{false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false}))
assert.False(t, Any([]bool{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}))
Expand All @@ -33,3 +70,23 @@ func TestAll(t *testing.T) {
assert.True(t, All([]bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true}))
assert.False(t, All([]bool{true, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true, true, true, true, true}))
}

func TestAnyEmpty(t *testing.T) {
assert.True(t, AnyEmpty(false, []string{faker.Username(), faker.Name(), faker.Sentence(), ""}))
assert.False(t, AnyEmpty(false, []string{faker.Username(), " ", faker.Name(), faker.Sentence()}))
assert.True(t, AnyEmpty(true, []string{faker.Username(), " ", faker.Name(), faker.Sentence()}))
assert.True(t, AnyEmpty(false, []string{"", faker.Name(), faker.Sentence()}))
assert.True(t, AnyEmpty(false, []string{faker.Username(), "", faker.Name(), faker.Sentence()}))
assert.True(t, AnyEmpty(false, []string{faker.Username(), "", faker.Name(), "", faker.Sentence()}))
assert.False(t, AnyEmpty(false, []string{faker.Username(), faker.Name(), faker.Sentence()}))
}

func TestAllNotEmpty(t *testing.T) {
assert.False(t, AllNotEmpty(false, []string{faker.Username(), faker.Name(), faker.Sentence(), ""}))
assert.False(t, AllNotEmpty(false, []string{"", faker.Name(), faker.Sentence()}))
assert.False(t, AllNotEmpty(false, []string{faker.Username(), "", faker.Name(), faker.Sentence()}))
assert.True(t, AllNotEmpty(false, []string{faker.Username(), " ", faker.Name(), faker.Sentence()}))
assert.False(t, AllNotEmpty(true, []string{faker.Username(), " ", faker.Name(), faker.Sentence()}))
assert.False(t, AllNotEmpty(false, []string{faker.Username(), "", faker.Name(), "", faker.Sentence()}))
assert.True(t, AllNotEmpty(false, []string{faker.Username(), faker.Name(), faker.Sentence()}))
}
6 changes: 3 additions & 3 deletions utils/module.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Version=1.48.0
Version=1.49.0
MajorVersion=1
MinorVersion=48
MinorVersion=49
PatchVersion=0
CommitHash=3aa6349c524af72e3eb4eb96321c162f071e69e5
CommitHash=252a7ebceb348952f19c508ee71a4cb586e8edeb
22 changes: 22 additions & 0 deletions utils/platform/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package platform

import "github.com/ARM-software/golang-utils/utils/subprocess/command"

// WithPrivileges redefines a command so that it is run with elevated privileges.
// For instance, on Linux, if the current user has enough privileges, the command will be run as is.
// Otherwise, `sudo` will be used if defined as the sudo (See `DefineSudoCommand`).
// Similar scenario will happen on Windows, although the elevated command is defined using `DefineSudoCommand`.
func WithPrivileges(cmd *command.CommandAsDifferentUser) (cmdWithPrivileges *command.CommandAsDifferentUser) {
cmdWithPrivileges = cmd
if cmdWithPrivileges == nil {
cmdWithPrivileges = command.NewCommandAsDifferentUser()
}
hasPrivileges, err := IsCurrentUserAnAdmin()
if err != nil {
return
}
if !hasPrivileges {
cmdWithPrivileges.Prepend(getRunCommandWithPrivileges())
}
return
}
7 changes: 3 additions & 4 deletions utils/platform/cmd_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ var (
sudoCommand = command.Sudo()
)

// DefineSudoCommand defines the command to run to be `root` or a user with enough privileges to manage accounts.
// DefineSudoCommand defines the command to run to be `root` or a user with enough privileges (superuser).
// e.g.
// - args="sudo" to run commands as `root`
// - args="su", "tom" if `tom` has enough privileges to run the command
// - args="gosu", "tom" if `tom` has enough privileges to run the command in a container and `gosu` is installed
func DefineSudoCommand(args ...string) {
sudoCommand = command.NewCommandAsDifferentUser(args...)
}

func defineCommandWithPrivileges(args ...string) (string, []string) {
return sudoCommand.RedefineCommand(args...)
func getRunCommandWithPrivileges() *command.CommandAsDifferentUser {
return sudoCommand
}
33 changes: 33 additions & 0 deletions utils/platform/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package platform

import (
"fmt"
"strings"
"testing"

"github.com/bxcodec/faker/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ARM-software/golang-utils/utils/subprocess/command"
)

func TestWithPrivileges(t *testing.T) {
admin, err := IsCurrentUserAnAdmin()
require.NoError(t, err)
name := faker.Username()
cmd := WithPrivileges(command.Gosu(name)).RedefineInShellForm("test", "1", "2", "3")
if admin {
assert.Equal(t, fmt.Sprintf("gosu %v test 1 2 3", name), cmd)
} else {
assert.True(t, strings.Contains(cmd, fmt.Sprintf("gosu %v test 1 2 3", name)))
assert.False(t, strings.HasPrefix(cmd, fmt.Sprintf("gosu %v test 1 2 3", name)))
}
cmd = WithPrivileges(nil).RedefineInShellForm("test", "1", "2", "3")
if admin {
assert.Equal(t, "test 1 2 3", cmd)
} else {
assert.True(t, strings.Contains(cmd, "test 1 2 3"))
assert.False(t, strings.HasPrefix(cmd, "test 1 2 3"))
}
}
25 changes: 25 additions & 0 deletions utils/platform/cmd_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build windows
// +build windows

package platform

import "github.com/ARM-software/golang-utils/utils/subprocess/command"

var (
// runAsAdmin describes the command to use to run command as Administrator
// See https://ss64.com/nt/syntax-elevate.html
// see https://lazyadmin.nl/it/runas-command/
// see https://www.tenforums.com/general-support/111929-how-use-runas-without-password-prompt.html
runAsAdmin = command.RunAs("Administrator")
)

// DefineRunAsAdmin defines the command to run as Administrator.
// e.g.
// - args="runas", "/user:adrien" to run commands as `adrien`
func DefineRunAsAdmin(args ...string) {
runAsAdmin = command.NewCommandAsDifferentUser(args...)
}

func getRunCommandWithPrivileges() *command.CommandAsDifferentUser {
return runAsAdmin
}
50 changes: 50 additions & 0 deletions utils/platform/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,56 @@ func HasUser(username string) (found bool, err error) {
return
}

// IsCurrentUserAnAdmin states whether the current user is a superuser or not.
func IsCurrentUserAnAdmin() (admin bool, err error) {
cuser, err := user.Current()
if err != nil {
err = fmt.Errorf("%w: cannot fetch the current user: %v", commonerrors.ErrUnexpected, err.Error())
return
}
admin, err = IsUserAdmin(cuser)
return
}

// IsUserAdmin states whether the user is a superuser or not. Similar to IsAdmin but may use more checks.
func IsUserAdmin(user *user.User) (admin bool, err error) {
if user == nil {
err = fmt.Errorf("%w: missing user", commonerrors.ErrUndefined)
return
}
admin, subErr := isUserAdmin(user)
if subErr == nil {
return
}
admin, err = IsAdmin(user.Username)
err = ConvertUserGroupError(err)
return
}

// IsAdmin states whether the user is a superuser or not.
func IsAdmin(username string) (admin bool, err error) {
found, subErr := HasUser(username)
if !found && subErr == nil {
return
}
admin, err = isAdmin(username)
err = ConvertUserGroupError(err)
if err == nil {
return
}
// Make more check if username is current user
current, subErr := user.Current()
if subErr != nil {
return
}
if current.Username != username {
return
}
admin, err = isCurrentAdmin()
err = ConvertUserGroupError(err)
return
}

// HasGroup checks whether a group exists
func HasGroup(groupName string) (found bool, err error) {
group, err := user.LookupGroup(groupName)
Expand Down
Loading

0 comments on commit 9262a39

Please sign in to comment.