Skip to content

Commit

Permalink
Removed the need to use METALCLOUD_ADMIN and METALCLOUD_USER_EMAIL
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandrubordei committed Aug 13, 2024
2 parents f6cbc4e + 5a7a622 commit 363f3e8
Show file tree
Hide file tree
Showing 34 changed files with 2,546 additions and 2,294 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ Configure credentials as environment variables:
```bash
export METALCLOUD_API_KEY="<your key>"
export METALCLOUD_ENDPOINT="https://api.bigstep.com"
export METALCLOUD_USER_EMAIL="<your email>"
```

### Getting a list of supported commands
Expand Down Expand Up @@ -168,10 +167,7 @@ Some commands depend on various permissions. For instance you cannot access anot

### Admin commands

To enable admin commands set the following environment variable:
```bash
export METALCLOUD_ADMIN="true"
```
If the user has the "admin_view" permission, additional commands will be visible.

## Debugging information

Expand Down
60 changes: 38 additions & 22 deletions cmd/metalcloud-cli/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"regexp"
"slices"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -53,10 +54,6 @@ func initClients() (map[string]metalcloud.MetalCloudClient, error) {

for clientName, suffix := range endpointSuffixes {

if (clientName == configuration.DeveloperEndpoint || clientName == configuration.ExtendedEndpoint) && !configuration.IsAdmin() {
continue
}

client, err := initClient(suffix)
if err != nil {
return nil, err
Expand All @@ -66,6 +63,20 @@ func initClients() (map[string]metalcloud.MetalCloudClient, error) {
return clients, nil
}

func getUser(userId int, client metalcloud.MetalCloudClient) (*metalcloud.User, error) {
return client.UserGet(userId)
}

func getUserPermissions(userId int, client metalcloud.MetalCloudClient) ([]string, error) {

user, err := getUser(userId, client)
if err != nil {
return []string{}, err
}

return (*user).UserPermissions, nil
}

func getUserIdFromAPIKey(apiKey string) (int, error) {
components := strings.Split(apiKey, ":")
if len(components) != 2 {
Expand All @@ -80,10 +91,6 @@ func initClient(endpointSuffix string) (metalcloud.MetalCloudClient, error) {
return nil, fmt.Errorf("METALCLOUD_API_KEY must be set")
}

if v := os.Getenv("METALCLOUD_USER_EMAIL"); v == "" {
return nil, fmt.Errorf("METALCLOUD_USER_EMAIL must be set")
}

apiKey := os.Getenv("METALCLOUD_API_KEY")
err := validateAPIKey(apiKey)
if err != nil {
Expand Down Expand Up @@ -121,7 +128,7 @@ func initClient(endpointSuffix string) (metalcloud.MetalCloudClient, error) {
Endpoint: endpoint,
LoggingEnabled: isLoggingEnabled(),
InsecureSkipVerify: insecureSkipVerify,
User: os.Getenv("METALCLOUD_USER_EMAIL"),
User: "",
UserID: userId,
AuthenticationMethod: metalcloud.AuthMethodBearer,
Timeout: timeout,
Expand All @@ -130,9 +137,9 @@ func initClient(endpointSuffix string) (metalcloud.MetalCloudClient, error) {
return metalcloud.GetMetalcloudClientWithOptions(options)
}

func getHelp(clients map[string]metalcloud.MetalCloudClient) string {
func getHelp(clients map[string]metalcloud.MetalCloudClient, permissions []string) string {
var sb strings.Builder
cmds := getCommands(clients)
cmds := getCommands(clients, permissions)
for _, c := range cmds {
c.InitFunc(&c)
}
Expand All @@ -159,7 +166,7 @@ func validateAPIKey(apiKey string) error {
return nil
}

func getCommands(clients map[string]metalcloud.MetalCloudClient) []command.Command {
func getCommands(clients map[string]metalcloud.MetalCloudClient, permissions []string) []command.Command {
commands := [][]command.Command{
apply.ApplyCmds,
custom_isos.CustomISOCmds,
Expand Down Expand Up @@ -198,42 +205,51 @@ func getCommands(clients map[string]metalcloud.MetalCloudClient) []command.Comma

filteredCommands := []command.Command{}
for _, commandSet := range commands {
commands := fitlerCommandSet(commandSet, clients)
commands := fitlerCommandSet(commandSet, clients, permissions)
filteredCommands = append(filteredCommands, commands...)
}

return filteredCommands
}

// fitlerCommandSet Filters commands based on endpoint availability for client
func fitlerCommandSet(commandSet []command.Command, clients map[string]metalcloud.MetalCloudClient) []command.Command {
func fitlerCommandSet(commandSet []command.Command, clients map[string]metalcloud.MetalCloudClient, permissions []string) []command.Command {
filteredCommands := []command.Command{}
for _, command := range commandSet {
if endpointAvailableForCommand(command, clients) && commandVisibleForUser(command) {
if endpointAvailableForCommand(command, clients, permissions) && commandVisibleForUser(command, permissions) {

filteredCommands = append(filteredCommands, command)
}
}
return filteredCommands
}

// endpointAvailableForCommand Checks if the instantiated endpoint clients include the one needed for the command
func endpointAvailableForCommand(command command.Command, clients map[string]metalcloud.MetalCloudClient) bool {
if configuration.IsAdmin() {
return clients[command.AdminEndpoint] != nil
func endpointAvailableForCommand(c command.Command, clients map[string]metalcloud.MetalCloudClient, permissions []string) bool {
if slices.Contains(permissions, command.ADMIN_ACCESS) {

return clients[c.AdminEndpoint] != nil
}
return clients[command.Endpoint] != nil
return clients[c.Endpoint] != nil
}

// commandVisibleForUser returns true if the current user (which could be admin or not) has the ability to see the respective command
func commandVisibleForUser(command command.Command) bool {
if command.UserOnly && configuration.IsAdmin() {
func commandVisibleForUser(c command.Command, permissions []string) bool {

if c.UserOnly && slices.Contains(permissions, command.ADMIN_ACCESS) {
return false
}

if command.AdminOnly && !configuration.IsAdmin() {
if c.AdminOnly && !slices.Contains(permissions, command.ADMIN_ACCESS) {
return false
}

for _, permission := range c.PermissionsRequired {
if !slices.Contains(permissions, permission) {
return false
}
}

return true
}

Expand Down
12 changes: 8 additions & 4 deletions cmd/metalcloud-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,23 @@ func main() {
configuration.SetConsoleIOChannel(os.Stdin, os.Stdout)

clients, err := initClients()

if err != nil {
fmt.Fprintf(configuration.GetStdout(), "Could not initialize metal cloud client %s\n", err)
os.Exit(-1)
}

userId := clients[configuration.UserEndpoint].GetUserID()
permissions, err := getUserPermissions(userId, clients[configuration.UserEndpoint])

if len(os.Args) < 2 {
//fmt.Fprintf(GetStdout(), "Invalid command! Use 'help' for a list of commands.\n")
fmt.Fprintf(configuration.GetStdout(), "%s\n", getHelp(clients))
fmt.Fprintf(configuration.GetStdout(), "%s\n", getHelp(clients, permissions))
os.Exit(-1)
}

if os.Args[1] == "help" {
fmt.Fprintf(configuration.GetStdout(), "%s\n", getHelp(clients))
fmt.Fprintf(configuration.GetStdout(), "%s\n", getHelp(clients, permissions))
os.Exit(0)
}

Expand All @@ -40,9 +44,9 @@ func main() {

tableformatter.DefaultFoldAtLength = 1000

commands := getCommands(clients)
commands := getCommands(clients, permissions)

err = command.ExecuteCommand(os.Args, commands, clients)
err = command.ExecuteCommand(os.Args, commands, clients, permissions)

if err != nil {
fmt.Fprintf(configuration.GetStdout(), "%s\n", err)
Expand Down
46 changes: 20 additions & 26 deletions cmd/metalcloud-cli/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ func TestValidateAPIKey(t *testing.T) {
func TestInitClient(t *testing.T) {

envs := []string{
"METALCLOUD_USER_EMAIL",
"METALCLOUD_API_KEY",
"METALCLOUD_ENDPOINT",
}
Expand All @@ -62,8 +61,6 @@ func TestInitClient(t *testing.T) {
t.Errorf("Should have been able to test for missing env")
}

os.Setenv("METALCLOUD_USER_EMAIL", "user")

if _, err := initClient("METALCLOUD_ENDPOINT"); err == nil {
t.Errorf("Should have been able to test for missing env")
}
Expand Down Expand Up @@ -95,10 +92,8 @@ func TestInitClients(t *testing.T) {
RegisterTestingT(t)

envs := []string{
"METALCLOUD_USER_EMAIL",
"METALCLOUD_API_KEY",
"METALCLOUD_ENDPOINT",
"METALCLOUD_ADMIN",
}

currentEnvVals := map[string]string{}
Expand All @@ -109,22 +104,13 @@ func TestInitClients(t *testing.T) {
}
}

os.Setenv("METALCLOUD_USER_EMAIL", "user@user.com")
os.Setenv("METALCLOUD_API_KEY", fmt.Sprintf("%d:%s", rand.Intn(100), RandStringBytes(63)))
os.Setenv("METALCLOUD_ENDPOINT", "http://test1/1")

clients, err := initClients()
Expect(err).To(BeNil())
Expect(clients).To(Not(BeNil()))
Expect(clients[configuration.UserEndpoint]).To(Not(BeNil()))
Expect(clients[configuration.ExtendedEndpoint]).To(BeNil())
Expect(clients[configuration.DeveloperEndpoint]).To(BeNil())

os.Setenv("METALCLOUD_ADMIN", "true")

clients, err = initClients()
Expect(clients).To(Not(BeNil()))
Expect(clients[configuration.UserEndpoint]).To(Not(BeNil()))
Expect(clients[configuration.ExtendedEndpoint]).To(Not(BeNil()))
Expect(clients[configuration.DeveloperEndpoint]).To(Not(BeNil()))

Expand Down Expand Up @@ -169,15 +155,18 @@ func TestExecuteCommand(t *testing.T) {
configuration.UserEndpoint: client,
"": client,
}

permissions := []string{}

//check with wrong commands first, should return err
err := command.ExecuteCommand([]string{"", "test", "test"}, commands, clients)
err := command.ExecuteCommand([]string{"", "test", "test"}, commands, clients, permissions)
Expect(err).NotTo(BeNil())

execFuncExecuted = false
initFuncExecuted = false

//should execute stuff help and not return error
err = command.ExecuteCommand([]string{"", "s", "p"}, commands, clients)
err = command.ExecuteCommand([]string{"", "s", "p"}, commands, clients, permissions)
Expect(err).To(BeNil())
Expect(execFuncExecuted).To(BeTrue())
Expect(initFuncExecuted).To(BeTrue())
Expand All @@ -186,15 +175,15 @@ func TestExecuteCommand(t *testing.T) {
initFuncExecuted = false

//should execute stuff help and not return error
err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients)
err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients, permissions)
Expect(err).To(BeNil())
Expect(execFuncExecuted).To(BeTrue())
Expect(initFuncExecuted).To(BeTrue())
Expect(execFuncExecutedOnDeveloperEndpoint).To(BeFalse())

//should refuse to execute call on unset endpoint
commands[0].Endpoint = configuration.DeveloperEndpoint
err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients)
err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients, permissions)
Expect(err).NotTo(BeNil())

//check with correct endpoint
Expand All @@ -204,12 +193,12 @@ func TestExecuteCommand(t *testing.T) {
//should execute the call if endoint set, on the right endpoint
clients[configuration.DeveloperEndpoint] = devClient

err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients)
err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients, permissions)
Expect(err).To(BeNil())
Expect(execFuncExecutedOnDeveloperEndpoint).To(BeTrue())

//should show list of possible predicates if correct subject provided
err = command.ExecuteCommand([]string{"", "tests"}, commands, clients)
err = command.ExecuteCommand([]string{"", "tests"}, commands, clients, permissions)
Expect(err).NotTo(BeNil())
Expect(err.Error()).To(ContainSubstring("testp"))
Expect(execFuncExecuted).To(BeTrue())
Expand All @@ -222,7 +211,7 @@ func TestExecuteCommand(t *testing.T) {
// but subject has nil predicate
commands[0].Predicate = command.NilDefaultStr
devClient.EXPECT().GetEndpoint().Return("developer").Times(1)
err = command.ExecuteCommand([]string{"", "tests"}, commands, clients)
err = command.ExecuteCommand([]string{"", "tests"}, commands, clients, permissions)
Expect(err).To(BeNil())
Expect(execFuncExecuted).To(BeTrue())
Expect(initFuncExecuted).To(BeTrue())
Expand All @@ -237,7 +226,7 @@ func TestExecuteCommand(t *testing.T) {

devClient.EXPECT().GetEndpoint().Return("developer").Times(1)

err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients)
err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients, permissions)
Expect(err).To(BeNil())
Expect(execFuncExecuted).To(BeTrue())
Expect(initFuncExecuted).To(BeTrue())
Expand All @@ -256,7 +245,7 @@ func TestExecuteCommand(t *testing.T) {
commands[0].AdminEndpoint = ""
commands[0].Endpoint = configuration.UserEndpoint

err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients)
err = command.ExecuteCommand([]string{"", "tests", "testp"}, commands, clients, permissions)
Expect(err).To(BeNil())
Expect(execFuncExecuted).To(BeTrue())
Expect(initFuncExecuted).To(BeTrue())
Expand Down Expand Up @@ -299,9 +288,12 @@ func TestGetHelp(t *testing.T) {
clients := map[string]metalcloud.MetalCloudClient{
"": client,
}
cmds := getCommands(clients)

s := getHelp(clients)
permissions := []string{}

cmds := getCommands(clients, permissions)

s := getHelp(clients, permissions)
for _, c := range cmds {
Expect(s).To(ContainSubstring(c.Description))
}
Expand Down Expand Up @@ -363,7 +355,9 @@ func TestCheckForDuplicates(t *testing.T) {
"": client,
}

commands = getCommands(clients)
permissions := []string{}

commands = getCommands(clients, permissions)

for i := 0; i < len(commands); i++ {
for j := i + 1; j < len(commands); j++ {
Expand Down
2 changes: 1 addition & 1 deletion examples/subnet.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
datacenter: ro-bucharest
datacenter: us-chi-qts01-dc
prefix: 100.255.226.0
netmask: 255.255.255.0
size: 24
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/golang/mock v1.6.0
github.com/jwalton/gchalk v1.3.0
github.com/kdomanski/iso9660 v0.3.1
github.com/metalsoft-io/metal-cloud-sdk-go/v3 v3.1.4
github.com/metalsoft-io/metal-cloud-sdk-go/v3 v3.2.0
github.com/metalsoft-io/tableformatter v1.0.9
github.com/onsi/gomega v1.27.10
github.com/savaki/jq v0.0.0-20161209013833-0e6baecebbf8
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/metalsoft-io/metal-cloud-sdk-go/v3 v3.1.4 h1:om+bVw1QMAwESITnsshRLoJsQVaq4L1pbYzJsV4aatM=
github.com/metalsoft-io/metal-cloud-sdk-go/v3 v3.1.4/go.mod h1:dqfX7h4uUXKZb8zAJOL5HCL56eQ4xpBEp29ibqhkXLI=
github.com/metalsoft-io/metal-cloud-sdk-go/v3 v3.2.0 h1:lizLoJD+8kveWUud4CpgFZjfk0uH9R6fbQCzWduSX8s=
github.com/metalsoft-io/metal-cloud-sdk-go/v3 v3.2.0/go.mod h1:dqfX7h4uUXKZb8zAJOL5HCL56eQ4xpBEp29ibqhkXLI=
github.com/metalsoft-io/tableformatter v1.0.9 h1:8BvMqrFLI9nfdJWjK69jEZRPxoz1qjhb/0ru1XsjQhw=
github.com/metalsoft-io/tableformatter v1.0.9/go.mod h1:4ldLr91sWT/bIRTJlArzw+FfKkBMQ5IbuuyL8IZKf/Q=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
Expand Down
Loading

0 comments on commit 363f3e8

Please sign in to comment.