// Copyright Earl Warren // Copyright Loïc Dachary // Copyright twenty-panda // SPDX-License-Identifier: MIT package gitlab import ( "context" "fmt" "net/http" "time" "code.forgejo.org/f3/gof3/v3/f3" "code.forgejo.org/f3/gof3/v3/id" f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3" "code.forgejo.org/f3/gof3/v3/tree/generic" "code.forgejo.org/f3/gof3/v3/util" "gitlab.com/gitlab-org/api/client-go" ) type user struct { common gitlabUser *gitlab.User Password string } var _ f3_tree.ForgeDriverInterface = &user{} func newUser() generic.NodeDriverInterface { return &user{} } func (o *user) SetNative(user any) { o.gitlabUser = user.(*gitlab.User) } func (o *user) GetNativeID() string { return fmt.Sprintf("%d", o.gitlabUser.ID) } func (o *user) NewFormat() f3.Interface { node := o.GetNode() return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind()) } func (o *user) ToFormat() f3.Interface { if o.gitlabUser == nil { return o.NewFormat() } return &f3.User{ Common: f3.NewCommon(fmt.Sprintf("%d", o.gitlabUser.ID)), UserName: o.gitlabUser.Username, Name: o.gitlabUser.Name, Email: o.gitlabUser.Email, IsAdmin: o.gitlabUser.IsAdmin, Password: o.Password, } } func (o *user) FromFormat(content f3.Interface) { user := content.(*f3.User) o.gitlabUser = &gitlab.User{ ID: int(util.ParseInt(user.GetID())), Username: user.UserName, Name: user.Name, Email: user.Email, IsAdmin: user.IsAdmin, } o.Password = user.Password } func (o *user) Get(ctx context.Context) bool { node := o.GetNode() o.Trace("%s", node.GetID()) if node.GetID() == id.NilID { panic("GetID() == 0") } user, resp, err := o.getClient().Users.GetUser(node.GetID().Int(), gitlab.GetUsersOptions{}) if resp.StatusCode == http.StatusNotFound { return false } if err != nil { panic(fmt.Errorf("user %v %w", o, err)) } o.gitlabUser = user return true } func (o *user) Patch(context.Context) { } func (o *user) Put(context.Context) id.NodeID { skipConfirmation := true if o.Password == "" { o.Password = util.RandSeq(30) } u, _, err := o.getClient().Users.CreateUser(&gitlab.CreateUserOptions{ Username: &o.gitlabUser.Username, Name: &o.gitlabUser.Name, Email: &o.gitlabUser.Email, Password: &o.Password, Admin: &o.gitlabUser.IsAdmin, SkipConfirmation: &skipConfirmation, }) if err != nil { panic(fmt.Errorf("%v: %w", o.gitlabUser, err)) } o.gitlabUser = u o.Trace("%s %d", o.gitlabUser.Username, o.gitlabUser.ID) return id.NewNodeID(u.ID) } func (o *user) deleteUser(ctx context.Context) int { u := fmt.Sprintf("users/%d", o.gitlabUser.ID) type deleteUserOptions struct { HardDelete *bool `url:"hard_delete,omitempty" json:"hard_delete,omitempty"` } hardDelete := true req, err := o.getClient().NewRequest(http.MethodDelete, u, &deleteUserOptions{ HardDelete: &hardDelete, }, nil) if err != nil { panic(fmt.Errorf("NewRequest: %v %v: %w", o.gitlabUser.ID, o.gitlabUser.Username, err)) } resp, err := o.getClient().Do(req, nil) if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusNotFound { panic(fmt.Errorf("unexpected status code: %v %v: %v %w", o.gitlabUser.ID, o.gitlabUser.Username, resp.StatusCode, err)) } if resp.StatusCode != http.StatusNotFound && err != nil { panic(fmt.Errorf("Do: %v %v: %w", o.gitlabUser.ID, o.gitlabUser.Username, err)) } return resp.StatusCode } func (o *user) Delete(ctx context.Context) { o.Trace("%s %d", o.gitlabUser.Username, o.gitlabUser.ID) statusCode := o.deleteUser(ctx) if statusCode != http.StatusNoContent { panic(fmt.Errorf("expected user deletion to return 204 but got %d", statusCode)) } loop := 100 for i := 0; i < loop; i++ { if o.deleteUser(ctx) == http.StatusNotFound { o.Trace("user deletion complete") return } o.Trace("waiting for asynchronous user deletion (%d/%d)", i, loop) time.Sleep(5 * time.Second) } o.Trace("user still present after %d attempts", loop) }