Verified Commit 59e65523 authored by Timo Furrer's avatar Timo Furrer Committed by GitLab
Browse files

Merge branch 'runner-controller-scopes' into 'main'

Implement endpoints for runner controller scopes

See merge request gitlab-org/api/client-go!2758
parents 80bf5c30 ad3c0939
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -124,6 +124,9 @@ linters:
        text: unused-parameter
        linters:
          - revive
      - path: ^testing/
        linters:
          - revive
    paths:
      - third_party$
      - builtin$
+2 −0
Original line number Diff line number Diff line
@@ -276,6 +276,7 @@ type Client struct {
	ResourceStateEvents              ResourceStateEventsServiceInterface
	ResourceWeightEvents             ResourceWeightEventsServiceInterface
	RunnerControllers                RunnerControllersServiceInterface
	RunnerControllerScopes           RunnerControllerScopesServiceInterface
	RunnerControllerTokens           RunnerControllerTokensServiceInterface
	Runners                          RunnersServiceInterface
	Search                           SearchServiceInterface
@@ -596,6 +597,7 @@ func NewAuthSourceClient(as AuthSource, options ...ClientOptionFunc) (*Client, e
	c.ResourceStateEvents = &ResourceStateEventsService{client: c}
	c.ResourceWeightEvents = &ResourceWeightEventsService{client: c}
	c.RunnerControllers = &RunnerControllersService{client: c}
	c.RunnerControllerScopes = &RunnerControllerScopesService{client: c}
	c.RunnerControllerTokens = &RunnerControllerTokensService{client: c}
	c.Runners = &RunnersService{client: c}
	c.Search = &SearchService{client: c}
+1 −0
Original line number Diff line number Diff line
@@ -141,6 +141,7 @@ var serviceMap = map[any]any{
	&ResourceMilestoneEventsService{}:          (*ResourceMilestoneEventsServiceInterface)(nil),
	&ResourceStateEventsService{}:              (*ResourceStateEventsServiceInterface)(nil),
	&ResourceWeightEventsService{}:             (*ResourceWeightEventsServiceInterface)(nil),
	&RunnerControllerScopesService{}:           (*RunnerControllerScopesServiceInterface)(nil),
	&RunnerControllerTokensService{}:           (*RunnerControllerTokensServiceInterface)(nil),
	&RunnerControllersService{}:                (*RunnerControllersServiceInterface)(nil),
	&RunnersService{}:                          (*RunnersServiceInterface)(nil),
+90 −0
Original line number Diff line number Diff line
package gitlab

import (
	"net/http"
	"time"
)

type (
	// RunnerControllerScopesServiceInterface handles communication with the
	// runner controller scopes related methods of the GitLab API. This is an
	// admin-only endpoint.
	//
	// Note: This API is experimental and may change or be removed in future versions.
	//
	// GitLab API docs: https://docs.gitlab.com/api/runner_controllers/#runner-controller-scopes
	RunnerControllerScopesServiceInterface interface {
		// ListRunnerControllerScopes lists all scopes for a specific runner
		// controller. This is an admin-only endpoint.
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/runner_controllers/#list-all-scopes-for-a-runner-controller
		ListRunnerControllerScopes(rid int64, options ...RequestOptionFunc) (*RunnerControllerScopes, *Response, error)
		// AddRunnerControllerInstanceScope adds an instance-level scope to a
		// runner controller. This is an admin-only endpoint.
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/runner_controllers/#add-instance-level-scope
		AddRunnerControllerInstanceScope(rid int64, options ...RequestOptionFunc) (*RunnerControllerInstanceLevelScoping, *Response, error)
		// RemoveRunnerControllerInstanceScope removes an instance-level scope
		// from a runner controller. This is an admin-only endpoint.
		//
		// GitLab API docs:
		// https://docs.gitlab.com/api/runner_controllers/#remove-instance-level-scope
		RemoveRunnerControllerInstanceScope(rid int64, options ...RequestOptionFunc) (*Response, error)
	}

	// RunnerControllerScopesService handles communication with the runner
	// controller scopes related methods of the GitLab API. This is an admin-only
	// endpoint.
	//
	// Note: This API is experimental and may change or be removed in future versions.
	//
	// GitLab API docs: https://docs.gitlab.com/api/runner_controllers/#runner-controller-scopes
	RunnerControllerScopesService struct {
		client *Client
	}
)

var _ RunnerControllerScopesServiceInterface = (*RunnerControllerScopesService)(nil)

// RunnerControllerInstanceLevelScoping represents an instance-level scoping
// for a GitLab runner controller.
//
// GitLab API docs: https://docs.gitlab.com/api/runner_controllers/#runner-controller-scopes
type RunnerControllerInstanceLevelScoping struct {
	CreatedAt *time.Time `json:"created_at"`
	UpdatedAt *time.Time `json:"updated_at"`
}

// RunnerControllerScopes represents all scopes configured for a GitLab runner
// controller.
//
// GitLab API docs: https://docs.gitlab.com/api/runner_controllers/#runner-controller-scopes
type RunnerControllerScopes struct {
	InstanceLevelScopings []*RunnerControllerInstanceLevelScoping `json:"instance_level_scopings"`
}

func (s *RunnerControllerScopesService) ListRunnerControllerScopes(rid int64, options ...RequestOptionFunc) (*RunnerControllerScopes, *Response, error) {
	return do[*RunnerControllerScopes](s.client,
		withPath("runner_controllers/%d/scopes", rid),
		withRequestOpts(options...),
	)
}

func (s *RunnerControllerScopesService) AddRunnerControllerInstanceScope(rid int64, options ...RequestOptionFunc) (*RunnerControllerInstanceLevelScoping, *Response, error) {
	return do[*RunnerControllerInstanceLevelScoping](s.client,
		withMethod(http.MethodPost),
		withPath("runner_controllers/%d/scopes/instance", rid),
		withRequestOpts(options...),
	)
}

func (s *RunnerControllerScopesService) RemoveRunnerControllerInstanceScope(rid int64, options ...RequestOptionFunc) (*Response, error) {
	_, resp, err := do[none](s.client,
		withMethod(http.MethodDelete),
		withPath("runner_controllers/%d/scopes/instance", rid),
		withRequestOpts(options...),
	)
	return resp, err
}
+111 −0
Original line number Diff line number Diff line
package gitlab

import (
	"fmt"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestListRunnerControllerScopes(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)

	// GIVEN a runner controller with an instance-level scope exists
	mux.HandleFunc("/api/v4/runner_controllers/1/scopes", func(w http.ResponseWriter, r *http.Request) {
		// WHEN listing scopes for the runner controller
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, `{
			"instance_level_scopings": [
				{
					"created_at": "2026-01-01T00:00:00.000Z",
					"updated_at": "2026-01-01T00:00:00.000Z"
				}
			]
		}`)
	})

	scopes, _, err := client.RunnerControllerScopes.ListRunnerControllerScopes(1)
	assert.NoError(t, err)

	// THEN the scopes are returned with instance-level scopings
	want := &RunnerControllerScopes{
		InstanceLevelScopings: []*RunnerControllerInstanceLevelScoping{
			{
				CreatedAt: Ptr(time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)),
				UpdatedAt: Ptr(time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)),
			},
		},
	}
	assert.Equal(t, want, scopes)
}

func TestListRunnerControllerScopes_Empty(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)

	// GIVEN a runner controller with no scopes exists
	mux.HandleFunc("/api/v4/runner_controllers/1/scopes", func(w http.ResponseWriter, r *http.Request) {
		// WHEN listing scopes for the runner controller
		testMethod(t, r, http.MethodGet)
		fmt.Fprint(w, `{
			"instance_level_scopings": []
		}`)
	})

	scopes, _, err := client.RunnerControllerScopes.ListRunnerControllerScopes(1)
	assert.NoError(t, err)

	// THEN empty instance_level_scopings array is returned
	want := &RunnerControllerScopes{
		InstanceLevelScopings: []*RunnerControllerInstanceLevelScoping{},
	}
	assert.Equal(t, want, scopes)
}

func TestAddRunnerControllerInstanceScope(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)

	// GIVEN a runner controller without an instance-level scope exists
	mux.HandleFunc("/api/v4/runner_controllers/1/scopes/instance", func(w http.ResponseWriter, r *http.Request) {
		// WHEN adding an instance-level scope
		testMethod(t, r, http.MethodPost)
		w.WriteHeader(http.StatusCreated)
		fmt.Fprint(w, `{
			"created_at": "2026-01-01T00:00:00.000Z",
			"updated_at": "2026-01-01T00:00:00.000Z"
		}`)
	})

	scoping, resp, err := client.RunnerControllerScopes.AddRunnerControllerInstanceScope(1)
	assert.NoError(t, err)
	assert.Equal(t, http.StatusCreated, resp.StatusCode)

	// THEN the created instance-level scoping is returned
	want := &RunnerControllerInstanceLevelScoping{
		CreatedAt: Ptr(time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)),
		UpdatedAt: Ptr(time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC)),
	}
	assert.Equal(t, want, scoping)
}

func TestRemoveRunnerControllerInstanceScope(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)

	// GIVEN a runner controller with an instance-level scope exists
	mux.HandleFunc("/api/v4/runner_controllers/1/scopes/instance", func(w http.ResponseWriter, r *http.Request) {
		// WHEN removing the instance-level scope
		testMethod(t, r, http.MethodDelete)
		w.WriteHeader(http.StatusNoContent)
	})

	resp, err := client.RunnerControllerScopes.RemoveRunnerControllerInstanceScope(1)
	assert.NoError(t, err)

	// THEN 204 No Content is returned
	assert.Equal(t, http.StatusNoContent, resp.StatusCode)
}
Loading