Some checks failed
Native Verification / Build Docs (pull_request) Successful in 1m12s
Native Verification / Build App-Service Native (pull_request) Successful in 1m27s
Native Verification / Build Daemon Native (pull_request) Successful in 42s
Lint and Test Charts / lint-test (pull_request_target) Failing after 19s
Lint and Test Charts / test-version (pull_request_target) Successful in 0s
Lint and Test Charts / push-image (pull_request_target) Failing after 15s
Lint and Test Charts / upload-cli (pull_request_target) Failing after 1m15s
Lint and Test Charts / upload-daemon (pull_request_target) Failing after 1m12s
Lint and Test Charts / push-deps (pull_request_target) Has been skipped
Lint and Test Charts / push-deps-arm64 (pull_request_target) Has been skipped
Lint and Test Charts / push-image-arm64 (pull_request_target) Has been cancelled
Lint and Test Charts / upload-package (pull_request_target) Has been cancelled
Lint and Test Charts / install-test (pull_request_target) Has been cancelled
194 lines
5.5 KiB
Go
194 lines
5.5 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
|
|
"github.com/beclab/beos/cli/pkg/utils"
|
|
"github.com/beclab/beos/framework/app-service/api/sys.bytetrade.io/v1alpha1"
|
|
"github.com/pkg/errors"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/validation"
|
|
|
|
iamv1alpha2 "github.com/beclab/api/iam/v1alpha2"
|
|
"github.com/spf13/cobra"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
type createUserOptions struct {
|
|
name string
|
|
displayName string
|
|
domain string
|
|
role string
|
|
resourceLimit
|
|
password string
|
|
description string
|
|
kubeConfig string
|
|
}
|
|
|
|
func NewCmdCreateUser() *cobra.Command {
|
|
o := &createUserOptions{}
|
|
cmd := &cobra.Command{
|
|
Use: "create {name}",
|
|
Aliases: []string{"add", "new"},
|
|
Short: "create a new user",
|
|
Args: cobra.ExactArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
o.name = args[0]
|
|
if err := o.Validate(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if err := o.Run(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
},
|
|
}
|
|
o.AddFlags(cmd)
|
|
return cmd
|
|
}
|
|
|
|
func (o *createUserOptions) AddFlags(cmd *cobra.Command) {
|
|
cmd.Flags().StringVar(&o.displayName, "display-name", "", "display name (optional)")
|
|
cmd.Flags().StringVar(&o.domain, "domain", "", "domain (optional, defaults to the beOS Pro system's domain)")
|
|
cmd.Flags().StringVarP(&o.role, "role", "r", "normal", "owner role (optional, one of owner, admin, normal)")
|
|
cmd.Flags().StringVarP(&o.memoryLimit, "memory-limit", "m", defaultMemoryLimit, "memory limit (optional)")
|
|
cmd.Flags().StringVarP(&o.cpuLimit, "cpu-limit", "c", defaultCPULimit, "cpu limit (optional)")
|
|
cmd.Flags().StringVarP(&o.password, "password", "p", "", "initial password (optional)")
|
|
cmd.Flags().StringVar(&o.description, "description", "", "user description (optional)")
|
|
cmd.Flags().StringVar(&o.kubeConfig, "kubeconfig", "", "path to kubeconfig file (optional)")
|
|
}
|
|
|
|
func (o *createUserOptions) Validate() error {
|
|
if o.name == "" {
|
|
return fmt.Errorf("name is required")
|
|
}
|
|
|
|
if errs := validation.IsDNS1123Subdomain(o.name); len(errs) > 0 {
|
|
return fmt.Errorf("invalid name: %s", strings.Join(errs, ","))
|
|
}
|
|
|
|
if o.domain != "" {
|
|
if errs := validation.IsDNS1123Subdomain(o.domain); len(errs) > 0 {
|
|
return fmt.Errorf("invalid domain: %s", strings.Join(errs, ","))
|
|
}
|
|
if len(strings.Split(o.domain, ".")) < 2 {
|
|
return errors.New("invalid domain: should be a domain with at least two segments separated by dots")
|
|
}
|
|
for _, label := range strings.Split(o.domain, ".") {
|
|
if errs := validation.IsDNS1123Label(label); len(errs) > 0 {
|
|
return fmt.Errorf("invalid domain: %s", strings.Join(errs, ","))
|
|
}
|
|
}
|
|
}
|
|
|
|
if o.role != "" {
|
|
if o.role != roleOwner && o.role != roleAdmin && o.role != roleNormal {
|
|
return fmt.Errorf("invalid role: should be one of owner, admin, or normal")
|
|
}
|
|
}
|
|
|
|
if err := validateResourceLimit(o.resourceLimit); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (o *createUserOptions) Run() error {
|
|
ctx := context.Background()
|
|
userClient, err := newUserClientFromKubeConfig(o.kubeConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if o.memoryLimit == "" {
|
|
o.memoryLimit = defaultMemoryLimit
|
|
}
|
|
|
|
if o.cpuLimit == "" {
|
|
o.cpuLimit = defaultCPULimit
|
|
}
|
|
|
|
if o.domain == "" {
|
|
var system v1alpha1.Terminus
|
|
err := userClient.Get(ctx, types.NamespacedName{Name: systemObjectName}, &system)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get system info: %v", err)
|
|
}
|
|
o.domain = system.Spec.Settings[systemObjectDomainKey]
|
|
}
|
|
|
|
var userList iamv1alpha2.UserList
|
|
err = userClient.List(ctx, &userList)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list users to set creator: %w", err)
|
|
}
|
|
var owners []iamv1alpha2.User
|
|
for _, user := range userList.Items {
|
|
if role, ok := user.Annotations[annotationKeyRole]; ok && role == roleOwner {
|
|
owners = append(owners, user)
|
|
}
|
|
}
|
|
if len(owners) > 1 {
|
|
fmt.Printf("Warning: multiple owners found\n")
|
|
}
|
|
if o.role == roleOwner && len(owners) > 0 {
|
|
return fmt.Errorf("an owner '%s' already exists", owners[0].Name)
|
|
}
|
|
|
|
if o.password == "" {
|
|
password, passwordEncrypted, err := utils.GenerateEncryptedPassword(8)
|
|
if err != nil {
|
|
return fmt.Errorf("error generating password: %v", err)
|
|
}
|
|
log.Println("generated initial password:", password)
|
|
o.password = passwordEncrypted
|
|
} else {
|
|
o.password = utils.MD5(o.password + "@beOSPro2025")
|
|
}
|
|
|
|
olaresName := fmt.Sprintf("%s@%s", o.name, o.domain)
|
|
|
|
user := &iamv1alpha2.User{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: iamv1alpha2.SchemeGroupVersion.String(),
|
|
Kind: iamv1alpha2.ResourceKindUser,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: o.name,
|
|
Annotations: map[string]string{
|
|
"bytetrade.io/creator": creatorCLI,
|
|
annotationKeyRole: o.role,
|
|
"bytetrade.io/is-ephemeral": "true",
|
|
"bytetrade.io/terminus-name": olaresName,
|
|
"bytetrade.io/launcher-auth-policy": "two_factor",
|
|
"bytetrade.io/launcher-access-level": "1",
|
|
annotationKeyMemoryLimit: o.memoryLimit,
|
|
annotationKeyCPULimit: o.cpuLimit,
|
|
"iam.kubesphere.io/sync-to-lldap": "true",
|
|
"iam.kubesphere.io/synced-to-lldap": "false",
|
|
},
|
|
},
|
|
Spec: iamv1alpha2.UserSpec{
|
|
DisplayName: o.displayName,
|
|
Email: olaresName,
|
|
InitialPassword: o.password,
|
|
Description: o.description,
|
|
},
|
|
}
|
|
|
|
if o.role == roleOwner || o.role == roleAdmin {
|
|
user.Spec.Groups = append(user.Spec.Groups, lldapGroupAdmin)
|
|
}
|
|
|
|
err = userClient.Create(ctx, user)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create user: %w", err)
|
|
}
|
|
|
|
fmt.Printf("User '%s' created successfully\n", o.name)
|
|
return nil
|
|
}
|