mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-11 03:57:01 +00:00
Introduce new preview auto-scaling mode for ARC. (#2153)
Co-authored-by: Cory Miller <cory-miller@github.com> Co-authored-by: Nikola Jokic <nikola-jokic@github.com> Co-authored-by: Ava Stancu <AvaStancu@github.com> Co-authored-by: Ferenc Hammerl <fhammerl@github.com> Co-authored-by: Francesco Renzi <rentziass@github.com> Co-authored-by: Bassem Dghaidi <Link-@github.com>
This commit is contained in:
1101
github/actions/client.go
Normal file
1101
github/actions/client.go
Normal file
File diff suppressed because it is too large
Load Diff
75
github/actions/client_generate_jit_test.go
Normal file
75
github/actions/client_generate_jit_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package actions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/github/actions"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenerateJitRunnerConfig(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
|
||||
t.Run("Get JIT Config for Runner", func(t *testing.T) {
|
||||
name := "Get JIT Config for Runner"
|
||||
want := &actions.RunnerScaleSetJitRunnerConfig{}
|
||||
response := []byte(`{"count":1,"value":[{"id":1,"name":"scale-set-name"}]}`)
|
||||
|
||||
runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{}
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(response)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
got, err := actionsClient.GenerateJitRunnerConfig(context.Background(), runnerSettings, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateJitRunnerConfig got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GenerateJitRunnerConfig(%v) mismatch (-want +got):\n%s", name, diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{}
|
||||
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.RetryWaitMax = 1 * time.Millisecond
|
||||
retryClient.RetryMax = 1
|
||||
|
||||
actualRetry := 0
|
||||
expectedRetry := retryClient.RetryMax + 1
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
_, _ = actionsClient.GenerateJitRunnerConfig(context.Background(), runnerSettings, 1)
|
||||
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
})
|
||||
}
|
||||
144
github/actions/client_job_acquisition_test.go
Normal file
144
github/actions/client_job_acquisition_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package actions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/github/actions"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAcquireJobs(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
|
||||
t.Run("Acquire Job", func(t *testing.T) {
|
||||
name := "Acquire Job"
|
||||
|
||||
want := []int64{1}
|
||||
response := []byte(`{"value": [1]}`)
|
||||
|
||||
session := &actions.RunnerScaleSetSession{
|
||||
RunnerScaleSet: &actions.RunnerScaleSet{Id: 1},
|
||||
MessageQueueAccessToken: "abc",
|
||||
}
|
||||
requestIDs := want
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(response)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
}
|
||||
|
||||
got, err := actionsClient.AcquireJobs(context.Background(), session.RunnerScaleSet.Id, session.MessageQueueAccessToken, requestIDs)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateRunnerScaleSet got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetRunnerScaleSet(%v) mismatch (-want +got):\n%s", name, diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
session := &actions.RunnerScaleSetSession{
|
||||
RunnerScaleSet: &actions.RunnerScaleSet{Id: 1},
|
||||
MessageQueueAccessToken: "abc",
|
||||
}
|
||||
var requestIDs []int64 = []int64{1}
|
||||
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.RetryWaitMax = 1 * time.Millisecond
|
||||
retryClient.RetryMax = 1
|
||||
|
||||
actualRetry := 0
|
||||
expectedRetry := retryClient.RetryMax + 1
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
}
|
||||
|
||||
_, _ = actionsClient.AcquireJobs(context.Background(), session.RunnerScaleSet.Id, session.MessageQueueAccessToken, requestIDs)
|
||||
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAcquirableJobs(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
|
||||
t.Run("Acquire Job", func(t *testing.T) {
|
||||
name := "Acquire Job"
|
||||
|
||||
want := &actions.AcquirableJobList{}
|
||||
response := []byte(`{"count": 0}`)
|
||||
|
||||
runnerScaleSet := &actions.RunnerScaleSet{Id: 1}
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(response)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
got, err := actionsClient.GetAcquirableJobs(context.Background(), runnerScaleSet.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("GetAcquirableJobs got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetAcquirableJobs(%v) mismatch (-want +got):\n%s", name, diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
runnerScaleSet := &actions.RunnerScaleSet{Id: 1}
|
||||
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.RetryWaitMax = 1 * time.Millisecond
|
||||
retryClient.RetryMax = 1
|
||||
|
||||
actualRetry := 0
|
||||
expectedRetry := retryClient.RetryMax + 1
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
_, _ = actionsClient.GetAcquirableJobs(context.Background(), runnerScaleSet.Id)
|
||||
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
})
|
||||
}
|
||||
269
github/actions/client_runner_scale_set_message_test.go
Normal file
269
github/actions/client_runner_scale_set_message_test.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package actions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/github/actions"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetMessage(t *testing.T) {
|
||||
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 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(response)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
got, err := actionsClient.GetMessage(context.Background(), s.URL, token, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMessage got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetMessage mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.RetryWaitMax = 1 * time.Nanosecond
|
||||
retryClient.RetryMax = 1
|
||||
|
||||
actualRetry := 0
|
||||
expectedRetry := retryClient.RetryMax + 1
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
_, _ = actionsClient.GetMessage(context.Background(), s.URL, token, 0)
|
||||
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
})
|
||||
|
||||
t.Run("Custom retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryMax := 1
|
||||
retryWaitMax := 1 * time.Nanosecond
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
_, _ = actionsClient.GetMessage(context.Background(), s.URL, token, 0)
|
||||
expectedRetry := retryMax + 1
|
||||
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) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetMessage(context.Background(), s.URL, token, 0)
|
||||
if err == nil {
|
||||
t.Fatalf("GetMessage did not get exepected error, ")
|
||||
}
|
||||
var expectedErr *actions.MessageQueueTokenExpiredError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Status code not found", func(t *testing.T) {
|
||||
want := actions.ActionsError{
|
||||
Message: "Request returned status: 404 Not Found",
|
||||
StatusCode: 404,
|
||||
}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetMessage(context.Background(), s.URL, token, 0)
|
||||
if err == nil {
|
||||
t.Fatalf("GetMessage did not get exepected error, ")
|
||||
}
|
||||
if diff := cmp.Diff(want.Error(), err.Error()); diff != "" {
|
||||
t.Errorf("GetMessage mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetMessage(context.Background(), s.URL, token, 0)
|
||||
if err == nil {
|
||||
t.Fatalf("GetMessage did not get exepected error,")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestDeleteMessage(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
runnerScaleSetMessage := &actions.RunnerScaleSetMessage{
|
||||
MessageId: 1,
|
||||
MessageType: "rssType",
|
||||
}
|
||||
|
||||
t.Run("Delete existing message", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err := actionsClient.DeleteMessage(context.Background(), s.URL, token, runnerScaleSetMessage.MessageId)
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteMessage got unexepected error, %v", err)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Message token expired", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err := actionsClient.DeleteMessage(context.Background(), s.URL, token, 0)
|
||||
if err == nil {
|
||||
t.Fatalf("DeleteMessage did not get exepected error, ")
|
||||
}
|
||||
var expectedErr *actions.MessageQueueTokenExpiredError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err := actionsClient.DeleteMessage(context.Background(), s.URL, token, runnerScaleSetMessage.MessageId)
|
||||
if err == nil {
|
||||
t.Fatalf("DeleteMessage did not get exepected error")
|
||||
}
|
||||
var expectedErr *actions.ActionsError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryMax := 1
|
||||
retryClient.RetryWaitMax = time.Nanosecond
|
||||
retryClient.RetryMax = retryMax
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_ = actionsClient.DeleteMessage(context.Background(), s.URL, token, runnerScaleSetMessage.MessageId)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(rsl)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err = actionsClient.DeleteMessage(context.Background(), s.URL, token, runnerScaleSetMessage.MessageId+1)
|
||||
var expectedErr *actions.ActionsError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
}
|
||||
244
github/actions/client_runner_scale_set_session_test.go
Normal file
244
github/actions/client_runner_scale_set_session_test.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package actions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/github/actions"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateMessageSession(t *testing.T) {
|
||||
t.Run("CreateMessageSession unmarshals correctly", func(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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",
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(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)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
retryMax := 1
|
||||
retryWaitMax := 1 * time.Microsecond
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &srv.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
|
||||
got, err := actionsClient.CreateMessageSession(context.Background(), runnerScaleSet.Id, owner)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateMessageSession got unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Fatalf("CreateMessageSession got unexpected diff: -want +got: %v", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreateMessageSession unmarshals errors into ActionsError", func(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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{
|
||||
ExceptionName: "CSharpExceptionNameHere",
|
||||
Message: "could not do something",
|
||||
StatusCode: http.StatusBadRequest,
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
resp := []byte(`{"typeName": "CSharpExceptionNameHere","message": "could not do something"}`)
|
||||
w.Write(resp)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
retryMax := 1
|
||||
retryWaitMax := 1 * time.Microsecond
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &srv.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
|
||||
got, err := actionsClient.CreateMessageSession(context.Background(), runnerScaleSet.Id, owner)
|
||||
if err == nil {
|
||||
t.Fatalf("CreateMessageSession did not get expected error: %v", got)
|
||||
}
|
||||
|
||||
errorTypeForComparison := &actions.ActionsError{}
|
||||
if isActionsError := errors.As(err, &errorTypeForComparison); !isActionsError {
|
||||
t.Fatalf("CreateMessageSession expected to be able to parse the error into ActionsError type: %v", err)
|
||||
}
|
||||
|
||||
gotErr := err.(*actions.ActionsError)
|
||||
|
||||
if diff := cmp.Diff(want, gotErr); diff != "" {
|
||||
t.Fatalf("CreateMessageSession got unexpected diff: -want +got: %v", diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreateMessageSession call is retried the correct amount of times", func(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
gotRetries++
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
retryMax := 3
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
wantRetries := retryMax + 1
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &srv.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
|
||||
_, _ = actionsClient.CreateMessageSession(context.Background(), runnerScaleSet.Id, owner)
|
||||
|
||||
assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteMessageSession(t *testing.T) {
|
||||
t.Run("DeleteMessageSession call is retried the correct amount of times", func(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
gotRetries++
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
retryMax := 3
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
wantRetries := retryMax + 1
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &srv.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
|
||||
sessionId := uuid.New()
|
||||
|
||||
_ = actionsClient.DeleteMessageSession(context.Background(), runnerScaleSet.Id, &sessionId)
|
||||
|
||||
assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRefreshMessageSession(t *testing.T) {
|
||||
t.Run("RefreshMessageSession call is retried the correct amount of times", func(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
gotRetries++
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
retryMax := 3
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
wantRetries := retryMax + 1
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &srv.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
|
||||
sessionId := uuid.New()
|
||||
|
||||
_, _ = actionsClient.RefreshMessageSession(context.Background(), runnerScaleSet.Id, &sessionId)
|
||||
|
||||
assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries)
|
||||
})
|
||||
}
|
||||
858
github/actions/client_runner_scale_set_test.go
Normal file
858
github/actions/client_runner_scale_set_test.go
Normal file
@@ -0,0 +1,858 @@
|
||||
package actions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/github/actions"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetRunnerScaleSet(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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"}]}`)
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(runnerScaleSetsResp)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
got, err := actionsClient.GetRunnerScaleSet(context.Background(), scaleSetName)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateRunnerScaleSet got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetRunnerScaleSet(%v) mismatch (-want +got):\n%s", scaleSetName, diff)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("GetRunnerScaleSet calls correct url", func(t *testing.T) {
|
||||
runnerScaleSetsResp := []byte(`{"count":1,"value":[{"id":1,"name":"ScaleSet"}]}`)
|
||||
url := url.URL{}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(runnerScaleSetsResp)
|
||||
url = *r.URL
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetRunnerScaleSet(context.Background(), scaleSetName)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateRunnerScaleSet got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
u := url.String()
|
||||
expectedUrl := fmt.Sprintf("/_apis/runtime/runnerscalesets?name=%s&api-version=6.0-preview", scaleSetName)
|
||||
assert.Equal(t, expectedUrl, u)
|
||||
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Status code not found", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetRunnerScaleSet(context.Background(), scaleSetName)
|
||||
if err == nil {
|
||||
t.Fatalf("GetRunnerScaleSet did not get exepected error, ")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetRunnerScaleSet(context.Background(), scaleSetName)
|
||||
if err == nil {
|
||||
t.Fatalf("GetRunnerScaleSet did not get exepected error,")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
retryClient.RetryWaitMax = retryWaitMax
|
||||
retryClient.RetryMax = retryMax
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, _ = actionsClient.GetRunnerScaleSet(context.Background(), scaleSetName)
|
||||
expectedRetry := retryMax + 1
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Custom retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
_, _ = actionsClient.GetRunnerScaleSet(context.Background(), scaleSetName)
|
||||
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"}]}`)
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(runnerScaleSetsResp)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
got, _ := actionsClient.GetRunnerScaleSet(context.Background(), scaleSetName)
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetRunnerScaleSet(%v) mismatch (-want +got):\n%s", scaleSetName, diff)
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Multiple runner scale sets found", func(t *testing.T) {
|
||||
wantErr := fmt.Errorf("multiple runner scale sets found with name %s", scaleSetName)
|
||||
runnerScaleSetsResp := []byte(`{"count":2,"value":[{"id":1,"name":"ScaleSet"}]}`)
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(runnerScaleSetsResp)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetRunnerScaleSet(context.Background(), scaleSetName)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("GetRunnerScaleSet did not get exepected error, %v", wantErr)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantErr.Error(), err.Error()); diff != "" {
|
||||
t.Errorf("GetRunnerScaleSet(%v) mismatch (-want +got):\n%s", scaleSetName, diff)
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestGetRunnerScaleSetById(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(rsl)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
got, err := actionsClient.GetRunnerScaleSetById(context.Background(), runnerScaleSet.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRunnerScaleSetById got unexepected error, %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetRunnerScaleSetById(%d) mismatch (-want +got):\n%s", runnerScaleSet.Id, diff)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("GetRunnerScaleSetById calls correct url", func(t *testing.T) {
|
||||
rsl, err := json.Marshal(&runnerScaleSet)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
url := url.URL{}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(rsl)
|
||||
url = *r.URL
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err = actionsClient.GetRunnerScaleSetById(context.Background(), runnerScaleSet.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRunnerScaleSetById got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
u := url.String()
|
||||
expectedUrl := fmt.Sprintf("/_apis/runtime/runnerscalesets/%d?api-version=6.0-preview", runnerScaleSet.Id)
|
||||
assert.Equal(t, expectedUrl, u)
|
||||
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Status code not found", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetRunnerScaleSetById(context.Background(), runnerScaleSet.Id)
|
||||
if err == nil {
|
||||
t.Fatalf("GetRunnerScaleSetById did not get exepected error, ")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.GetRunnerScaleSetById(context.Background(), runnerScaleSet.Id)
|
||||
if err == nil {
|
||||
t.Fatalf("GetRunnerScaleSetById did not get exepected error,")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
retryClient.RetryWaitMax = retryWaitMax
|
||||
retryClient.RetryMax = retryMax
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, _ = actionsClient.GetRunnerScaleSetById(context.Background(), runnerScaleSet.Id)
|
||||
expectedRetry := retryMax + 1
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Custom retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
_, _ = actionsClient.GetRunnerScaleSetById(context.Background(), runnerScaleSet.Id)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(rsl)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
got, _ := actionsClient.GetRunnerScaleSetById(context.Background(), runnerScaleSet.Id)
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetRunnerScaleSetById(%v) mismatch (-want +got):\n%s", runnerScaleSet.Id, diff)
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestCreateRunnerScaleSet(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(rsl)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
got, err := actionsClient.CreateRunnerScaleSet(context.Background(), &runnerScaleSet)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateRunnerScaleSet got exepected error, %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("CreateRunnerScaleSet(%d) mismatch (-want +got):\n%s", runnerScaleSet.Id, diff)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("CreateRunnerScaleSet calls correct url", func(t *testing.T) {
|
||||
rsl, err := json.Marshal(&runnerScaleSet)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
url := url.URL{}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(rsl)
|
||||
url = *r.URL
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err = actionsClient.CreateRunnerScaleSet(context.Background(), &runnerScaleSet)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateRunnerScaleSet got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
u := url.String()
|
||||
expectedUrl := "/_apis/runtime/runnerscalesets?api-version=6.0-preview"
|
||||
assert.Equal(t, expectedUrl, u)
|
||||
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.CreateRunnerScaleSet(context.Background(), &runnerScaleSet)
|
||||
if err == nil {
|
||||
t.Fatalf("CreateRunnerScaleSet did not get exepected error, %v", &actions.ActionsError{})
|
||||
}
|
||||
var expectedErr *actions.ActionsError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
retryClient.RetryMax = retryMax
|
||||
retryClient.RetryWaitMax = retryWaitMax
|
||||
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, _ = actionsClient.CreateRunnerScaleSet(context.Background(), &runnerScaleSet)
|
||||
expectedRetry := retryMax + 1
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Custom retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
_, _ = actionsClient.CreateRunnerScaleSet(context.Background(), &runnerScaleSet)
|
||||
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) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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("Update existing scale set", func(t *testing.T) {
|
||||
want := &runnerScaleSet
|
||||
rsl, err := json.Marshal(want)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(rsl)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
got, err := actionsClient.UpdateRunnerScaleSet(context.Background(), runnerScaleSet.Id, want)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateRunnerScaleSet got exepected error, %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("UpdateRunnerScaleSet(%d) mismatch (-want +got):\n%s", runnerScaleSet.Id, diff)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("UpdateRunnerScaleSet calls correct url", func(t *testing.T) {
|
||||
rsl, err := json.Marshal(&runnerScaleSet)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
url := url.URL{}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(rsl)
|
||||
url = *r.URL
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err = actionsClient.UpdateRunnerScaleSet(context.Background(), runnerScaleSet.Id, &runnerScaleSet)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateRunnerScaleSet got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
u := url.String()
|
||||
expectedUrl := fmt.Sprintf("/_apis/runtime/runnerscalesets/%d?api-version=6.0-preview", runnerScaleSet.Id)
|
||||
assert.Equal(t, expectedUrl, u)
|
||||
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Status code not found", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.UpdateRunnerScaleSet(context.Background(), runnerScaleSet.Id, &runnerScaleSet)
|
||||
if err == nil {
|
||||
t.Fatalf("UpdateRunnerScaleSet did not get exepected error,")
|
||||
}
|
||||
var expectedErr *actions.ActionsError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, err := actionsClient.UpdateRunnerScaleSet(context.Background(), runnerScaleSet.Id, &runnerScaleSet)
|
||||
if err == nil {
|
||||
t.Fatalf("UpdateRunnerScaleSet did not get exepected error")
|
||||
}
|
||||
var expectedErr *actions.ActionsError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
retryClient.RetryWaitMax = retryWaitMax
|
||||
retryClient.RetryMax = retryMax
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_, _ = actionsClient.UpdateRunnerScaleSet(context.Background(), runnerScaleSet.Id, &runnerScaleSet)
|
||||
expectedRetry := retryMax + 1
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Custom retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
_, _ = actionsClient.UpdateRunnerScaleSet(context.Background(), runnerScaleSet.Id, &runnerScaleSet)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(rsl)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
got, err := actionsClient.UpdateRunnerScaleSet(context.Background(), runnerScaleSet.Id, &runnerScaleSet)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateRunnerScaleSet got unexepected error, %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("UpdateRunnerScaleSet(%v) mismatch (-want +got):\n%s", runnerScaleSet.Id, diff)
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestDeleteRunnerScaleSet(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
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("Delete existing scale set", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err := actionsClient.DeleteRunnerScaleSet(context.Background(), runnerScaleSet.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteRunnerScaleSet got unexepected error, %v", err)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("DeleteRunnerScaleSet calls correct url", func(t *testing.T) {
|
||||
url := url.URL{}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
url = *r.URL
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err := actionsClient.DeleteRunnerScaleSet(context.Background(), runnerScaleSet.Id)
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteRunnerScaleSet got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
u := url.String()
|
||||
expectedUrl := fmt.Sprintf("/_apis/runtime/runnerscalesets/%d?api-version=6.0-preview", runnerScaleSet.Id)
|
||||
assert.Equal(t, expectedUrl, u)
|
||||
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Status code not found", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err := actionsClient.DeleteRunnerScaleSet(context.Background(), runnerScaleSet.Id)
|
||||
if err == nil {
|
||||
t.Fatalf("DeleteRunnerScaleSet did not get exepected error, ")
|
||||
}
|
||||
var expectedErr *actions.ActionsError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
}))
|
||||
defer s.Close()
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err := actionsClient.DeleteRunnerScaleSet(context.Background(), runnerScaleSet.Id)
|
||||
if err == nil {
|
||||
t.Fatalf("DeleteRunnerScaleSet did not get exepected error")
|
||||
}
|
||||
var expectedErr *actions.ActionsError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
retryClient.RetryWaitMax = retryWaitMax
|
||||
retryClient.RetryMax = retryMax
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
_ = actionsClient.DeleteRunnerScaleSet(context.Background(), runnerScaleSet.Id)
|
||||
expectedRetry := retryMax + 1
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Custom retries on server error", func(t *testing.T) {
|
||||
actualRetry := 0
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
retryMax := 1
|
||||
retryWaitMax, err := time.ParseDuration("1µs")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
RetryMax: &retryMax,
|
||||
RetryWaitMax: &retryWaitMax,
|
||||
}
|
||||
_ = actionsClient.DeleteRunnerScaleSet(context.Background(), runnerScaleSet.Id)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(rsl)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
err = actionsClient.DeleteRunnerScaleSet(context.Background(), runnerScaleSet.Id)
|
||||
var expectedErr *actions.ActionsError
|
||||
require.True(t, errors.As(err, &expectedErr))
|
||||
},
|
||||
)
|
||||
}
|
||||
219
github/actions/client_runner_test.go
Normal file
219
github/actions/client_runner_test.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package actions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/github/actions"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var tokenExpireAt = time.Now().Add(10 * time.Minute)
|
||||
|
||||
func TestGetRunner(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
|
||||
t.Run("Get Runner", func(t *testing.T) {
|
||||
name := "Get Runner"
|
||||
var runnerID int64 = 1
|
||||
want := &actions.RunnerReference{
|
||||
Id: int(runnerID),
|
||||
Name: "self-hosted-ubuntu",
|
||||
}
|
||||
response := []byte(`{"id": 1, "name": "self-hosted-ubuntu"}`)
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(response)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
got, err := actionsClient.GetRunner(context.Background(), runnerID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRunner got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetRunner(%v) mismatch (-want +got):\n%s", name, diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
var runnerID int64 = 1
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.RetryWaitMax = 1 * time.Millisecond
|
||||
retryClient.RetryMax = 1
|
||||
|
||||
actualRetry := 0
|
||||
expectedRetry := retryClient.RetryMax + 1
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
httpClient := retryClient.StandardClient()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
_, _ = actionsClient.GetRunner(context.Background(), runnerID)
|
||||
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRunnerByName(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
|
||||
t.Run("Get Runner by Name", func(t *testing.T) {
|
||||
var runnerID int64 = 1
|
||||
var runnerName string = "self-hosted-ubuntu"
|
||||
want := &actions.RunnerReference{
|
||||
Id: int(runnerID),
|
||||
Name: runnerName,
|
||||
}
|
||||
response := []byte(`{"count": 1, "value": [{"id": 1, "name": "self-hosted-ubuntu"}]}`)
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(response)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
got, err := actionsClient.GetRunnerByName(context.Background(), runnerName)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRunnerByName got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("GetRunnerByName(%v) mismatch (-want +got):\n%s", runnerName, diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Get Runner by name with not exist runner", func(t *testing.T) {
|
||||
var runnerName string = "self-hosted-ubuntu"
|
||||
response := []byte(`{"count": 0, "value": []}`)
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(response)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
got, err := actionsClient.GetRunnerByName(context.Background(), runnerName)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRunnerByName got unexepected error, %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff((*actions.RunnerReference)(nil), got); diff != "" {
|
||||
t.Errorf("GetRunnerByName(%v) mismatch (-want +got):\n%s", runnerName, diff)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
var runnerName string = "self-hosted-ubuntu"
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.RetryWaitMax = 1 * time.Millisecond
|
||||
retryClient.RetryMax = 1
|
||||
|
||||
actualRetry := 0
|
||||
expectedRetry := retryClient.RetryMax + 1
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
httpClient := retryClient.StandardClient()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
_, _ = actionsClient.GetRunnerByName(context.Background(), runnerName)
|
||||
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteRunner(t *testing.T) {
|
||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
|
||||
t.Run("Delete Runner", func(t *testing.T) {
|
||||
var runnerID int64 = 1
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
actionsClient := actions.Client{
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
if err := actionsClient.RemoveRunner(context.Background(), runnerID); err != nil {
|
||||
t.Fatalf("RemoveRunner got unexepected error, %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Default retries on server error", func(t *testing.T) {
|
||||
var runnerID int64 = 1
|
||||
|
||||
retryClient := retryablehttp.NewClient()
|
||||
retryClient.RetryWaitMax = 1 * time.Millisecond
|
||||
retryClient.RetryMax = 1
|
||||
|
||||
actualRetry := 0
|
||||
expectedRetry := retryClient.RetryMax + 1
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
actualRetry++
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
httpClient := retryClient.StandardClient()
|
||||
actionsClient := actions.Client{
|
||||
Client: httpClient,
|
||||
ActionsServiceURL: &s.URL,
|
||||
ActionsServiceAdminToken: &token,
|
||||
ActionsServiceAdminTokenExpiresAt: &tokenExpireAt,
|
||||
}
|
||||
|
||||
_ = actionsClient.RemoveRunner(context.Background(), runnerID)
|
||||
|
||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
||||
})
|
||||
}
|
||||
71
github/actions/errors.go
Normal file
71
github/actions/errors.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ActionsError struct {
|
||||
ExceptionName string `json:"typeName,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (e *ActionsError) Error() string {
|
||||
return fmt.Sprintf("%v - had issue communicating with Actions backend: %v", e.StatusCode, e.Message)
|
||||
}
|
||||
|
||||
func ParseActionsErrorFromResponse(response *http.Response) error {
|
||||
if response.ContentLength == 0 {
|
||||
message := "Request returned status: " + response.Status
|
||||
return &ActionsError{
|
||||
ExceptionName: "unknown",
|
||||
Message: message,
|
||||
StatusCode: response.StatusCode,
|
||||
}
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body = trimByteOrderMark(body)
|
||||
contentType, ok := response.Header["Content-Type"]
|
||||
if ok && len(contentType) > 0 && strings.Contains(contentType[0], "text/plain") {
|
||||
message := string(body)
|
||||
statusCode := response.StatusCode
|
||||
return &ActionsError{
|
||||
Message: message,
|
||||
StatusCode: statusCode,
|
||||
}
|
||||
}
|
||||
|
||||
actionsError := &ActionsError{StatusCode: response.StatusCode}
|
||||
if err := json.Unmarshal(body, &actionsError); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return actionsError
|
||||
}
|
||||
|
||||
type MessageQueueTokenExpiredError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *MessageQueueTokenExpiredError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
type HttpClientSideError struct {
|
||||
msg string
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e *HttpClientSideError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
235
github/actions/fake/client.go
Normal file
235
github/actions/fake/client.go
Normal file
@@ -0,0 +1,235 @@
|
||||
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 WithGetRunner(runner *actions.RunnerReference, err error) Option {
|
||||
return func(f *FakeClient) {
|
||||
f.getRunnerResult.RunnerReference = runner
|
||||
f.getRunnerResult.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 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
|
||||
}
|
||||
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.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, 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) 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) (*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
|
||||
}
|
||||
43
github/actions/fake/multi_client.go
Normal file
43
github/actions/fake/multi_client.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package fake
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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, creds actions.ActionsAuth, namespace string) (actions.ActionsService, error) {
|
||||
return f.defaultClient, f.defaultErr
|
||||
}
|
||||
|
||||
func (f *fakeMultiClient) GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData actions.KubernetesSecretData) (actions.ActionsService, error) {
|
||||
return f.defaultClient, f.defaultErr
|
||||
}
|
||||
348
github/actions/mock_ActionsService.go
Normal file
348
github/actions/mock_ActionsService.go
Normal file
@@ -0,0 +1,348 @@
|
||||
// Code generated by mockery v2.16.0. DO NOT EDIT.
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
uuid "github.com/google/uuid"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockActionsService is an autogenerated mock type for the ActionsService type
|
||||
type MockActionsService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// AcquireJobs provides a mock function with given fields: ctx, runnerScaleSetId, messageQueueAccessToken, requestIds
|
||||
func (_m *MockActionsService) AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error) {
|
||||
ret := _m.Called(ctx, runnerScaleSetId, messageQueueAccessToken, requestIds)
|
||||
|
||||
var r0 []int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int, string, []int64) []int64); ok {
|
||||
r0 = rf(ctx, runnerScaleSetId, messageQueueAccessToken, requestIds)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]int64)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int, string, []int64) error); ok {
|
||||
r1 = rf(ctx, runnerScaleSetId, messageQueueAccessToken, requestIds)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreateMessageSession provides a mock function with given fields: ctx, runnerScaleSetId, owner
|
||||
func (_m *MockActionsService) CreateMessageSession(ctx context.Context, runnerScaleSetId int, owner string) (*RunnerScaleSetSession, error) {
|
||||
ret := _m.Called(ctx, runnerScaleSetId, owner)
|
||||
|
||||
var r0 *RunnerScaleSetSession
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int, string) *RunnerScaleSetSession); ok {
|
||||
r0 = rf(ctx, runnerScaleSetId, owner)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerScaleSetSession)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int, string) error); ok {
|
||||
r1 = rf(ctx, runnerScaleSetId, owner)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreateRunnerScaleSet provides a mock function with given fields: ctx, runnerScaleSet
|
||||
func (_m *MockActionsService) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error) {
|
||||
ret := _m.Called(ctx, runnerScaleSet)
|
||||
|
||||
var r0 *RunnerScaleSet
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *RunnerScaleSet) *RunnerScaleSet); ok {
|
||||
r0 = rf(ctx, runnerScaleSet)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerScaleSet)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *RunnerScaleSet) error); ok {
|
||||
r1 = rf(ctx, runnerScaleSet)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteMessage provides a mock function with given fields: ctx, messageQueueUrl, messageQueueAccessToken, messageId
|
||||
func (_m *MockActionsService) DeleteMessage(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, messageId int64) error {
|
||||
ret := _m.Called(ctx, messageQueueUrl, messageQueueAccessToken, messageId)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) error); ok {
|
||||
r0 = rf(ctx, messageQueueUrl, messageQueueAccessToken, messageId)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteMessageSession provides a mock function with given fields: ctx, runnerScaleSetId, sessionId
|
||||
func (_m *MockActionsService) DeleteMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) error {
|
||||
ret := _m.Called(ctx, runnerScaleSetId, sessionId)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int, *uuid.UUID) error); ok {
|
||||
r0 = rf(ctx, runnerScaleSetId, sessionId)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GenerateJitRunnerConfig provides a mock function with given fields: ctx, jitRunnerSetting, scaleSetId
|
||||
func (_m *MockActionsService) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *RunnerScaleSetJitRunnerSetting, scaleSetId int) (*RunnerScaleSetJitRunnerConfig, error) {
|
||||
ret := _m.Called(ctx, jitRunnerSetting, scaleSetId)
|
||||
|
||||
var r0 *RunnerScaleSetJitRunnerConfig
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *RunnerScaleSetJitRunnerSetting, int) *RunnerScaleSetJitRunnerConfig); ok {
|
||||
r0 = rf(ctx, jitRunnerSetting, scaleSetId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerScaleSetJitRunnerConfig)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *RunnerScaleSetJitRunnerSetting, int) error); ok {
|
||||
r1 = rf(ctx, jitRunnerSetting, scaleSetId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAcquirableJobs provides a mock function with given fields: ctx, runnerScaleSetId
|
||||
func (_m *MockActionsService) GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*AcquirableJobList, error) {
|
||||
ret := _m.Called(ctx, runnerScaleSetId)
|
||||
|
||||
var r0 *AcquirableJobList
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int) *AcquirableJobList); ok {
|
||||
r0 = rf(ctx, runnerScaleSetId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*AcquirableJobList)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||
r1 = rf(ctx, runnerScaleSetId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetMessage provides a mock function with given fields: ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId
|
||||
func (_m *MockActionsService) GetMessage(ctx context.Context, messageQueueUrl string, messageQueueAccessToken string, lastMessageId int64) (*RunnerScaleSetMessage, error) {
|
||||
ret := _m.Called(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
||||
|
||||
var r0 *RunnerScaleSetMessage
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *RunnerScaleSetMessage); ok {
|
||||
r0 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerScaleSetMessage)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) error); ok {
|
||||
r1 = rf(ctx, messageQueueUrl, messageQueueAccessToken, lastMessageId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRunner provides a mock function with given fields: ctx, runnerId
|
||||
func (_m *MockActionsService) GetRunner(ctx context.Context, runnerId int64) (*RunnerReference, error) {
|
||||
ret := _m.Called(ctx, runnerId)
|
||||
|
||||
var r0 *RunnerReference
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) *RunnerReference); ok {
|
||||
r0 = rf(ctx, runnerId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerReference)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, runnerId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRunnerByName provides a mock function with given fields: ctx, runnerName
|
||||
func (_m *MockActionsService) GetRunnerByName(ctx context.Context, runnerName string) (*RunnerReference, error) {
|
||||
ret := _m.Called(ctx, runnerName)
|
||||
|
||||
var r0 *RunnerReference
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *RunnerReference); ok {
|
||||
r0 = rf(ctx, runnerName)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerReference)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, runnerName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRunnerGroupByName provides a mock function with given fields: ctx, runnerGroup
|
||||
func (_m *MockActionsService) GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*RunnerGroup, error) {
|
||||
ret := _m.Called(ctx, runnerGroup)
|
||||
|
||||
var r0 *RunnerGroup
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *RunnerGroup); ok {
|
||||
r0 = rf(ctx, runnerGroup)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerGroup)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, runnerGroup)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRunnerScaleSet provides a mock function with given fields: ctx, runnerScaleSetName
|
||||
func (_m *MockActionsService) GetRunnerScaleSet(ctx context.Context, runnerScaleSetName string) (*RunnerScaleSet, error) {
|
||||
ret := _m.Called(ctx, runnerScaleSetName)
|
||||
|
||||
var r0 *RunnerScaleSet
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) *RunnerScaleSet); ok {
|
||||
r0 = rf(ctx, runnerScaleSetName)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerScaleSet)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, runnerScaleSetName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRunnerScaleSetById provides a mock function with given fields: ctx, runnerScaleSetId
|
||||
func (_m *MockActionsService) GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int) (*RunnerScaleSet, error) {
|
||||
ret := _m.Called(ctx, runnerScaleSetId)
|
||||
|
||||
var r0 *RunnerScaleSet
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int) *RunnerScaleSet); ok {
|
||||
r0 = rf(ctx, runnerScaleSetId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerScaleSet)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||
r1 = rf(ctx, runnerScaleSetId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RefreshMessageSession provides a mock function with given fields: ctx, runnerScaleSetId, sessionId
|
||||
func (_m *MockActionsService) RefreshMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*RunnerScaleSetSession, error) {
|
||||
ret := _m.Called(ctx, runnerScaleSetId, sessionId)
|
||||
|
||||
var r0 *RunnerScaleSetSession
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int, *uuid.UUID) *RunnerScaleSetSession); ok {
|
||||
r0 = rf(ctx, runnerScaleSetId, sessionId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerScaleSetSession)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int, *uuid.UUID) error); ok {
|
||||
r1 = rf(ctx, runnerScaleSetId, sessionId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// RemoveRunner provides a mock function with given fields: ctx, runnerId
|
||||
func (_m *MockActionsService) RemoveRunner(ctx context.Context, runnerId int64) error {
|
||||
ret := _m.Called(ctx, runnerId)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, runnerId)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewMockActionsService interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewMockActionsService(t mockConstructorTestingTNewMockActionsService) *MockActionsService {
|
||||
mock := &MockActionsService{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
103
github/actions/mock_SessionService.go
Normal file
103
github/actions/mock_SessionService.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// Code generated by mockery v2.16.0. DO NOT EDIT.
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockSessionService is an autogenerated mock type for the SessionService type
|
||||
type MockSessionService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// AcquireJobs provides a mock function with given fields: ctx, requestIds
|
||||
func (_m *MockSessionService) AcquireJobs(ctx context.Context, requestIds []int64) ([]int64, error) {
|
||||
ret := _m.Called(ctx, requestIds)
|
||||
|
||||
var r0 []int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []int64) []int64); ok {
|
||||
r0 = rf(ctx, requestIds)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]int64)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, []int64) error); ok {
|
||||
r1 = rf(ctx, requestIds)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Close provides a mock function with given fields:
|
||||
func (_m *MockSessionService) Close() error {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteMessage provides a mock function with given fields: ctx, messageId
|
||||
func (_m *MockSessionService) DeleteMessage(ctx context.Context, messageId int64) error {
|
||||
ret := _m.Called(ctx, messageId)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, messageId)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetMessage provides a mock function with given fields: ctx, lastMessageId
|
||||
func (_m *MockSessionService) GetMessage(ctx context.Context, lastMessageId int64) (*RunnerScaleSetMessage, error) {
|
||||
ret := _m.Called(ctx, lastMessageId)
|
||||
|
||||
var r0 *RunnerScaleSetMessage
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) *RunnerScaleSetMessage); ok {
|
||||
r0 = rf(ctx, lastMessageId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*RunnerScaleSetMessage)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok {
|
||||
r1 = rf(ctx, lastMessageId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewMockSessionService interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewMockSessionService(t mockConstructorTestingTNewMockSessionService) *MockSessionService {
|
||||
mock := &MockSessionService{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
164
github/actions/multi_client.go
Normal file
164
github/actions/multi_client.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
type MultiClient interface {
|
||||
GetClientFor(ctx context.Context, githubConfigURL string, creds ActionsAuth, namespace string) (ActionsService, error)
|
||||
GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData KubernetesSecretData) (ActionsService, error)
|
||||
}
|
||||
|
||||
type multiClient struct {
|
||||
// To lock adding and removing of individual clients.
|
||||
mu sync.Mutex
|
||||
clients map[ActionsClientKey]*actionsClientWrapper
|
||||
|
||||
logger logr.Logger
|
||||
userAgent string
|
||||
}
|
||||
|
||||
type GitHubAppAuth struct {
|
||||
AppID int64
|
||||
AppInstallationID int64
|
||||
AppPrivateKey string
|
||||
}
|
||||
|
||||
type ActionsAuth struct {
|
||||
// GitHub App
|
||||
AppCreds *GitHubAppAuth
|
||||
|
||||
// GitHub PAT
|
||||
Token string
|
||||
}
|
||||
|
||||
type ActionsClientKey struct {
|
||||
ActionsURL string
|
||||
Auth ActionsAuth
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type actionsClientWrapper struct {
|
||||
// To lock client usage when tokens are being refreshed.
|
||||
mu sync.Mutex
|
||||
|
||||
client ActionsService
|
||||
}
|
||||
|
||||
func NewMultiClient(userAgent string, logger logr.Logger) MultiClient {
|
||||
return &multiClient{
|
||||
mu: sync.Mutex{},
|
||||
clients: make(map[ActionsClientKey]*actionsClientWrapper),
|
||||
logger: logger,
|
||||
userAgent: userAgent,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string, creds ActionsAuth, namespace string) (ActionsService, error) {
|
||||
m.logger.Info("retrieve actions client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
||||
|
||||
parsedGitHubURL, err := url.Parse(githubConfigURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if creds.Token == "" && creds.AppCreds == nil {
|
||||
return nil, fmt.Errorf("no credentials provided. either a PAT or GitHub App credentials should be provided")
|
||||
}
|
||||
|
||||
if creds.Token != "" && creds.AppCreds != nil {
|
||||
return nil, fmt.Errorf("both PAT and GitHub App credentials provided. should only provide one")
|
||||
}
|
||||
|
||||
key := ActionsClientKey{
|
||||
ActionsURL: parsedGitHubURL.String(),
|
||||
Namespace: namespace,
|
||||
}
|
||||
|
||||
if creds.AppCreds != nil {
|
||||
key.Auth = ActionsAuth{
|
||||
AppCreds: creds.AppCreds,
|
||||
}
|
||||
}
|
||||
|
||||
if creds.Token != "" {
|
||||
key.Auth = ActionsAuth{
|
||||
Token: creds.Token,
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
clientWrapper, has := m.clients[key]
|
||||
if has {
|
||||
m.logger.Info("using cache client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
||||
return clientWrapper.client, nil
|
||||
}
|
||||
|
||||
m.logger.Info("creating new client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
||||
|
||||
client, err := NewClient(ctx, githubConfigURL, &creds, m.userAgent, m.logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.clients[key] = &actionsClientWrapper{
|
||||
mu: sync.Mutex{},
|
||||
client: client,
|
||||
}
|
||||
|
||||
m.logger.Info("successfully created new client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
type KubernetesSecretData map[string][]byte
|
||||
|
||||
func (m *multiClient) GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData KubernetesSecretData) (ActionsService, error) {
|
||||
if len(secretData) == 0 {
|
||||
return nil, fmt.Errorf("must provide secret data with either PAT or GitHub App Auth")
|
||||
}
|
||||
|
||||
token := string(secretData["github_token"])
|
||||
hasToken := len(token) > 0
|
||||
|
||||
appID := string(secretData["github_app_id"])
|
||||
appInstallationID := string(secretData["github_app_installation_id"])
|
||||
appPrivateKey := string(secretData["github_app_private_key"])
|
||||
hasGitHubAppAuth := len(appID) > 0 && len(appInstallationID) > 0 && len(appPrivateKey) > 0
|
||||
|
||||
if hasToken && hasGitHubAppAuth {
|
||||
return nil, fmt.Errorf("must provide secret with only PAT or GitHub App Auth to avoid ambiguity in client behavior")
|
||||
}
|
||||
|
||||
if !hasToken && !hasGitHubAppAuth {
|
||||
return nil, fmt.Errorf("neither PAT nor GitHub App Auth credentials provided in secret")
|
||||
}
|
||||
|
||||
auth := ActionsAuth{}
|
||||
|
||||
if hasToken {
|
||||
auth.Token = token
|
||||
return m.GetClientFor(ctx, githubConfigURL, auth, namespace)
|
||||
}
|
||||
|
||||
parsedAppID, err := strconv.ParseInt(appID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedAppInstallationID, err := strconv.ParseInt(appInstallationID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth.AppCreds = &GitHubAppAuth{AppID: parsedAppID, AppInstallationID: parsedAppInstallationID, AppPrivateKey: appPrivateKey}
|
||||
return m.GetClientFor(ctx, githubConfigURL, auth, namespace)
|
||||
}
|
||||
163
github/actions/multi_client_test.go
Normal file
163
github/actions/multi_client_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/logging"
|
||||
)
|
||||
|
||||
func TestAddClient(t *testing.T) {
|
||||
logger, err := logging.NewLogger(logging.LogLevelDebug, logging.LogFormatText)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: creating logger: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
multiClient := NewMultiClient("test-user-agent", logger).(*multiClient)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "actions/runners/registration-token") {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
token := "abc-123"
|
||||
rt := ®istrationToken{Token: &token}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(rt); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(r.URL.Path, "actions/runner-registration") {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
url := "actions.github.com/abc"
|
||||
jwt := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
||||
adminConnInfo := &ActionsServiceAdminConnection{ActionsServiceUrl: &url, AdminToken: &jwt}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(adminConnInfo); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(r.URL.Path, "/access_tokens") {
|
||||
w.Header().Set("Content-Type", "application/vnd.github+json")
|
||||
|
||||
t, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z07:00")
|
||||
accessToken := &accessToken{
|
||||
Token: "abc-123",
|
||||
ExpiresAt: t,
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(accessToken); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
want := 1
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v/github/github", srv.URL), ActionsAuth{Token: "PAT"}, "namespace"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want++ // New repo
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v/github/actions", srv.URL), ActionsAuth{Token: "PAT"}, "namespace"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Repeat
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v/github/github", srv.URL), ActionsAuth{Token: "PAT"}, "namespace"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want++ // New namespace
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v/github/github", srv.URL), ActionsAuth{Token: "PAT"}, "other"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want++ // New pat
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v/github/github", srv.URL), ActionsAuth{Token: "other"}, "other"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want++ // New org
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v/github", srv.URL), ActionsAuth{Token: "PAT"}, "other"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// No org, repo, enterprise
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v", srv.URL), ActionsAuth{Token: "PAT"}, "other"); err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want++ // Test keying on GitHub App
|
||||
appAuth := &GitHubAppAuth{
|
||||
AppID: 1,
|
||||
AppPrivateKey: `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWgIBAAKBgHXfRT9cv9UY9fAAD4+1RshpfSSZe277urfEmPfX3/Og9zJYRk//
|
||||
CZrJVD1CaBZDiIyQsNEzjta7r4UsqWdFOggiNN2E7ZTFQjMSaFkVgrzHqWuiaCBf
|
||||
/BjbKPn4SMDmTzHvIe7Nel76hBdCaVgu6mYCW5jmuSH5qz/yR1U1J/WJAgMBAAEC
|
||||
gYARWGWsSU3BYgbu5lNj5l0gKMXNmPhdAJYdbMTF0/KUu18k/XB7XSBgsre+vALt
|
||||
I8r4RGKApoGif8P4aPYUyE8dqA1bh0X3Fj1TCz28qoUL5//dA+pigCRS20H7HM3C
|
||||
ojoqF7+F+4F2sXmzFNd1NgY5RxFPYosTT7OnUiFuu2IisQJBALnMLe09LBnjuHXR
|
||||
xxR65DDNxWPQLBjW3dL+ubLcwr7922l6ZIQsVjdeE0ItEUVRjjJ9/B/Jq9VJ/Lw4
|
||||
g9LCkkMCQQCiaM2f7nYmGivPo9hlAbq5lcGJ5CCYFfeeYzTxMqum7Mbqe4kk5lgb
|
||||
X6gWd0Izg2nGdAEe/97DClO6VpKcPbpDAkBTR/JOJN1fvXMxXJaf13XxakrQMr+R
|
||||
Yr6LlSInykyAz8lJvlLP7A+5QbHgN9NF/wh+GXqpxPwA3ukqdSqhjhWBAkBn6mDv
|
||||
HPgR5xrzL6XM8y9TgaOlJAdK6HtYp6d/UOmN0+Butf6JUq07TphRT5tXNJVgemch
|
||||
O5x/9UKfbrc+KyzbAkAo97TfFC+mZhU1N5fFelaRu4ikPxlp642KRUSkOh8GEkNf
|
||||
jQ97eJWiWtDcsMUhcZgoB5ydHcFlrBIn6oBcpge5
|
||||
-----END RSA PRIVATE KEY-----`,
|
||||
}
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v/github/github", srv.URL), ActionsAuth{AppCreds: appAuth}, "other"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Repeat last to verify GitHub App keys are mapped together
|
||||
if _, err := multiClient.GetClientFor(ctx, fmt.Sprintf("%v/github/github", srv.URL), ActionsAuth{AppCreds: appAuth}, "other"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(multiClient.clients) != want {
|
||||
t.Fatalf("GetClientFor: unexpected number of clients: got=%v want=%v", len(multiClient.clients), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateJWT(t *testing.T) {
|
||||
key := `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWgIBAAKBgHXfRT9cv9UY9fAAD4+1RshpfSSZe277urfEmPfX3/Og9zJYRk//
|
||||
CZrJVD1CaBZDiIyQsNEzjta7r4UsqWdFOggiNN2E7ZTFQjMSaFkVgrzHqWuiaCBf
|
||||
/BjbKPn4SMDmTzHvIe7Nel76hBdCaVgu6mYCW5jmuSH5qz/yR1U1J/WJAgMBAAEC
|
||||
gYARWGWsSU3BYgbu5lNj5l0gKMXNmPhdAJYdbMTF0/KUu18k/XB7XSBgsre+vALt
|
||||
I8r4RGKApoGif8P4aPYUyE8dqA1bh0X3Fj1TCz28qoUL5//dA+pigCRS20H7HM3C
|
||||
ojoqF7+F+4F2sXmzFNd1NgY5RxFPYosTT7OnUiFuu2IisQJBALnMLe09LBnjuHXR
|
||||
xxR65DDNxWPQLBjW3dL+ubLcwr7922l6ZIQsVjdeE0ItEUVRjjJ9/B/Jq9VJ/Lw4
|
||||
g9LCkkMCQQCiaM2f7nYmGivPo9hlAbq5lcGJ5CCYFfeeYzTxMqum7Mbqe4kk5lgb
|
||||
X6gWd0Izg2nGdAEe/97DClO6VpKcPbpDAkBTR/JOJN1fvXMxXJaf13XxakrQMr+R
|
||||
Yr6LlSInykyAz8lJvlLP7A+5QbHgN9NF/wh+GXqpxPwA3ukqdSqhjhWBAkBn6mDv
|
||||
HPgR5xrzL6XM8y9TgaOlJAdK6HtYp6d/UOmN0+Butf6JUq07TphRT5tXNJVgemch
|
||||
O5x/9UKfbrc+KyzbAkAo97TfFC+mZhU1N5fFelaRu4ikPxlp642KRUSkOh8GEkNf
|
||||
jQ97eJWiWtDcsMUhcZgoB5ydHcFlrBIn6oBcpge5
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
auth := &GitHubAppAuth{
|
||||
AppID: 123,
|
||||
AppPrivateKey: key,
|
||||
}
|
||||
jwt, err := createJWTForGitHubApp(auth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(jwt)
|
||||
}
|
||||
14
github/actions/sessionservice.go
Normal file
14
github/actions/sessionservice.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
//go:generate mockery --inpackage --name=SessionService
|
||||
type SessionService interface {
|
||||
GetMessage(ctx context.Context, lastMessageId int64) (*RunnerScaleSetMessage, error)
|
||||
DeleteMessage(ctx context.Context, messageId int64) error
|
||||
AcquireJobs(ctx context.Context, requestIds []int64) ([]int64, error)
|
||||
io.Closer
|
||||
}
|
||||
153
github/actions/types.go
Normal file
153
github/actions/types.go
Normal file
@@ -0,0 +1,153 @@
|
||||
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"`
|
||||
JobWorkflowRef string `json:"jobWorkflowRef"`
|
||||
JobDisplayName string `json:"jobDisplayName"`
|
||||
WorkflowRunId int64 `json:"workflowRunId"`
|
||||
EventName string `json:"eventName"`
|
||||
RequestLabels []string `json:"requestLabels"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user