From dc7c858e683fa85801d9d133c753983e9cb6e9fc Mon Sep 17 00:00:00 2001 From: Nikola Jokic Date: Mon, 16 Mar 2026 14:39:55 +0100 Subject: [PATCH] Remove actions client (#4405) --- .mockery.yaml | 9 - .../v1alpha1/tls_config_test.go | 105 -- cmd/ghalistener/config/config_client_test.go | 53 - .../ephemeralrunner_controller.go | 18 +- .../ephemeralrunner_controller_test.go | 8 +- .../ephemeralrunnerset_controller.go | 16 +- github/actions/{config.go => actions.go} | 0 github/actions/actions_server_test.go | 113 -- github/actions/byte_order_mark_test.go | 61 - github/actions/client.go | 1287 -------------- github/actions/client_generate_jit_test.go | 95 - github/actions/client_job_acquisition_test.go | 171 -- github/actions/client_proxy_test.go | 39 - .../client_runner_scale_set_message_test.go | 248 --- .../client_runner_scale_set_session_test.go | 220 --- .../actions/client_runner_scale_set_test.go | 424 ----- github/actions/client_runner_test.go | 218 --- github/actions/client_tls_test.go | 178 -- github/actions/config_test.go | 208 --- github/actions/errors.go | 125 -- github/actions/errors_test.go | 202 --- github/actions/fake/client.go | 286 --- github/actions/fake/multi_client.go | 40 - github/actions/github_api_request_test.go | 248 --- github/actions/identifier_test.go | 158 -- github/actions/mocks_test.go | 1539 ----------------- github/actions/multi_client.go | 102 -- github/actions/multi_client_test.go | 131 -- github/actions/sessionservice.go | 13 - github/actions/types.go | 158 -- github/actions/user_agent_test.go | 24 - 31 files changed, 8 insertions(+), 6489 deletions(-) delete mode 100644 apis/actions.github.com/v1alpha1/tls_config_test.go rename github/actions/{config.go => actions.go} (100%) delete mode 100644 github/actions/actions_server_test.go delete mode 100644 github/actions/byte_order_mark_test.go delete mode 100644 github/actions/client.go delete mode 100644 github/actions/client_generate_jit_test.go delete mode 100644 github/actions/client_job_acquisition_test.go delete mode 100644 github/actions/client_proxy_test.go delete mode 100644 github/actions/client_runner_scale_set_message_test.go delete mode 100644 github/actions/client_runner_scale_set_session_test.go delete mode 100644 github/actions/client_runner_scale_set_test.go delete mode 100644 github/actions/client_runner_test.go delete mode 100644 github/actions/client_tls_test.go delete mode 100644 github/actions/config_test.go delete mode 100644 github/actions/errors.go delete mode 100644 github/actions/errors_test.go delete mode 100644 github/actions/fake/client.go delete mode 100644 github/actions/fake/multi_client.go delete mode 100644 github/actions/github_api_request_test.go delete mode 100644 github/actions/identifier_test.go delete mode 100644 github/actions/mocks_test.go delete mode 100644 github/actions/multi_client.go delete mode 100644 github/actions/multi_client_test.go delete mode 100644 github/actions/sessionservice.go delete mode 100644 github/actions/types.go delete mode 100644 github/actions/user_agent_test.go diff --git a/.mockery.yaml b/.mockery.yaml index be39c018..4cffec64 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -9,15 +9,6 @@ pkgname: "{{.SrcPackageName}}" recursive: false template: testify packages: - github.com/actions/actions-runner-controller/github/actions: - config: - inpackage: true - dir: "{{.InterfaceDir}}" - filename: "mocks_test.go" - pkgname: "actions" - interfaces: - ActionsService: - SessionService: github.com/actions/actions-runner-controller/cmd/ghalistener/metrics: config: all: true diff --git a/apis/actions.github.com/v1alpha1/tls_config_test.go b/apis/actions.github.com/v1alpha1/tls_config_test.go deleted file mode 100644 index e05bf81b..00000000 --- a/apis/actions.github.com/v1alpha1/tls_config_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package v1alpha1_test - -import ( - "crypto/tls" - "crypto/x509" - "net/http" - "os" - "path/filepath" - "testing" - - "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" - "github.com/actions/actions-runner-controller/github/actions/testserver" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" -) - -func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) { - t.Run("returns an error if CertificateFrom not specified", func(t *testing.T) { - c := &v1alpha1.TLSConfig{ - CertificateFrom: nil, - } - - pool, err := c.ToCertPool(nil) - assert.Nil(t, pool) - - require.Error(t, err) - assert.Equal(t, err.Error(), "certificateFrom not specified") - }) - - t.Run("returns an error if CertificateFrom.ConfigMapKeyRef not specified", func(t *testing.T) { - c := &v1alpha1.TLSConfig{ - CertificateFrom: &v1alpha1.TLSCertificateSource{}, - } - - pool, err := c.ToCertPool(nil) - assert.Nil(t, pool) - - require.Error(t, err) - assert.Equal(t, err.Error(), "configMapKeyRef not specified") - }) - - t.Run("returns a valid cert pool with correct configuration", func(t *testing.T) { - c := &v1alpha1.TLSConfig{ - CertificateFrom: &v1alpha1.TLSCertificateSource{ - ConfigMapKeyRef: &v1.ConfigMapKeySelector{ - LocalObjectReference: v1.LocalObjectReference{ - Name: "name", - }, - Key: "key", - }, - }, - } - - certsFolder := filepath.Join( - "../../../", - "github", - "actions", - "testdata", - ) - - fetcher := func(name, key string) ([]byte, error) { - cert, err := os.ReadFile(filepath.Join(certsFolder, "rootCA.crt")) - require.NoError(t, err) - - pool := x509.NewCertPool() - ok := pool.AppendCertsFromPEM(cert) - assert.True(t, ok) - - return cert, nil - } - - pool, err := c.ToCertPool(fetcher) - require.NoError(t, err) - require.NotNil(t, pool) - - // can be used to communicate with a server - serverSuccessfullyCalled := false - server := testserver.NewUnstarted(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - serverSuccessfullyCalled = true - w.WriteHeader(http.StatusOK) - })) - - cert, err := tls.LoadX509KeyPair( - filepath.Join(certsFolder, "server.crt"), - filepath.Join(certsFolder, "server.key"), - ) - require.NoError(t, err) - - server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} - server.StartTLS() - - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: pool, - }, - }, - } - - _, err = client.Get(server.URL) - assert.NoError(t, err) - assert.True(t, serverSuccessfullyCalled) - }) -} diff --git a/cmd/ghalistener/config/config_client_test.go b/cmd/ghalistener/config/config_client_test.go index 797adf9c..2997b64f 100644 --- a/cmd/ghalistener/config/config_client_test.go +++ b/cmd/ghalistener/config/config_client_test.go @@ -1,18 +1,13 @@ package config_test import ( - "context" - "crypto/tls" "encoding/json" "log/slog" - "net/http" "os" - "path/filepath" "testing" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig" "github.com/actions/actions-runner-controller/cmd/ghalistener/config" - "github.com/actions/actions-runner-controller/github/actions/testserver" "github.com/actions/scaleset" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,54 +15,6 @@ import ( var discardLogger = slog.New(slog.DiscardHandler) -func TestCustomerServerRootCA(t *testing.T) { - ctx := context.Background() - certsFolder := filepath.Join( - "../../../", - "github", - "actions", - "testdata", - ) - certPath := filepath.Join(certsFolder, "server.crt") - keyPath := filepath.Join(certsFolder, "server.key") - - serverCalledSuccessfully := false - - server := testserver.NewUnstarted(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - serverCalledSuccessfully = true - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"count": 0}`)) - })) - cert, err := tls.LoadX509KeyPair(certPath, keyPath) - require.NoError(t, err) - - server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} - server.StartTLS() - - var certsString string - rootCA, err := os.ReadFile(filepath.Join(certsFolder, "rootCA.crt")) - require.NoError(t, err) - certsString = string(rootCA) - - intermediate, err := os.ReadFile(filepath.Join(certsFolder, "intermediate.crt")) - require.NoError(t, err) - certsString = certsString + string(intermediate) - - config := config.Config{ - ConfigureURL: server.ConfigURLForOrg("myorg"), - ServerRootCA: certsString, - AppConfig: &appconfig.AppConfig{ - Token: "token", - }, - } - - client, err := config.ActionsClient(discardLogger) - require.NoError(t, err) - _, err = client.GetRunnerScaleSet(ctx, 1, "test") - require.NoError(t, err) - assert.True(t, serverCalledSuccessfully) -} - func TestProxySettings(t *testing.T) { assertHasProxy := func(t *testing.T, debugInfo string, want bool) { type debugInfoContent struct { diff --git a/controllers/actions.github.com/ephemeralrunner_controller.go b/controllers/actions.github.com/ephemeralrunner_controller.go index b5e667ef..bb06790d 100644 --- a/controllers/actions.github.com/ephemeralrunner_controller.go +++ b/controllers/actions.github.com/ephemeralrunner_controller.go @@ -20,13 +20,11 @@ import ( "context" "errors" "fmt" - "net/http" "strconv" "strings" "time" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" - "github.com/actions/actions-runner-controller/github/actions" "github.com/actions/scaleset" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -410,12 +408,8 @@ func (r *EphemeralRunnerReconciler) deleteEphemeralRunnerOrPod(ctx context.Conte func (r *EphemeralRunnerReconciler) cleanupRunnerFromService(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (ok bool, err error) { if err := r.deleteRunnerFromService(ctx, ephemeralRunner, log); err != nil { - actionsError := &actions.ActionsError{} - if !errors.As(err, &actionsError) { - return false, err - } - - if actionsError.StatusCode == http.StatusBadRequest && actionsError.IsException("JobStillRunningException") { + if errors.Is(err, scaleset.JobStillRunningError) { + log.Info("Runner job is still running, cannot remove the runner from the service yet") return false, nil } @@ -625,16 +619,10 @@ func (r *EphemeralRunnerReconciler) createRunnerJitConfig(ctx context.Context, e return jitConfig, nil } - actionsError := &actions.ActionsError{} - if !errors.As(err, &actionsError) { + if !errors.Is(err, scaleset.RunnerExistsError) { return nil, fmt.Errorf("failed to generate JIT config with generic error: %w", err) } - if actionsError.StatusCode != http.StatusConflict || - !actionsError.IsException("AgentExistsException") { - return nil, fmt.Errorf("failed to generate JIT config with Actions service error: %w", err) - } - // If the runner with the name we want already exists it means: // - We might have a name collision. // - Our previous reconciliation loop failed to update the diff --git a/controllers/actions.github.com/ephemeralrunner_controller_test.go b/controllers/actions.github.com/ephemeralrunner_controller_test.go index 24d11e15..bbb87275 100644 --- a/controllers/actions.github.com/ephemeralrunner_controller_test.go +++ b/controllers/actions.github.com/ephemeralrunner_controller_test.go @@ -13,7 +13,6 @@ import ( "time" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" - "github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient" scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake" @@ -1113,12 +1112,7 @@ var _ = Describe("EphemeralRunner", func() { scalefake.NewClient( scalefake.WithGetRunner( nil, - &actions.ActionsError{ - StatusCode: http.StatusNotFound, - Err: &actions.ActionsExceptionError{ - ExceptionName: "AgentNotFoundException", - }, - }, + scaleset.RunnerNotFoundError, ), scalefake.WithGenerateJitRunnerConfig( &scaleset.RunnerScaleSetJitRunnerConfig{ diff --git a/controllers/actions.github.com/ephemeralrunnerset_controller.go b/controllers/actions.github.com/ephemeralrunnerset_controller.go index b1f06f1b..75c4667e 100644 --- a/controllers/actions.github.com/ephemeralrunnerset_controller.go +++ b/controllers/actions.github.com/ephemeralrunnerset_controller.go @@ -20,7 +20,6 @@ import ( "context" "errors" "fmt" - "net/http" "sort" "strconv" @@ -28,6 +27,7 @@ import ( "github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics" "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient" "github.com/actions/actions-runner-controller/github/actions" + "github.com/actions/scaleset" "github.com/go-logr/logr" "go.uber.org/multierr" corev1 "k8s.io/api/core/v1" @@ -48,9 +48,8 @@ const ( // EphemeralRunnerSetReconciler reconciles a EphemeralRunnerSet object type EphemeralRunnerSetReconciler struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme - ActionsClient actions.MultiClient + Log logr.Logger + Scheme *runtime.Scheme PublishMetrics bool @@ -484,14 +483,7 @@ func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Co func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, actionsClient multiclient.Client, log logr.Logger) (bool, error) { if err := actionsClient.RemoveRunner(ctx, int64(ephemeralRunner.Status.RunnerId)); err != nil { - actionsError := &actions.ActionsError{} - if !errors.As(err, &actionsError) { - log.Error(err, "failed to remove runner from the service", "name", ephemeralRunner.Name, "runnerId", ephemeralRunner.Status.RunnerId) - return false, err - } - - if actionsError.StatusCode == http.StatusBadRequest && - actionsError.IsException("JobStillRunningException") { + if errors.Is(err, scaleset.JobStillRunningError) { log.Info("Runner is still running a job, skipping deletion", "name", ephemeralRunner.Name, "runnerId", ephemeralRunner.Status.RunnerId) return false, nil } diff --git a/github/actions/config.go b/github/actions/actions.go similarity index 100% rename from github/actions/config.go rename to github/actions/actions.go diff --git a/github/actions/actions_server_test.go b/github/actions/actions_server_test.go deleted file mode 100644 index 6ce7e016..00000000 --- a/github/actions/actions_server_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package actions_test - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/golang-jwt/jwt/v4" - "github.com/stretchr/testify/require" -) - -// newActionsServer returns a new httptest.Server that handles the -// authentication requests neeeded to create a new client. Any requests not -// made to the /actions/runners/registration-token or -// /actions/runner-registration endpoints will be handled by the provided -// handler. The returned server is started and will be automatically closed -// when the test ends. -func newActionsServer(t *testing.T, handler http.Handler, options ...actionsServerOption) *actionsServer { - s := httptest.NewServer(nil) - server := &actionsServer{ - Server: s, - } - t.Cleanup(func() { - server.Close() - }) - - for _, option := range options { - option(server) - } - - h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // handle getRunnerRegistrationToken - if strings.HasSuffix(r.URL.Path, "/runners/registration-token") { - w.WriteHeader(http.StatusCreated) - w.Write([]byte(`{"token":"token"}`)) - return - } - - // handle getActionsServiceAdminConnection - if strings.HasSuffix(r.URL.Path, "/actions/runner-registration") { - if server.token == "" { - server.token = defaultActionsToken(t) - } - - w.Write([]byte(`{"url":"` + s.URL + `/tenant/123/","token":"` + server.token + `"}`)) - return - } - - handler.ServeHTTP(w, r) - }) - - server.Config.Handler = h - - return server -} - -type actionsServerOption func(*actionsServer) - -type actionsServer struct { - *httptest.Server - - token string -} - -func (s *actionsServer) configURLForOrg(org string) string { - return s.URL + "/" + org -} - -func defaultActionsToken(t *testing.T) string { - claims := &jwt.RegisteredClaims{ - IssuedAt: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute)), - ExpiresAt: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), - Issuer: "123", - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(samplePrivateKey)) - require.NoError(t, err) - tokenString, err := token.SignedString(privateKey) - require.NoError(t, err) - return tokenString -} - -const samplePrivateKey = `-----BEGIN PRIVATE KEY----- -MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQC7tgquvNIp+Ik3 -rRVZ9r0zJLsSzTHqr2dA6EUUmpRiQ25MzjMqKqu0OBwvh/pZyfjSIkKrhIridNK4 -DWnPfPWHE2K3Muh0X2sClxtqiiFmXsvbiTzhUm5a+zCcv0pJCWYnKi0HmyXpAXjJ -iN8mWliZN896verVYXWrod7EaAnuST4TiJeqZYW4bBBG81fPNc/UP4j6CKAW8nx9 -HtcX6ApvlHeCLZUTW/qhGLO0nLKoEOr3tXCPW5VjKzlm134Dl+8PN6f1wv6wMAoA -lo7Ha5+c74jhPL6gHXg7cRaHQmuJCJrtl8qbLkFAulfkBixBw/6i11xoM/MOC64l -TWmXqrxTAgMBAAECgf9zYlxfL+rdHRXCoOm7pUeSPL0dWaPFP12d/Z9LSlDAt/h6 -Pd+eqYEwhf795SAbJuzNp51Ls6LUGnzmLOdojKwfqJ51ahT1qbcBcMZNOcvtGqZ9 -xwLG993oyR49C361Lf2r8mKrdrR5/fW0B1+1s6A+eRFivqFOtsOc4V4iMeHYsCVJ -hM7yMu0UfpolDJA/CzopsoGq3UuQlibUEUxKULza06aDjg/gBH3PnP+fQ1m0ovDY -h0pX6SCq5fXVJFS+Pbpu7j2ePNm3mr0qQhrUONZq0qhGN/piCbBZe1CqWApyO7nA -B95VChhL1eYs1BKvQePh12ap83woIUcW2mJF2F0CgYEA+aERTuKWEm+zVNKS9t3V -qNhecCOpayKM9OlALIK/9W6KBS+pDsjQQteQAUAItjvLiDjd5KsrtSgjbSgr66IP -b615Pakywe5sdnVGzSv+07KMzuFob9Hj6Xv9als9Y2geVhUZB2Frqve/UCjmC56i -zuQTSele5QKCSSTFBV3423cCgYEAwIBv9ChsI+mse6vPaqSPpZ2n237anThMcP33 -aS0luYXqMWXZ0TQ/uSmCElY4G3xqNo8szzfy6u0HpldeUsEUsIcBNUV5kIIb8wKu -Zmgcc8gBIjJkyUJI4wuz9G/fegEUj3u6Cttmmj4iWLzCRscRJdfGpqwRIhOGyXb9 -2Rur5QUCgYAGWIPaH4R1H4XNiDTYNbdyvV1ZOG7cHFq89xj8iK5cjNzRWO7RQ2WX -7WbpwTj3ePmpktiBMaDA0C5mXfkP2mTOD/jfCmgR6f+z2zNbj9zAgO93at9+yDUl -AFPm2j7rQgBTa+HhACb+h6HDZebDMNsuqzmaTWZuJ+wr89VWV5c17QKBgH3jwNNQ -mCAIUidynaulQNfTOZIe7IMC7WK7g9CBmPkx7Y0uiXr6C25hCdJKFllLTP6vNWOy -uCcQqf8LhgDiilBDifO3op9xpyuOJlWMYocJVkxx3l2L/rSU07PYcbKNAFAxXuJ4 -xym51qZnkznMN5ei/CPFxVKeqHgaXDpekVStAoGAV3pSWAKDXY/42XEHixrCTqLW -kBxfaf3g7iFnl3u8+7Z/7Cb4ZqFcw0bRJseKuR9mFvBhcZxSErbMDEYrevefU9aM -APeCxEyw6hJXgbWKoG7Fw2g2HP3ytCJ4YzH0zNitHjk/1h4BG7z8cEQILCSv5mN2 -etFcaQuTHEZyRhhJ4BU= ------END PRIVATE KEY-----` diff --git a/github/actions/byte_order_mark_test.go b/github/actions/byte_order_mark_test.go deleted file mode 100644 index 107dd92a..00000000 --- a/github/actions/byte_order_mark_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package actions_test - -import ( - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestClient_Do(t *testing.T) { - t.Run("trims byte order mark from response if present", func(t *testing.T) { - t.Run("when there is no body", func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - })) - defer server.Close() - - client, err := actions.NewClient("https://localhost/org/repo", &actions.ActionsAuth{Token: "token"}) - require.NoError(t, err) - - req, err := http.NewRequest("GET", server.URL, nil) - require.NoError(t, err) - - resp, err := client.Do(req) - require.NoError(t, err) - - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) - assert.Empty(t, string(body)) - }) - - responses := []string{ - "\xef\xbb\xbf{\"foo\":\"bar\"}", - "{\"foo\":\"bar\"}", - } - - for _, response := range responses { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(response)) - })) - defer server.Close() - - client, err := actions.NewClient("https://localhost/org/repo", &actions.ActionsAuth{Token: "token"}) - require.NoError(t, err) - - req, err := http.NewRequest("GET", server.URL, nil) - require.NoError(t, err) - - resp, err := client.Do(req) - require.NoError(t, err) - - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\"}", string(body)) - } - }) -} diff --git a/github/actions/client.go b/github/actions/client.go deleted file mode 100644 index f2ebc251..00000000 --- a/github/actions/client.go +++ /dev/null @@ -1,1287 +0,0 @@ -package actions - -import ( - "bytes" - "context" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "encoding/json" - "errors" - "fmt" - "io" - "maps" - "math/rand" - "net/http" - "net/url" - "strconv" - "sync" - "time" - - "github.com/actions/actions-runner-controller/build" - "github.com/go-logr/logr" - "github.com/golang-jwt/jwt/v4" - "github.com/google/uuid" - "github.com/hashicorp/go-retryablehttp" -) - -const ( - runnerEndpoint = "_apis/distributedtask/pools/0/agents" - scaleSetEndpoint = "_apis/runtime/runnerscalesets" - apiVersionQueryParam = "api-version=6.0-preview" -) - -// Header used to propagate capacity information to the back-end -const HeaderScaleSetMaxCapacity = "X-ScaleSetMaxCapacity" - -type ActionsService interface { - GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error) - GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int) (*RunnerScaleSet, error) - GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*RunnerGroup, error) - CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error) - UpdateRunnerScaleSet(ctx context.Context, runnerScaleSetId int, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error) - DeleteRunnerScaleSet(ctx context.Context, runnerScaleSetId int) error - - CreateMessageSession(ctx context.Context, runnerScaleSetId int, owner string) (*RunnerScaleSetSession, error) - DeleteMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) error - RefreshMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*RunnerScaleSetSession, error) - - AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error) - GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*AcquirableJobList, error) - - GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error) - DeleteMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, messageId int64) error - - GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int) (*RunnerScaleSetJitRunnerConfig, error) - - GetRunner(ctx context.Context, runnerId int64) (*RunnerReference, error) - GetRunnerByName(ctx context.Context, runnerName string) (*RunnerReference, error) - RemoveRunner(ctx context.Context, runnerId int64) error - - SetUserAgent(info UserAgentInfo) -} - -type clientLogger struct { - logr.Logger -} - -func (l *clientLogger) Info(msg string, keysAndValues ...interface{}) { - l.Logger.Info(msg, keysAndValues...) -} - -func (l *clientLogger) Debug(msg string, keysAndValues ...interface{}) { - // discard debug log -} - -func (l *clientLogger) Error(msg string, keysAndValues ...interface{}) { - l.Logger.Error(errors.New(msg), "Retryable client error", keysAndValues...) -} - -func (l *clientLogger) Warn(msg string, keysAndValues ...interface{}) { - l.Logger.Info(msg, keysAndValues...) -} - -var _ retryablehttp.LeveledLogger = &clientLogger{} - -type Client struct { - *http.Client - - // lock for refreshing the ActionsServiceAdminToken and ActionsServiceAdminTokenExpiresAt - mu sync.Mutex - - // TODO: Convert to unexported fields once refactor of Listener is complete - ActionsServiceAdminToken string - ActionsServiceAdminTokenExpiresAt time.Time - ActionsServiceURL string - - retryMax int - retryWaitMax time.Duration - - creds *ActionsAuth - config *GitHubConfig - logger logr.Logger - userAgent UserAgentInfo - - rootCAs *x509.CertPool - tlsInsecureSkipVerify bool - - proxyFunc ProxyFunc -} - -var _ ActionsService = &Client{} - -type ProxyFunc func(req *http.Request) (*url.URL, error) - -type ClientOption func(*Client) - -type UserAgentInfo struct { - // Version is the version of the controller - Version string - // CommitSHA is the git commit SHA of the controller - CommitSHA string - // ScaleSetID is the ID of the scale set - ScaleSetID int - // HasProxy is true if the controller is running behind a proxy - HasProxy bool - // Subsystem is the subsystem such as listener, controller, etc. - // Each system may pick its own subsystem name. - Subsystem string -} - -func (u UserAgentInfo) String() string { - scaleSetID := "NA" - if u.ScaleSetID > 0 { - scaleSetID = strconv.Itoa(u.ScaleSetID) - } - - proxy := "Proxy/disabled" - if u.HasProxy { - proxy = "Proxy/enabled" - } - - return fmt.Sprintf("actions-runner-controller/%s (%s; %s) ScaleSetID/%s (%s)", u.Version, u.CommitSHA, u.Subsystem, scaleSetID, proxy) -} - -func WithLogger(logger logr.Logger) ClientOption { - return func(c *Client) { - c.logger = logger - } -} - -func WithRetryMax(retryMax int) ClientOption { - return func(c *Client) { - c.retryMax = retryMax - } -} - -func WithRetryWaitMax(retryWaitMax time.Duration) ClientOption { - return func(c *Client) { - c.retryWaitMax = retryWaitMax - } -} - -func WithRootCAs(rootCAs *x509.CertPool) ClientOption { - return func(c *Client) { - c.rootCAs = rootCAs - } -} - -func WithoutTLSVerify() ClientOption { - return func(c *Client) { - c.tlsInsecureSkipVerify = true - } -} - -func WithProxy(proxyFunc ProxyFunc) ClientOption { - return func(c *Client) { - c.proxyFunc = proxyFunc - } -} - -func NewClient(githubConfigURL string, creds *ActionsAuth, options ...ClientOption) (*Client, error) { - config, err := ParseGitHubConfigFromURL(githubConfigURL) - if err != nil { - return nil, fmt.Errorf("failed to parse githubConfigURL: %w", err) - } - - ac := &Client{ - creds: creds, - config: config, - logger: logr.Discard(), - - // retryablehttp defaults - retryMax: 4, - retryWaitMax: 30 * time.Second, - userAgent: UserAgentInfo{ - Version: build.Version, - CommitSHA: build.CommitSHA, - ScaleSetID: 0, - }, - } - - for _, option := range options { - option(ac) - } - - retryClient := retryablehttp.NewClient() - retryClient.Logger = &clientLogger{Logger: ac.logger} - - retryClient.RetryMax = ac.retryMax - retryClient.RetryWaitMax = ac.retryWaitMax - - retryClient.HTTPClient.Timeout = 5 * time.Minute // timeout must be > 1m to accomodate long polling - - transport, ok := retryClient.HTTPClient.Transport.(*http.Transport) - if !ok { - // this should always be true, because retryablehttp.NewClient() uses - // cleanhttp.DefaultPooledTransport() - return nil, fmt.Errorf("failed to get http transport from retryablehttp client") - } - if transport.TLSClientConfig == nil { - transport.TLSClientConfig = &tls.Config{} - } - - if ac.rootCAs != nil { - transport.TLSClientConfig.RootCAs = ac.rootCAs - } - - if ac.tlsInsecureSkipVerify { - transport.TLSClientConfig.InsecureSkipVerify = true - } - - transport.Proxy = ac.proxyFunc - - retryClient.HTTPClient.Transport = transport - ac.Client = retryClient.StandardClient() - - return ac, nil -} - -func (c *Client) SetUserAgent(info UserAgentInfo) { - c.userAgent = info -} - -// Identifier returns a string to help identify a client uniquely. -// This is used for caching client instances and understanding when a config -// change warrants creating a new client. Any changes to Client that would -// require a new client should be reflected here. -func (c *Client) Identifier() string { - identifier := fmt.Sprintf("configURL:%q,", c.config.ConfigURL.String()) - - if c.creds.Token != "" { - identifier += fmt.Sprintf("token:%q,", c.creds.Token) - } - - if c.creds.AppCreds != nil { - identifier += fmt.Sprintf( - "appID:%q,installationID:%q,key:%q", - c.creds.AppCreds.AppID, - strconv.FormatInt(c.creds.AppCreds.AppInstallationID, 10), - c.creds.AppCreds.AppPrivateKey, - ) - } - - if c.rootCAs != nil { - // ignoring because this cert pool is intended not to come from SystemCertPool - // nolint:staticcheck - identifier += fmt.Sprintf("rootCAs:%q", c.rootCAs.Subjects()) - } - - return uuid.NewHash(sha256.New(), uuid.NameSpaceOID, []byte(identifier), 6).String() -} - -func (c *Client) Do(req *http.Request) (*http.Response, error) { - resp, err := c.Client.Do(req) - if err != nil { - // If we have a response even with an error, include the status code - if resp != nil { - return nil, fmt.Errorf("client request failed with status code %d: %w", resp.StatusCode, err) - } - return nil, fmt.Errorf("client request failed: %w", err) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read the response body: %w", err) - } - err = resp.Body.Close() - if err != nil { - return nil, fmt.Errorf("failed to close the response body: %w", err) - } - - body = trimByteOrderMark(body) - resp.Body = io.NopCloser(bytes.NewReader(body)) - return resp, nil -} - -func (c *Client) NewGitHubAPIRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) { - u := c.config.GitHubAPIURL(path) - req, err := http.NewRequestWithContext(ctx, method, u.String(), body) - if err != nil { - return nil, fmt.Errorf("failed to create new GitHub API request: %w", err) - } - - req.Header.Set("User-Agent", c.userAgent.String()) - - return req, nil -} - -func (c *Client) NewActionsServiceRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) { - err := c.updateTokenIfNeeded(ctx) - if err != nil { - return nil, fmt.Errorf("failed to issue update token if needed: %w", err) - } - - parsedPath, err := url.Parse(path) - if err != nil { - return nil, fmt.Errorf("failed to parse path %q: %w", path, err) - } - - urlString, err := url.JoinPath(c.ActionsServiceURL, parsedPath.Path) - if err != nil { - return nil, fmt.Errorf("failed to join path (actions_service_url=%q, parsedPath=%q): %w", c.ActionsServiceURL, parsedPath.Path, err) - } - - u, err := url.Parse(urlString) - if err != nil { - return nil, fmt.Errorf("failed to parse url string %q: %w", urlString, err) - } - - q := u.Query() - maps.Copy(q, parsedPath.Query()) - - if q.Get("api-version") == "" { - q.Set("api-version", "6.0-preview") - } - u.RawQuery = q.Encode() - - req, err := http.NewRequestWithContext(ctx, method, u.String(), body) - if err != nil { - return nil, fmt.Errorf("failed to create new request with context: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.ActionsServiceAdminToken)) - req.Header.Set("User-Agent", c.userAgent.String()) - - return req, nil -} - -func (c *Client) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error) { - path := fmt.Sprintf("/%s?runnerGroupId=%d&name=%s", scaleSetEndpoint, runnerGroupId, runnerScaleSetName) - req, err := c.NewActionsServiceRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusOK { - return nil, ParseActionsErrorFromResponse(resp) - } - - var runnerScaleSetList *runnerScaleSetsResponse - if err := json.NewDecoder(resp.Body).Decode(&runnerScaleSetList); err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - if runnerScaleSetList.Count == 0 { - return nil, nil - } - if runnerScaleSetList.Count > 1 { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: fmt.Errorf("multiple runner scale sets found with name %q", runnerScaleSetName), - } - } - - return &runnerScaleSetList.RunnerScaleSets[0], nil -} - -func (c *Client) GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int) (*RunnerScaleSet, error) { - path := fmt.Sprintf("/%s/%d", scaleSetEndpoint, runnerScaleSetId) - req, err := c.NewActionsServiceRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusOK { - return nil, ParseActionsErrorFromResponse(resp) - } - - var runnerScaleSet *RunnerScaleSet - if err := json.NewDecoder(resp.Body).Decode(&runnerScaleSet); err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - return runnerScaleSet, nil -} - -func (c *Client) GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*RunnerGroup, error) { - path := fmt.Sprintf("/_apis/runtime/runnergroups/?groupName=%s", runnerGroup) - req, err := c.NewActionsServiceRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusOK { - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - return nil, fmt.Errorf("unexpected status code: %w", &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: errors.New(string(body)), - }) - } - - var runnerGroupList *RunnerGroupList - err = json.NewDecoder(resp.Body).Decode(&runnerGroupList) - if err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - - if runnerGroupList.Count == 0 { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: fmt.Errorf("no runner group found with name %q", runnerGroup), - } - } - - if runnerGroupList.Count > 1 { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: fmt.Errorf("multiple runner group found with name %q", runnerGroup), - } - } - - return &runnerGroupList.RunnerGroups[0], nil -} - -func (c *Client) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error) { - body, err := json.Marshal(runnerScaleSet) - if err != nil { - return nil, fmt.Errorf("failed to marshal runner scale set: %w", err) - } - - req, err := c.NewActionsServiceRequest(ctx, http.MethodPost, scaleSetEndpoint, bytes.NewReader(body)) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusOK { - return nil, ParseActionsErrorFromResponse(resp) - } - var createdRunnerScaleSet *RunnerScaleSet - if err := json.NewDecoder(resp.Body).Decode(&createdRunnerScaleSet); err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - return createdRunnerScaleSet, nil -} - -func (c *Client) UpdateRunnerScaleSet(ctx context.Context, runnerScaleSetId int, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error) { - path := fmt.Sprintf("%s/%d", scaleSetEndpoint, runnerScaleSetId) - - body, err := json.Marshal(runnerScaleSet) - if err != nil { - return nil, fmt.Errorf("failed to marshal runner scale set: %w", err) - } - - req, err := c.NewActionsServiceRequest(ctx, http.MethodPatch, path, bytes.NewReader(body)) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusOK { - return nil, ParseActionsErrorFromResponse(resp) - } - - var updatedRunnerScaleSet *RunnerScaleSet - if err := json.NewDecoder(resp.Body).Decode(&updatedRunnerScaleSet); err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - return updatedRunnerScaleSet, nil -} - -func (c *Client) DeleteRunnerScaleSet(ctx context.Context, runnerScaleSetId int) error { - path := fmt.Sprintf("/%s/%d", scaleSetEndpoint, runnerScaleSetId) - req, err := c.NewActionsServiceRequest(ctx, http.MethodDelete, path, nil) - if err != nil { - return fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusNoContent { - return ParseActionsErrorFromResponse(resp) - } - - defer resp.Body.Close() - return nil -} - -func (c *Client) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error) { - u, err := url.Parse(messageQueueUrl) - if err != nil { - return nil, fmt.Errorf("failed to parse message queue url: %w", err) - } - - if lastMessageId > 0 { - q := u.Query() - q.Set("lastMessageId", strconv.FormatInt(lastMessageId, 10)) - u.RawQuery = q.Encode() - } - - if maxCapacity < 0 { - return nil, fmt.Errorf("maxCapacity must be greater than or equal to 0") - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) - if err != nil { - return nil, fmt.Errorf("failed to create new request with context: %w", err) - } - - req.Header.Set("Accept", "application/json; api-version=6.0-preview") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", messageQueueAccessToken)) - req.Header.Set("User-Agent", c.userAgent.String()) - req.Header.Set(HeaderScaleSetMaxCapacity, strconv.Itoa(maxCapacity)) - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode == http.StatusAccepted { - defer resp.Body.Close() - return nil, nil - } - - if resp.StatusCode != http.StatusOK { - if resp.StatusCode != http.StatusUnauthorized { - return nil, ParseActionsErrorFromResponse(resp) - } - - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - body = trimByteOrderMark(body) - if err != nil { - return nil, &ActionsError{ - ActivityID: resp.Header.Get(HeaderActionsActivityID), - StatusCode: resp.StatusCode, - Err: err, - } - } - return nil, &MessageQueueTokenExpiredError{ - activityID: resp.Header.Get(HeaderActionsActivityID), - statusCode: resp.StatusCode, - msg: string(body), - } - } - - var message *RunnerScaleSetMessage - if err := json.NewDecoder(resp.Body).Decode(&message); err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - return message, nil -} - -func (c *Client) DeleteMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, messageId int64) error { - u, err := url.Parse(messageQueueUrl) - if err != nil { - return fmt.Errorf("failed to parse message queue url: %w", err) - } - - u.Path = fmt.Sprintf("%s/%d", u.Path, messageId) - - req, err := http.NewRequestWithContext(ctx, http.MethodDelete, u.String(), nil) - if err != nil { - return fmt.Errorf("failed to create new request with context: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", messageQueueAccessToken)) - req.Header.Set("User-Agent", c.userAgent.String()) - - resp, err := c.Do(req) - if err != nil { - return fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusNoContent { - if resp.StatusCode != http.StatusUnauthorized { - return ParseActionsErrorFromResponse(resp) - } - - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - body = trimByteOrderMark(body) - if err != nil { - return &ActionsError{ - ActivityID: resp.Header.Get(HeaderActionsActivityID), - StatusCode: resp.StatusCode, - Err: err, - } - } - return &MessageQueueTokenExpiredError{ - activityID: resp.Header.Get(HeaderActionsActivityID), - statusCode: resp.StatusCode, - msg: string(body), - } - } - return nil -} - -func (c *Client) CreateMessageSession(ctx context.Context, runnerScaleSetId int, owner string) (*RunnerScaleSetSession, error) { - path := fmt.Sprintf("/%s/%d/sessions", scaleSetEndpoint, runnerScaleSetId) - - newSession := &RunnerScaleSetSession{ - OwnerName: owner, - } - - requestData, err := json.Marshal(newSession) - if err != nil { - return nil, fmt.Errorf("failed to marshal new session: %w", err) - } - - createdSession := &RunnerScaleSetSession{} - - if err = c.doSessionRequest(ctx, http.MethodPost, path, bytes.NewBuffer(requestData), http.StatusOK, createdSession); err != nil { - return nil, fmt.Errorf("failed to do the session request: %w", err) - } - - return createdSession, nil -} - -func (c *Client) DeleteMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) error { - path := fmt.Sprintf("/%s/%d/sessions/%s", scaleSetEndpoint, runnerScaleSetId, sessionId.String()) - return c.doSessionRequest(ctx, http.MethodDelete, path, nil, http.StatusNoContent, nil) -} - -func (c *Client) RefreshMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*RunnerScaleSetSession, error) { - path := fmt.Sprintf("/%s/%d/sessions/%s", scaleSetEndpoint, runnerScaleSetId, sessionId.String()) - refreshedSession := &RunnerScaleSetSession{} - if err := c.doSessionRequest(ctx, http.MethodPatch, path, nil, http.StatusOK, refreshedSession); err != nil { - return nil, fmt.Errorf("failed to do the session request: %w", err) - } - return refreshedSession, nil -} - -func (c *Client) doSessionRequest(ctx context.Context, method, path string, requestData io.Reader, expectedResponseStatusCode int, responseUnmarshalTarget any) error { - req, err := c.NewActionsServiceRequest(ctx, method, path, requestData) - if err != nil { - return fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode == expectedResponseStatusCode { - if responseUnmarshalTarget == nil { - return nil - } - - if err := json.NewDecoder(resp.Body).Decode(responseUnmarshalTarget); err != nil { - return &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - - return nil - } - - if resp.StatusCode >= 400 && resp.StatusCode < 500 { - return ParseActionsErrorFromResponse(resp) - } - - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - body = trimByteOrderMark(body) - if err != nil { - return &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - - return fmt.Errorf("unexpected status code: %w", &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: errors.New(string(body)), - }) -} - -func (c *Client) AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error) { - u := fmt.Sprintf("%s/%s/%d/acquirejobs?api-version=6.0-preview", c.ActionsServiceURL, scaleSetEndpoint, runnerScaleSetId) - - body, err := json.Marshal(requestIds) - if err != nil { - return nil, fmt.Errorf("failed to marshal request ids: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create new request with context: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", messageQueueAccessToken)) - req.Header.Set("User-Agent", c.userAgent.String()) - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusOK { - if resp.StatusCode != http.StatusUnauthorized { - return nil, ParseActionsErrorFromResponse(resp) - } - - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - body = trimByteOrderMark(body) - if err != nil { - return nil, &ActionsError{ - ActivityID: resp.Header.Get(HeaderActionsActivityID), - StatusCode: resp.StatusCode, - Err: err, - } - } - - return nil, &MessageQueueTokenExpiredError{ - activityID: resp.Header.Get(HeaderActionsActivityID), - statusCode: resp.StatusCode, - msg: string(body), - } - } - - var acquiredJobs *Int64List - err = json.NewDecoder(resp.Body).Decode(&acquiredJobs) - if err != nil { - return nil, &ActionsError{ - ActivityID: resp.Header.Get(HeaderActionsActivityID), - StatusCode: resp.StatusCode, - Err: err, - } - } - - return acquiredJobs.Value, nil -} - -func (c *Client) GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*AcquirableJobList, error) { - path := fmt.Sprintf("/%s/%d/acquirablejobs", scaleSetEndpoint, runnerScaleSetId) - - req, err := c.NewActionsServiceRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode == http.StatusNoContent { - defer resp.Body.Close() - return &AcquirableJobList{Count: 0, Jobs: []AcquirableJob{}}, nil - } - - if resp.StatusCode != http.StatusOK { - return nil, ParseActionsErrorFromResponse(resp) - } - - var acquirableJobList *AcquirableJobList - err = json.NewDecoder(resp.Body).Decode(&acquirableJobList) - if err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - - return acquirableJobList, nil -} - -func (c *Client) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int) (*RunnerScaleSetJitRunnerConfig, error) { - path := fmt.Sprintf("/%s/%d/generatejitconfig", scaleSetEndpoint, scaleSetId) - - body, err := json.Marshal(jitRunnerSetting) - if err != nil { - return nil, fmt.Errorf("failed to marshal runner settings: %w", err) - } - - req, err := c.NewActionsServiceRequest(ctx, http.MethodPost, path, bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - // Include the URL and method in the error for better debugging - return nil, fmt.Errorf("failed to issue the request %s %s: %w", req.Method, req.URL.String(), err) - } - - if resp.StatusCode != http.StatusOK { - return nil, ParseActionsErrorFromResponse(resp) - } - - var runnerJitConfig *RunnerScaleSetJitRunnerConfig - if err := json.NewDecoder(resp.Body).Decode(&runnerJitConfig); err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - return runnerJitConfig, nil -} - -func (c *Client) GetRunner(ctx context.Context, runnerId int64) (*RunnerReference, error) { - path := fmt.Sprintf("/%s/%d", runnerEndpoint, runnerId) - - req, err := c.NewActionsServiceRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusOK { - return nil, ParseActionsErrorFromResponse(resp) - } - - var runnerReference *RunnerReference - if err := json.NewDecoder(resp.Body).Decode(&runnerReference); err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - - return runnerReference, nil -} - -func (c *Client) GetRunnerByName(ctx context.Context, runnerName string) (*RunnerReference, error) { - path := fmt.Sprintf("/%s?agentName=%s", runnerEndpoint, runnerName) - - req, err := c.NewActionsServiceRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusOK { - return nil, ParseActionsErrorFromResponse(resp) - } - - var runnerList *RunnerReferenceList - if err := json.NewDecoder(resp.Body).Decode(&runnerList); err != nil { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: err, - } - } - - if runnerList.Count == 0 { - return nil, nil - } - - if runnerList.Count > 1 { - return nil, &ActionsError{ - StatusCode: resp.StatusCode, - ActivityID: resp.Header.Get(HeaderActionsActivityID), - Err: fmt.Errorf("multiple runner found with name %s", runnerName), - } - } - - return &runnerList.RunnerReferences[0], nil -} - -func (c *Client) RemoveRunner(ctx context.Context, runnerId int64) error { - path := fmt.Sprintf("/%s/%d", runnerEndpoint, runnerId) - - req, err := c.NewActionsServiceRequest(ctx, http.MethodDelete, path, nil) - if err != nil { - return fmt.Errorf("failed to create new actions service request: %w", err) - } - - resp, err := c.Do(req) - if err != nil { - return fmt.Errorf("failed to issue the request: %w", err) - } - - if resp.StatusCode != http.StatusNoContent { - return ParseActionsErrorFromResponse(resp) - } - - defer resp.Body.Close() - return nil -} - -type registrationToken struct { - Token *string `json:"token,omitempty"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` -} - -func (c *Client) getRunnerRegistrationToken(ctx context.Context) (*registrationToken, error) { - path, err := createRegistrationTokenPath(c.config) - if err != nil { - return nil, fmt.Errorf("failed to create registration token path: %w", err) - } - - var buf bytes.Buffer - req, err := c.NewGitHubAPIRequest(ctx, http.MethodPost, path, &buf) - if err != nil { - return nil, fmt.Errorf("failed to create new GitHub API request: %w", err) - } - - bearerToken := "" - - if c.creds.Token != "" { - bearerToken = fmt.Sprintf("Bearer %v", c.creds.Token) - } else { - accessToken, err := c.fetchAccessToken(ctx, c.config.ConfigURL.String(), c.creds.AppCreds) - if err != nil { - return nil, fmt.Errorf("failed to fetch access token: %w", err) - } - - bearerToken = fmt.Sprintf("Bearer %v", accessToken.Token) - } - - req.Header.Set("Content-Type", "application/vnd.github.v3+json") - req.Header.Set("Authorization", bearerToken) - - c.logger.Info("getting runner registration token", "registrationTokenURL", req.URL.String()) - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read the body: %w", err) - } - return nil, &GitHubAPIError{ - StatusCode: resp.StatusCode, - RequestID: resp.Header.Get(HeaderGitHubRequestID), - Err: errors.New(string(body)), - } - } - - var registrationToken *registrationToken - if err := json.NewDecoder(resp.Body).Decode(®istrationToken); err != nil { - return nil, &GitHubAPIError{ - StatusCode: resp.StatusCode, - RequestID: resp.Header.Get(HeaderGitHubRequestID), - Err: err, - } - } - - return registrationToken, nil -} - -// Format: https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app -type accessToken struct { - Token string `json:"token"` - ExpiresAt time.Time `json:"expires_at"` -} - -func (c *Client) fetchAccessToken(ctx context.Context, gitHubConfigURL string, creds *GitHubAppAuth) (*accessToken, error) { - accessTokenJWT, err := createJWTForGitHubApp(creds) - if err != nil { - return nil, fmt.Errorf("failed to create JWT for GitHub app: %w", err) - } - - path := fmt.Sprintf("/app/installations/%v/access_tokens", creds.AppInstallationID) - req, err := c.NewGitHubAPIRequest(ctx, http.MethodPost, path, nil) - if err != nil { - return nil, fmt.Errorf("failed to create new GitHub API request: %w", err) - } - - req.Header.Set("Content-Type", "application/vnd.github+json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessTokenJWT)) - - c.logger.Info("getting access token for GitHub App auth", "accessTokenURL", req.URL.String()) - - resp, err := c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - errMsg := fmt.Sprintf("failed to get access token for GitHub App auth (%v)", resp.Status) - if body, err := io.ReadAll(resp.Body); err == nil { - errMsg = fmt.Sprintf("%s: %s", errMsg, string(body)) - } - - return nil, &GitHubAPIError{ - StatusCode: resp.StatusCode, - RequestID: resp.Header.Get(HeaderGitHubRequestID), - Err: errors.New(errMsg), - } - } - - // Format: https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app - var accessToken *accessToken - if err = json.NewDecoder(resp.Body).Decode(&accessToken); err != nil { - return nil, &GitHubAPIError{ - StatusCode: resp.StatusCode, - RequestID: resp.Header.Get(HeaderGitHubRequestID), - Err: err, - } - } - return accessToken, nil -} - -type ActionsServiceAdminConnection struct { - ActionsServiceUrl *string `json:"url,omitempty"` - AdminToken *string `json:"token,omitempty"` -} - -func (c *Client) getActionsServiceAdminConnection(ctx context.Context, rt *registrationToken) (*ActionsServiceAdminConnection, error) { - path := "/actions/runner-registration" - - body := struct { - Url string `json:"url"` - RunnerEvent string `json:"runner_event"` - }{ - Url: c.config.ConfigURL.String(), - RunnerEvent: "register", - } - - buf := &bytes.Buffer{} - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(false) - - if err := enc.Encode(body); err != nil { - return nil, fmt.Errorf("failed to encode body: %w", err) - } - - req, err := c.NewGitHubAPIRequest(ctx, http.MethodPost, path, buf) - if err != nil { - return nil, fmt.Errorf("failed to create new GitHub API request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("RemoteAuth %s", *rt.Token)) - - c.logger.Info("getting Actions tenant URL and JWT", "registrationURL", req.URL.String()) - - var resp *http.Response - retry := 0 - for { - var err error - resp, err = c.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to issue the request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode >= 200 && resp.StatusCode <= 299 { - break - } - - var innerErr error - body, err := io.ReadAll(resp.Body) - if err != nil { - innerErr = err - } else { - innerErr = errors.New(string(body)) - } - - if resp.StatusCode != http.StatusUnauthorized && resp.StatusCode != http.StatusForbidden { - return nil, &GitHubAPIError{ - StatusCode: resp.StatusCode, - RequestID: resp.Header.Get(HeaderGitHubRequestID), - Err: innerErr, - } - } - - retry++ - if retry > 5 { - return nil, fmt.Errorf("unable to register runner after 3 retries: %w", &GitHubAPIError{ - StatusCode: resp.StatusCode, - RequestID: resp.Header.Get(HeaderGitHubRequestID), - Err: innerErr, - }) - } - // Add exponential backoff + jitter to avoid thundering herd - // This will generate a backoff schedule: - // 1: 1s - // 2: 3s - // 3: 4s - // 4: 8s - // 5: 17s - baseDelay := 500 * time.Millisecond - jitter := time.Duration(rand.Intn(1000)) - maxDelay := 20 * time.Second - delay := baseDelay*(1< maxDelay { - delay = maxDelay - } - - time.Sleep(delay) - } - - var actionsServiceAdminConnection *ActionsServiceAdminConnection - if err := json.NewDecoder(resp.Body).Decode(&actionsServiceAdminConnection); err != nil { - return nil, &GitHubAPIError{ - StatusCode: resp.StatusCode, - RequestID: resp.Header.Get(HeaderGitHubRequestID), - Err: err, - } - } - - return actionsServiceAdminConnection, nil -} - -func createRegistrationTokenPath(config *GitHubConfig) (string, error) { - switch config.Scope { - case GitHubScopeOrganization: - path := fmt.Sprintf("/orgs/%s/actions/runners/registration-token", config.Organization) - return path, nil - - case GitHubScopeEnterprise: - path := fmt.Sprintf("/enterprises/%s/actions/runners/registration-token", config.Enterprise) - return path, nil - - case GitHubScopeRepository: - path := fmt.Sprintf("/repos/%s/%s/actions/runners/registration-token", config.Organization, config.Repository) - return path, nil - - default: - return "", fmt.Errorf("unknown scope for config url: %s", config.ConfigURL) - } -} - -func createJWTForGitHubApp(appAuth *GitHubAppAuth) (string, error) { - // Encode as JWT - // See https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app - - // Going back in time a bit helps with clock skew. - issuedAt := time.Now().Add(-60 * time.Second) - // Max expiration date is 10 minutes. - expiresAt := issuedAt.Add(9 * time.Minute) - claims := &jwt.RegisteredClaims{ - IssuedAt: jwt.NewNumericDate(issuedAt), - ExpiresAt: jwt.NewNumericDate(expiresAt), - Issuer: appAuth.AppID, - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - - privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(appAuth.AppPrivateKey)) - if err != nil { - return "", fmt.Errorf("failed to parse RSA private key from PEM: %w", err) - } - - return token.SignedString(privateKey) -} - -// Returns slice of body without utf-8 byte order mark. -// If BOM does not exist body is returned unchanged. -func trimByteOrderMark(body []byte) []byte { - return bytes.TrimPrefix(body, []byte("\xef\xbb\xbf")) -} - -func actionsServiceAdminTokenExpiresAt(jwtToken string) (time.Time, error) { - type JwtClaims struct { - jwt.RegisteredClaims - } - token, _, err := jwt.NewParser().ParseUnverified(jwtToken, &JwtClaims{}) - if err != nil { - return time.Time{}, fmt.Errorf("failed to parse jwt token: %w", err) - } - - if claims, ok := token.Claims.(*JwtClaims); ok { - return claims.ExpiresAt.Time, nil - } - - return time.Time{}, fmt.Errorf("failed to parse token claims to get expire at") -} - -func (c *Client) updateTokenIfNeeded(ctx context.Context) error { - c.mu.Lock() - defer c.mu.Unlock() - - aboutToExpire := time.Now().Add(60 * time.Second).After(c.ActionsServiceAdminTokenExpiresAt) - if !aboutToExpire && !c.ActionsServiceAdminTokenExpiresAt.IsZero() { - return nil - } - - c.logger.Info("refreshing token", "githubConfigUrl", c.config.ConfigURL.String()) - rt, err := c.getRunnerRegistrationToken(ctx) - if err != nil { - return fmt.Errorf("failed to get runner registration token on refresh: %w", err) - } - - adminConnInfo, err := c.getActionsServiceAdminConnection(ctx, rt) - if err != nil { - return fmt.Errorf("failed to get actions service admin connection on refresh: %w", err) - } - - c.ActionsServiceURL = *adminConnInfo.ActionsServiceUrl - c.ActionsServiceAdminToken = *adminConnInfo.AdminToken - c.ActionsServiceAdminTokenExpiresAt, err = actionsServiceAdminTokenExpiresAt(*adminConnInfo.AdminToken) - if err != nil { - return fmt.Errorf("failed to get admin token expire at on refresh: %w", err) - } - - return nil -} diff --git a/github/actions/client_generate_jit_test.go b/github/actions/client_generate_jit_test.go deleted file mode 100644 index ee77ca7d..00000000 --- a/github/actions/client_generate_jit_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package actions_test - -import ( - "context" - "errors" - "net/http" - "testing" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGenerateJitRunnerConfig(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("Get JIT Config for Runner", func(t *testing.T) { - want := &actions.RunnerScaleSetJitRunnerConfig{} - response := []byte(`{"count":1,"value":[{"id":1,"name":"scale-set-name"}]}`) - - runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{} - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(response) - })) - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GenerateJitRunnerConfig(ctx, runnerSettings, 1) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{} - - retryMax := 1 - actualRetry := 0 - expectedRetry := retryMax + 1 - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(1), - actions.WithRetryWaitMax(1*time.Millisecond), - ) - require.NoError(t, err) - - _, err = client.GenerateJitRunnerConfig(ctx, runnerSettings, 1) - assert.NotNil(t, err) - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) - - t.Run("Error includes HTTP method and URL when request fails", func(t *testing.T) { - runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{} - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - })) - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(0), // No retries to get immediate error - actions.WithRetryWaitMax(1*time.Millisecond), - ) - require.NoError(t, err) - - _, err = client.GenerateJitRunnerConfig(ctx, runnerSettings, 1) - require.NotNil(t, err) - // Verify error message includes HTTP method and URL for better debugging - errMsg := err.Error() - assert.Contains(t, errMsg, "POST", "Error message should include HTTP method") - assert.Contains(t, errMsg, "generatejitconfig", "Error message should include URL path") - - // The error might be an ActionsError (if response was received) or a wrapped error (if Do() failed) - // In either case, the error message should include request details - var actionsErr *actions.ActionsError - if errors.As(err, &actionsErr) { - // If we got an ActionsError, verify the status code is included - assert.Equal(t, http.StatusInternalServerError, actionsErr.StatusCode) - } - // If it's a wrapped error from Do(), the error message already includes the method and URL - // which is what we're testing for - }) -} diff --git a/github/actions/client_job_acquisition_test.go b/github/actions/client_job_acquisition_test.go deleted file mode 100644 index d155df09..00000000 --- a/github/actions/client_job_acquisition_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package actions_test - -import ( - "context" - "errors" - "net/http" - "strings" - "testing" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAcquireJobs(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("Acquire Job", func(t *testing.T) { - want := []int64{1} - response := []byte(`{"value": [1]}`) - - session := &actions.RunnerScaleSetSession{ - RunnerScaleSet: &actions.RunnerScaleSet{Id: 1}, - MessageQueueAccessToken: "abc", - } - requestIDs := want - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasSuffix(r.URL.Path, "/acquirablejobs") { - w.Write([]byte(`{"count": 1}`)) - return - } - - w.Write(response) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetAcquirableJobs(ctx, 1) - require.NoError(t, err) - - got, err := client.AcquireJobs(ctx, session.RunnerScaleSet.Id, session.MessageQueueAccessToken, requestIDs) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - session := &actions.RunnerScaleSetSession{ - RunnerScaleSet: &actions.RunnerScaleSet{Id: 1}, - MessageQueueAccessToken: "abc", - } - var requestIDs = []int64{1} - - retryMax := 1 - actualRetry := 0 - expectedRetry := retryMax + 1 - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasSuffix(r.URL.Path, "/acquirablejobs") { - w.Write([]byte(`{"count": 1}`)) - return - } - - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(1*time.Millisecond), - ) - require.NoError(t, err) - - _, err = client.GetAcquirableJobs(ctx, 1) - require.NoError(t, err) - - _, err = client.AcquireJobs(context.Background(), session.RunnerScaleSet.Id, session.MessageQueueAccessToken, requestIDs) - assert.NotNil(t, err) - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) - - t.Run("Should return MessageQueueTokenExpiredError when http error is not Unauthorized", func(t *testing.T) { - want := []int64{1} - - session := &actions.RunnerScaleSetSession{ - RunnerScaleSet: &actions.RunnerScaleSet{Id: 1}, - MessageQueueAccessToken: "abc", - } - requestIDs := want - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasSuffix(r.URL.Path, "/acquirablejobs") { - w.Write([]byte(`{"count": 1}`)) - return - } - if r.Method == http.MethodPost { - http.Error(w, "Session expired", http.StatusUnauthorized) - return - } - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetAcquirableJobs(ctx, 1) - require.NoError(t, err) - - got, err := client.AcquireJobs(ctx, session.RunnerScaleSet.Id, session.MessageQueueAccessToken, requestIDs) - require.Error(t, err) - assert.Nil(t, got) - var expectedErr *actions.MessageQueueTokenExpiredError - assert.True(t, errors.As(err, &expectedErr)) - }) -} - -func TestGetAcquirableJobs(t *testing.T) { - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("Acquire Job", func(t *testing.T) { - want := &actions.AcquirableJobList{} - response := []byte(`{"count": 0}`) - - runnerScaleSet := &actions.RunnerScaleSet{Id: 1} - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(response) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetAcquirableJobs(context.Background(), runnerScaleSet.Id) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - runnerScaleSet := &actions.RunnerScaleSet{Id: 1} - - retryMax := 1 - - actualRetry := 0 - expectedRetry := retryMax + 1 - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(1*time.Millisecond), - ) - require.NoError(t, err) - - _, err = client.GetAcquirableJobs(context.Background(), runnerScaleSet.Id) - require.Error(t, err) - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) -} diff --git a/github/actions/client_proxy_test.go b/github/actions/client_proxy_test.go deleted file mode 100644 index c63d41a2..00000000 --- a/github/actions/client_proxy_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package actions_test - -import ( - "net/http" - "net/url" - "testing" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/actions/actions-runner-controller/github/actions/testserver" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/net/http/httpproxy" -) - -func TestClientProxy(t *testing.T) { - serverCalled := false - - proxy := testserver.New(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - serverCalled = true - })) - - proxyConfig := &httpproxy.Config{ - HTTPProxy: proxy.URL, - } - proxyFunc := func(req *http.Request) (*url.URL, error) { - return proxyConfig.ProxyFunc()(req.URL) - } - - c, err := actions.NewClient("http://github.com/org/repo", nil, actions.WithProxy(proxyFunc)) - require.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, "http://example.com", nil) - require.NoError(t, err) - - _, err = c.Do(req) - require.NoError(t, err) - - assert.True(t, serverCalled) -} diff --git a/github/actions/client_runner_scale_set_message_test.go b/github/actions/client_runner_scale_set_message_test.go deleted file mode 100644 index 8b15a835..00000000 --- a/github/actions/client_runner_scale_set_message_test.go +++ /dev/null @@ -1,248 +0,0 @@ -package actions_test - -import ( - "context" - "encoding/json" - "errors" - "net/http" - "strconv" - "testing" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetMessage(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE" - runnerScaleSetMessage := &actions.RunnerScaleSetMessage{ - MessageId: 1, - MessageType: "rssType", - } - - t.Run("Get Runner Scale Set Message", func(t *testing.T) { - want := runnerScaleSetMessage - response := []byte(`{"messageId":1,"messageType":"rssType"}`) - s := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(response) - })) - - client, err := actions.NewClient(s.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetMessage(ctx, s.URL, token, 0, 10) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("GetMessage sets the last message id if not 0", func(t *testing.T) { - want := runnerScaleSetMessage - response := []byte(`{"messageId":1,"messageType":"rssType"}`) - s := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - assert.Equal(t, "1", q.Get("lastMessageId")) - w.Write(response) - })) - - client, err := actions.NewClient(s.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetMessage(ctx, s.URL, token, 1, 10) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - retryMax := 1 - - actualRetry := 0 - expectedRetry := retryMax + 1 - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(1*time.Millisecond), - ) - require.NoError(t, err) - - _, err = client.GetMessage(ctx, server.URL, token, 0, 10) - assert.NotNil(t, err) - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) - - t.Run("Message token expired", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnauthorized) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetMessage(ctx, server.URL, token, 0, 10) - require.NotNil(t, err) - - var expectedErr *actions.MessageQueueTokenExpiredError - require.True(t, errors.As(err, &expectedErr)) - }) - - t.Run("Status code not found", func(t *testing.T) { - want := actions.ActionsError{ - Err: errors.New("unknown exception"), - StatusCode: 404, - } - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetMessage(ctx, server.URL, token, 0, 10) - require.NotNil(t, err) - assert.Equal(t, want.Error(), err.Error()) - }) - - t.Run("Error when Content-Type is text/plain", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Header().Set("Content-Type", "text/plain") - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetMessage(ctx, server.URL, token, 0, 10) - assert.NotNil(t, err) - }) - - t.Run("Capacity error handling", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - hc := r.Header.Get(actions.HeaderScaleSetMaxCapacity) - c, err := strconv.Atoi(hc) - require.NoError(t, err) - assert.GreaterOrEqual(t, c, 0) - - w.WriteHeader(http.StatusBadRequest) - w.Header().Set("Content-Type", "text/plain") - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetMessage(ctx, server.URL, token, 0, -1) - require.Error(t, err) - // Ensure we don't send requests with negative capacity - assert.False(t, errors.Is(err, &actions.ActionsError{})) - - _, err = client.GetMessage(ctx, server.URL, token, 0, 0) - assert.Error(t, err) - var expectedErr *actions.ActionsError - assert.ErrorAs(t, err, &expectedErr) - assert.Equal(t, http.StatusBadRequest, expectedErr.StatusCode) - }) -} - -func TestDeleteMessage(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE" - runnerScaleSetMessage := &actions.RunnerScaleSetMessage{ - MessageId: 1, - MessageType: "rssType", - } - - t.Run("Delete existing message", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - err = client.DeleteMessage(ctx, server.URL, token, runnerScaleSetMessage.MessageId) - assert.Nil(t, err) - }) - - t.Run("Message token expired", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnauthorized) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - err = client.DeleteMessage(ctx, server.URL, token, 0) - require.NotNil(t, err) - var expectedErr *actions.MessageQueueTokenExpiredError - assert.True(t, errors.As(err, &expectedErr)) - }) - - t.Run("Error when Content-Type is text/plain", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Header().Set("Content-Type", "text/plain") - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - err = client.DeleteMessage(ctx, server.URL, token, runnerScaleSetMessage.MessageId) - require.NotNil(t, err) - var expectedErr *actions.ActionsError - assert.True(t, errors.As(err, &expectedErr)) - }, - ) - - t.Run("Default retries on server error", func(t *testing.T) { - actualRetry := 0 - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - retryMax := 1 - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(1*time.Nanosecond), - ) - require.NoError(t, err) - err = client.DeleteMessage(ctx, server.URL, token, runnerScaleSetMessage.MessageId) - assert.NotNil(t, err) - expectedRetry := retryMax + 1 - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) - - t.Run("No message found", func(t *testing.T) { - want := (*actions.RunnerScaleSetMessage)(nil) - rsl, err := json.Marshal(want) - require.NoError(t, err) - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(rsl) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - err = client.DeleteMessage(ctx, server.URL, token, runnerScaleSetMessage.MessageId+1) - var expectedErr *actions.ActionsError - require.True(t, errors.As(err, &expectedErr)) - }) -} diff --git a/github/actions/client_runner_scale_set_session_test.go b/github/actions/client_runner_scale_set_session_test.go deleted file mode 100644 index 317e0cd2..00000000 --- a/github/actions/client_runner_scale_set_session_test.go +++ /dev/null @@ -1,220 +0,0 @@ -package actions_test - -import ( - "context" - "errors" - "net/http" - "testing" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const exampleRequestID = "5ddf2050-dae0-013c-9159-04421ad31b68" - -func TestCreateMessageSession(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("CreateMessageSession unmarshals correctly", func(t *testing.T) { - owner := "foo" - runnerScaleSet := actions.RunnerScaleSet{ - Id: 1, - Name: "ScaleSet", - CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), - RunnerSetting: actions.RunnerSetting{}, - } - - want := &actions.RunnerScaleSetSession{ - OwnerName: "foo", - RunnerScaleSet: &actions.RunnerScaleSet{ - Id: 1, - Name: "ScaleSet", - }, - MessageQueueUrl: "http://fake.actions.github.com/123", - MessageQueueAccessToken: "fake.jwt.here", - } - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - resp := []byte(`{ - "ownerName": "foo", - "runnerScaleSet": { - "id": 1, - "name": "ScaleSet" - }, - "messageQueueUrl": "http://fake.actions.github.com/123", - "messageQueueAccessToken": "fake.jwt.here" - }`) - w.Write(resp) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.CreateMessageSession(ctx, runnerScaleSet.Id, owner) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("CreateMessageSession unmarshals errors into ActionsError", func(t *testing.T) { - owner := "foo" - runnerScaleSet := actions.RunnerScaleSet{ - Id: 1, - Name: "ScaleSet", - CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), - RunnerSetting: actions.RunnerSetting{}, - } - - want := &actions.ActionsError{ - ActivityID: exampleRequestID, - StatusCode: http.StatusBadRequest, - Err: &actions.ActionsExceptionError{ - ExceptionName: "CSharpExceptionNameHere", - Message: "could not do something", - }, - } - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Header().Set(actions.HeaderActionsActivityID, exampleRequestID) - w.WriteHeader(http.StatusBadRequest) - resp := []byte(`{"typeName": "CSharpExceptionNameHere","message": "could not do something"}`) - w.Write(resp) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.CreateMessageSession(ctx, runnerScaleSet.Id, owner) - require.NotNil(t, err) - - errorTypeForComparison := &actions.ActionsError{} - assert.True( - t, - errors.As(err, &errorTypeForComparison), - "CreateMessageSession expected to be able to parse the error into ActionsError type: %v", - err, - ) - - assert.Equal(t, want, errorTypeForComparison) - }) - - t.Run("CreateMessageSession call is retried the correct amount of times", func(t *testing.T) { - owner := "foo" - runnerScaleSet := actions.RunnerScaleSet{ - Id: 1, - Name: "ScaleSet", - CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), - RunnerSetting: actions.RunnerSetting{}, - } - - gotRetries := 0 - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - gotRetries++ - })) - - retryMax := 3 - retryWaitMax := 1 * time.Microsecond - - wantRetries := retryMax + 1 - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(retryWaitMax), - ) - require.NoError(t, err) - - _, err = client.CreateMessageSession(ctx, runnerScaleSet.Id, owner) - assert.NotNil(t, err) - assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries) - }) -} - -func TestDeleteMessageSession(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("DeleteMessageSession call is retried the correct amount of times", func(t *testing.T) { - runnerScaleSet := actions.RunnerScaleSet{ - Id: 1, - Name: "ScaleSet", - CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), - RunnerSetting: actions.RunnerSetting{}, - } - - gotRetries := 0 - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - gotRetries++ - })) - - retryMax := 3 - retryWaitMax := 1 * time.Microsecond - - wantRetries := retryMax + 1 - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(retryWaitMax), - ) - require.NoError(t, err) - - sessionId := uuid.New() - - err = client.DeleteMessageSession(ctx, runnerScaleSet.Id, &sessionId) - assert.NotNil(t, err) - assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries) - }) -} - -func TestRefreshMessageSession(t *testing.T) { - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("RefreshMessageSession call is retried the correct amount of times", func(t *testing.T) { - runnerScaleSet := actions.RunnerScaleSet{ - Id: 1, - Name: "ScaleSet", - CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), - RunnerSetting: actions.RunnerSetting{}, - } - - gotRetries := 0 - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - gotRetries++ - })) - - retryMax := 3 - retryWaitMax := 1 * time.Microsecond - - wantRetries := retryMax + 1 - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(retryWaitMax), - ) - require.NoError(t, err) - - sessionId := uuid.New() - - _, err = client.RefreshMessageSession(context.Background(), runnerScaleSet.Id, &sessionId) - assert.NotNil(t, err) - assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries) - }) -} diff --git a/github/actions/client_runner_scale_set_test.go b/github/actions/client_runner_scale_set_test.go deleted file mode 100644 index bb48ae5f..00000000 --- a/github/actions/client_runner_scale_set_test.go +++ /dev/null @@ -1,424 +0,0 @@ -package actions_test - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "testing" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetRunnerScaleSet(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - scaleSetName := "ScaleSet" - runnerScaleSet := actions.RunnerScaleSet{Id: 1, Name: scaleSetName} - - t.Run("Get existing scale set", func(t *testing.T) { - want := &runnerScaleSet - runnerScaleSetsResp := []byte(`{"count":1,"value":[{"id":1,"name":"ScaleSet"}]}`) - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(runnerScaleSetsResp) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunnerScaleSet(ctx, 1, scaleSetName) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("GetRunnerScaleSet calls correct url", func(t *testing.T) { - runnerScaleSetsResp := []byte(`{"count":1,"value":[{"id":1,"name":"ScaleSet"}]}`) - url := url.URL{} - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(runnerScaleSetsResp) - url = *r.URL - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName) - require.NoError(t, err) - - expectedPath := "/tenant/123/_apis/runtime/runnerscalesets" - assert.Equal(t, expectedPath, url.Path) - assert.Equal(t, scaleSetName, url.Query().Get("name")) - assert.Equal(t, "6.0-preview", url.Query().Get("api-version")) - }) - - t.Run("Status code not found", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName) - assert.NotNil(t, err) - }) - - t.Run("Error when Content-Type is text/plain", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Header().Set("Content-Type", "text/plain") - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName) - assert.NotNil(t, err) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - actualRetry := 0 - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - retryMax := 1 - retryWaitMax := 1 * time.Microsecond - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(retryWaitMax), - ) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName) - assert.NotNil(t, err) - expectedRetry := retryMax + 1 - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) - - t.Run("RunnerScaleSet count is zero", func(t *testing.T) { - want := (*actions.RunnerScaleSet)(nil) - runnerScaleSetsResp := []byte(`{"count":0,"value":[{"id":1,"name":"ScaleSet"}]}`) - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(runnerScaleSetsResp) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunnerScaleSet(ctx, 1, scaleSetName) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("Multiple runner scale sets found", func(t *testing.T) { - reqID := uuid.NewString() - wantErr := &actions.ActionsError{ - StatusCode: http.StatusOK, - ActivityID: reqID, - Err: fmt.Errorf("multiple runner scale sets found with name %q", scaleSetName), - } - runnerScaleSetsResp := []byte(`{"count":2,"value":[{"id":1,"name":"ScaleSet"}]}`) - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set(actions.HeaderActionsActivityID, reqID) - w.Write(runnerScaleSetsResp) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName) - require.NotNil(t, err) - assert.Equal(t, wantErr.Error(), err.Error()) - }) -} - -func TestGetRunnerScaleSetById(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - scaleSetCreationDateTime := time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC) - runnerScaleSet := actions.RunnerScaleSet{Id: 1, Name: "ScaleSet", CreatedOn: scaleSetCreationDateTime, RunnerSetting: actions.RunnerSetting{}} - - t.Run("Get existing scale set by Id", func(t *testing.T) { - want := &runnerScaleSet - rsl, err := json.Marshal(want) - require.NoError(t, err) - sservere := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(rsl) - })) - - client, err := actions.NewClient(sservere.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("GetRunnerScaleSetById calls correct url", func(t *testing.T) { - rsl, err := json.Marshal(&runnerScaleSet) - require.NoError(t, err) - - url := url.URL{} - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(rsl) - url = *r.URL - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id) - require.NoError(t, err) - - expectedPath := fmt.Sprintf("/tenant/123/_apis/runtime/runnerscalesets/%d", runnerScaleSet.Id) - assert.Equal(t, expectedPath, url.Path) - assert.Equal(t, "6.0-preview", url.Query().Get("api-version")) - }) - - t.Run("Status code not found", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id) - assert.NotNil(t, err) - }) - - t.Run("Error when Content-Type is text/plain", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Header().Set("Content-Type", "text/plain") - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id) - assert.NotNil(t, err) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - actualRetry := 0 - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - retryMax := 1 - retryWaitMax := 1 * time.Microsecond - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(retryWaitMax), - ) - require.NoError(t, err) - - _, err = client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id) - require.NotNil(t, err) - expectedRetry := retryMax + 1 - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) - - t.Run("No RunnerScaleSet found", func(t *testing.T) { - want := (*actions.RunnerScaleSet)(nil) - rsl, err := json.Marshal(want) - require.NoError(t, err) - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(rsl) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id) - require.NoError(t, err) - assert.Equal(t, want, got) - }) -} - -func TestCreateRunnerScaleSet(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - scaleSetCreationDateTime := time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC) - runnerScaleSet := actions.RunnerScaleSet{Id: 1, Name: "ScaleSet", CreatedOn: scaleSetCreationDateTime, RunnerSetting: actions.RunnerSetting{}} - - t.Run("Create runner scale set", func(t *testing.T) { - want := &runnerScaleSet - rsl, err := json.Marshal(want) - require.NoError(t, err) - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(rsl) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.CreateRunnerScaleSet(ctx, &runnerScaleSet) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("CreateRunnerScaleSet calls correct url", func(t *testing.T) { - rsl, err := json.Marshal(&runnerScaleSet) - require.NoError(t, err) - url := url.URL{} - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(rsl) - url = *r.URL - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.CreateRunnerScaleSet(ctx, &runnerScaleSet) - require.NoError(t, err) - - expectedPath := "/tenant/123/_apis/runtime/runnerscalesets" - assert.Equal(t, expectedPath, url.Path) - assert.Equal(t, "6.0-preview", url.Query().Get("api-version")) - }) - - t.Run("Error when Content-Type is text/plain", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Header().Set("Content-Type", "text/plain") - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.CreateRunnerScaleSet(ctx, &runnerScaleSet) - require.NotNil(t, err) - var expectedErr *actions.ActionsError - assert.True(t, errors.As(err, &expectedErr)) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - actualRetry := 0 - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - retryMax := 1 - retryWaitMax := 1 * time.Microsecond - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(retryWaitMax), - ) - require.NoError(t, err) - - _, err = client.CreateRunnerScaleSet(ctx, &runnerScaleSet) - require.NotNil(t, err) - expectedRetry := retryMax + 1 - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) -} - -func TestUpdateRunnerScaleSet(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - scaleSetCreationDateTime := time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC) - runnerScaleSet := actions.RunnerScaleSet{Id: 1, Name: "ScaleSet", RunnerGroupId: 1, RunnerGroupName: "group", CreatedOn: scaleSetCreationDateTime, RunnerSetting: actions.RunnerSetting{}} - - t.Run("Update runner scale set", func(t *testing.T) { - want := &runnerScaleSet - rsl, err := json.Marshal(want) - require.NoError(t, err) - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write(rsl) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.UpdateRunnerScaleSet(ctx, 1, &actions.RunnerScaleSet{RunnerGroupId: 1}) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("UpdateRunnerScaleSet calls correct url", func(t *testing.T) { - rsl, err := json.Marshal(&runnerScaleSet) - require.NoError(t, err) - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - expectedPath := "/tenant/123/_apis/runtime/runnerscalesets/1" - assert.Equal(t, expectedPath, r.URL.Path) - assert.Equal(t, http.MethodPatch, r.Method) - assert.Equal(t, "6.0-preview", r.URL.Query().Get("api-version")) - - w.Write(rsl) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - _, err = client.UpdateRunnerScaleSet(ctx, 1, &runnerScaleSet) - require.NoError(t, err) - }) -} - -func TestDeleteRunnerScaleSet(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("Delete runner scale set", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "DELETE", r.Method) - assert.Contains(t, r.URL.String(), "/_apis/runtime/runnerscalesets/10?api-version=6.0-preview") - w.WriteHeader(http.StatusNoContent) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - err = client.DeleteRunnerScaleSet(ctx, 10) - assert.NoError(t, err) - }) - - t.Run("Delete calls with error", func(t *testing.T) { - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "DELETE", r.Method) - assert.Contains(t, r.URL.String(), "/_apis/runtime/runnerscalesets/10?api-version=6.0-preview") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(`{"message": "test error"}`)) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - err = client.DeleteRunnerScaleSet(ctx, 10) - assert.ErrorContains(t, err, "test error") - }) -} diff --git a/github/actions/client_runner_test.go b/github/actions/client_runner_test.go deleted file mode 100644 index 40525bde..00000000 --- a/github/actions/client_runner_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package actions_test - -import ( - "context" - "net/http" - "testing" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetRunner(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("Get Runner", func(t *testing.T) { - var runnerID int64 = 1 - want := &actions.RunnerReference{ - Id: int(runnerID), - Name: "self-hosted-ubuntu", - } - response := []byte(`{"id": 1, "name": "self-hosted-ubuntu"}`) - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(response) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunner(ctx, runnerID) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - var runnerID int64 = 1 - retryWaitMax := 1 * time.Millisecond - retryMax := 1 - - actualRetry := 0 - expectedRetry := retryMax + 1 - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth, actions.WithRetryMax(retryMax), actions.WithRetryWaitMax(retryWaitMax)) - require.NoError(t, err) - - _, err = client.GetRunner(ctx, runnerID) - require.Error(t, err) - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) -} - -func TestGetRunnerByName(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("Get Runner by Name", func(t *testing.T) { - var runnerID int64 = 1 - var runnerName = "self-hosted-ubuntu" - want := &actions.RunnerReference{ - Id: int(runnerID), - Name: runnerName, - } - response := []byte(`{"count": 1, "value": [{"id": 1, "name": "self-hosted-ubuntu"}]}`) - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(response) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunnerByName(ctx, runnerName) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("Get Runner by name with not exist runner", func(t *testing.T) { - var runnerName = "self-hosted-ubuntu" - response := []byte(`{"count": 0, "value": []}`) - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(response) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunnerByName(ctx, runnerName) - require.NoError(t, err) - assert.Nil(t, got) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - var runnerName = "self-hosted-ubuntu" - - retryWaitMax := 1 * time.Millisecond - retryMax := 1 - - actualRetry := 0 - expectedRetry := retryMax + 1 - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth, actions.WithRetryMax(retryMax), actions.WithRetryWaitMax(retryWaitMax)) - require.NoError(t, err) - - _, err = client.GetRunnerByName(ctx, runnerName) - require.Error(t, err) - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) -} - -func TestDeleteRunner(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("Delete Runner", func(t *testing.T) { - var runnerID int64 = 1 - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - err = client.RemoveRunner(ctx, runnerID) - assert.NoError(t, err) - }) - - t.Run("Default retries on server error", func(t *testing.T) { - var runnerID int64 = 1 - - retryWaitMax := 1 * time.Millisecond - retryMax := 1 - - actualRetry := 0 - expectedRetry := retryMax + 1 - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusServiceUnavailable) - actualRetry++ - })) - - client, err := actions.NewClient( - server.configURLForOrg("my-org"), - auth, - actions.WithRetryMax(retryMax), - actions.WithRetryWaitMax(retryWaitMax), - ) - require.NoError(t, err) - - err = client.RemoveRunner(ctx, runnerID) - require.Error(t, err) - assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) - }) -} - -func TestGetRunnerGroupByName(t *testing.T) { - ctx := context.Background() - auth := &actions.ActionsAuth{ - Token: "token", - } - - t.Run("Get RunnerGroup by Name", func(t *testing.T) { - var runnerGroupID int64 = 1 - var runnerGroupName = "test-runner-group" - want := &actions.RunnerGroup{ - ID: runnerGroupID, - Name: runnerGroupName, - } - response := []byte(`{"count": 1, "value": [{"id": 1, "name": "test-runner-group"}]}`) - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(response) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunnerGroupByName(ctx, runnerGroupName) - require.NoError(t, err) - assert.Equal(t, want, got) - }) - - t.Run("Get RunnerGroup by name with not exist runner group", func(t *testing.T) { - var runnerGroupName = "test-runner-group" - response := []byte(`{"count": 0, "value": []}`) - - server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(response) - })) - - client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) - require.NoError(t, err) - - got, err := client.GetRunnerGroupByName(ctx, runnerGroupName) - assert.ErrorContains(t, err, "no runner group found with name") - assert.Nil(t, got) - }) -} diff --git a/github/actions/client_tls_test.go b/github/actions/client_tls_test.go deleted file mode 100644 index 30e052b5..00000000 --- a/github/actions/client_tls_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package actions_test - -import ( - "context" - "crypto/tls" - "crypto/x509" - "errors" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "runtime" - "strings" - "testing" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/golang-jwt/jwt/v4" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestServerWithSelfSignedCertificates(t *testing.T) { - ctx := context.Background() - // this handler is a very very barebones replica of actions api - // used during the creation of a a new client - var u string - h := func(w http.ResponseWriter, r *http.Request) { - // handle get registration token - if strings.HasSuffix(r.URL.Path, "/runners/registration-token") { - w.WriteHeader(http.StatusCreated) - w.Write([]byte(`{"token":"token"}`)) - return - } - - // handle getActionsServiceAdminConnection - if strings.HasSuffix(r.URL.Path, "/actions/runner-registration") { - claims := &jwt.RegisteredClaims{ - IssuedAt: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)), - ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Minute)), - Issuer: "123", - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(samplePrivateKey)) - require.NoError(t, err) - tokenString, err := token.SignedString(privateKey) - require.NoError(t, err) - w.Write([]byte(`{"url":"` + u + `","token":"` + tokenString + `"}`)) - return - } - - // default happy response for RemoveRunner - w.WriteHeader(http.StatusNoContent) - } - - certPath := filepath.Join("testdata", "server.crt") - keyPath := filepath.Join("testdata", "server.key") - - t.Run("client without ca certs", func(t *testing.T) { - server := startNewTLSTestServer(t, certPath, keyPath, http.HandlerFunc(h)) - u = server.URL - configURL := server.URL + "/my-org" - - auth := &actions.ActionsAuth{ - Token: "token", - } - client, err := actions.NewClient(configURL, auth) - require.NoError(t, err) - require.NotNil(t, client) - - err = client.RemoveRunner(ctx, 1) - require.NotNil(t, err) - - if runtime.GOOS == "linux" { - assert.True(t, errors.As(err, &x509.UnknownAuthorityError{})) - } - - // on macOS we only get an untyped error from the system verifying the - // certificate - if runtime.GOOS == "darwin" { - assert.True(t, strings.HasSuffix(err.Error(), "certificate is not trusted")) - } - }) - - t.Run("client with ca certs", func(t *testing.T) { - server := startNewTLSTestServer( - t, - certPath, - keyPath, - http.HandlerFunc(h), - ) - u = server.URL - configURL := server.URL + "/my-org" - - auth := &actions.ActionsAuth{ - Token: "token", - } - - cert, err := os.ReadFile(filepath.Join("testdata", "rootCA.crt")) - require.NoError(t, err) - - pool := x509.NewCertPool() - require.True(t, pool.AppendCertsFromPEM(cert)) - - client, err := actions.NewClient( - configURL, - auth, - actions.WithRootCAs(pool), - ) - require.NoError(t, err) - assert.NotNil(t, client) - - err = client.RemoveRunner(ctx, 1) - assert.NoError(t, err) - }) - - t.Run("client with ca chain certs", func(t *testing.T) { - server := startNewTLSTestServer( - t, - filepath.Join("testdata", "leaf.crt"), - filepath.Join("testdata", "leaf.key"), - http.HandlerFunc(h), - ) - u = server.URL - configURL := server.URL + "/my-org" - - auth := &actions.ActionsAuth{ - Token: "token", - } - - cert, err := os.ReadFile(filepath.Join("testdata", "intermediate.crt")) - require.NoError(t, err) - - pool := x509.NewCertPool() - require.True(t, pool.AppendCertsFromPEM(cert)) - - client, err := actions.NewClient( - configURL, - auth, - actions.WithRootCAs(pool), - actions.WithRetryMax(0), - ) - require.NoError(t, err) - require.NotNil(t, client) - - err = client.RemoveRunner(ctx, 1) - assert.NoError(t, err) - }) - - t.Run("client skipping tls verification", func(t *testing.T) { - server := startNewTLSTestServer(t, certPath, keyPath, http.HandlerFunc(h)) - configURL := server.URL + "/my-org" - - auth := &actions.ActionsAuth{ - Token: "token", - } - - client, err := actions.NewClient(configURL, auth, actions.WithoutTLSVerify()) - require.NoError(t, err) - assert.NotNil(t, client) - }) -} - -func startNewTLSTestServer(t *testing.T, certPath, keyPath string, handler http.Handler) *httptest.Server { - server := httptest.NewUnstartedServer(handler) - t.Cleanup(func() { - server.Close() - }) - - cert, err := tls.LoadX509KeyPair(certPath, keyPath) - require.NoError(t, err) - - server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} - server.StartTLS() - - return server -} diff --git a/github/actions/config_test.go b/github/actions/config_test.go deleted file mode 100644 index dc3aacbd..00000000 --- a/github/actions/config_test.go +++ /dev/null @@ -1,208 +0,0 @@ -package actions_test - -import ( - "errors" - "net/url" - "os" - "strings" - "testing" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGitHubConfig(t *testing.T) { - t.Run("when given a valid URL", func(t *testing.T) { - tests := []struct { - name string - configURL string - expected *actions.GitHubConfig - }{ - { - name: "repository URL", - configURL: "https://github.com/org/repo", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeRepository, - Enterprise: "", - Organization: "org", - Repository: "repo", - IsHosted: true, - }, - }, - { - name: "repository URL with trailing slash", - configURL: "https://github.com/org/repo/", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeRepository, - Enterprise: "", - Organization: "org", - Repository: "repo", - IsHosted: true, - }, - }, - { - name: "organization URL", - configURL: "https://github.com/org", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeOrganization, - Enterprise: "", - Organization: "org", - Repository: "", - IsHosted: true, - }, - }, - { - name: "enterprise URL", - configURL: "https://github.com/enterprises/my-enterprise", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeEnterprise, - Enterprise: "my-enterprise", - Organization: "", - Repository: "", - IsHosted: true, - }, - }, - { - name: "enterprise URL with trailing slash", - configURL: "https://github.com/enterprises/my-enterprise/", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeEnterprise, - Enterprise: "my-enterprise", - Organization: "", - Repository: "", - IsHosted: true, - }, - }, - { - name: "organization URL with www", - configURL: "https://www.github.com/org", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeOrganization, - Enterprise: "", - Organization: "org", - Repository: "", - IsHosted: true, - }, - }, - { - name: "organization URL with www and trailing slash", - configURL: "https://www.github.com/org/", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeOrganization, - Enterprise: "", - Organization: "org", - Repository: "", - IsHosted: true, - }, - }, - { - name: "github local URL", - configURL: "https://github.localhost/org", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeOrganization, - Enterprise: "", - Organization: "org", - Repository: "", - IsHosted: true, - }, - }, - { - name: "github local org URL", - configURL: "https://my-ghes.com/org", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeOrganization, - Enterprise: "", - Organization: "org", - Repository: "", - IsHosted: false, - }, - }, - { - name: "github local URL with trailing slash", - configURL: "https://my-ghes.com/org/", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeOrganization, - Enterprise: "", - Organization: "org", - Repository: "", - IsHosted: false, - }, - }, - { - name: "github local URL with ghe.com", - configURL: "https://my-ghes.ghe.com/org/", - expected: &actions.GitHubConfig{ - Scope: actions.GitHubScopeOrganization, - Enterprise: "", - Organization: "org", - Repository: "", - IsHosted: true, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - parsedURL, err := url.Parse(strings.Trim(test.configURL, "/")) - require.NoError(t, err) - test.expected.ConfigURL = parsedURL - - cfg, err := actions.ParseGitHubConfigFromURL(test.configURL) - require.NoError(t, err) - assert.Equal(t, test.expected, cfg) - }) - } - }) - - t.Run("when given an invalid URL", func(t *testing.T) { - invalidURLs := []string{ - "https://github.com/", - "https://github.com", - "https://github.com/some/random/path", - } - - for _, u := range invalidURLs { - _, err := actions.ParseGitHubConfigFromURL(u) - require.Error(t, err) - assert.True(t, errors.Is(err, actions.ErrInvalidGitHubConfigURL)) - } - }) -} - -func TestGitHubConfig_GitHubAPIURL(t *testing.T) { - t.Run("when hosted", func(t *testing.T) { - config, err := actions.ParseGitHubConfigFromURL("https://github.com/org/repo") - require.NoError(t, err) - assert.True(t, config.IsHosted) - - result := config.GitHubAPIURL("/some/path") - assert.Equal(t, "https://api.github.com/some/path", result.String()) - }) - t.Run("when hosted with ghe.com", func(t *testing.T) { - config, err := actions.ParseGitHubConfigFromURL("https://github.ghe.com/org/repo") - require.NoError(t, err) - assert.True(t, config.IsHosted) - - result := config.GitHubAPIURL("/some/path") - assert.Equal(t, "https://api.github.ghe.com/some/path", result.String()) - }) - t.Run("when not hosted", func(t *testing.T) { - config, err := actions.ParseGitHubConfigFromURL("https://ghes.com/org/repo") - require.NoError(t, err) - assert.False(t, config.IsHosted) - - result := config.GitHubAPIURL("/some/path") - assert.Equal(t, "https://ghes.com/api/v3/some/path", result.String()) - }) - t.Run("when not hosted with ghe.com", func(t *testing.T) { - os.Setenv("GITHUB_ACTIONS_FORCE_GHES", "1") - defer os.Unsetenv("GITHUB_ACTIONS_FORCE_GHES") - config, err := actions.ParseGitHubConfigFromURL("https://test.ghe.com/org/repo") - require.NoError(t, err) - assert.False(t, config.IsHosted) - - result := config.GitHubAPIURL("/some/path") - assert.Equal(t, "https://test.ghe.com/api/v3/some/path", result.String()) - }) -} diff --git a/github/actions/errors.go b/github/actions/errors.go deleted file mode 100644 index ee9b448f..00000000 --- a/github/actions/errors.go +++ /dev/null @@ -1,125 +0,0 @@ -package actions - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "strings" -) - -// Header names for request IDs -const ( - HeaderActionsActivityID = "ActivityId" - HeaderGitHubRequestID = "X-GitHub-Request-Id" -) - -type GitHubAPIError struct { - StatusCode int - RequestID string - Err error -} - -func (e *GitHubAPIError) Error() string { - return fmt.Sprintf("github api error: StatusCode %d, RequestID %q: %v", e.StatusCode, e.RequestID, e.Err) -} - -func (e *GitHubAPIError) Unwrap() error { - return e.Err -} - -type ActionsError struct { - ActivityID string - StatusCode int - Err error -} - -func (e *ActionsError) Error() string { - return fmt.Sprintf("actions error: StatusCode %d, ActivityId %q: %v", e.StatusCode, e.ActivityID, e.Err) -} - -func (e *ActionsError) Unwrap() error { - return e.Err -} - -func (e *ActionsError) IsException(target string) bool { - if ex, ok := e.Err.(*ActionsExceptionError); ok { - return strings.Contains(ex.ExceptionName, target) - } - return false -} - -type ActionsExceptionError struct { - ExceptionName string `json:"typeName,omitempty"` - Message string `json:"message,omitempty"` -} - -func (e *ActionsExceptionError) Error() string { - return fmt.Sprintf("%s: %s", e.ExceptionName, e.Message) -} - -func ParseActionsErrorFromResponse(response *http.Response) error { - if response.ContentLength == 0 { - return &ActionsError{ - ActivityID: response.Header.Get(HeaderActionsActivityID), - StatusCode: response.StatusCode, - Err: errors.New("unknown exception"), - } - } - - defer response.Body.Close() - body, err := io.ReadAll(response.Body) - if err != nil { - return &ActionsError{ - ActivityID: response.Header.Get(HeaderActionsActivityID), - StatusCode: response.StatusCode, - Err: err, - } - } - - body = trimByteOrderMark(body) - contentType, ok := response.Header["Content-Type"] - if ok && len(contentType) > 0 && strings.Contains(contentType[0], "text/plain") { - message := string(body) - return &ActionsError{ - ActivityID: response.Header.Get(HeaderActionsActivityID), - StatusCode: response.StatusCode, - Err: errors.New(message), - } - } - - var exception ActionsExceptionError - if err := json.Unmarshal(body, &exception); err != nil { - return &ActionsError{ - ActivityID: response.Header.Get(HeaderActionsActivityID), - StatusCode: response.StatusCode, - Err: err, - } - } - - return &ActionsError{ - ActivityID: response.Header.Get(HeaderActionsActivityID), - StatusCode: response.StatusCode, - Err: &exception, - } -} - -type MessageQueueTokenExpiredError struct { - activityID string - statusCode int - msg string -} - -func (e *MessageQueueTokenExpiredError) Error() string { - return fmt.Sprintf("MessageQueueTokenExpiredError: ActivityId %q, StatusCode %d: %s", e.activityID, e.statusCode, e.msg) -} - -type HttpClientSideError struct { - msg string - Code int -} - -func (e *HttpClientSideError) Error() string { - return e.msg -} diff --git a/github/actions/errors_test.go b/github/actions/errors_test.go deleted file mode 100644 index 581d9448..00000000 --- a/github/actions/errors_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package actions_test - -import ( - "errors" - "io" - "net/http" - "strings" - "testing" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestActionsError(t *testing.T) { - t.Run("contains the status code, activity ID, and error", func(t *testing.T) { - err := &actions.ActionsError{ - ActivityID: "activity-id", - StatusCode: 404, - Err: errors.New("example error description"), - } - - s := err.Error() - assert.Contains(t, s, "StatusCode 404") - assert.Contains(t, s, "ActivityId \"activity-id\"") - assert.Contains(t, s, "example error description") - }) - - t.Run("unwraps the error", func(t *testing.T) { - err := &actions.ActionsError{ - ActivityID: "activity-id", - StatusCode: 404, - Err: &actions.ActionsExceptionError{ - ExceptionName: "exception-name", - Message: "example error message", - }, - } - - assert.Equal(t, err.Unwrap(), err.Err) - }) - - t.Run("is exception is ok", func(t *testing.T) { - err := &actions.ActionsError{ - ActivityID: "activity-id", - StatusCode: 404, - Err: &actions.ActionsExceptionError{ - ExceptionName: "exception-name", - Message: "example error message", - }, - } - - var exception *actions.ActionsExceptionError - assert.True(t, errors.As(err, &exception)) - - assert.True(t, err.IsException("exception-name")) - }) - - t.Run("is exception is not ok", func(t *testing.T) { - tt := map[string]*actions.ActionsError{ - "not an exception": { - ActivityID: "activity-id", - StatusCode: 404, - Err: errors.New("example error description"), - }, - "not target exception": { - ActivityID: "activity-id", - StatusCode: 404, - Err: &actions.ActionsExceptionError{ - ExceptionName: "exception-name", - Message: "example error message", - }, - }, - } - - targetException := "target-exception" - for name, err := range tt { - t.Run(name, func(t *testing.T) { - assert.False(t, err.IsException(targetException)) - }) - } - }) -} - -func TestActionsExceptionError(t *testing.T) { - t.Run("contains the exception name and message", func(t *testing.T) { - err := &actions.ActionsExceptionError{ - ExceptionName: "exception-name", - Message: "example error message", - } - - s := err.Error() - assert.Contains(t, s, "exception-name") - assert.Contains(t, s, "example error message") - }) -} - -func TestGitHubAPIError(t *testing.T) { - t.Run("contains the status code, request ID, and error", func(t *testing.T) { - err := &actions.GitHubAPIError{ - StatusCode: 404, - RequestID: "request-id", - Err: errors.New("example error description"), - } - - s := err.Error() - assert.Contains(t, s, "StatusCode 404") - assert.Contains(t, s, "RequestID \"request-id\"") - assert.Contains(t, s, "example error description") - }) - - t.Run("unwraps the error", func(t *testing.T) { - err := &actions.GitHubAPIError{ - StatusCode: 404, - RequestID: "request-id", - Err: errors.New("example error description"), - } - - assert.Equal(t, err.Unwrap(), err.Err) - }) -} - -func TestParseActionsErrorFromResponse(t *testing.T) { - t.Run("empty content length", func(t *testing.T) { - response := &http.Response{ - ContentLength: 0, - Header: http.Header{}, - StatusCode: 404, - } - response.Header.Add(actions.HeaderActionsActivityID, "activity-id") - - err := actions.ParseActionsErrorFromResponse(response) - require.Error(t, err) - assert.Equal(t, "activity-id", err.(*actions.ActionsError).ActivityID) - assert.Equal(t, 404, err.(*actions.ActionsError).StatusCode) - assert.Equal(t, "unknown exception", err.(*actions.ActionsError).Err.Error()) - }) - - t.Run("contains text plain error", func(t *testing.T) { - errorMessage := "example error message" - response := &http.Response{ - ContentLength: int64(len(errorMessage)), - StatusCode: 404, - Header: http.Header{}, - Body: io.NopCloser(strings.NewReader(errorMessage)), - } - response.Header.Add(actions.HeaderActionsActivityID, "activity-id") - response.Header.Add("Content-Type", "text/plain") - - err := actions.ParseActionsErrorFromResponse(response) - require.Error(t, err) - var actionsError *actions.ActionsError - require.ErrorAs(t, err, &actionsError) - assert.Equal(t, "activity-id", actionsError.ActivityID) - assert.Equal(t, 404, actionsError.StatusCode) - assert.Equal(t, errorMessage, actionsError.Err.Error()) - }) - - t.Run("contains json error", func(t *testing.T) { - errorMessage := `{"typeName":"exception-name","message":"example error message"}` - response := &http.Response{ - ContentLength: int64(len(errorMessage)), - Header: http.Header{}, - StatusCode: 404, - Body: io.NopCloser(strings.NewReader(errorMessage)), - } - response.Header.Add(actions.HeaderActionsActivityID, "activity-id") - response.Header.Add("Content-Type", "application/json") - - err := actions.ParseActionsErrorFromResponse(response) - require.Error(t, err) - var actionsError *actions.ActionsError - require.ErrorAs(t, err, &actionsError) - assert.Equal(t, "activity-id", actionsError.ActivityID) - assert.Equal(t, 404, actionsError.StatusCode) - - inner, ok := actionsError.Err.(*actions.ActionsExceptionError) - require.True(t, ok) - assert.Equal(t, "exception-name", inner.ExceptionName) - assert.Equal(t, "example error message", inner.Message) - }) - - t.Run("wrapped exception error", func(t *testing.T) { - errorMessage := `{"typeName":"exception-name","message":"example error message"}` - response := &http.Response{ - ContentLength: int64(len(errorMessage)), - Header: http.Header{}, - StatusCode: 404, - Body: io.NopCloser(strings.NewReader(errorMessage)), - } - response.Header.Add(actions.HeaderActionsActivityID, "activity-id") - response.Header.Add("Content-Type", "application/json") - - err := actions.ParseActionsErrorFromResponse(response) - require.Error(t, err) - - var actionsExceptionError *actions.ActionsExceptionError - require.ErrorAs(t, err, &actionsExceptionError) - - assert.Equal(t, "exception-name", actionsExceptionError.ExceptionName) - assert.Equal(t, "example error message", actionsExceptionError.Message) - }) -} diff --git a/github/actions/fake/client.go b/github/actions/fake/client.go deleted file mode 100644 index a108b902..00000000 --- a/github/actions/fake/client.go +++ /dev/null @@ -1,286 +0,0 @@ -package fake - -import ( - "context" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/google/uuid" -) - -type Option func(*FakeClient) - -func WithGetRunnerScaleSetResult(scaleSet *actions.RunnerScaleSet, err error) Option { - return func(f *FakeClient) { - f.getRunnerScaleSetResult.RunnerScaleSet = scaleSet - f.getRunnerScaleSetResult.err = err - } -} - -func WithGetRunnerGroup(runnerGroup *actions.RunnerGroup, err error) Option { - return func(f *FakeClient) { - f.getRunnerGroupByNameResult.RunnerGroup = runnerGroup - f.getRunnerGroupByNameResult.err = err - } -} - -func WithGetRunner(runner *actions.RunnerReference, err error) Option { - return func(f *FakeClient) { - f.getRunnerResult.RunnerReference = runner - f.getRunnerResult.err = err - } -} - -func WithCreateRunnerScaleSet(scaleSet *actions.RunnerScaleSet, err error) Option { - return func(f *FakeClient) { - f.createRunnerScaleSetResult.RunnerScaleSet = scaleSet - f.createRunnerScaleSetResult.err = err - } -} - -func WithUpdateRunnerScaleSet(scaleSet *actions.RunnerScaleSet, err error) Option { - return func(f *FakeClient) { - f.updateRunnerScaleSetResult.RunnerScaleSet = scaleSet - f.updateRunnerScaleSetResult.err = err - } -} - -var defaultRunnerScaleSet = &actions.RunnerScaleSet{ - Id: 1, - Name: "testset", - RunnerGroupId: 1, - RunnerGroupName: "testgroup", - Labels: []actions.Label{{Type: "test", Name: "test"}}, - RunnerSetting: actions.RunnerSetting{}, - CreatedOn: time.Now(), - RunnerJitConfigUrl: "test.test.test", - Statistics: nil, -} - -var defaultUpdatedRunnerScaleSet = &actions.RunnerScaleSet{ - Id: 1, - Name: "testset", - RunnerGroupId: 2, - RunnerGroupName: "testgroup2", - Labels: []actions.Label{{Type: "test", Name: "test"}}, - RunnerSetting: actions.RunnerSetting{}, - CreatedOn: time.Now(), - RunnerJitConfigUrl: "test.test.test", - Statistics: nil, -} - -var defaultRunnerGroup = &actions.RunnerGroup{ - ID: 1, - Name: "testgroup", - Size: 1, - IsDefault: true, -} - -var sessionID = uuid.New() - -var defaultRunnerScaleSetSession = &actions.RunnerScaleSetSession{ - SessionId: &sessionID, - OwnerName: "testowner", - RunnerScaleSet: defaultRunnerScaleSet, - MessageQueueUrl: "https://test.url/path", - MessageQueueAccessToken: "faketoken", - Statistics: nil, -} - -var defaultAcquirableJob = &actions.AcquirableJob{ - AcquireJobUrl: "https://test.url", - MessageType: "", - RunnerRequestId: 1, - RepositoryName: "testrepo", - OwnerName: "testowner", - JobWorkflowRef: "workflowref", - EventName: "testevent", - RequestLabels: []string{"test"}, -} - -var defaultAcquirableJobList = &actions.AcquirableJobList{ - Count: 1, - Jobs: []actions.AcquirableJob{*defaultAcquirableJob}, -} - -var defaultRunnerReference = &actions.RunnerReference{ - Id: 1, - Name: "testrunner", - RunnerScaleSetId: 1, -} - -var defaultRunnerScaleSetMessage = &actions.RunnerScaleSetMessage{ - MessageId: 1, - MessageType: "test", - Body: "{}", - Statistics: nil, -} - -var defaultRunnerScaleSetJitRunnerConfig = &actions.RunnerScaleSetJitRunnerConfig{ - Runner: defaultRunnerReference, - EncodedJITConfig: "test", -} - -// FakeClient implements actions service -type FakeClient struct { - getRunnerScaleSetResult struct { - *actions.RunnerScaleSet - err error - } - getRunnerScaleSetByIdResult struct { - *actions.RunnerScaleSet - err error - } - getRunnerGroupByNameResult struct { - *actions.RunnerGroup - err error - } - - createRunnerScaleSetResult struct { - *actions.RunnerScaleSet - err error - } - updateRunnerScaleSetResult struct { - *actions.RunnerScaleSet - err error - } - deleteRunnerScaleSetResult struct { - err error - } - createMessageSessionResult struct { - *actions.RunnerScaleSetSession - err error - } - deleteMessageSessionResult struct { - err error - } - refreshMessageSessionResult struct { - *actions.RunnerScaleSetSession - err error - } - acquireJobsResult struct { - ids []int64 - err error - } - getAcquirableJobsResult struct { - *actions.AcquirableJobList - err error - } - getMessageResult struct { - *actions.RunnerScaleSetMessage - err error - } - deleteMessageResult struct { - err error - } - generateJitRunnerConfigResult struct { - *actions.RunnerScaleSetJitRunnerConfig - err error - } - getRunnerResult struct { - *actions.RunnerReference - err error - } - getRunnerByNameResult struct { - *actions.RunnerReference - err error - } - removeRunnerResult struct { - err error - } -} - -func NewFakeClient(options ...Option) actions.ActionsService { - f := &FakeClient{} - f.applyDefaults() - for _, opt := range options { - opt(f) - } - return f -} - -func (f *FakeClient) applyDefaults() { - f.getRunnerScaleSetResult.RunnerScaleSet = defaultRunnerScaleSet - f.getRunnerScaleSetByIdResult.RunnerScaleSet = defaultRunnerScaleSet - f.getRunnerGroupByNameResult.RunnerGroup = defaultRunnerGroup - f.createRunnerScaleSetResult.RunnerScaleSet = defaultRunnerScaleSet - f.updateRunnerScaleSetResult.RunnerScaleSet = defaultUpdatedRunnerScaleSet - f.createMessageSessionResult.RunnerScaleSetSession = defaultRunnerScaleSetSession - f.refreshMessageSessionResult.RunnerScaleSetSession = defaultRunnerScaleSetSession - f.acquireJobsResult.ids = []int64{1} - f.getAcquirableJobsResult.AcquirableJobList = defaultAcquirableJobList - f.getMessageResult.RunnerScaleSetMessage = defaultRunnerScaleSetMessage - f.generateJitRunnerConfigResult.RunnerScaleSetJitRunnerConfig = defaultRunnerScaleSetJitRunnerConfig - f.getRunnerResult.RunnerReference = defaultRunnerReference - f.getRunnerByNameResult.RunnerReference = defaultRunnerReference -} - -func (f *FakeClient) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*actions.RunnerScaleSet, error) { - return f.getRunnerScaleSetResult.RunnerScaleSet, f.getRunnerScaleSetResult.err -} - -func (f *FakeClient) GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int) (*actions.RunnerScaleSet, error) { - return f.getRunnerScaleSetByIdResult.RunnerScaleSet, f.getRunnerScaleSetResult.err -} - -func (f *FakeClient) GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*actions.RunnerGroup, error) { - return f.getRunnerGroupByNameResult.RunnerGroup, f.getRunnerGroupByNameResult.err -} - -func (f *FakeClient) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *actions.RunnerScaleSet) (*actions.RunnerScaleSet, error) { - return f.createRunnerScaleSetResult.RunnerScaleSet, f.createRunnerScaleSetResult.err -} - -func (f *FakeClient) UpdateRunnerScaleSet(ctx context.Context, runnerScaleSetId int, runnerScaleSet *actions.RunnerScaleSet) (*actions.RunnerScaleSet, error) { - return f.updateRunnerScaleSetResult.RunnerScaleSet, f.updateRunnerScaleSetResult.err -} - -func (f *FakeClient) DeleteRunnerScaleSet(ctx context.Context, runnerScaleSetId int) error { - return f.deleteRunnerScaleSetResult.err -} - -func (f *FakeClient) CreateMessageSession(ctx context.Context, runnerScaleSetId int, owner string) (*actions.RunnerScaleSetSession, error) { - return f.createMessageSessionResult.RunnerScaleSetSession, f.createMessageSessionResult.err -} - -func (f *FakeClient) DeleteMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) error { - return f.deleteMessageSessionResult.err -} - -func (f *FakeClient) RefreshMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*actions.RunnerScaleSetSession, error) { - return f.refreshMessageSessionResult.RunnerScaleSetSession, f.refreshMessageSessionResult.err -} - -func (f *FakeClient) AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error) { - return f.acquireJobsResult.ids, f.acquireJobsResult.err -} - -func (f *FakeClient) GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*actions.AcquirableJobList, error) { - return f.getAcquirableJobsResult.AcquirableJobList, f.getAcquirableJobsResult.err -} - -func (f *FakeClient) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*actions.RunnerScaleSetMessage, error) { - return f.getMessageResult.RunnerScaleSetMessage, f.getMessageResult.err -} - -func (f *FakeClient) DeleteMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, messageId int64) error { - return f.deleteMessageResult.err -} - -func (f *FakeClient) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *actions.RunnerScaleSetJitRunnerSetting, scaleSetId int) (*actions.RunnerScaleSetJitRunnerConfig, error) { - return f.generateJitRunnerConfigResult.RunnerScaleSetJitRunnerConfig, f.generateJitRunnerConfigResult.err -} - -func (f *FakeClient) GetRunner(ctx context.Context, runnerId int64) (*actions.RunnerReference, error) { - return f.getRunnerResult.RunnerReference, f.getRunnerResult.err -} - -func (f *FakeClient) GetRunnerByName(ctx context.Context, runnerName string) (*actions.RunnerReference, error) { - return f.getRunnerByNameResult.RunnerReference, f.getRunnerByNameResult.err -} - -func (f *FakeClient) RemoveRunner(ctx context.Context, runnerId int64) error { - return f.removeRunnerResult.err -} - -func (f *FakeClient) SetUserAgent(_ actions.UserAgentInfo) {} diff --git a/github/actions/fake/multi_client.go b/github/actions/fake/multi_client.go deleted file mode 100644 index 62d6a0e5..00000000 --- a/github/actions/fake/multi_client.go +++ /dev/null @@ -1,40 +0,0 @@ -package fake - -import ( - "context" - - "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig" - "github.com/actions/actions-runner-controller/github/actions" -) - -type MultiClientOption func(*fakeMultiClient) - -func WithDefaultClient(client actions.ActionsService, err error) MultiClientOption { - return func(f *fakeMultiClient) { - f.defaultClient = client - f.defaultErr = err - } -} - -type fakeMultiClient struct { - defaultClient actions.ActionsService - defaultErr error -} - -func NewMultiClient(opts ...MultiClientOption) actions.MultiClient { - f := &fakeMultiClient{} - - for _, opt := range opts { - opt(f) - } - - if f.defaultClient == nil { - f.defaultClient = NewFakeClient() - } - - return f -} - -func (f *fakeMultiClient) GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...actions.ClientOption) (actions.ActionsService, error) { - return f.defaultClient, f.defaultErr -} diff --git a/github/actions/github_api_request_test.go b/github/actions/github_api_request_test.go deleted file mode 100644 index 33912a2c..00000000 --- a/github/actions/github_api_request_test.go +++ /dev/null @@ -1,248 +0,0 @@ -package actions_test - -import ( - "context" - "encoding/json" - "io" - "net/http" - "net/url" - "strings" - "testing" - "time" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/actions/actions-runner-controller/github/actions/testserver" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var testUserAgent = actions.UserAgentInfo{ - Version: "test", - CommitSHA: "test", - ScaleSetID: 1, -} - -func TestNewGitHubAPIRequest(t *testing.T) { - ctx := context.Background() - - t.Run("uses the right host/path prefix", func(t *testing.T) { - scenarios := []struct { - configURL string - path string - expected string - }{ - { - configURL: "https://github.com/org/repo", - path: "/app/installations/123/access_tokens", - expected: "https://api.github.com/app/installations/123/access_tokens", - }, - { - configURL: "https://www.github.com/org/repo", - path: "/app/installations/123/access_tokens", - expected: "https://api.github.com/app/installations/123/access_tokens", - }, - { - configURL: "http://github.localhost/org/repo", - path: "/app/installations/123/access_tokens", - expected: "http://api.github.localhost/app/installations/123/access_tokens", - }, - { - configURL: "https://my-instance.com/org/repo", - path: "/app/installations/123/access_tokens", - expected: "https://my-instance.com/api/v3/app/installations/123/access_tokens", - }, - { - configURL: "http://localhost/org/repo", - path: "/app/installations/123/access_tokens", - expected: "http://localhost/api/v3/app/installations/123/access_tokens", - }, - } - - for _, scenario := range scenarios { - client, err := actions.NewClient(scenario.configURL, nil) - require.NoError(t, err) - - req, err := client.NewGitHubAPIRequest(ctx, http.MethodGet, scenario.path, nil) - require.NoError(t, err) - assert.Equal(t, scenario.expected, req.URL.String()) - } - }) - - t.Run("sets user agent header if present", func(t *testing.T) { - client, err := actions.NewClient("http://localhost/my-org", nil) - require.NoError(t, err) - - client.SetUserAgent(testUserAgent) - - req, err := client.NewGitHubAPIRequest(ctx, http.MethodGet, "/app/installations/123/access_tokens", nil) - require.NoError(t, err) - - assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent")) - }) - - t.Run("sets the body we pass", func(t *testing.T) { - client, err := actions.NewClient("http://localhost/my-org", nil) - require.NoError(t, err) - - req, err := client.NewGitHubAPIRequest( - ctx, - http.MethodGet, - "/app/installations/123/access_tokens", - strings.NewReader("the-body"), - ) - require.NoError(t, err) - - b, err := io.ReadAll(req.Body) - require.NoError(t, err) - assert.Equal(t, "the-body", string(b)) - }) -} - -func TestNewActionsServiceRequest(t *testing.T) { - ctx := context.Background() - defaultCreds := &actions.ActionsAuth{Token: "token"} - - t.Run("manages authentication", func(t *testing.T) { - t.Run("client is brand new", func(t *testing.T) { - token := defaultActionsToken(t) - server := testserver.New(t, nil, testserver.WithActionsToken(token)) - - client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds) - require.NoError(t, err) - - req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil) - require.NoError(t, err) - - assert.Equal(t, "Bearer "+token, req.Header.Get("Authorization")) - }) - - t.Run("admin token is about to expire", func(t *testing.T) { - newToken := defaultActionsToken(t) - server := testserver.New(t, nil, testserver.WithActionsToken(newToken)) - - client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds) - require.NoError(t, err) - client.ActionsServiceAdminToken = "expiring-token" - client.ActionsServiceAdminTokenExpiresAt = time.Now().Add(59 * time.Second) - - req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil) - require.NoError(t, err) - - assert.Equal(t, "Bearer "+newToken, req.Header.Get("Authorization")) - }) - - t.Run("admin token refresh failure", func(t *testing.T) { - newToken := defaultActionsToken(t) - errMessage := `{"message":"test"}` - unauthorizedHandler := func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(errMessage)) - } - server := testserver.New( - t, - nil, - testserver.WithActionsToken("random-token"), - testserver.WithActionsToken(newToken), - testserver.WithActionsRegistrationTokenHandler(unauthorizedHandler), - ) - client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds) - require.NoError(t, err) - expiringToken := "expiring-token" - expiresAt := time.Now().Add(59 * time.Second) - client.ActionsServiceAdminToken = expiringToken - client.ActionsServiceAdminTokenExpiresAt = expiresAt - _, err = client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil) - require.Error(t, err) - assert.Contains(t, err.Error(), errMessage) - assert.Equal(t, client.ActionsServiceAdminToken, expiringToken) - assert.Equal(t, client.ActionsServiceAdminTokenExpiresAt, expiresAt) - }) - - t.Run("admin token refresh retry", func(t *testing.T) { - newToken := defaultActionsToken(t) - errMessage := `{"message":"test"}` - - srv := "http://github.com/my-org" - resp := &actions.ActionsServiceAdminConnection{ - AdminToken: &newToken, - ActionsServiceUrl: &srv, - } - failures := 0 - unauthorizedHandler := func(w http.ResponseWriter, r *http.Request) { - if failures < 5 { - failures++ - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(errMessage)) - return - } - - w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(resp) - } - server := testserver.New(t, nil, testserver.WithActionsToken("random-token"), testserver.WithActionsToken(newToken), testserver.WithActionsRegistrationTokenHandler(unauthorizedHandler)) - client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds) - require.NoError(t, err) - expiringToken := "expiring-token" - expiresAt := time.Now().Add(59 * time.Second) - client.ActionsServiceAdminToken = expiringToken - client.ActionsServiceAdminTokenExpiresAt = expiresAt - - _, err = client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil) - require.NoError(t, err) - assert.Equal(t, client.ActionsServiceAdminToken, newToken) - assert.Equal(t, client.ActionsServiceURL, srv) - assert.NotEqual(t, client.ActionsServiceAdminTokenExpiresAt, expiresAt) - }) - - t.Run("token is currently valid", func(t *testing.T) { - tokenThatShouldNotBeFetched := defaultActionsToken(t) - server := testserver.New(t, nil, testserver.WithActionsToken(tokenThatShouldNotBeFetched)) - - client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds) - require.NoError(t, err) - client.ActionsServiceAdminToken = "healthy-token" - client.ActionsServiceAdminTokenExpiresAt = time.Now().Add(1 * time.Hour) - - req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil) - require.NoError(t, err) - - assert.Equal(t, "Bearer healthy-token", req.Header.Get("Authorization")) - }) - }) - - t.Run("builds the right URL including api version", func(t *testing.T) { - server := testserver.New(t, nil) - - client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds) - require.NoError(t, err) - - req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "/my/path?name=banana", nil) - require.NoError(t, err) - - serverURL, err := url.Parse(server.URL) - require.NoError(t, err) - - result := req.URL - assert.Equal(t, serverURL.Host, result.Host) - assert.Equal(t, "/tenant/123/my/path", result.Path) - assert.Equal(t, "banana", result.Query().Get("name")) - assert.Equal(t, "6.0-preview", result.Query().Get("api-version")) - }) - - t.Run("populates header", func(t *testing.T) { - server := testserver.New(t, nil) - - client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds) - require.NoError(t, err) - - client.SetUserAgent(testUserAgent) - - req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "/my/path", nil) - require.NoError(t, err) - - assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent")) - assert.Equal(t, "application/json", req.Header.Get("Content-Type")) - }) -} diff --git a/github/actions/identifier_test.go b/github/actions/identifier_test.go deleted file mode 100644 index 528e0521..00000000 --- a/github/actions/identifier_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package actions_test - -import ( - "crypto/x509" - "os" - "path/filepath" - "testing" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestClient_Identifier(t *testing.T) { - t.Run("configURL changes", func(t *testing.T) { - scenarios := []struct { - name string - url string - }{ - { - name: "url of a different repo", - url: "https://github.com/org/repo2", - }, - { - name: "url of an org", - url: "https://github.com/org", - }, - { - name: "url of an enterprise", - url: "https://github.com/enterprises/my-enterprise", - }, - { - name: "url of a self-hosted github", - url: "https://selfhosted.com/org/repo", - }, - } - - configURL := "https://github.com/org/repo" - defaultCreds := &actions.ActionsAuth{ - Token: "token", - } - oldClient, err := actions.NewClient(configURL, defaultCreds) - require.NoError(t, err) - - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - newClient, err := actions.NewClient(scenario.url, defaultCreds) - require.NoError(t, err) - assert.NotEqual(t, oldClient.Identifier(), newClient.Identifier()) - }) - } - }) - - t.Run("credentials change", func(t *testing.T) { - defaultTokenCreds := &actions.ActionsAuth{ - Token: "token", - } - defaultAppCreds := &actions.ActionsAuth{ - AppCreds: &actions.GitHubAppAuth{ - AppID: "123", - AppInstallationID: 123, - AppPrivateKey: "private key", - }, - } - - scenarios := []struct { - name string - old *actions.ActionsAuth - new *actions.ActionsAuth - }{ - { - name: "different token", - old: defaultTokenCreds, - new: &actions.ActionsAuth{ - Token: "new token", - }, - }, - { - name: "changing from token to github app", - old: defaultTokenCreds, - new: defaultAppCreds, - }, - { - name: "changing from github app to token", - old: defaultAppCreds, - new: defaultTokenCreds, - }, - { - name: "different github app", - old: defaultAppCreds, - new: &actions.ActionsAuth{ - AppCreds: &actions.GitHubAppAuth{ - AppID: "456", - AppInstallationID: 456, - AppPrivateKey: "new private key", - }, - }, - }, - } - - defaultConfigURL := "https://github.com/org/repo" - - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - oldClient, err := actions.NewClient(defaultConfigURL, scenario.old) - require.NoError(t, err) - - newClient, err := actions.NewClient(defaultConfigURL, scenario.new) - require.NoError(t, err) - assert.NotEqual(t, oldClient.Identifier(), newClient.Identifier()) - }) - } - }) - - t.Run("changes in TLS config", func(t *testing.T) { - configURL := "https://github.com/org/repo" - defaultCreds := &actions.ActionsAuth{ - Token: "token", - } - - noTlS, err := actions.NewClient(configURL, defaultCreds) - require.NoError(t, err) - - poolFromCert := func(t *testing.T, path string) *x509.CertPool { - t.Helper() - f, err := os.ReadFile(path) - require.NoError(t, err) - pool := x509.NewCertPool() - require.True(t, pool.AppendCertsFromPEM(f)) - return pool - } - - root, err := actions.NewClient( - configURL, - defaultCreds, - actions.WithRootCAs(poolFromCert(t, filepath.Join("testdata", "rootCA.crt"))), - ) - require.NoError(t, err) - - chain, err := actions.NewClient( - configURL, - defaultCreds, - actions.WithRootCAs(poolFromCert(t, filepath.Join("testdata", "intermediate.crt"))), - ) - require.NoError(t, err) - - clients := []*actions.Client{ - noTlS, - root, - chain, - } - identifiers := map[string]struct{}{} - for _, client := range clients { - identifiers[client.Identifier()] = struct{}{} - } - assert.Len(t, identifiers, len(clients), "all clients should have a unique identifier") - }) -} diff --git a/github/actions/mocks_test.go b/github/actions/mocks_test.go deleted file mode 100644 index 204236df..00000000 --- a/github/actions/mocks_test.go +++ /dev/null @@ -1,1539 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package actions - -import ( - "context" - - "github.com/google/uuid" - mock "github.com/stretchr/testify/mock" -) - -// NewMockActionsService creates a new instance of MockActionsService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockActionsService(t interface { - mock.TestingT - Cleanup(func()) -}) *MockActionsService { - mock := &MockActionsService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// MockActionsService is an autogenerated mock type for the ActionsService type -type MockActionsService struct { - mock.Mock -} - -type MockActionsService_Expecter struct { - mock *mock.Mock -} - -func (_m *MockActionsService) EXPECT() *MockActionsService_Expecter { - return &MockActionsService_Expecter{mock: &_m.Mock} -} - -// AcquireJobs provides a mock function for the type MockActionsService -func (_mock *MockActionsService) AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error) { - ret := _mock.Called(ctx, runnerScaleSetId, messageQueueAccessToken, requestIds) - - if len(ret) == 0 { - panic("no return value specified for AcquireJobs") - } - - var r0 []int64 - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int, string, []int64) ([]int64, error)); ok { - return returnFunc(ctx, runnerScaleSetId, messageQueueAccessToken, requestIds) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int, string, []int64) []int64); ok { - r0 = returnFunc(ctx, runnerScaleSetId, messageQueueAccessToken, requestIds) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]int64) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int, string, []int64) error); ok { - r1 = returnFunc(ctx, runnerScaleSetId, messageQueueAccessToken, requestIds) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_AcquireJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AcquireJobs' -type MockActionsService_AcquireJobs_Call struct { - *mock.Call -} - -// AcquireJobs is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSetId int -// - messageQueueAccessToken string -// - requestIds []int64 -func (_e *MockActionsService_Expecter) AcquireJobs(ctx interface{}, runnerScaleSetId interface{}, messageQueueAccessToken interface{}, requestIds interface{}) *MockActionsService_AcquireJobs_Call { - return &MockActionsService_AcquireJobs_Call{Call: _e.mock.On("AcquireJobs", ctx, runnerScaleSetId, messageQueueAccessToken, requestIds)} -} - -func (_c *MockActionsService_AcquireJobs_Call) Run(run func(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64)) *MockActionsService_AcquireJobs_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 []int64 - if args[3] != nil { - arg3 = args[3].([]int64) - } - run( - arg0, - arg1, - arg2, - arg3, - ) - }) - return _c -} - -func (_c *MockActionsService_AcquireJobs_Call) Return(int64s []int64, err error) *MockActionsService_AcquireJobs_Call { - _c.Call.Return(int64s, err) - return _c -} - -func (_c *MockActionsService_AcquireJobs_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error)) *MockActionsService_AcquireJobs_Call { - _c.Call.Return(run) - return _c -} - -// CreateMessageSession provides a mock function for the type MockActionsService -func (_mock *MockActionsService) CreateMessageSession(ctx context.Context, runnerScaleSetId int, owner string) (*RunnerScaleSetSession, error) { - ret := _mock.Called(ctx, runnerScaleSetId, owner) - - if len(ret) == 0 { - panic("no return value specified for CreateMessageSession") - } - - var r0 *RunnerScaleSetSession - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int, string) (*RunnerScaleSetSession, error)); ok { - return returnFunc(ctx, runnerScaleSetId, owner) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int, string) *RunnerScaleSetSession); ok { - r0 = returnFunc(ctx, runnerScaleSetId, owner) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSetSession) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int, string) error); ok { - r1 = returnFunc(ctx, runnerScaleSetId, owner) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_CreateMessageSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateMessageSession' -type MockActionsService_CreateMessageSession_Call struct { - *mock.Call -} - -// CreateMessageSession is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSetId int -// - owner string -func (_e *MockActionsService_Expecter) CreateMessageSession(ctx interface{}, runnerScaleSetId interface{}, owner interface{}) *MockActionsService_CreateMessageSession_Call { - return &MockActionsService_CreateMessageSession_Call{Call: _e.mock.On("CreateMessageSession", ctx, runnerScaleSetId, owner)} -} - -func (_c *MockActionsService_CreateMessageSession_Call) Run(run func(ctx context.Context, runnerScaleSetId int, owner string)) *MockActionsService_CreateMessageSession_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockActionsService_CreateMessageSession_Call) Return(runnerScaleSetSession *RunnerScaleSetSession, err error) *MockActionsService_CreateMessageSession_Call { - _c.Call.Return(runnerScaleSetSession, err) - return _c -} - -func (_c *MockActionsService_CreateMessageSession_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSetId int, owner string) (*RunnerScaleSetSession, error)) *MockActionsService_CreateMessageSession_Call { - _c.Call.Return(run) - return _c -} - -// CreateRunnerScaleSet provides a mock function for the type MockActionsService -func (_mock *MockActionsService) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error) { - ret := _mock.Called(ctx, runnerScaleSet) - - if len(ret) == 0 { - panic("no return value specified for CreateRunnerScaleSet") - } - - var r0 *RunnerScaleSet - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, *RunnerScaleSet) (*RunnerScaleSet, error)); ok { - return returnFunc(ctx, runnerScaleSet) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, *RunnerScaleSet) *RunnerScaleSet); ok { - r0 = returnFunc(ctx, runnerScaleSet) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSet) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, *RunnerScaleSet) error); ok { - r1 = returnFunc(ctx, runnerScaleSet) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_CreateRunnerScaleSet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRunnerScaleSet' -type MockActionsService_CreateRunnerScaleSet_Call struct { - *mock.Call -} - -// CreateRunnerScaleSet is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSet *RunnerScaleSet -func (_e *MockActionsService_Expecter) CreateRunnerScaleSet(ctx interface{}, runnerScaleSet interface{}) *MockActionsService_CreateRunnerScaleSet_Call { - return &MockActionsService_CreateRunnerScaleSet_Call{Call: _e.mock.On("CreateRunnerScaleSet", ctx, runnerScaleSet)} -} - -func (_c *MockActionsService_CreateRunnerScaleSet_Call) Run(run func(ctx context.Context, runnerScaleSet *RunnerScaleSet)) *MockActionsService_CreateRunnerScaleSet_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 *RunnerScaleSet - if args[1] != nil { - arg1 = args[1].(*RunnerScaleSet) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockActionsService_CreateRunnerScaleSet_Call) Return(runnerScaleSet1 *RunnerScaleSet, err error) *MockActionsService_CreateRunnerScaleSet_Call { - _c.Call.Return(runnerScaleSet1, err) - return _c -} - -func (_c *MockActionsService_CreateRunnerScaleSet_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error)) *MockActionsService_CreateRunnerScaleSet_Call { - _c.Call.Return(run) - return _c -} - -// DeleteMessage provides a mock function for the type MockActionsService -func (_mock *MockActionsService) DeleteMessage(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, messageId int64) error { - ret := _mock.Called(ctx, messageQueueUrl, messageQueueAccessToken, messageId) - - if len(ret) == 0 { - panic("no return value specified for DeleteMessage") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, int64) error); ok { - r0 = returnFunc(ctx, messageQueueUrl, messageQueueAccessToken, messageId) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// MockActionsService_DeleteMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteMessage' -type MockActionsService_DeleteMessage_Call struct { - *mock.Call -} - -// DeleteMessage is a helper method to define mock.On call -// - ctx context.Context -// - messageQueueUrl string -// - messageQueueAccessToken string -// - messageId int64 -func (_e *MockActionsService_Expecter) DeleteMessage(ctx interface{}, messageQueueUrl interface{}, messageQueueAccessToken interface{}, messageId interface{}) *MockActionsService_DeleteMessage_Call { - return &MockActionsService_DeleteMessage_Call{Call: _e.mock.On("DeleteMessage", ctx, messageQueueUrl, messageQueueAccessToken, messageId)} -} - -func (_c *MockActionsService_DeleteMessage_Call) Run(run func(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, messageId int64)) *MockActionsService_DeleteMessage_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 int64 - if args[3] != nil { - arg3 = args[3].(int64) - } - run( - arg0, - arg1, - arg2, - arg3, - ) - }) - return _c -} - -func (_c *MockActionsService_DeleteMessage_Call) Return(err error) *MockActionsService_DeleteMessage_Call { - _c.Call.Return(err) - return _c -} - -func (_c *MockActionsService_DeleteMessage_Call) RunAndReturn(run func(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, messageId int64) error) *MockActionsService_DeleteMessage_Call { - _c.Call.Return(run) - return _c -} - -// DeleteMessageSession provides a mock function for the type MockActionsService -func (_mock *MockActionsService) DeleteMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) error { - ret := _mock.Called(ctx, runnerScaleSetId, sessionId) - - if len(ret) == 0 { - panic("no return value specified for DeleteMessageSession") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int, *uuid.UUID) error); ok { - r0 = returnFunc(ctx, runnerScaleSetId, sessionId) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// MockActionsService_DeleteMessageSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteMessageSession' -type MockActionsService_DeleteMessageSession_Call struct { - *mock.Call -} - -// DeleteMessageSession is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSetId int -// - sessionId *uuid.UUID -func (_e *MockActionsService_Expecter) DeleteMessageSession(ctx interface{}, runnerScaleSetId interface{}, sessionId interface{}) *MockActionsService_DeleteMessageSession_Call { - return &MockActionsService_DeleteMessageSession_Call{Call: _e.mock.On("DeleteMessageSession", ctx, runnerScaleSetId, sessionId)} -} - -func (_c *MockActionsService_DeleteMessageSession_Call) Run(run func(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID)) *MockActionsService_DeleteMessageSession_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 *uuid.UUID - if args[2] != nil { - arg2 = args[2].(*uuid.UUID) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockActionsService_DeleteMessageSession_Call) Return(err error) *MockActionsService_DeleteMessageSession_Call { - _c.Call.Return(err) - return _c -} - -func (_c *MockActionsService_DeleteMessageSession_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) error) *MockActionsService_DeleteMessageSession_Call { - _c.Call.Return(run) - return _c -} - -// DeleteRunnerScaleSet provides a mock function for the type MockActionsService -func (_mock *MockActionsService) DeleteRunnerScaleSet(ctx context.Context, runnerScaleSetId int) error { - ret := _mock.Called(ctx, runnerScaleSetId) - - if len(ret) == 0 { - panic("no return value specified for DeleteRunnerScaleSet") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int) error); ok { - r0 = returnFunc(ctx, runnerScaleSetId) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// MockActionsService_DeleteRunnerScaleSet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteRunnerScaleSet' -type MockActionsService_DeleteRunnerScaleSet_Call struct { - *mock.Call -} - -// DeleteRunnerScaleSet is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSetId int -func (_e *MockActionsService_Expecter) DeleteRunnerScaleSet(ctx interface{}, runnerScaleSetId interface{}) *MockActionsService_DeleteRunnerScaleSet_Call { - return &MockActionsService_DeleteRunnerScaleSet_Call{Call: _e.mock.On("DeleteRunnerScaleSet", ctx, runnerScaleSetId)} -} - -func (_c *MockActionsService_DeleteRunnerScaleSet_Call) Run(run func(ctx context.Context, runnerScaleSetId int)) *MockActionsService_DeleteRunnerScaleSet_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockActionsService_DeleteRunnerScaleSet_Call) Return(err error) *MockActionsService_DeleteRunnerScaleSet_Call { - _c.Call.Return(err) - return _c -} - -func (_c *MockActionsService_DeleteRunnerScaleSet_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSetId int) error) *MockActionsService_DeleteRunnerScaleSet_Call { - _c.Call.Return(run) - return _c -} - -// GenerateJitRunnerConfig provides a mock function for the type MockActionsService -func (_mock *MockActionsService) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int) (*RunnerScaleSetJitRunnerConfig, error) { - ret := _mock.Called(ctx, jitRunnerSetting, scaleSetId) - - if len(ret) == 0 { - panic("no return value specified for GenerateJitRunnerConfig") - } - - var r0 *RunnerScaleSetJitRunnerConfig - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, *RunnerScaleSetJitRunnerSetting, int) (*RunnerScaleSetJitRunnerConfig, error)); ok { - return returnFunc(ctx, jitRunnerSetting, scaleSetId) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, *RunnerScaleSetJitRunnerSetting, int) *RunnerScaleSetJitRunnerConfig); ok { - r0 = returnFunc(ctx, jitRunnerSetting, scaleSetId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSetJitRunnerConfig) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, *RunnerScaleSetJitRunnerSetting, int) error); ok { - r1 = returnFunc(ctx, jitRunnerSetting, scaleSetId) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_GenerateJitRunnerConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateJitRunnerConfig' -type MockActionsService_GenerateJitRunnerConfig_Call struct { - *mock.Call -} - -// GenerateJitRunnerConfig is a helper method to define mock.On call -// - ctx context.Context -// - jitRunnerSetting *RunnerScaleSetJitRunnerSetting -// - scaleSetId int -func (_e *MockActionsService_Expecter) GenerateJitRunnerConfig(ctx interface{}, jitRunnerSetting interface{}, scaleSetId interface{}) *MockActionsService_GenerateJitRunnerConfig_Call { - return &MockActionsService_GenerateJitRunnerConfig_Call{Call: _e.mock.On("GenerateJitRunnerConfig", ctx, jitRunnerSetting, scaleSetId)} -} - -func (_c *MockActionsService_GenerateJitRunnerConfig_Call) Run(run func(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int)) *MockActionsService_GenerateJitRunnerConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 *RunnerScaleSetJitRunnerSetting - if args[1] != nil { - arg1 = args[1].(*RunnerScaleSetJitRunnerSetting) - } - var arg2 int - if args[2] != nil { - arg2 = args[2].(int) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockActionsService_GenerateJitRunnerConfig_Call) Return(runnerScaleSetJitRunnerConfig *RunnerScaleSetJitRunnerConfig, err error) *MockActionsService_GenerateJitRunnerConfig_Call { - _c.Call.Return(runnerScaleSetJitRunnerConfig, err) - return _c -} - -func (_c *MockActionsService_GenerateJitRunnerConfig_Call) RunAndReturn(run func(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int) (*RunnerScaleSetJitRunnerConfig, error)) *MockActionsService_GenerateJitRunnerConfig_Call { - _c.Call.Return(run) - return _c -} - -// GetAcquirableJobs provides a mock function for the type MockActionsService -func (_mock *MockActionsService) GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*AcquirableJobList, error) { - ret := _mock.Called(ctx, runnerScaleSetId) - - if len(ret) == 0 { - panic("no return value specified for GetAcquirableJobs") - } - - var r0 *AcquirableJobList - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int) (*AcquirableJobList, error)); ok { - return returnFunc(ctx, runnerScaleSetId) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int) *AcquirableJobList); ok { - r0 = returnFunc(ctx, runnerScaleSetId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*AcquirableJobList) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int) error); ok { - r1 = returnFunc(ctx, runnerScaleSetId) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_GetAcquirableJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAcquirableJobs' -type MockActionsService_GetAcquirableJobs_Call struct { - *mock.Call -} - -// GetAcquirableJobs is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSetId int -func (_e *MockActionsService_Expecter) GetAcquirableJobs(ctx interface{}, runnerScaleSetId interface{}) *MockActionsService_GetAcquirableJobs_Call { - return &MockActionsService_GetAcquirableJobs_Call{Call: _e.mock.On("GetAcquirableJobs", ctx, runnerScaleSetId)} -} - -func (_c *MockActionsService_GetAcquirableJobs_Call) Run(run func(ctx context.Context, runnerScaleSetId int)) *MockActionsService_GetAcquirableJobs_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockActionsService_GetAcquirableJobs_Call) Return(acquirableJobList *AcquirableJobList, err error) *MockActionsService_GetAcquirableJobs_Call { - _c.Call.Return(acquirableJobList, err) - return _c -} - -func (_c *MockActionsService_GetAcquirableJobs_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSetId int) (*AcquirableJobList, error)) *MockActionsService_GetAcquirableJobs_Call { - _c.Call.Return(run) - return _c -} - -// GetMessage provides a mock function for the type MockActionsService -func (_mock *MockActionsService) GetMessage(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error) { - ret := _mock.Called(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity) - - if len(ret) == 0 { - panic("no return value specified for GetMessage") - } - - var r0 *RunnerScaleSetMessage - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, int64, int) (*RunnerScaleSetMessage, error)); ok { - return returnFunc(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, int64, int) *RunnerScaleSetMessage); ok { - r0 = returnFunc(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSetMessage) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, int64, int) error); ok { - r1 = returnFunc(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_GetMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMessage' -type MockActionsService_GetMessage_Call struct { - *mock.Call -} - -// GetMessage is a helper method to define mock.On call -// - ctx context.Context -// - messageQueueUrl string -// - messageQueueAccessToken string -// - lastMessageId int64 -// - maxCapacity int -func (_e *MockActionsService_Expecter) GetMessage(ctx interface{}, messageQueueUrl interface{}, messageQueueAccessToken interface{}, lastMessageId interface{}, maxCapacity interface{}) *MockActionsService_GetMessage_Call { - return &MockActionsService_GetMessage_Call{Call: _e.mock.On("GetMessage", ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId, maxCapacity)} -} - -func (_c *MockActionsService_GetMessage_Call) Run(run func(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, lastMessageId int64, maxCapacity int)) *MockActionsService_GetMessage_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 int64 - if args[3] != nil { - arg3 = args[3].(int64) - } - var arg4 int - if args[4] != nil { - arg4 = args[4].(int) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - ) - }) - return _c -} - -func (_c *MockActionsService_GetMessage_Call) Return(runnerScaleSetMessage *RunnerScaleSetMessage, err error) *MockActionsService_GetMessage_Call { - _c.Call.Return(runnerScaleSetMessage, err) - return _c -} - -func (_c *MockActionsService_GetMessage_Call) RunAndReturn(run func(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error)) *MockActionsService_GetMessage_Call { - _c.Call.Return(run) - return _c -} - -// GetRunner provides a mock function for the type MockActionsService -func (_mock *MockActionsService) GetRunner(ctx context.Context, runnerId int64) (*RunnerReference, error) { - ret := _mock.Called(ctx, runnerId) - - if len(ret) == 0 { - panic("no return value specified for GetRunner") - } - - var r0 *RunnerReference - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int64) (*RunnerReference, error)); ok { - return returnFunc(ctx, runnerId) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int64) *RunnerReference); ok { - r0 = returnFunc(ctx, runnerId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerReference) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int64) error); ok { - r1 = returnFunc(ctx, runnerId) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_GetRunner_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRunner' -type MockActionsService_GetRunner_Call struct { - *mock.Call -} - -// GetRunner is a helper method to define mock.On call -// - ctx context.Context -// - runnerId int64 -func (_e *MockActionsService_Expecter) GetRunner(ctx interface{}, runnerId interface{}) *MockActionsService_GetRunner_Call { - return &MockActionsService_GetRunner_Call{Call: _e.mock.On("GetRunner", ctx, runnerId)} -} - -func (_c *MockActionsService_GetRunner_Call) Run(run func(ctx context.Context, runnerId int64)) *MockActionsService_GetRunner_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int64 - if args[1] != nil { - arg1 = args[1].(int64) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockActionsService_GetRunner_Call) Return(runnerReference *RunnerReference, err error) *MockActionsService_GetRunner_Call { - _c.Call.Return(runnerReference, err) - return _c -} - -func (_c *MockActionsService_GetRunner_Call) RunAndReturn(run func(ctx context.Context, runnerId int64) (*RunnerReference, error)) *MockActionsService_GetRunner_Call { - _c.Call.Return(run) - return _c -} - -// GetRunnerByName provides a mock function for the type MockActionsService -func (_mock *MockActionsService) GetRunnerByName(ctx context.Context, runnerName string) (*RunnerReference, error) { - ret := _mock.Called(ctx, runnerName) - - if len(ret) == 0 { - panic("no return value specified for GetRunnerByName") - } - - var r0 *RunnerReference - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*RunnerReference, error)); ok { - return returnFunc(ctx, runnerName) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, string) *RunnerReference); ok { - r0 = returnFunc(ctx, runnerName) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerReference) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = returnFunc(ctx, runnerName) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_GetRunnerByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRunnerByName' -type MockActionsService_GetRunnerByName_Call struct { - *mock.Call -} - -// GetRunnerByName is a helper method to define mock.On call -// - ctx context.Context -// - runnerName string -func (_e *MockActionsService_Expecter) GetRunnerByName(ctx interface{}, runnerName interface{}) *MockActionsService_GetRunnerByName_Call { - return &MockActionsService_GetRunnerByName_Call{Call: _e.mock.On("GetRunnerByName", ctx, runnerName)} -} - -func (_c *MockActionsService_GetRunnerByName_Call) Run(run func(ctx context.Context, runnerName string)) *MockActionsService_GetRunnerByName_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockActionsService_GetRunnerByName_Call) Return(runnerReference *RunnerReference, err error) *MockActionsService_GetRunnerByName_Call { - _c.Call.Return(runnerReference, err) - return _c -} - -func (_c *MockActionsService_GetRunnerByName_Call) RunAndReturn(run func(ctx context.Context, runnerName string) (*RunnerReference, error)) *MockActionsService_GetRunnerByName_Call { - _c.Call.Return(run) - return _c -} - -// GetRunnerGroupByName provides a mock function for the type MockActionsService -func (_mock *MockActionsService) GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*RunnerGroup, error) { - ret := _mock.Called(ctx, runnerGroup) - - if len(ret) == 0 { - panic("no return value specified for GetRunnerGroupByName") - } - - var r0 *RunnerGroup - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, string) (*RunnerGroup, error)); ok { - return returnFunc(ctx, runnerGroup) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, string) *RunnerGroup); ok { - r0 = returnFunc(ctx, runnerGroup) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerGroup) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = returnFunc(ctx, runnerGroup) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_GetRunnerGroupByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRunnerGroupByName' -type MockActionsService_GetRunnerGroupByName_Call struct { - *mock.Call -} - -// GetRunnerGroupByName is a helper method to define mock.On call -// - ctx context.Context -// - runnerGroup string -func (_e *MockActionsService_Expecter) GetRunnerGroupByName(ctx interface{}, runnerGroup interface{}) *MockActionsService_GetRunnerGroupByName_Call { - return &MockActionsService_GetRunnerGroupByName_Call{Call: _e.mock.On("GetRunnerGroupByName", ctx, runnerGroup)} -} - -func (_c *MockActionsService_GetRunnerGroupByName_Call) Run(run func(ctx context.Context, runnerGroup string)) *MockActionsService_GetRunnerGroupByName_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockActionsService_GetRunnerGroupByName_Call) Return(runnerGroup1 *RunnerGroup, err error) *MockActionsService_GetRunnerGroupByName_Call { - _c.Call.Return(runnerGroup1, err) - return _c -} - -func (_c *MockActionsService_GetRunnerGroupByName_Call) RunAndReturn(run func(ctx context.Context, runnerGroup string) (*RunnerGroup, error)) *MockActionsService_GetRunnerGroupByName_Call { - _c.Call.Return(run) - return _c -} - -// GetRunnerScaleSet provides a mock function for the type MockActionsService -func (_mock *MockActionsService) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error) { - ret := _mock.Called(ctx, runnerGroupId, runnerScaleSetName) - - if len(ret) == 0 { - panic("no return value specified for GetRunnerScaleSet") - } - - var r0 *RunnerScaleSet - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int, string) (*RunnerScaleSet, error)); ok { - return returnFunc(ctx, runnerGroupId, runnerScaleSetName) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int, string) *RunnerScaleSet); ok { - r0 = returnFunc(ctx, runnerGroupId, runnerScaleSetName) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSet) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int, string) error); ok { - r1 = returnFunc(ctx, runnerGroupId, runnerScaleSetName) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_GetRunnerScaleSet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRunnerScaleSet' -type MockActionsService_GetRunnerScaleSet_Call struct { - *mock.Call -} - -// GetRunnerScaleSet is a helper method to define mock.On call -// - ctx context.Context -// - runnerGroupId int -// - runnerScaleSetName string -func (_e *MockActionsService_Expecter) GetRunnerScaleSet(ctx interface{}, runnerGroupId interface{}, runnerScaleSetName interface{}) *MockActionsService_GetRunnerScaleSet_Call { - return &MockActionsService_GetRunnerScaleSet_Call{Call: _e.mock.On("GetRunnerScaleSet", ctx, runnerGroupId, runnerScaleSetName)} -} - -func (_c *MockActionsService_GetRunnerScaleSet_Call) Run(run func(ctx context.Context, runnerGroupId int, runnerScaleSetName string)) *MockActionsService_GetRunnerScaleSet_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockActionsService_GetRunnerScaleSet_Call) Return(runnerScaleSet *RunnerScaleSet, err error) *MockActionsService_GetRunnerScaleSet_Call { - _c.Call.Return(runnerScaleSet, err) - return _c -} - -func (_c *MockActionsService_GetRunnerScaleSet_Call) RunAndReturn(run func(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error)) *MockActionsService_GetRunnerScaleSet_Call { - _c.Call.Return(run) - return _c -} - -// GetRunnerScaleSetById provides a mock function for the type MockActionsService -func (_mock *MockActionsService) GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int) (*RunnerScaleSet, error) { - ret := _mock.Called(ctx, runnerScaleSetId) - - if len(ret) == 0 { - panic("no return value specified for GetRunnerScaleSetById") - } - - var r0 *RunnerScaleSet - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int) (*RunnerScaleSet, error)); ok { - return returnFunc(ctx, runnerScaleSetId) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int) *RunnerScaleSet); ok { - r0 = returnFunc(ctx, runnerScaleSetId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSet) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int) error); ok { - r1 = returnFunc(ctx, runnerScaleSetId) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_GetRunnerScaleSetById_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRunnerScaleSetById' -type MockActionsService_GetRunnerScaleSetById_Call struct { - *mock.Call -} - -// GetRunnerScaleSetById is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSetId int -func (_e *MockActionsService_Expecter) GetRunnerScaleSetById(ctx interface{}, runnerScaleSetId interface{}) *MockActionsService_GetRunnerScaleSetById_Call { - return &MockActionsService_GetRunnerScaleSetById_Call{Call: _e.mock.On("GetRunnerScaleSetById", ctx, runnerScaleSetId)} -} - -func (_c *MockActionsService_GetRunnerScaleSetById_Call) Run(run func(ctx context.Context, runnerScaleSetId int)) *MockActionsService_GetRunnerScaleSetById_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockActionsService_GetRunnerScaleSetById_Call) Return(runnerScaleSet *RunnerScaleSet, err error) *MockActionsService_GetRunnerScaleSetById_Call { - _c.Call.Return(runnerScaleSet, err) - return _c -} - -func (_c *MockActionsService_GetRunnerScaleSetById_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSetId int) (*RunnerScaleSet, error)) *MockActionsService_GetRunnerScaleSetById_Call { - _c.Call.Return(run) - return _c -} - -// RefreshMessageSession provides a mock function for the type MockActionsService -func (_mock *MockActionsService) RefreshMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*RunnerScaleSetSession, error) { - ret := _mock.Called(ctx, runnerScaleSetId, sessionId) - - if len(ret) == 0 { - panic("no return value specified for RefreshMessageSession") - } - - var r0 *RunnerScaleSetSession - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int, *uuid.UUID) (*RunnerScaleSetSession, error)); ok { - return returnFunc(ctx, runnerScaleSetId, sessionId) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int, *uuid.UUID) *RunnerScaleSetSession); ok { - r0 = returnFunc(ctx, runnerScaleSetId, sessionId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSetSession) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int, *uuid.UUID) error); ok { - r1 = returnFunc(ctx, runnerScaleSetId, sessionId) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_RefreshMessageSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RefreshMessageSession' -type MockActionsService_RefreshMessageSession_Call struct { - *mock.Call -} - -// RefreshMessageSession is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSetId int -// - sessionId *uuid.UUID -func (_e *MockActionsService_Expecter) RefreshMessageSession(ctx interface{}, runnerScaleSetId interface{}, sessionId interface{}) *MockActionsService_RefreshMessageSession_Call { - return &MockActionsService_RefreshMessageSession_Call{Call: _e.mock.On("RefreshMessageSession", ctx, runnerScaleSetId, sessionId)} -} - -func (_c *MockActionsService_RefreshMessageSession_Call) Run(run func(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID)) *MockActionsService_RefreshMessageSession_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 *uuid.UUID - if args[2] != nil { - arg2 = args[2].(*uuid.UUID) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockActionsService_RefreshMessageSession_Call) Return(runnerScaleSetSession *RunnerScaleSetSession, err error) *MockActionsService_RefreshMessageSession_Call { - _c.Call.Return(runnerScaleSetSession, err) - return _c -} - -func (_c *MockActionsService_RefreshMessageSession_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*RunnerScaleSetSession, error)) *MockActionsService_RefreshMessageSession_Call { - _c.Call.Return(run) - return _c -} - -// RemoveRunner provides a mock function for the type MockActionsService -func (_mock *MockActionsService) RemoveRunner(ctx context.Context, runnerId int64) error { - ret := _mock.Called(ctx, runnerId) - - if len(ret) == 0 { - panic("no return value specified for RemoveRunner") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int64) error); ok { - r0 = returnFunc(ctx, runnerId) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// MockActionsService_RemoveRunner_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveRunner' -type MockActionsService_RemoveRunner_Call struct { - *mock.Call -} - -// RemoveRunner is a helper method to define mock.On call -// - ctx context.Context -// - runnerId int64 -func (_e *MockActionsService_Expecter) RemoveRunner(ctx interface{}, runnerId interface{}) *MockActionsService_RemoveRunner_Call { - return &MockActionsService_RemoveRunner_Call{Call: _e.mock.On("RemoveRunner", ctx, runnerId)} -} - -func (_c *MockActionsService_RemoveRunner_Call) Run(run func(ctx context.Context, runnerId int64)) *MockActionsService_RemoveRunner_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int64 - if args[1] != nil { - arg1 = args[1].(int64) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockActionsService_RemoveRunner_Call) Return(err error) *MockActionsService_RemoveRunner_Call { - _c.Call.Return(err) - return _c -} - -func (_c *MockActionsService_RemoveRunner_Call) RunAndReturn(run func(ctx context.Context, runnerId int64) error) *MockActionsService_RemoveRunner_Call { - _c.Call.Return(run) - return _c -} - -// SetUserAgent provides a mock function for the type MockActionsService -func (_mock *MockActionsService) SetUserAgent(info UserAgentInfo) { - _mock.Called(info) - return -} - -// MockActionsService_SetUserAgent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUserAgent' -type MockActionsService_SetUserAgent_Call struct { - *mock.Call -} - -// SetUserAgent is a helper method to define mock.On call -// - info UserAgentInfo -func (_e *MockActionsService_Expecter) SetUserAgent(info interface{}) *MockActionsService_SetUserAgent_Call { - return &MockActionsService_SetUserAgent_Call{Call: _e.mock.On("SetUserAgent", info)} -} - -func (_c *MockActionsService_SetUserAgent_Call) Run(run func(info UserAgentInfo)) *MockActionsService_SetUserAgent_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 UserAgentInfo - if args[0] != nil { - arg0 = args[0].(UserAgentInfo) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *MockActionsService_SetUserAgent_Call) Return() *MockActionsService_SetUserAgent_Call { - _c.Call.Return() - return _c -} - -func (_c *MockActionsService_SetUserAgent_Call) RunAndReturn(run func(info UserAgentInfo)) *MockActionsService_SetUserAgent_Call { - _c.Run(run) - return _c -} - -// UpdateRunnerScaleSet provides a mock function for the type MockActionsService -func (_mock *MockActionsService) UpdateRunnerScaleSet(ctx context.Context, runnerScaleSetId int, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error) { - ret := _mock.Called(ctx, runnerScaleSetId, runnerScaleSet) - - if len(ret) == 0 { - panic("no return value specified for UpdateRunnerScaleSet") - } - - var r0 *RunnerScaleSet - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int, *RunnerScaleSet) (*RunnerScaleSet, error)); ok { - return returnFunc(ctx, runnerScaleSetId, runnerScaleSet) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int, *RunnerScaleSet) *RunnerScaleSet); ok { - r0 = returnFunc(ctx, runnerScaleSetId, runnerScaleSet) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSet) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int, *RunnerScaleSet) error); ok { - r1 = returnFunc(ctx, runnerScaleSetId, runnerScaleSet) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockActionsService_UpdateRunnerScaleSet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateRunnerScaleSet' -type MockActionsService_UpdateRunnerScaleSet_Call struct { - *mock.Call -} - -// UpdateRunnerScaleSet is a helper method to define mock.On call -// - ctx context.Context -// - runnerScaleSetId int -// - runnerScaleSet *RunnerScaleSet -func (_e *MockActionsService_Expecter) UpdateRunnerScaleSet(ctx interface{}, runnerScaleSetId interface{}, runnerScaleSet interface{}) *MockActionsService_UpdateRunnerScaleSet_Call { - return &MockActionsService_UpdateRunnerScaleSet_Call{Call: _e.mock.On("UpdateRunnerScaleSet", ctx, runnerScaleSetId, runnerScaleSet)} -} - -func (_c *MockActionsService_UpdateRunnerScaleSet_Call) Run(run func(ctx context.Context, runnerScaleSetId int, runnerScaleSet *RunnerScaleSet)) *MockActionsService_UpdateRunnerScaleSet_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 *RunnerScaleSet - if args[2] != nil { - arg2 = args[2].(*RunnerScaleSet) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockActionsService_UpdateRunnerScaleSet_Call) Return(runnerScaleSet1 *RunnerScaleSet, err error) *MockActionsService_UpdateRunnerScaleSet_Call { - _c.Call.Return(runnerScaleSet1, err) - return _c -} - -func (_c *MockActionsService_UpdateRunnerScaleSet_Call) RunAndReturn(run func(ctx context.Context, runnerScaleSetId int, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error)) *MockActionsService_UpdateRunnerScaleSet_Call { - _c.Call.Return(run) - return _c -} - -// NewMockSessionService creates a new instance of MockSessionService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockSessionService(t interface { - mock.TestingT - Cleanup(func()) -}) *MockSessionService { - mock := &MockSessionService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// MockSessionService is an autogenerated mock type for the SessionService type -type MockSessionService struct { - mock.Mock -} - -type MockSessionService_Expecter struct { - mock *mock.Mock -} - -func (_m *MockSessionService) EXPECT() *MockSessionService_Expecter { - return &MockSessionService_Expecter{mock: &_m.Mock} -} - -// AcquireJobs provides a mock function for the type MockSessionService -func (_mock *MockSessionService) AcquireJobs(ctx context.Context, requestIds []int64) ([]int64, error) { - ret := _mock.Called(ctx, requestIds) - - if len(ret) == 0 { - panic("no return value specified for AcquireJobs") - } - - var r0 []int64 - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, []int64) ([]int64, error)); ok { - return returnFunc(ctx, requestIds) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, []int64) []int64); ok { - r0 = returnFunc(ctx, requestIds) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]int64) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, []int64) error); ok { - r1 = returnFunc(ctx, requestIds) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockSessionService_AcquireJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AcquireJobs' -type MockSessionService_AcquireJobs_Call struct { - *mock.Call -} - -// AcquireJobs is a helper method to define mock.On call -// - ctx context.Context -// - requestIds []int64 -func (_e *MockSessionService_Expecter) AcquireJobs(ctx interface{}, requestIds interface{}) *MockSessionService_AcquireJobs_Call { - return &MockSessionService_AcquireJobs_Call{Call: _e.mock.On("AcquireJobs", ctx, requestIds)} -} - -func (_c *MockSessionService_AcquireJobs_Call) Run(run func(ctx context.Context, requestIds []int64)) *MockSessionService_AcquireJobs_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []int64 - if args[1] != nil { - arg1 = args[1].([]int64) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockSessionService_AcquireJobs_Call) Return(int64s []int64, err error) *MockSessionService_AcquireJobs_Call { - _c.Call.Return(int64s, err) - return _c -} - -func (_c *MockSessionService_AcquireJobs_Call) RunAndReturn(run func(ctx context.Context, requestIds []int64) ([]int64, error)) *MockSessionService_AcquireJobs_Call { - _c.Call.Return(run) - return _c -} - -// Close provides a mock function for the type MockSessionService -func (_mock *MockSessionService) Close() error { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for Close") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func() error); ok { - r0 = returnFunc() - } else { - r0 = ret.Error(0) - } - return r0 -} - -// MockSessionService_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type MockSessionService_Close_Call struct { - *mock.Call -} - -// Close is a helper method to define mock.On call -func (_e *MockSessionService_Expecter) Close() *MockSessionService_Close_Call { - return &MockSessionService_Close_Call{Call: _e.mock.On("Close")} -} - -func (_c *MockSessionService_Close_Call) Run(run func()) *MockSessionService_Close_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockSessionService_Close_Call) Return(err error) *MockSessionService_Close_Call { - _c.Call.Return(err) - return _c -} - -func (_c *MockSessionService_Close_Call) RunAndReturn(run func() error) *MockSessionService_Close_Call { - _c.Call.Return(run) - return _c -} - -// DeleteMessage provides a mock function for the type MockSessionService -func (_mock *MockSessionService) DeleteMessage(ctx context.Context, messageId int64) error { - ret := _mock.Called(ctx, messageId) - - if len(ret) == 0 { - panic("no return value specified for DeleteMessage") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int64) error); ok { - r0 = returnFunc(ctx, messageId) - } else { - r0 = ret.Error(0) - } - return r0 -} - -// MockSessionService_DeleteMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteMessage' -type MockSessionService_DeleteMessage_Call struct { - *mock.Call -} - -// DeleteMessage is a helper method to define mock.On call -// - ctx context.Context -// - messageId int64 -func (_e *MockSessionService_Expecter) DeleteMessage(ctx interface{}, messageId interface{}) *MockSessionService_DeleteMessage_Call { - return &MockSessionService_DeleteMessage_Call{Call: _e.mock.On("DeleteMessage", ctx, messageId)} -} - -func (_c *MockSessionService_DeleteMessage_Call) Run(run func(ctx context.Context, messageId int64)) *MockSessionService_DeleteMessage_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int64 - if args[1] != nil { - arg1 = args[1].(int64) - } - run( - arg0, - arg1, - ) - }) - return _c -} - -func (_c *MockSessionService_DeleteMessage_Call) Return(err error) *MockSessionService_DeleteMessage_Call { - _c.Call.Return(err) - return _c -} - -func (_c *MockSessionService_DeleteMessage_Call) RunAndReturn(run func(ctx context.Context, messageId int64) error) *MockSessionService_DeleteMessage_Call { - _c.Call.Return(run) - return _c -} - -// GetMessage provides a mock function for the type MockSessionService -func (_mock *MockSessionService) GetMessage(ctx context.Context, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error) { - ret := _mock.Called(ctx, lastMessageId, maxCapacity) - - if len(ret) == 0 { - panic("no return value specified for GetMessage") - } - - var r0 *RunnerScaleSetMessage - var r1 error - if returnFunc, ok := ret.Get(0).(func(context.Context, int64, int) (*RunnerScaleSetMessage, error)); ok { - return returnFunc(ctx, lastMessageId, maxCapacity) - } - if returnFunc, ok := ret.Get(0).(func(context.Context, int64, int) *RunnerScaleSetMessage); ok { - r0 = returnFunc(ctx, lastMessageId, maxCapacity) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RunnerScaleSetMessage) - } - } - if returnFunc, ok := ret.Get(1).(func(context.Context, int64, int) error); ok { - r1 = returnFunc(ctx, lastMessageId, maxCapacity) - } else { - r1 = ret.Error(1) - } - return r0, r1 -} - -// MockSessionService_GetMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMessage' -type MockSessionService_GetMessage_Call struct { - *mock.Call -} - -// GetMessage is a helper method to define mock.On call -// - ctx context.Context -// - lastMessageId int64 -// - maxCapacity int -func (_e *MockSessionService_Expecter) GetMessage(ctx interface{}, lastMessageId interface{}, maxCapacity interface{}) *MockSessionService_GetMessage_Call { - return &MockSessionService_GetMessage_Call{Call: _e.mock.On("GetMessage", ctx, lastMessageId, maxCapacity)} -} - -func (_c *MockSessionService_GetMessage_Call) Run(run func(ctx context.Context, lastMessageId int64, maxCapacity int)) *MockSessionService_GetMessage_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 int64 - if args[1] != nil { - arg1 = args[1].(int64) - } - var arg2 int - if args[2] != nil { - arg2 = args[2].(int) - } - run( - arg0, - arg1, - arg2, - ) - }) - return _c -} - -func (_c *MockSessionService_GetMessage_Call) Return(runnerScaleSetMessage *RunnerScaleSetMessage, err error) *MockSessionService_GetMessage_Call { - _c.Call.Return(runnerScaleSetMessage, err) - return _c -} - -func (_c *MockSessionService_GetMessage_Call) RunAndReturn(run func(ctx context.Context, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error)) *MockSessionService_GetMessage_Call { - _c.Call.Return(run) - return _c -} diff --git a/github/actions/multi_client.go b/github/actions/multi_client.go deleted file mode 100644 index b9ed5873..00000000 --- a/github/actions/multi_client.go +++ /dev/null @@ -1,102 +0,0 @@ -package actions - -import ( - "context" - "fmt" - "sync" - - "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig" - "github.com/go-logr/logr" -) - -type MultiClient interface { - GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...ClientOption) (ActionsService, error) -} - -type multiClient struct { - // To lock adding and removing of individual clients. - mu sync.Mutex - clients map[ActionsClientKey]*Client - - logger logr.Logger -} - -type GitHubAppAuth struct { - // AppID is the ID or the Client ID of the application - AppID string - AppInstallationID int64 - AppPrivateKey string -} - -type ActionsAuth struct { - // GitHub App - AppCreds *GitHubAppAuth - - // GitHub PAT - Token string -} - -type ActionsClientKey struct { - Identifier string - Namespace string -} - -func NewMultiClient(logger logr.Logger) MultiClient { - return &multiClient{ - mu: sync.Mutex{}, - clients: make(map[ActionsClientKey]*Client), - logger: logger, - } -} - -func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...ClientOption) (ActionsService, error) { - m.logger.Info("retrieve actions client", "githubConfigURL", githubConfigURL, "namespace", namespace) - - if err := appConfig.Validate(); err != nil { - return nil, fmt.Errorf("failed to validate app config: %w", err) - } - - var creds ActionsAuth - if len(appConfig.Token) > 0 { - creds.Token = appConfig.Token - } else { - creds.AppCreds = &GitHubAppAuth{ - AppID: appConfig.AppID, - AppInstallationID: appConfig.AppInstallationID, - AppPrivateKey: appConfig.AppPrivateKey, - } - } - - client, err := NewClient( - githubConfigURL, - &creds, - append([]ClientOption{ - WithLogger(m.logger), - }, options...)..., - ) - if err != nil { - return nil, fmt.Errorf("failed to instantiate new client: %w", err) - } - - m.mu.Lock() - defer m.mu.Unlock() - - key := ActionsClientKey{ - Identifier: client.Identifier(), - Namespace: namespace, - } - - cachedClient, has := m.clients[key] - if has && cachedClient.rootCAs.Equal(client.rootCAs) { - m.logger.Info("using cache client", "githubConfigURL", githubConfigURL, "namespace", namespace) - return cachedClient, nil - } - - m.logger.Info("creating new client", "githubConfigURL", githubConfigURL, "namespace", namespace) - - m.clients[key] = client - - m.logger.Info("successfully created new client", "githubConfigURL", githubConfigURL, "namespace", namespace) - - return client, nil -} diff --git a/github/actions/multi_client_test.go b/github/actions/multi_client_test.go deleted file mode 100644 index 95a65572..00000000 --- a/github/actions/multi_client_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package actions - -import ( - "context" - "fmt" - "testing" - - "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig" - "github.com/go-logr/logr" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var testUserAgent = UserAgentInfo{ - Version: "test", - CommitSHA: "test", - ScaleSetID: 1, -} - -func TestMultiClientCaching(t *testing.T) { - logger := logr.Discard() - ctx := context.Background() - multiClient := NewMultiClient(logger).(*multiClient) - - defaultNamespace := "default" - defaultConfigURL := "https://github.com/org/repo" - defaultCreds := &appconfig.AppConfig{ - Token: "token", - } - defaultAuth := ActionsAuth{ - Token: defaultCreds.Token, - } - client, err := NewClient(defaultConfigURL, &defaultAuth) - require.NoError(t, err) - - multiClient.clients[ActionsClientKey{client.Identifier(), defaultNamespace}] = client - - // Verify that the client is cached - cachedClient, err := multiClient.GetClientFor( - ctx, - defaultConfigURL, - defaultCreds, - defaultNamespace, - ) - require.NoError(t, err) - assert.Equal(t, client, cachedClient) - assert.Len(t, multiClient.clients, 1) - - // Asking for a different client results in creating and caching a new client - otherNamespace := "other" - newClient, err := multiClient.GetClientFor( - ctx, - defaultConfigURL, - defaultCreds, - otherNamespace, - ) - require.NoError(t, err) - assert.NotEqual(t, client, newClient) - assert.Len(t, multiClient.clients, 2) -} - -func TestMultiClientOptions(t *testing.T) { - logger := logr.Discard() - ctx := context.Background() - - defaultNamespace := "default" - defaultConfigURL := "https://github.com/org/repo" - - t.Run("GetClientFor", func(t *testing.T) { - defaultCreds := &appconfig.AppConfig{ - Token: "token", - } - - multiClient := NewMultiClient(logger) - service, err := multiClient.GetClientFor( - ctx, - defaultConfigURL, - defaultCreds, - defaultNamespace, - ) - service.SetUserAgent(testUserAgent) - - require.NoError(t, err) - - client := service.(*Client) - req, err := client.NewGitHubAPIRequest(ctx, "GET", "/test", nil) - require.NoError(t, err) - assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent")) - }) -} - -func TestCreateJWT(t *testing.T) { - key := `-----BEGIN PRIVATE KEY----- -MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQC7tgquvNIp+Ik3 -rRVZ9r0zJLsSzTHqr2dA6EUUmpRiQ25MzjMqKqu0OBwvh/pZyfjSIkKrhIridNK4 -DWnPfPWHE2K3Muh0X2sClxtqiiFmXsvbiTzhUm5a+zCcv0pJCWYnKi0HmyXpAXjJ -iN8mWliZN896verVYXWrod7EaAnuST4TiJeqZYW4bBBG81fPNc/UP4j6CKAW8nx9 -HtcX6ApvlHeCLZUTW/qhGLO0nLKoEOr3tXCPW5VjKzlm134Dl+8PN6f1wv6wMAoA -lo7Ha5+c74jhPL6gHXg7cRaHQmuJCJrtl8qbLkFAulfkBixBw/6i11xoM/MOC64l -TWmXqrxTAgMBAAECgf9zYlxfL+rdHRXCoOm7pUeSPL0dWaPFP12d/Z9LSlDAt/h6 -Pd+eqYEwhf795SAbJuzNp51Ls6LUGnzmLOdojKwfqJ51ahT1qbcBcMZNOcvtGqZ9 -xwLG993oyR49C361Lf2r8mKrdrR5/fW0B1+1s6A+eRFivqFOtsOc4V4iMeHYsCVJ -hM7yMu0UfpolDJA/CzopsoGq3UuQlibUEUxKULza06aDjg/gBH3PnP+fQ1m0ovDY -h0pX6SCq5fXVJFS+Pbpu7j2ePNm3mr0qQhrUONZq0qhGN/piCbBZe1CqWApyO7nA -B95VChhL1eYs1BKvQePh12ap83woIUcW2mJF2F0CgYEA+aERTuKWEm+zVNKS9t3V -qNhecCOpayKM9OlALIK/9W6KBS+pDsjQQteQAUAItjvLiDjd5KsrtSgjbSgr66IP -b615Pakywe5sdnVGzSv+07KMzuFob9Hj6Xv9als9Y2geVhUZB2Frqve/UCjmC56i -zuQTSele5QKCSSTFBV3423cCgYEAwIBv9ChsI+mse6vPaqSPpZ2n237anThMcP33 -aS0luYXqMWXZ0TQ/uSmCElY4G3xqNo8szzfy6u0HpldeUsEUsIcBNUV5kIIb8wKu -Zmgcc8gBIjJkyUJI4wuz9G/fegEUj3u6Cttmmj4iWLzCRscRJdfGpqwRIhOGyXb9 -2Rur5QUCgYAGWIPaH4R1H4XNiDTYNbdyvV1ZOG7cHFq89xj8iK5cjNzRWO7RQ2WX -7WbpwTj3ePmpktiBMaDA0C5mXfkP2mTOD/jfCmgR6f+z2zNbj9zAgO93at9+yDUl -AFPm2j7rQgBTa+HhACb+h6HDZebDMNsuqzmaTWZuJ+wr89VWV5c17QKBgH3jwNNQ -mCAIUidynaulQNfTOZIe7IMC7WK7g9CBmPkx7Y0uiXr6C25hCdJKFllLTP6vNWOy -uCcQqf8LhgDiilBDifO3op9xpyuOJlWMYocJVkxx3l2L/rSU07PYcbKNAFAxXuJ4 -xym51qZnkznMN5ei/CPFxVKeqHgaXDpekVStAoGAV3pSWAKDXY/42XEHixrCTqLW -kBxfaf3g7iFnl3u8+7Z/7Cb4ZqFcw0bRJseKuR9mFvBhcZxSErbMDEYrevefU9aM -APeCxEyw6hJXgbWKoG7Fw2g2HP3ytCJ4YzH0zNitHjk/1h4BG7z8cEQILCSv5mN2 -etFcaQuTHEZyRhhJ4BU= ------END PRIVATE KEY-----` - - auth := &GitHubAppAuth{ - AppID: "123", - AppPrivateKey: key, - } - jwt, err := createJWTForGitHubApp(auth) - if err != nil { - t.Fatal(err) - } - fmt.Println(jwt) -} diff --git a/github/actions/sessionservice.go b/github/actions/sessionservice.go deleted file mode 100644 index cdecd602..00000000 --- a/github/actions/sessionservice.go +++ /dev/null @@ -1,13 +0,0 @@ -package actions - -import ( - "context" - "io" -) - -type SessionService interface { - GetMessage(ctx context.Context, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error) - DeleteMessage(ctx context.Context, messageId int64) error - AcquireJobs(ctx context.Context, requestIds []int64) ([]int64, error) - io.Closer -} diff --git a/github/actions/types.go b/github/actions/types.go deleted file mode 100644 index 713096ba..00000000 --- a/github/actions/types.go +++ /dev/null @@ -1,158 +0,0 @@ -package actions - -import ( - "time" - - "github.com/google/uuid" -) - -type AcquirableJobList struct { - Count int `json:"count"` - Jobs []AcquirableJob `json:"value"` -} - -type AcquirableJob struct { - AcquireJobUrl string `json:"acquireJobUrl"` - MessageType string `json:"messageType"` - RunnerRequestId int64 `json:"runnerRequestId"` - RepositoryName string `json:"repositoryName"` - OwnerName string `json:"ownerName"` - JobWorkflowRef string `json:"jobWorkflowRef"` - EventName string `json:"eventName"` - RequestLabels []string `json:"requestLabels"` -} - -type Int64List struct { - Count int `json:"count"` - Value []int64 `json:"value"` -} - -type JobAvailable struct { - AcquireJobUrl string `json:"acquireJobUrl"` - JobMessageBase -} - -type JobAssigned struct { - JobMessageBase -} - -type JobStarted struct { - RunnerID int `json:"runnerId"` - RunnerName string `json:"runnerName"` - JobMessageBase -} - -type JobCompleted struct { - Result string `json:"result"` - RunnerId int `json:"runnerId"` - RunnerName string `json:"runnerName"` - JobMessageBase -} - -type JobMessageType struct { - MessageType string `json:"messageType"` -} - -type JobMessageBase struct { - JobMessageType - RunnerRequestID int64 `json:"runnerRequestId"` - RepositoryName string `json:"repositoryName"` - OwnerName string `json:"ownerName"` - JobID string `json:"jobId"` - JobWorkflowRef string `json:"jobWorkflowRef"` - JobDisplayName string `json:"jobDisplayName"` - WorkflowRunID int64 `json:"workflowRunId"` - EventName string `json:"eventName"` - RequestLabels []string `json:"requestLabels"` - QueueTime time.Time `json:"queueTime"` - ScaleSetAssignTime time.Time `json:"scaleSetAssignTime"` - RunnerAssignTime time.Time `json:"runnerAssignTime"` - FinishTime time.Time `json:"finishTime"` -} - -type Label struct { - Type string `json:"type"` - Name string `json:"name"` -} - -type RunnerGroup struct { - ID int64 `json:"id"` - Name string `json:"name"` - Size int64 `json:"size"` - IsDefault bool `json:"isDefaultGroup"` -} - -type RunnerGroupList struct { - Count int `json:"count"` - RunnerGroups []RunnerGroup `json:"value"` -} - -type RunnerScaleSet struct { - Id int `json:"id,omitempty"` - Name string `json:"name,omitempty"` - RunnerGroupId int `json:"runnerGroupId,omitempty"` - RunnerGroupName string `json:"runnerGroupName,omitempty"` - Labels []Label `json:"labels,omitempty"` - RunnerSetting RunnerSetting `json:"RunnerSetting,omitempty"` - CreatedOn time.Time `json:"createdOn,omitempty"` - RunnerJitConfigUrl string `json:"runnerJitConfigUrl,omitempty"` - Statistics *RunnerScaleSetStatistic `json:"statistics,omitempty"` -} - -type RunnerScaleSetJitRunnerSetting struct { - Name string `json:"name"` - WorkFolder string `json:"workFolder"` -} - -type RunnerScaleSetMessage struct { - MessageId int64 `json:"messageId"` - MessageType string `json:"messageType"` - Body string `json:"body"` - Statistics *RunnerScaleSetStatistic `json:"statistics"` -} - -type runnerScaleSetsResponse struct { - Count int `json:"count"` - RunnerScaleSets []RunnerScaleSet `json:"value"` -} - -type RunnerScaleSetSession struct { - SessionId *uuid.UUID `json:"sessionId,omitempty"` - OwnerName string `json:"ownerName,omitempty"` - RunnerScaleSet *RunnerScaleSet `json:"runnerScaleSet,omitempty"` - MessageQueueUrl string `json:"messageQueueUrl,omitempty"` - MessageQueueAccessToken string `json:"messageQueueAccessToken,omitempty"` - Statistics *RunnerScaleSetStatistic `json:"statistics,omitempty"` -} - -type RunnerScaleSetStatistic struct { - TotalAvailableJobs int `json:"totalAvailableJobs"` - TotalAcquiredJobs int `json:"totalAcquiredJobs"` - TotalAssignedJobs int `json:"totalAssignedJobs"` - TotalRunningJobs int `json:"totalRunningJobs"` - TotalRegisteredRunners int `json:"totalRegisteredRunners"` - TotalBusyRunners int `json:"totalBusyRunners"` - TotalIdleRunners int `json:"totalIdleRunners"` -} - -type RunnerSetting struct { - Ephemeral bool `json:"ephemeral,omitempty"` - IsElastic bool `json:"isElastic,omitempty"` - DisableUpdate bool `json:"disableUpdate,omitempty"` -} - -type RunnerReferenceList struct { - Count int `json:"count"` - RunnerReferences []RunnerReference `json:"value"` -} - -type RunnerReference struct { - Id int `json:"id"` - Name string `json:"name"` - RunnerScaleSetId int `json:"runnerScaleSetId"` -} - -type RunnerScaleSetJitRunnerConfig struct { - Runner *RunnerReference `json:"runner"` - EncodedJITConfig string `json:"encodedJITConfig"` -} diff --git a/github/actions/user_agent_test.go b/github/actions/user_agent_test.go deleted file mode 100644 index e79586b6..00000000 --- a/github/actions/user_agent_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package actions_test - -import ( - "testing" - - "github.com/actions/actions-runner-controller/github/actions" - "github.com/stretchr/testify/assert" -) - -func TestUserAgentInfoString(t *testing.T) { - userAgentInfo := actions.UserAgentInfo{ - Version: "0.1.0", - CommitSHA: "1234567890abcdef", - ScaleSetID: 10, - HasProxy: true, - Subsystem: "test", - } - - userAgent := userAgentInfo.String() - expectedProduct := "actions-runner-controller/0.1.0 (1234567890abcdef; test)" - assert.Contains(t, userAgent, expectedProduct) - expectedScaleSet := "ScaleSetID/10 (Proxy/enabled)" - assert.Contains(t, userAgent, expectedScaleSet) -}