Repository: xanzy/go-gitlab
Pull request #2012 (2024-09-12 08:21:38)

Add ListServiceAccounts function to list all service accounts
First try to contribute to this awesome project. 

Implementing ListServiceAccounts from https://docs.gitlab.com/ee/api/user_service_accounts.html#list-all-service-account-users

this is pretty straight forward implementation, only once concern i have, as GroupsService also has same functionality, but just for group service accounts.
Therefore reused ListServiceAccountOptions as they are same. If i would change this, then it could break already existing implementations.

this could fix: https://github.com/xanzy/go-gitlab/issues/2011


Patch:
diff --git a/users.go b/users.go
index f463952..f856678 100644
--- a/users.go
+++ b/users.go
@@ -61,6 +61,16 @@ type BasicUser struct {
 	WebURL    string     `json:"web_url"`
 }
 
+// ServiceAccount represents a GitLab service account.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/user_service_accounts.html
+type ServiceAccount struct {
+	ID       int    `json:"id"`
+	Username string `json:"username"`
+	Name     string `json:"name"`
+}
+
 // User represents a GitLab user.
 //
 // GitLab API docs: https://docs.gitlab.com/ee/api/users.html
@@ -1543,9 +1553,10 @@ func (s *UsersService) CreateUserRunner(opts *CreateUserRunnerOptions, options .
 	return r, resp, nil
 }
 
-// CreateServiceAccountUser creates a new service account user. Note only administrators can create new service account users.
+// CreateServiceAccountUser creates a new service account user.
 //
-// GitLab API docs: https://docs.gitlab.com/ee/api/users.html#create-service-account-user
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/users.html#create-service-account-user
 func (s *UsersService) CreateServiceAccountUser(options ...RequestOptionFunc) (*User, *Response, error) {
 	req, err := s.client.NewRequest(http.MethodPost, "service_accounts", nil, options)
 	if err != nil {
@@ -1561,6 +1572,25 @@ func (s *UsersService) CreateServiceAccountUser(options ...RequestOptionFunc) (*
 	return usr, resp, nil
 }
 
+// ListServiceAccounts lists all service accounts.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/users.html#create-service-account-user
+func (s *UsersService) ListServiceAccounts(opt *ListServiceAccountsOptions, options ...RequestOptionFunc) ([]*ServiceAccount, *Response, error) {
+	req, err := s.client.NewRequest(http.MethodGet, "service_accounts", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var sas []*ServiceAccount
+	resp, err := s.client.Do(req, &sas)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return sas, resp, nil
+}
+
 // UploadAvatar uploads an avatar to the current user.
 //
 // GitLab API docs:


Test patch:
diff --git a/testdata/get_serviceaccounts.json b/testdata/get_serviceaccounts.json
new file mode 100644
index 0000000..dc63ac4
--- /dev/null
+++ b/testdata/get_serviceaccounts.json
@@ -0,0 +1,12 @@
+[
+  {
+    "id": 114,
+    "username": "service_account_33",
+    "name": "Service account user"
+  },
+  {
+    "id": 137,
+    "username": "service_account_34",
+    "name": "john doe"
+  }
+]
diff --git a/users_test.go b/users_test.go
index 79cede7..2809592 100644
--- a/users_test.go
+++ b/users_test.go
@@ -893,3 +893,29 @@ func TestUploadAvatarUser(t *testing.T) {
 		t.Fatalf("Users.UploadAvatar returns an error: %v", err)
 	}
 }
+func TestListServiceAccounts(t *testing.T) {
+	mux, client := setup(t)
+
+	path := "/api/v4/service_accounts"
+
+	mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodGet)
+		mustWriteHTTPResponse(t, w, "testdata/get_serviceaccounts.json")
+	})
+
+	serviceaccounts, _, err := client.Users.ListServiceAccounts(&ListServiceAccountsOptions{})
+	require.NoError(t, err)
+	want := []*ServiceAccount{
+		{
+			ID:       114,
+			Username: "service_account_33",
+			Name:     "Service account user",
+		},
+		{
+			ID:       137,
+			Username: "service_account_34",
+			Name:     "john doe",
+		},
+	}
+	require.Equal(t, want, serviceaccounts)
+}