diff --git a/cmd/dbaas_acl_create.go b/cmd/dbaas_acl_create.go index 1d619dd0..3bba8ef5 100644 --- a/cmd/dbaas_acl_create.go +++ b/cmd/dbaas_acl_create.go @@ -1 +1,95 @@ package cmd + +import ( + "context" + "fmt" + "github.com/exoscale/cli/pkg/globalstate" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +type dbaasAclCreateCmd struct { + cliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"create"` + Name string `cli-flag:"name" cli-usage:"Name of the DBaaS service"` + Username string `cli-flag:"username" cli-usage:"Username for the ACL entry"` + ServiceType string `cli-flag:"type" cli-short:"t" cli-usage:"Type of the DBaaS service (e.g., kafka opensearch)"` + Pattern string `cli-flag:"pattern" cli-usage:"The pattern for the ACL rule (index* for OpenSearch or topic for Kafka, max 249 characters)"` + Permission string `cli-flag:"permission" cli-usage:"Permission to apply (should be one of admin, read, readwrite, write, or deny (only for OpenSearch))"` +} + +// Command aliases (none in this case) +func (c *dbaasAclCreateCmd) cmdAliases() []string { return nil } + +// Short description for the command +func (c *dbaasAclCreateCmd) cmdShort() string { + return "Create an ACL entry for a DBaaS service" +} + +// Long description for the command +func (c *dbaasAclCreateCmd) cmdLong() string { + return `This command creates an ACL entry for a specified DBaaS service, automatically searching for the service across all available zones.` +} + +func (c *dbaasAclCreateCmd) cmdPreRun(cmd *cobra.Command, args []string) error { + return cliCommandDefaultPreRun(c, cmd, args) +} + +// Main run logic for showing ACL details +func (c *dbaasAclCreateCmd) cmdRun(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + // Validate required inputs + if c.Name == "" || c.Username == "" || c.ServiceType == "" || c.Permission == "" || c.Pattern == "" { + return fmt.Errorf("all --name, --username, --type, --permission and --pattern flags must be specified") + } + + // Fetch all available zones + zones, err := globalstate.EgoscaleV3Client.ListZones(ctx) + if err != nil { + return fmt.Errorf("error fetching zones: %w", err) + } + + // Iterate through zones to find the service + var serviceZone string + var dbType v3.DBAASDatabaseName + var client *v3.Client + found := false + + for _, zone := range zones.Zones { + db, err := dbaasGetV3(ctx, c.Name, string(zone.Name)) + if err == nil { + dbType = v3.DBAASDatabaseName(db.Type) + serviceZone = string(zone.Name) + client, err = switchClientZoneV3(ctx, globalstate.EgoscaleV3Client, v3.ZoneName(serviceZone)) + if err != nil { + return fmt.Errorf("error initializing client for zone %s: %w", serviceZone, err) + } + found = true + break + } + } + + if !found { + return fmt.Errorf("service %q not found in any zone", c.Name) + } + // Validate the service type + if string(dbType) != c.ServiceType { + return fmt.Errorf("service type mismatch: expected %q but got %q for service %q", c.ServiceType, dbType, c.Name) + } + + switch dbType { + case "kafka": + return c.createKafka(ctx, client, c.Name) + case "opensearch": + return c.createOpensearch(ctx, client, c.Name) + default: + return fmt.Errorf("create ACL unsupported for service type %q", dbType) + } +} +func init() { + cobra.CheckErr(registerCLICommand(dbaasAclCmd, &dbaasAclCreateCmd{ + cliCommandSettings: defaultCLICmdSettings(), + })) +} diff --git a/cmd/dbaas_acl_create_kafka.go b/cmd/dbaas_acl_create_kafka.go new file mode 100644 index 00000000..573aaa53 --- /dev/null +++ b/cmd/dbaas_acl_create_kafka.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "context" + "fmt" + + v3 "github.com/exoscale/egoscale/v3" +) + +func (c *dbaasAclCreateCmd) createKafka(ctx context.Context, client *v3.Client, serviceName string) error { + // Define the new Kafka ACL entry + newAcl := v3.DBAASKafkaTopicAclEntry{ + Username: c.Username, + Topic: c.Pattern, + Permission: v3.DBAASKafkaTopicAclEntryPermission(c.Permission), + } + + // Trigger the creation of the ACL entry + op, err := client.CreateDBAASKafkaTopicAclConfig(ctx, serviceName, newAcl) + if err != nil { + return fmt.Errorf("error creating ACL entry for service %q: %w", serviceName, err) + } + + // Use decorateAsyncOperation to handle the operation and provide user feedback + decorateAsyncOperation(fmt.Sprintf("Creating Kafka ACL entry for user %q", c.Username), func() { + op, err = client.Wait(ctx, op, v3.OperationStateSuccess) + }) + + if err != nil { + return fmt.Errorf("error completing ACL creation: %w", err) + } + + fmt.Printf("Kafka ACL entry for user %q successfully created in service %q\n", c.Username, serviceName) + return nil +} diff --git a/cmd/dbaas_acl_create_opensearch.go b/cmd/dbaas_acl_create_opensearch.go new file mode 100644 index 00000000..abc4d0d1 --- /dev/null +++ b/cmd/dbaas_acl_create_opensearch.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "context" + "fmt" + v3 "github.com/exoscale/egoscale/v3" +) + +func (c *dbaasAclCreateCmd) createOpensearch(ctx context.Context, client *v3.Client, serviceName string) error { + aclsConfig, err := client.GetDBAASOpensearchAclConfig(ctx, serviceName) + if err != nil { + return fmt.Errorf("error fetching ACL configuration for service %q: %w", serviceName, err) + } + + // Check if an entry with the same username already exists + for _, acl := range aclsConfig.Acls { + if string(acl.Username) == c.Username { + return fmt.Errorf("ACL entry for username %q already exists in service %q", c.Username, serviceName) + } + } + + // Create a new ACL entry + newAcl := v3.DBAASOpensearchAclConfigAcls{ + Username: v3.DBAASUserUsername(c.Username), + Rules: []v3.DBAASOpensearchAclConfigAclsRules{ + {Index: c.Pattern, Permission: v3.EnumOpensearchRulePermission(c.Permission)}, + }, + } + + // Append the new entry to the existing ACLs + aclsConfig.Acls = append(aclsConfig.Acls, newAcl) + + // Update the configuration with the new entry + op, err := client.UpdateDBAASOpensearchAclConfig(ctx, serviceName, *aclsConfig) + if err != nil { + return fmt.Errorf("error updating ACL configuration for service %q: %w", serviceName, err) + } + + // Use decorateAsyncOperation to wait for the operation and provide user feedback + decorateAsyncOperation(fmt.Sprintf("Creating ACL entry for user %q", c.Username), func() { + op, err = client.Wait(ctx, op, v3.OperationStateSuccess) + }) + + fmt.Printf("ACL entry for username %q created successfully in service %q\n", c.Username, serviceName) + return nil +}