Remove actions client (#4405)
This commit is contained in:
@@ -9,15 +9,6 @@ pkgname: "{{.SrcPackageName}}"
|
|||||||
recursive: false
|
recursive: false
|
||||||
template: testify
|
template: testify
|
||||||
packages:
|
packages:
|
||||||
github.com/actions/actions-runner-controller/github/actions:
|
|
||||||
config:
|
|
||||||
inpackage: true
|
|
||||||
dir: "{{.InterfaceDir}}"
|
|
||||||
filename: "mocks_test.go"
|
|
||||||
pkgname: "actions"
|
|
||||||
interfaces:
|
|
||||||
ActionsService:
|
|
||||||
SessionService:
|
|
||||||
github.com/actions/actions-runner-controller/cmd/ghalistener/metrics:
|
github.com/actions/actions-runner-controller/cmd/ghalistener/metrics:
|
||||||
config:
|
config:
|
||||||
all: true
|
all: true
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
package v1alpha1_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
|
|
||||||
t.Run("returns an error if CertificateFrom not specified", func(t *testing.T) {
|
|
||||||
c := &v1alpha1.TLSConfig{
|
|
||||||
CertificateFrom: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := c.ToCertPool(nil)
|
|
||||||
assert.Nil(t, pool)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, err.Error(), "certificateFrom not specified")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("returns an error if CertificateFrom.ConfigMapKeyRef not specified", func(t *testing.T) {
|
|
||||||
c := &v1alpha1.TLSConfig{
|
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{},
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := c.ToCertPool(nil)
|
|
||||||
assert.Nil(t, pool)
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, err.Error(), "configMapKeyRef not specified")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("returns a valid cert pool with correct configuration", func(t *testing.T) {
|
|
||||||
c := &v1alpha1.TLSConfig{
|
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
|
||||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
|
||||||
Name: "name",
|
|
||||||
},
|
|
||||||
Key: "key",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
certsFolder := filepath.Join(
|
|
||||||
"../../../",
|
|
||||||
"github",
|
|
||||||
"actions",
|
|
||||||
"testdata",
|
|
||||||
)
|
|
||||||
|
|
||||||
fetcher := func(name, key string) ([]byte, error) {
|
|
||||||
cert, err := os.ReadFile(filepath.Join(certsFolder, "rootCA.crt"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
ok := pool.AppendCertsFromPEM(cert)
|
|
||||||
assert.True(t, ok)
|
|
||||||
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := c.ToCertPool(fetcher)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, pool)
|
|
||||||
|
|
||||||
// can be used to communicate with a server
|
|
||||||
serverSuccessfullyCalled := false
|
|
||||||
server := testserver.NewUnstarted(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
serverSuccessfullyCalled = true
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(
|
|
||||||
filepath.Join(certsFolder, "server.crt"),
|
|
||||||
filepath.Join(certsFolder, "server.key"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
||||||
server.StartTLS()
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
RootCAs: pool,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Get(server.URL)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, serverSuccessfullyCalled)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,13 @@
|
|||||||
package config_test
|
package config_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
||||||
"github.com/actions/actions-runner-controller/cmd/ghalistener/config"
|
"github.com/actions/actions-runner-controller/cmd/ghalistener/config"
|
||||||
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
|
||||||
"github.com/actions/scaleset"
|
"github.com/actions/scaleset"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -20,54 +15,6 @@ import (
|
|||||||
|
|
||||||
var discardLogger = slog.New(slog.DiscardHandler)
|
var discardLogger = slog.New(slog.DiscardHandler)
|
||||||
|
|
||||||
func TestCustomerServerRootCA(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
certsFolder := filepath.Join(
|
|
||||||
"../../../",
|
|
||||||
"github",
|
|
||||||
"actions",
|
|
||||||
"testdata",
|
|
||||||
)
|
|
||||||
certPath := filepath.Join(certsFolder, "server.crt")
|
|
||||||
keyPath := filepath.Join(certsFolder, "server.key")
|
|
||||||
|
|
||||||
serverCalledSuccessfully := false
|
|
||||||
|
|
||||||
server := testserver.NewUnstarted(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
serverCalledSuccessfully = true
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(`{"count": 0}`))
|
|
||||||
}))
|
|
||||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
||||||
server.StartTLS()
|
|
||||||
|
|
||||||
var certsString string
|
|
||||||
rootCA, err := os.ReadFile(filepath.Join(certsFolder, "rootCA.crt"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
certsString = string(rootCA)
|
|
||||||
|
|
||||||
intermediate, err := os.ReadFile(filepath.Join(certsFolder, "intermediate.crt"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
certsString = certsString + string(intermediate)
|
|
||||||
|
|
||||||
config := config.Config{
|
|
||||||
ConfigureURL: server.ConfigURLForOrg("myorg"),
|
|
||||||
ServerRootCA: certsString,
|
|
||||||
AppConfig: &appconfig.AppConfig{
|
|
||||||
Token: "token",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := config.ActionsClient(discardLogger)
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = client.GetRunnerScaleSet(ctx, 1, "test")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, serverCalledSuccessfully)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxySettings(t *testing.T) {
|
func TestProxySettings(t *testing.T) {
|
||||||
assertHasProxy := func(t *testing.T, debugInfo string, want bool) {
|
assertHasProxy := func(t *testing.T, debugInfo string, want bool) {
|
||||||
type debugInfoContent struct {
|
type debugInfoContent struct {
|
||||||
|
|||||||
@@ -20,13 +20,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/actions/scaleset"
|
"github.com/actions/scaleset"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -410,12 +408,8 @@ func (r *EphemeralRunnerReconciler) deleteEphemeralRunnerOrPod(ctx context.Conte
|
|||||||
|
|
||||||
func (r *EphemeralRunnerReconciler) cleanupRunnerFromService(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (ok bool, err error) {
|
func (r *EphemeralRunnerReconciler) cleanupRunnerFromService(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (ok bool, err error) {
|
||||||
if err := r.deleteRunnerFromService(ctx, ephemeralRunner, log); err != nil {
|
if err := r.deleteRunnerFromService(ctx, ephemeralRunner, log); err != nil {
|
||||||
actionsError := &actions.ActionsError{}
|
if errors.Is(err, scaleset.JobStillRunningError) {
|
||||||
if !errors.As(err, &actionsError) {
|
log.Info("Runner job is still running, cannot remove the runner from the service yet")
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if actionsError.StatusCode == http.StatusBadRequest && actionsError.IsException("JobStillRunningException") {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,16 +619,10 @@ func (r *EphemeralRunnerReconciler) createRunnerJitConfig(ctx context.Context, e
|
|||||||
return jitConfig, nil
|
return jitConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsError := &actions.ActionsError{}
|
if !errors.Is(err, scaleset.RunnerExistsError) {
|
||||||
if !errors.As(err, &actionsError) {
|
|
||||||
return nil, fmt.Errorf("failed to generate JIT config with generic error: %w", err)
|
return nil, fmt.Errorf("failed to generate JIT config with generic error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if actionsError.StatusCode != http.StatusConflict ||
|
|
||||||
!actionsError.IsException("AgentExistsException") {
|
|
||||||
return nil, fmt.Errorf("failed to generate JIT config with Actions service error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the runner with the name we want already exists it means:
|
// If the runner with the name we want already exists it means:
|
||||||
// - We might have a name collision.
|
// - We might have a name collision.
|
||||||
// - Our previous reconciliation loop failed to update the
|
// - Our previous reconciliation loop failed to update the
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
|
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
|
||||||
scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
|
scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
|
||||||
@@ -1113,12 +1112,7 @@ var _ = Describe("EphemeralRunner", func() {
|
|||||||
scalefake.NewClient(
|
scalefake.NewClient(
|
||||||
scalefake.WithGetRunner(
|
scalefake.WithGetRunner(
|
||||||
nil,
|
nil,
|
||||||
&actions.ActionsError{
|
scaleset.RunnerNotFoundError,
|
||||||
StatusCode: http.StatusNotFound,
|
|
||||||
Err: &actions.ActionsExceptionError{
|
|
||||||
ExceptionName: "AgentNotFoundException",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
scalefake.WithGenerateJitRunnerConfig(
|
scalefake.WithGenerateJitRunnerConfig(
|
||||||
&scaleset.RunnerScaleSetJitRunnerConfig{
|
&scaleset.RunnerScaleSetJitRunnerConfig{
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -28,6 +27,7 @@ import (
|
|||||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
|
"github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
|
||||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
|
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
|
"github.com/actions/scaleset"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -48,9 +48,8 @@ const (
|
|||||||
// EphemeralRunnerSetReconciler reconciles a EphemeralRunnerSet object
|
// EphemeralRunnerSetReconciler reconciles a EphemeralRunnerSet object
|
||||||
type EphemeralRunnerSetReconciler struct {
|
type EphemeralRunnerSetReconciler struct {
|
||||||
client.Client
|
client.Client
|
||||||
Log logr.Logger
|
Log logr.Logger
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
ActionsClient actions.MultiClient
|
|
||||||
|
|
||||||
PublishMetrics bool
|
PublishMetrics bool
|
||||||
|
|
||||||
@@ -484,14 +483,7 @@ func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Co
|
|||||||
|
|
||||||
func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, actionsClient multiclient.Client, log logr.Logger) (bool, error) {
|
func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, actionsClient multiclient.Client, log logr.Logger) (bool, error) {
|
||||||
if err := actionsClient.RemoveRunner(ctx, int64(ephemeralRunner.Status.RunnerId)); err != nil {
|
if err := actionsClient.RemoveRunner(ctx, int64(ephemeralRunner.Status.RunnerId)); err != nil {
|
||||||
actionsError := &actions.ActionsError{}
|
if errors.Is(err, scaleset.JobStillRunningError) {
|
||||||
if !errors.As(err, &actionsError) {
|
|
||||||
log.Error(err, "failed to remove runner from the service", "name", ephemeralRunner.Name, "runnerId", ephemeralRunner.Status.RunnerId)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if actionsError.StatusCode == http.StatusBadRequest &&
|
|
||||||
actionsError.IsException("JobStillRunningException") {
|
|
||||||
log.Info("Runner is still running a job, skipping deletion", "name", ephemeralRunner.Name, "runnerId", ephemeralRunner.Status.RunnerId)
|
log.Info("Runner is still running a job, skipping deletion", "name", ephemeralRunner.Name, "runnerId", ephemeralRunner.Status.RunnerId)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newActionsServer returns a new httptest.Server that handles the
|
|
||||||
// authentication requests neeeded to create a new client. Any requests not
|
|
||||||
// made to the /actions/runners/registration-token or
|
|
||||||
// /actions/runner-registration endpoints will be handled by the provided
|
|
||||||
// handler. The returned server is started and will be automatically closed
|
|
||||||
// when the test ends.
|
|
||||||
func newActionsServer(t *testing.T, handler http.Handler, options ...actionsServerOption) *actionsServer {
|
|
||||||
s := httptest.NewServer(nil)
|
|
||||||
server := &actionsServer{
|
|
||||||
Server: s,
|
|
||||||
}
|
|
||||||
t.Cleanup(func() {
|
|
||||||
server.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, option := range options {
|
|
||||||
option(server)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// handle getRunnerRegistrationToken
|
|
||||||
if strings.HasSuffix(r.URL.Path, "/runners/registration-token") {
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
w.Write([]byte(`{"token":"token"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle getActionsServiceAdminConnection
|
|
||||||
if strings.HasSuffix(r.URL.Path, "/actions/runner-registration") {
|
|
||||||
if server.token == "" {
|
|
||||||
server.token = defaultActionsToken(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(`{"url":"` + s.URL + `/tenant/123/","token":"` + server.token + `"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
|
|
||||||
server.Config.Handler = h
|
|
||||||
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
|
|
||||||
type actionsServerOption func(*actionsServer)
|
|
||||||
|
|
||||||
type actionsServer struct {
|
|
||||||
*httptest.Server
|
|
||||||
|
|
||||||
token string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *actionsServer) configURLForOrg(org string) string {
|
|
||||||
return s.URL + "/" + org
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultActionsToken(t *testing.T) string {
|
|
||||||
claims := &jwt.RegisteredClaims{
|
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now().Add(-10 * time.Minute)),
|
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)),
|
|
||||||
Issuer: "123",
|
|
||||||
}
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
|
||||||
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(samplePrivateKey))
|
|
||||||
require.NoError(t, err)
|
|
||||||
tokenString, err := token.SignedString(privateKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return tokenString
|
|
||||||
}
|
|
||||||
|
|
||||||
const samplePrivateKey = `-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQC7tgquvNIp+Ik3
|
|
||||||
rRVZ9r0zJLsSzTHqr2dA6EUUmpRiQ25MzjMqKqu0OBwvh/pZyfjSIkKrhIridNK4
|
|
||||||
DWnPfPWHE2K3Muh0X2sClxtqiiFmXsvbiTzhUm5a+zCcv0pJCWYnKi0HmyXpAXjJ
|
|
||||||
iN8mWliZN896verVYXWrod7EaAnuST4TiJeqZYW4bBBG81fPNc/UP4j6CKAW8nx9
|
|
||||||
HtcX6ApvlHeCLZUTW/qhGLO0nLKoEOr3tXCPW5VjKzlm134Dl+8PN6f1wv6wMAoA
|
|
||||||
lo7Ha5+c74jhPL6gHXg7cRaHQmuJCJrtl8qbLkFAulfkBixBw/6i11xoM/MOC64l
|
|
||||||
TWmXqrxTAgMBAAECgf9zYlxfL+rdHRXCoOm7pUeSPL0dWaPFP12d/Z9LSlDAt/h6
|
|
||||||
Pd+eqYEwhf795SAbJuzNp51Ls6LUGnzmLOdojKwfqJ51ahT1qbcBcMZNOcvtGqZ9
|
|
||||||
xwLG993oyR49C361Lf2r8mKrdrR5/fW0B1+1s6A+eRFivqFOtsOc4V4iMeHYsCVJ
|
|
||||||
hM7yMu0UfpolDJA/CzopsoGq3UuQlibUEUxKULza06aDjg/gBH3PnP+fQ1m0ovDY
|
|
||||||
h0pX6SCq5fXVJFS+Pbpu7j2ePNm3mr0qQhrUONZq0qhGN/piCbBZe1CqWApyO7nA
|
|
||||||
B95VChhL1eYs1BKvQePh12ap83woIUcW2mJF2F0CgYEA+aERTuKWEm+zVNKS9t3V
|
|
||||||
qNhecCOpayKM9OlALIK/9W6KBS+pDsjQQteQAUAItjvLiDjd5KsrtSgjbSgr66IP
|
|
||||||
b615Pakywe5sdnVGzSv+07KMzuFob9Hj6Xv9als9Y2geVhUZB2Frqve/UCjmC56i
|
|
||||||
zuQTSele5QKCSSTFBV3423cCgYEAwIBv9ChsI+mse6vPaqSPpZ2n237anThMcP33
|
|
||||||
aS0luYXqMWXZ0TQ/uSmCElY4G3xqNo8szzfy6u0HpldeUsEUsIcBNUV5kIIb8wKu
|
|
||||||
Zmgcc8gBIjJkyUJI4wuz9G/fegEUj3u6Cttmmj4iWLzCRscRJdfGpqwRIhOGyXb9
|
|
||||||
2Rur5QUCgYAGWIPaH4R1H4XNiDTYNbdyvV1ZOG7cHFq89xj8iK5cjNzRWO7RQ2WX
|
|
||||||
7WbpwTj3ePmpktiBMaDA0C5mXfkP2mTOD/jfCmgR6f+z2zNbj9zAgO93at9+yDUl
|
|
||||||
AFPm2j7rQgBTa+HhACb+h6HDZebDMNsuqzmaTWZuJ+wr89VWV5c17QKBgH3jwNNQ
|
|
||||||
mCAIUidynaulQNfTOZIe7IMC7WK7g9CBmPkx7Y0uiXr6C25hCdJKFllLTP6vNWOy
|
|
||||||
uCcQqf8LhgDiilBDifO3op9xpyuOJlWMYocJVkxx3l2L/rSU07PYcbKNAFAxXuJ4
|
|
||||||
xym51qZnkznMN5ei/CPFxVKeqHgaXDpekVStAoGAV3pSWAKDXY/42XEHixrCTqLW
|
|
||||||
kBxfaf3g7iFnl3u8+7Z/7Cb4ZqFcw0bRJseKuR9mFvBhcZxSErbMDEYrevefU9aM
|
|
||||||
APeCxEyw6hJXgbWKoG7Fw2g2HP3ytCJ4YzH0zNitHjk/1h4BG7z8cEQILCSv5mN2
|
|
||||||
etFcaQuTHEZyRhhJ4BU=
|
|
||||||
-----END PRIVATE KEY-----`
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClient_Do(t *testing.T) {
|
|
||||||
t.Run("trims byte order mark from response if present", func(t *testing.T) {
|
|
||||||
t.Run("when there is no body", func(t *testing.T) {
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
client, err := actions.NewClient("https://localhost/org/repo", &actions.ActionsAuth{Token: "token"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", server.URL, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, string(body))
|
|
||||||
})
|
|
||||||
|
|
||||||
responses := []string{
|
|
||||||
"\xef\xbb\xbf{\"foo\":\"bar\"}",
|
|
||||||
"{\"foo\":\"bar\"}",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, response := range responses {
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write([]byte(response))
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
client, err := actions.NewClient("https://localhost/org/repo", &actions.ActionsAuth{Token: "token"})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", server.URL, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, "{\"foo\":\"bar\"}", string(body))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,95 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenerateJitRunnerConfig(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Get JIT Config for Runner", func(t *testing.T) {
|
|
||||||
want := &actions.RunnerScaleSetJitRunnerConfig{}
|
|
||||||
response := []byte(`{"count":1,"value":[{"id":1,"name":"scale-set-name"}]}`)
|
|
||||||
|
|
||||||
runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{}
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GenerateJitRunnerConfig(ctx, runnerSettings, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{}
|
|
||||||
|
|
||||||
retryMax := 1
|
|
||||||
actualRetry := 0
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(1),
|
|
||||||
actions.WithRetryWaitMax(1*time.Millisecond),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GenerateJitRunnerConfig(ctx, runnerSettings, 1)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Error includes HTTP method and URL when request fails", func(t *testing.T) {
|
|
||||||
runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{}
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(0), // No retries to get immediate error
|
|
||||||
actions.WithRetryWaitMax(1*time.Millisecond),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GenerateJitRunnerConfig(ctx, runnerSettings, 1)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
// Verify error message includes HTTP method and URL for better debugging
|
|
||||||
errMsg := err.Error()
|
|
||||||
assert.Contains(t, errMsg, "POST", "Error message should include HTTP method")
|
|
||||||
assert.Contains(t, errMsg, "generatejitconfig", "Error message should include URL path")
|
|
||||||
|
|
||||||
// The error might be an ActionsError (if response was received) or a wrapped error (if Do() failed)
|
|
||||||
// In either case, the error message should include request details
|
|
||||||
var actionsErr *actions.ActionsError
|
|
||||||
if errors.As(err, &actionsErr) {
|
|
||||||
// If we got an ActionsError, verify the status code is included
|
|
||||||
assert.Equal(t, http.StatusInternalServerError, actionsErr.StatusCode)
|
|
||||||
}
|
|
||||||
// If it's a wrapped error from Do(), the error message already includes the method and URL
|
|
||||||
// which is what we're testing for
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAcquireJobs(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Acquire Job", func(t *testing.T) {
|
|
||||||
want := []int64{1}
|
|
||||||
response := []byte(`{"value": [1]}`)
|
|
||||||
|
|
||||||
session := &actions.RunnerScaleSetSession{
|
|
||||||
RunnerScaleSet: &actions.RunnerScaleSet{Id: 1},
|
|
||||||
MessageQueueAccessToken: "abc",
|
|
||||||
}
|
|
||||||
requestIDs := want
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if strings.HasSuffix(r.URL.Path, "/acquirablejobs") {
|
|
||||||
w.Write([]byte(`{"count": 1}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetAcquirableJobs(ctx, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.AcquireJobs(ctx, session.RunnerScaleSet.Id, session.MessageQueueAccessToken, requestIDs)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
session := &actions.RunnerScaleSetSession{
|
|
||||||
RunnerScaleSet: &actions.RunnerScaleSet{Id: 1},
|
|
||||||
MessageQueueAccessToken: "abc",
|
|
||||||
}
|
|
||||||
var requestIDs = []int64{1}
|
|
||||||
|
|
||||||
retryMax := 1
|
|
||||||
actualRetry := 0
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if strings.HasSuffix(r.URL.Path, "/acquirablejobs") {
|
|
||||||
w.Write([]byte(`{"count": 1}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(1*time.Millisecond),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetAcquirableJobs(ctx, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.AcquireJobs(context.Background(), session.RunnerScaleSet.Id, session.MessageQueueAccessToken, requestIDs)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Should return MessageQueueTokenExpiredError when http error is not Unauthorized", func(t *testing.T) {
|
|
||||||
want := []int64{1}
|
|
||||||
|
|
||||||
session := &actions.RunnerScaleSetSession{
|
|
||||||
RunnerScaleSet: &actions.RunnerScaleSet{Id: 1},
|
|
||||||
MessageQueueAccessToken: "abc",
|
|
||||||
}
|
|
||||||
requestIDs := want
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if strings.HasSuffix(r.URL.Path, "/acquirablejobs") {
|
|
||||||
w.Write([]byte(`{"count": 1}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method == http.MethodPost {
|
|
||||||
http.Error(w, "Session expired", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetAcquirableJobs(ctx, 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.AcquireJobs(ctx, session.RunnerScaleSet.Id, session.MessageQueueAccessToken, requestIDs)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
var expectedErr *actions.MessageQueueTokenExpiredError
|
|
||||||
assert.True(t, errors.As(err, &expectedErr))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetAcquirableJobs(t *testing.T) {
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Acquire Job", func(t *testing.T) {
|
|
||||||
want := &actions.AcquirableJobList{}
|
|
||||||
response := []byte(`{"count": 0}`)
|
|
||||||
|
|
||||||
runnerScaleSet := &actions.RunnerScaleSet{Id: 1}
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetAcquirableJobs(context.Background(), runnerScaleSet.Id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
runnerScaleSet := &actions.RunnerScaleSet{Id: 1}
|
|
||||||
|
|
||||||
retryMax := 1
|
|
||||||
|
|
||||||
actualRetry := 0
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(1*time.Millisecond),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetAcquirableJobs(context.Background(), runnerScaleSet.Id)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"golang.org/x/net/http/httpproxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClientProxy(t *testing.T) {
|
|
||||||
serverCalled := false
|
|
||||||
|
|
||||||
proxy := testserver.New(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
serverCalled = true
|
|
||||||
}))
|
|
||||||
|
|
||||||
proxyConfig := &httpproxy.Config{
|
|
||||||
HTTPProxy: proxy.URL,
|
|
||||||
}
|
|
||||||
proxyFunc := func(req *http.Request) (*url.URL, error) {
|
|
||||||
return proxyConfig.ProxyFunc()(req.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := actions.NewClient("http://github.com/org/repo", nil, actions.WithProxy(proxyFunc))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = c.Do(req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.True(t, serverCalled)
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetMessage(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
|
||||||
runnerScaleSetMessage := &actions.RunnerScaleSetMessage{
|
|
||||||
MessageId: 1,
|
|
||||||
MessageType: "rssType",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Get Runner Scale Set Message", func(t *testing.T) {
|
|
||||||
want := runnerScaleSetMessage
|
|
||||||
response := []byte(`{"messageId":1,"messageType":"rssType"}`)
|
|
||||||
s := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(s.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetMessage(ctx, s.URL, token, 0, 10)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetMessage sets the last message id if not 0", func(t *testing.T) {
|
|
||||||
want := runnerScaleSetMessage
|
|
||||||
response := []byte(`{"messageId":1,"messageType":"rssType"}`)
|
|
||||||
s := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
q := r.URL.Query()
|
|
||||||
assert.Equal(t, "1", q.Get("lastMessageId"))
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(s.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetMessage(ctx, s.URL, token, 1, 10)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
retryMax := 1
|
|
||||||
|
|
||||||
actualRetry := 0
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(1*time.Millisecond),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0, 10)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Message token expired", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0, 10)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
var expectedErr *actions.MessageQueueTokenExpiredError
|
|
||||||
require.True(t, errors.As(err, &expectedErr))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Status code not found", func(t *testing.T) {
|
|
||||||
want := actions.ActionsError{
|
|
||||||
Err: errors.New("unknown exception"),
|
|
||||||
StatusCode: 404,
|
|
||||||
}
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0, 10)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
assert.Equal(t, want.Error(), err.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0, 10)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Capacity error handling", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
hc := r.Header.Get(actions.HeaderScaleSetMaxCapacity)
|
|
||||||
c, err := strconv.Atoi(hc)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.GreaterOrEqual(t, c, 0)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0, -1)
|
|
||||||
require.Error(t, err)
|
|
||||||
// Ensure we don't send requests with negative capacity
|
|
||||||
assert.False(t, errors.Is(err, &actions.ActionsError{}))
|
|
||||||
|
|
||||||
_, err = client.GetMessage(ctx, server.URL, token, 0, 0)
|
|
||||||
assert.Error(t, err)
|
|
||||||
var expectedErr *actions.ActionsError
|
|
||||||
assert.ErrorAs(t, err, &expectedErr)
|
|
||||||
assert.Equal(t, http.StatusBadRequest, expectedErr.StatusCode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteMessage(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjI1MTYyMzkwMjJ9.tlrHslTmDkoqnc4Kk9ISoKoUNDfHo-kjlH-ByISBqzE"
|
|
||||||
runnerScaleSetMessage := &actions.RunnerScaleSetMessage{
|
|
||||||
MessageId: 1,
|
|
||||||
MessageType: "rssType",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Delete existing message", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.DeleteMessage(ctx, server.URL, token, runnerScaleSetMessage.MessageId)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Message token expired", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.DeleteMessage(ctx, server.URL, token, 0)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
var expectedErr *actions.MessageQueueTokenExpiredError
|
|
||||||
assert.True(t, errors.As(err, &expectedErr))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.DeleteMessage(ctx, server.URL, token, runnerScaleSetMessage.MessageId)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
var expectedErr *actions.ActionsError
|
|
||||||
assert.True(t, errors.As(err, &expectedErr))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
actualRetry := 0
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
retryMax := 1
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(1*time.Nanosecond),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = client.DeleteMessage(ctx, server.URL, token, runnerScaleSetMessage.MessageId)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("No message found", func(t *testing.T) {
|
|
||||||
want := (*actions.RunnerScaleSetMessage)(nil)
|
|
||||||
rsl, err := json.Marshal(want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(rsl)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.DeleteMessage(ctx, server.URL, token, runnerScaleSetMessage.MessageId+1)
|
|
||||||
var expectedErr *actions.ActionsError
|
|
||||||
require.True(t, errors.As(err, &expectedErr))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
const exampleRequestID = "5ddf2050-dae0-013c-9159-04421ad31b68"
|
|
||||||
|
|
||||||
func TestCreateMessageSession(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("CreateMessageSession unmarshals correctly", func(t *testing.T) {
|
|
||||||
owner := "foo"
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{
|
|
||||||
Id: 1,
|
|
||||||
Name: "ScaleSet",
|
|
||||||
CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
|
|
||||||
RunnerSetting: actions.RunnerSetting{},
|
|
||||||
}
|
|
||||||
|
|
||||||
want := &actions.RunnerScaleSetSession{
|
|
||||||
OwnerName: "foo",
|
|
||||||
RunnerScaleSet: &actions.RunnerScaleSet{
|
|
||||||
Id: 1,
|
|
||||||
Name: "ScaleSet",
|
|
||||||
},
|
|
||||||
MessageQueueUrl: "http://fake.actions.github.com/123",
|
|
||||||
MessageQueueAccessToken: "fake.jwt.here",
|
|
||||||
}
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
resp := []byte(`{
|
|
||||||
"ownerName": "foo",
|
|
||||||
"runnerScaleSet": {
|
|
||||||
"id": 1,
|
|
||||||
"name": "ScaleSet"
|
|
||||||
},
|
|
||||||
"messageQueueUrl": "http://fake.actions.github.com/123",
|
|
||||||
"messageQueueAccessToken": "fake.jwt.here"
|
|
||||||
}`)
|
|
||||||
w.Write(resp)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.CreateMessageSession(ctx, runnerScaleSet.Id, owner)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("CreateMessageSession unmarshals errors into ActionsError", func(t *testing.T) {
|
|
||||||
owner := "foo"
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{
|
|
||||||
Id: 1,
|
|
||||||
Name: "ScaleSet",
|
|
||||||
CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
|
|
||||||
RunnerSetting: actions.RunnerSetting{},
|
|
||||||
}
|
|
||||||
|
|
||||||
want := &actions.ActionsError{
|
|
||||||
ActivityID: exampleRequestID,
|
|
||||||
StatusCode: http.StatusBadRequest,
|
|
||||||
Err: &actions.ActionsExceptionError{
|
|
||||||
ExceptionName: "CSharpExceptionNameHere",
|
|
||||||
Message: "could not do something",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Header().Set(actions.HeaderActionsActivityID, exampleRequestID)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
resp := []byte(`{"typeName": "CSharpExceptionNameHere","message": "could not do something"}`)
|
|
||||||
w.Write(resp)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.CreateMessageSession(ctx, runnerScaleSet.Id, owner)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
errorTypeForComparison := &actions.ActionsError{}
|
|
||||||
assert.True(
|
|
||||||
t,
|
|
||||||
errors.As(err, &errorTypeForComparison),
|
|
||||||
"CreateMessageSession expected to be able to parse the error into ActionsError type: %v",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert.Equal(t, want, errorTypeForComparison)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("CreateMessageSession call is retried the correct amount of times", func(t *testing.T) {
|
|
||||||
owner := "foo"
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{
|
|
||||||
Id: 1,
|
|
||||||
Name: "ScaleSet",
|
|
||||||
CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
|
|
||||||
RunnerSetting: actions.RunnerSetting{},
|
|
||||||
}
|
|
||||||
|
|
||||||
gotRetries := 0
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
gotRetries++
|
|
||||||
}))
|
|
||||||
|
|
||||||
retryMax := 3
|
|
||||||
retryWaitMax := 1 * time.Microsecond
|
|
||||||
|
|
||||||
wantRetries := retryMax + 1
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(retryWaitMax),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.CreateMessageSession(ctx, runnerScaleSet.Id, owner)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteMessageSession(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("DeleteMessageSession call is retried the correct amount of times", func(t *testing.T) {
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{
|
|
||||||
Id: 1,
|
|
||||||
Name: "ScaleSet",
|
|
||||||
CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
|
|
||||||
RunnerSetting: actions.RunnerSetting{},
|
|
||||||
}
|
|
||||||
|
|
||||||
gotRetries := 0
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
gotRetries++
|
|
||||||
}))
|
|
||||||
|
|
||||||
retryMax := 3
|
|
||||||
retryWaitMax := 1 * time.Microsecond
|
|
||||||
|
|
||||||
wantRetries := retryMax + 1
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(retryWaitMax),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
sessionId := uuid.New()
|
|
||||||
|
|
||||||
err = client.DeleteMessageSession(ctx, runnerScaleSet.Id, &sessionId)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRefreshMessageSession(t *testing.T) {
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("RefreshMessageSession call is retried the correct amount of times", func(t *testing.T) {
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{
|
|
||||||
Id: 1,
|
|
||||||
Name: "ScaleSet",
|
|
||||||
CreatedOn: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
|
|
||||||
RunnerSetting: actions.RunnerSetting{},
|
|
||||||
}
|
|
||||||
|
|
||||||
gotRetries := 0
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
gotRetries++
|
|
||||||
}))
|
|
||||||
|
|
||||||
retryMax := 3
|
|
||||||
retryWaitMax := 1 * time.Microsecond
|
|
||||||
|
|
||||||
wantRetries := retryMax + 1
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(retryWaitMax),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
sessionId := uuid.New()
|
|
||||||
|
|
||||||
_, err = client.RefreshMessageSession(context.Background(), runnerScaleSet.Id, &sessionId)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.Equalf(t, gotRetries, wantRetries, "CreateMessageSession got unexpected retry count: got=%v, want=%v", gotRetries, wantRetries)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,424 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetRunnerScaleSet(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
scaleSetName := "ScaleSet"
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{Id: 1, Name: scaleSetName}
|
|
||||||
|
|
||||||
t.Run("Get existing scale set", func(t *testing.T) {
|
|
||||||
want := &runnerScaleSet
|
|
||||||
runnerScaleSetsResp := []byte(`{"count":1,"value":[{"id":1,"name":"ScaleSet"}]}`)
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(runnerScaleSetsResp)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunnerScaleSet(ctx, 1, scaleSetName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetRunnerScaleSet calls correct url", func(t *testing.T) {
|
|
||||||
runnerScaleSetsResp := []byte(`{"count":1,"value":[{"id":1,"name":"ScaleSet"}]}`)
|
|
||||||
url := url.URL{}
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(runnerScaleSetsResp)
|
|
||||||
url = *r.URL
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expectedPath := "/tenant/123/_apis/runtime/runnerscalesets"
|
|
||||||
assert.Equal(t, expectedPath, url.Path)
|
|
||||||
assert.Equal(t, scaleSetName, url.Query().Get("name"))
|
|
||||||
assert.Equal(t, "6.0-preview", url.Query().Get("api-version"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Status code not found", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
actualRetry := 0
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
retryMax := 1
|
|
||||||
retryWaitMax := 1 * time.Microsecond
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(retryWaitMax),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("RunnerScaleSet count is zero", func(t *testing.T) {
|
|
||||||
want := (*actions.RunnerScaleSet)(nil)
|
|
||||||
runnerScaleSetsResp := []byte(`{"count":0,"value":[{"id":1,"name":"ScaleSet"}]}`)
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(runnerScaleSetsResp)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunnerScaleSet(ctx, 1, scaleSetName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Multiple runner scale sets found", func(t *testing.T) {
|
|
||||||
reqID := uuid.NewString()
|
|
||||||
wantErr := &actions.ActionsError{
|
|
||||||
StatusCode: http.StatusOK,
|
|
||||||
ActivityID: reqID,
|
|
||||||
Err: fmt.Errorf("multiple runner scale sets found with name %q", scaleSetName),
|
|
||||||
}
|
|
||||||
runnerScaleSetsResp := []byte(`{"count":2,"value":[{"id":1,"name":"ScaleSet"}]}`)
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Header().Set(actions.HeaderActionsActivityID, reqID)
|
|
||||||
w.Write(runnerScaleSetsResp)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
assert.Equal(t, wantErr.Error(), err.Error())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRunnerScaleSetById(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
scaleSetCreationDateTime := time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{Id: 1, Name: "ScaleSet", CreatedOn: scaleSetCreationDateTime, RunnerSetting: actions.RunnerSetting{}}
|
|
||||||
|
|
||||||
t.Run("Get existing scale set by Id", func(t *testing.T) {
|
|
||||||
want := &runnerScaleSet
|
|
||||||
rsl, err := json.Marshal(want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
sservere := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(rsl)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(sservere.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("GetRunnerScaleSetById calls correct url", func(t *testing.T) {
|
|
||||||
rsl, err := json.Marshal(&runnerScaleSet)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
url := url.URL{}
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(rsl)
|
|
||||||
url = *r.URL
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expectedPath := fmt.Sprintf("/tenant/123/_apis/runtime/runnerscalesets/%d", runnerScaleSet.Id)
|
|
||||||
assert.Equal(t, expectedPath, url.Path)
|
|
||||||
assert.Equal(t, "6.0-preview", url.Query().Get("api-version"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Status code not found", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
actualRetry := 0
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
retryMax := 1
|
|
||||||
retryWaitMax := 1 * time.Microsecond
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(retryWaitMax),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("No RunnerScaleSet found", func(t *testing.T) {
|
|
||||||
want := (*actions.RunnerScaleSet)(nil)
|
|
||||||
rsl, err := json.Marshal(want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(rsl)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunnerScaleSetById(ctx, runnerScaleSet.Id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateRunnerScaleSet(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
scaleSetCreationDateTime := time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{Id: 1, Name: "ScaleSet", CreatedOn: scaleSetCreationDateTime, RunnerSetting: actions.RunnerSetting{}}
|
|
||||||
|
|
||||||
t.Run("Create runner scale set", func(t *testing.T) {
|
|
||||||
want := &runnerScaleSet
|
|
||||||
rsl, err := json.Marshal(want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(rsl)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.CreateRunnerScaleSet(ctx, &runnerScaleSet)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("CreateRunnerScaleSet calls correct url", func(t *testing.T) {
|
|
||||||
rsl, err := json.Marshal(&runnerScaleSet)
|
|
||||||
require.NoError(t, err)
|
|
||||||
url := url.URL{}
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(rsl)
|
|
||||||
url = *r.URL
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.CreateRunnerScaleSet(ctx, &runnerScaleSet)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expectedPath := "/tenant/123/_apis/runtime/runnerscalesets"
|
|
||||||
assert.Equal(t, expectedPath, url.Path)
|
|
||||||
assert.Equal(t, "6.0-preview", url.Query().Get("api-version"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Error when Content-Type is text/plain", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.CreateRunnerScaleSet(ctx, &runnerScaleSet)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
var expectedErr *actions.ActionsError
|
|
||||||
assert.True(t, errors.As(err, &expectedErr))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
actualRetry := 0
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
retryMax := 1
|
|
||||||
retryWaitMax := 1 * time.Microsecond
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(retryWaitMax),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.CreateRunnerScaleSet(ctx, &runnerScaleSet)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateRunnerScaleSet(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
scaleSetCreationDateTime := time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
runnerScaleSet := actions.RunnerScaleSet{Id: 1, Name: "ScaleSet", RunnerGroupId: 1, RunnerGroupName: "group", CreatedOn: scaleSetCreationDateTime, RunnerSetting: actions.RunnerSetting{}}
|
|
||||||
|
|
||||||
t.Run("Update runner scale set", func(t *testing.T) {
|
|
||||||
want := &runnerScaleSet
|
|
||||||
rsl, err := json.Marshal(want)
|
|
||||||
require.NoError(t, err)
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Write(rsl)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.UpdateRunnerScaleSet(ctx, 1, &actions.RunnerScaleSet{RunnerGroupId: 1})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("UpdateRunnerScaleSet calls correct url", func(t *testing.T) {
|
|
||||||
rsl, err := json.Marshal(&runnerScaleSet)
|
|
||||||
require.NoError(t, err)
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
expectedPath := "/tenant/123/_apis/runtime/runnerscalesets/1"
|
|
||||||
assert.Equal(t, expectedPath, r.URL.Path)
|
|
||||||
assert.Equal(t, http.MethodPatch, r.Method)
|
|
||||||
assert.Equal(t, "6.0-preview", r.URL.Query().Get("api-version"))
|
|
||||||
|
|
||||||
w.Write(rsl)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.UpdateRunnerScaleSet(ctx, 1, &runnerScaleSet)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteRunnerScaleSet(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Delete runner scale set", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
assert.Equal(t, "DELETE", r.Method)
|
|
||||||
assert.Contains(t, r.URL.String(), "/_apis/runtime/runnerscalesets/10?api-version=6.0-preview")
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.DeleteRunnerScaleSet(ctx, 10)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Delete calls with error", func(t *testing.T) {
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
assert.Equal(t, "DELETE", r.Method)
|
|
||||||
assert.Contains(t, r.URL.String(), "/_apis/runtime/runnerscalesets/10?api-version=6.0-preview")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte(`{"message": "test error"}`))
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.DeleteRunnerScaleSet(ctx, 10)
|
|
||||||
assert.ErrorContains(t, err, "test error")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetRunner(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Get Runner", func(t *testing.T) {
|
|
||||||
var runnerID int64 = 1
|
|
||||||
want := &actions.RunnerReference{
|
|
||||||
Id: int(runnerID),
|
|
||||||
Name: "self-hosted-ubuntu",
|
|
||||||
}
|
|
||||||
response := []byte(`{"id": 1, "name": "self-hosted-ubuntu"}`)
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunner(ctx, runnerID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
var runnerID int64 = 1
|
|
||||||
retryWaitMax := 1 * time.Millisecond
|
|
||||||
retryMax := 1
|
|
||||||
|
|
||||||
actualRetry := 0
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth, actions.WithRetryMax(retryMax), actions.WithRetryWaitMax(retryWaitMax))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunner(ctx, runnerID)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRunnerByName(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Get Runner by Name", func(t *testing.T) {
|
|
||||||
var runnerID int64 = 1
|
|
||||||
var runnerName = "self-hosted-ubuntu"
|
|
||||||
want := &actions.RunnerReference{
|
|
||||||
Id: int(runnerID),
|
|
||||||
Name: runnerName,
|
|
||||||
}
|
|
||||||
response := []byte(`{"count": 1, "value": [{"id": 1, "name": "self-hosted-ubuntu"}]}`)
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunnerByName(ctx, runnerName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Get Runner by name with not exist runner", func(t *testing.T) {
|
|
||||||
var runnerName = "self-hosted-ubuntu"
|
|
||||||
response := []byte(`{"count": 0, "value": []}`)
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunnerByName(ctx, runnerName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
var runnerName = "self-hosted-ubuntu"
|
|
||||||
|
|
||||||
retryWaitMax := 1 * time.Millisecond
|
|
||||||
retryMax := 1
|
|
||||||
|
|
||||||
actualRetry := 0
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth, actions.WithRetryMax(retryMax), actions.WithRetryWaitMax(retryWaitMax))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.GetRunnerByName(ctx, runnerName)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteRunner(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Delete Runner", func(t *testing.T) {
|
|
||||||
var runnerID int64 = 1
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.RemoveRunner(ctx, runnerID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Default retries on server error", func(t *testing.T) {
|
|
||||||
var runnerID int64 = 1
|
|
||||||
|
|
||||||
retryWaitMax := 1 * time.Millisecond
|
|
||||||
retryMax := 1
|
|
||||||
|
|
||||||
actualRetry := 0
|
|
||||||
expectedRetry := retryMax + 1
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
actualRetry++
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
server.configURLForOrg("my-org"),
|
|
||||||
auth,
|
|
||||||
actions.WithRetryMax(retryMax),
|
|
||||||
actions.WithRetryWaitMax(retryWaitMax),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.RemoveRunner(ctx, runnerID)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRunnerGroupByName(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("Get RunnerGroup by Name", func(t *testing.T) {
|
|
||||||
var runnerGroupID int64 = 1
|
|
||||||
var runnerGroupName = "test-runner-group"
|
|
||||||
want := &actions.RunnerGroup{
|
|
||||||
ID: runnerGroupID,
|
|
||||||
Name: runnerGroupName,
|
|
||||||
}
|
|
||||||
response := []byte(`{"count": 1, "value": [{"id": 1, "name": "test-runner-group"}]}`)
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunnerGroupByName(ctx, runnerGroupName)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, want, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Get RunnerGroup by name with not exist runner group", func(t *testing.T) {
|
|
||||||
var runnerGroupName = "test-runner-group"
|
|
||||||
response := []byte(`{"count": 0, "value": []}`)
|
|
||||||
|
|
||||||
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(response)
|
|
||||||
}))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := client.GetRunnerGroupByName(ctx, runnerGroupName)
|
|
||||||
assert.ErrorContains(t, err, "no runner group found with name")
|
|
||||||
assert.Nil(t, got)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestServerWithSelfSignedCertificates(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
// this handler is a very very barebones replica of actions api
|
|
||||||
// used during the creation of a a new client
|
|
||||||
var u string
|
|
||||||
h := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// handle get registration token
|
|
||||||
if strings.HasSuffix(r.URL.Path, "/runners/registration-token") {
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
w.Write([]byte(`{"token":"token"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle getActionsServiceAdminConnection
|
|
||||||
if strings.HasSuffix(r.URL.Path, "/actions/runner-registration") {
|
|
||||||
claims := &jwt.RegisteredClaims{
|
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)),
|
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Minute)),
|
|
||||||
Issuer: "123",
|
|
||||||
}
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
|
||||||
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(samplePrivateKey))
|
|
||||||
require.NoError(t, err)
|
|
||||||
tokenString, err := token.SignedString(privateKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
w.Write([]byte(`{"url":"` + u + `","token":"` + tokenString + `"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// default happy response for RemoveRunner
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
certPath := filepath.Join("testdata", "server.crt")
|
|
||||||
keyPath := filepath.Join("testdata", "server.key")
|
|
||||||
|
|
||||||
t.Run("client without ca certs", func(t *testing.T) {
|
|
||||||
server := startNewTLSTestServer(t, certPath, keyPath, http.HandlerFunc(h))
|
|
||||||
u = server.URL
|
|
||||||
configURL := server.URL + "/my-org"
|
|
||||||
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
client, err := actions.NewClient(configURL, auth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, client)
|
|
||||||
|
|
||||||
err = client.RemoveRunner(ctx, 1)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
|
||||||
assert.True(t, errors.As(err, &x509.UnknownAuthorityError{}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// on macOS we only get an untyped error from the system verifying the
|
|
||||||
// certificate
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
assert.True(t, strings.HasSuffix(err.Error(), "certificate is not trusted"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("client with ca certs", func(t *testing.T) {
|
|
||||||
server := startNewTLSTestServer(
|
|
||||||
t,
|
|
||||||
certPath,
|
|
||||||
keyPath,
|
|
||||||
http.HandlerFunc(h),
|
|
||||||
)
|
|
||||||
u = server.URL
|
|
||||||
configURL := server.URL + "/my-org"
|
|
||||||
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := os.ReadFile(filepath.Join("testdata", "rootCA.crt"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
require.True(t, pool.AppendCertsFromPEM(cert))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
configURL,
|
|
||||||
auth,
|
|
||||||
actions.WithRootCAs(pool),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, client)
|
|
||||||
|
|
||||||
err = client.RemoveRunner(ctx, 1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("client with ca chain certs", func(t *testing.T) {
|
|
||||||
server := startNewTLSTestServer(
|
|
||||||
t,
|
|
||||||
filepath.Join("testdata", "leaf.crt"),
|
|
||||||
filepath.Join("testdata", "leaf.key"),
|
|
||||||
http.HandlerFunc(h),
|
|
||||||
)
|
|
||||||
u = server.URL
|
|
||||||
configURL := server.URL + "/my-org"
|
|
||||||
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := os.ReadFile(filepath.Join("testdata", "intermediate.crt"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
require.True(t, pool.AppendCertsFromPEM(cert))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(
|
|
||||||
configURL,
|
|
||||||
auth,
|
|
||||||
actions.WithRootCAs(pool),
|
|
||||||
actions.WithRetryMax(0),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, client)
|
|
||||||
|
|
||||||
err = client.RemoveRunner(ctx, 1)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("client skipping tls verification", func(t *testing.T) {
|
|
||||||
server := startNewTLSTestServer(t, certPath, keyPath, http.HandlerFunc(h))
|
|
||||||
configURL := server.URL + "/my-org"
|
|
||||||
|
|
||||||
auth := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := actions.NewClient(configURL, auth, actions.WithoutTLSVerify())
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, client)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func startNewTLSTestServer(t *testing.T, certPath, keyPath string, handler http.Handler) *httptest.Server {
|
|
||||||
server := httptest.NewUnstartedServer(handler)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
server.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
||||||
server.StartTLS()
|
|
||||||
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGitHubConfig(t *testing.T) {
|
|
||||||
t.Run("when given a valid URL", func(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
configURL string
|
|
||||||
expected *actions.GitHubConfig
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "repository URL",
|
|
||||||
configURL: "https://github.com/org/repo",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeRepository,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "repo",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "repository URL with trailing slash",
|
|
||||||
configURL: "https://github.com/org/repo/",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeRepository,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "repo",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "organization URL",
|
|
||||||
configURL: "https://github.com/org",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeOrganization,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "enterprise URL",
|
|
||||||
configURL: "https://github.com/enterprises/my-enterprise",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeEnterprise,
|
|
||||||
Enterprise: "my-enterprise",
|
|
||||||
Organization: "",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "enterprise URL with trailing slash",
|
|
||||||
configURL: "https://github.com/enterprises/my-enterprise/",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeEnterprise,
|
|
||||||
Enterprise: "my-enterprise",
|
|
||||||
Organization: "",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "organization URL with www",
|
|
||||||
configURL: "https://www.github.com/org",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeOrganization,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "organization URL with www and trailing slash",
|
|
||||||
configURL: "https://www.github.com/org/",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeOrganization,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "github local URL",
|
|
||||||
configURL: "https://github.localhost/org",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeOrganization,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "github local org URL",
|
|
||||||
configURL: "https://my-ghes.com/org",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeOrganization,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "github local URL with trailing slash",
|
|
||||||
configURL: "https://my-ghes.com/org/",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeOrganization,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "github local URL with ghe.com",
|
|
||||||
configURL: "https://my-ghes.ghe.com/org/",
|
|
||||||
expected: &actions.GitHubConfig{
|
|
||||||
Scope: actions.GitHubScopeOrganization,
|
|
||||||
Enterprise: "",
|
|
||||||
Organization: "org",
|
|
||||||
Repository: "",
|
|
||||||
IsHosted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
parsedURL, err := url.Parse(strings.Trim(test.configURL, "/"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
test.expected.ConfigURL = parsedURL
|
|
||||||
|
|
||||||
cfg, err := actions.ParseGitHubConfigFromURL(test.configURL)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, test.expected, cfg)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("when given an invalid URL", func(t *testing.T) {
|
|
||||||
invalidURLs := []string{
|
|
||||||
"https://github.com/",
|
|
||||||
"https://github.com",
|
|
||||||
"https://github.com/some/random/path",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, u := range invalidURLs {
|
|
||||||
_, err := actions.ParseGitHubConfigFromURL(u)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.True(t, errors.Is(err, actions.ErrInvalidGitHubConfigURL))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitHubConfig_GitHubAPIURL(t *testing.T) {
|
|
||||||
t.Run("when hosted", func(t *testing.T) {
|
|
||||||
config, err := actions.ParseGitHubConfigFromURL("https://github.com/org/repo")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, config.IsHosted)
|
|
||||||
|
|
||||||
result := config.GitHubAPIURL("/some/path")
|
|
||||||
assert.Equal(t, "https://api.github.com/some/path", result.String())
|
|
||||||
})
|
|
||||||
t.Run("when hosted with ghe.com", func(t *testing.T) {
|
|
||||||
config, err := actions.ParseGitHubConfigFromURL("https://github.ghe.com/org/repo")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, config.IsHosted)
|
|
||||||
|
|
||||||
result := config.GitHubAPIURL("/some/path")
|
|
||||||
assert.Equal(t, "https://api.github.ghe.com/some/path", result.String())
|
|
||||||
})
|
|
||||||
t.Run("when not hosted", func(t *testing.T) {
|
|
||||||
config, err := actions.ParseGitHubConfigFromURL("https://ghes.com/org/repo")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.False(t, config.IsHosted)
|
|
||||||
|
|
||||||
result := config.GitHubAPIURL("/some/path")
|
|
||||||
assert.Equal(t, "https://ghes.com/api/v3/some/path", result.String())
|
|
||||||
})
|
|
||||||
t.Run("when not hosted with ghe.com", func(t *testing.T) {
|
|
||||||
os.Setenv("GITHUB_ACTIONS_FORCE_GHES", "1")
|
|
||||||
defer os.Unsetenv("GITHUB_ACTIONS_FORCE_GHES")
|
|
||||||
config, err := actions.ParseGitHubConfigFromURL("https://test.ghe.com/org/repo")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.False(t, config.IsHosted)
|
|
||||||
|
|
||||||
result := config.GitHubAPIURL("/some/path")
|
|
||||||
assert.Equal(t, "https://test.ghe.com/api/v3/some/path", result.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Header names for request IDs
|
|
||||||
const (
|
|
||||||
HeaderActionsActivityID = "ActivityId"
|
|
||||||
HeaderGitHubRequestID = "X-GitHub-Request-Id"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GitHubAPIError struct {
|
|
||||||
StatusCode int
|
|
||||||
RequestID string
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *GitHubAPIError) Error() string {
|
|
||||||
return fmt.Sprintf("github api error: StatusCode %d, RequestID %q: %v", e.StatusCode, e.RequestID, e.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *GitHubAPIError) Unwrap() error {
|
|
||||||
return e.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActionsError struct {
|
|
||||||
ActivityID string
|
|
||||||
StatusCode int
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ActionsError) Error() string {
|
|
||||||
return fmt.Sprintf("actions error: StatusCode %d, ActivityId %q: %v", e.StatusCode, e.ActivityID, e.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ActionsError) Unwrap() error {
|
|
||||||
return e.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ActionsError) IsException(target string) bool {
|
|
||||||
if ex, ok := e.Err.(*ActionsExceptionError); ok {
|
|
||||||
return strings.Contains(ex.ExceptionName, target)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActionsExceptionError struct {
|
|
||||||
ExceptionName string `json:"typeName,omitempty"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ActionsExceptionError) Error() string {
|
|
||||||
return fmt.Sprintf("%s: %s", e.ExceptionName, e.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseActionsErrorFromResponse(response *http.Response) error {
|
|
||||||
if response.ContentLength == 0 {
|
|
||||||
return &ActionsError{
|
|
||||||
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
|
||||||
StatusCode: response.StatusCode,
|
|
||||||
Err: errors.New("unknown exception"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
body, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return &ActionsError{
|
|
||||||
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
|
||||||
StatusCode: response.StatusCode,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body = trimByteOrderMark(body)
|
|
||||||
contentType, ok := response.Header["Content-Type"]
|
|
||||||
if ok && len(contentType) > 0 && strings.Contains(contentType[0], "text/plain") {
|
|
||||||
message := string(body)
|
|
||||||
return &ActionsError{
|
|
||||||
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
|
||||||
StatusCode: response.StatusCode,
|
|
||||||
Err: errors.New(message),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var exception ActionsExceptionError
|
|
||||||
if err := json.Unmarshal(body, &exception); err != nil {
|
|
||||||
return &ActionsError{
|
|
||||||
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
|
||||||
StatusCode: response.StatusCode,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ActionsError{
|
|
||||||
ActivityID: response.Header.Get(HeaderActionsActivityID),
|
|
||||||
StatusCode: response.StatusCode,
|
|
||||||
Err: &exception,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageQueueTokenExpiredError struct {
|
|
||||||
activityID string
|
|
||||||
statusCode int
|
|
||||||
msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MessageQueueTokenExpiredError) Error() string {
|
|
||||||
return fmt.Sprintf("MessageQueueTokenExpiredError: ActivityId %q, StatusCode %d: %s", e.activityID, e.statusCode, e.msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpClientSideError struct {
|
|
||||||
msg string
|
|
||||||
Code int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *HttpClientSideError) Error() string {
|
|
||||||
return e.msg
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestActionsError(t *testing.T) {
|
|
||||||
t.Run("contains the status code, activity ID, and error", func(t *testing.T) {
|
|
||||||
err := &actions.ActionsError{
|
|
||||||
ActivityID: "activity-id",
|
|
||||||
StatusCode: 404,
|
|
||||||
Err: errors.New("example error description"),
|
|
||||||
}
|
|
||||||
|
|
||||||
s := err.Error()
|
|
||||||
assert.Contains(t, s, "StatusCode 404")
|
|
||||||
assert.Contains(t, s, "ActivityId \"activity-id\"")
|
|
||||||
assert.Contains(t, s, "example error description")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("unwraps the error", func(t *testing.T) {
|
|
||||||
err := &actions.ActionsError{
|
|
||||||
ActivityID: "activity-id",
|
|
||||||
StatusCode: 404,
|
|
||||||
Err: &actions.ActionsExceptionError{
|
|
||||||
ExceptionName: "exception-name",
|
|
||||||
Message: "example error message",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, err.Unwrap(), err.Err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("is exception is ok", func(t *testing.T) {
|
|
||||||
err := &actions.ActionsError{
|
|
||||||
ActivityID: "activity-id",
|
|
||||||
StatusCode: 404,
|
|
||||||
Err: &actions.ActionsExceptionError{
|
|
||||||
ExceptionName: "exception-name",
|
|
||||||
Message: "example error message",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var exception *actions.ActionsExceptionError
|
|
||||||
assert.True(t, errors.As(err, &exception))
|
|
||||||
|
|
||||||
assert.True(t, err.IsException("exception-name"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("is exception is not ok", func(t *testing.T) {
|
|
||||||
tt := map[string]*actions.ActionsError{
|
|
||||||
"not an exception": {
|
|
||||||
ActivityID: "activity-id",
|
|
||||||
StatusCode: 404,
|
|
||||||
Err: errors.New("example error description"),
|
|
||||||
},
|
|
||||||
"not target exception": {
|
|
||||||
ActivityID: "activity-id",
|
|
||||||
StatusCode: 404,
|
|
||||||
Err: &actions.ActionsExceptionError{
|
|
||||||
ExceptionName: "exception-name",
|
|
||||||
Message: "example error message",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
targetException := "target-exception"
|
|
||||||
for name, err := range tt {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert.False(t, err.IsException(targetException))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestActionsExceptionError(t *testing.T) {
|
|
||||||
t.Run("contains the exception name and message", func(t *testing.T) {
|
|
||||||
err := &actions.ActionsExceptionError{
|
|
||||||
ExceptionName: "exception-name",
|
|
||||||
Message: "example error message",
|
|
||||||
}
|
|
||||||
|
|
||||||
s := err.Error()
|
|
||||||
assert.Contains(t, s, "exception-name")
|
|
||||||
assert.Contains(t, s, "example error message")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGitHubAPIError(t *testing.T) {
|
|
||||||
t.Run("contains the status code, request ID, and error", func(t *testing.T) {
|
|
||||||
err := &actions.GitHubAPIError{
|
|
||||||
StatusCode: 404,
|
|
||||||
RequestID: "request-id",
|
|
||||||
Err: errors.New("example error description"),
|
|
||||||
}
|
|
||||||
|
|
||||||
s := err.Error()
|
|
||||||
assert.Contains(t, s, "StatusCode 404")
|
|
||||||
assert.Contains(t, s, "RequestID \"request-id\"")
|
|
||||||
assert.Contains(t, s, "example error description")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("unwraps the error", func(t *testing.T) {
|
|
||||||
err := &actions.GitHubAPIError{
|
|
||||||
StatusCode: 404,
|
|
||||||
RequestID: "request-id",
|
|
||||||
Err: errors.New("example error description"),
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, err.Unwrap(), err.Err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseActionsErrorFromResponse(t *testing.T) {
|
|
||||||
t.Run("empty content length", func(t *testing.T) {
|
|
||||||
response := &http.Response{
|
|
||||||
ContentLength: 0,
|
|
||||||
Header: http.Header{},
|
|
||||||
StatusCode: 404,
|
|
||||||
}
|
|
||||||
response.Header.Add(actions.HeaderActionsActivityID, "activity-id")
|
|
||||||
|
|
||||||
err := actions.ParseActionsErrorFromResponse(response)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, "activity-id", err.(*actions.ActionsError).ActivityID)
|
|
||||||
assert.Equal(t, 404, err.(*actions.ActionsError).StatusCode)
|
|
||||||
assert.Equal(t, "unknown exception", err.(*actions.ActionsError).Err.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("contains text plain error", func(t *testing.T) {
|
|
||||||
errorMessage := "example error message"
|
|
||||||
response := &http.Response{
|
|
||||||
ContentLength: int64(len(errorMessage)),
|
|
||||||
StatusCode: 404,
|
|
||||||
Header: http.Header{},
|
|
||||||
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
|
||||||
}
|
|
||||||
response.Header.Add(actions.HeaderActionsActivityID, "activity-id")
|
|
||||||
response.Header.Add("Content-Type", "text/plain")
|
|
||||||
|
|
||||||
err := actions.ParseActionsErrorFromResponse(response)
|
|
||||||
require.Error(t, err)
|
|
||||||
var actionsError *actions.ActionsError
|
|
||||||
require.ErrorAs(t, err, &actionsError)
|
|
||||||
assert.Equal(t, "activity-id", actionsError.ActivityID)
|
|
||||||
assert.Equal(t, 404, actionsError.StatusCode)
|
|
||||||
assert.Equal(t, errorMessage, actionsError.Err.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("contains json error", func(t *testing.T) {
|
|
||||||
errorMessage := `{"typeName":"exception-name","message":"example error message"}`
|
|
||||||
response := &http.Response{
|
|
||||||
ContentLength: int64(len(errorMessage)),
|
|
||||||
Header: http.Header{},
|
|
||||||
StatusCode: 404,
|
|
||||||
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
|
||||||
}
|
|
||||||
response.Header.Add(actions.HeaderActionsActivityID, "activity-id")
|
|
||||||
response.Header.Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
err := actions.ParseActionsErrorFromResponse(response)
|
|
||||||
require.Error(t, err)
|
|
||||||
var actionsError *actions.ActionsError
|
|
||||||
require.ErrorAs(t, err, &actionsError)
|
|
||||||
assert.Equal(t, "activity-id", actionsError.ActivityID)
|
|
||||||
assert.Equal(t, 404, actionsError.StatusCode)
|
|
||||||
|
|
||||||
inner, ok := actionsError.Err.(*actions.ActionsExceptionError)
|
|
||||||
require.True(t, ok)
|
|
||||||
assert.Equal(t, "exception-name", inner.ExceptionName)
|
|
||||||
assert.Equal(t, "example error message", inner.Message)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("wrapped exception error", func(t *testing.T) {
|
|
||||||
errorMessage := `{"typeName":"exception-name","message":"example error message"}`
|
|
||||||
response := &http.Response{
|
|
||||||
ContentLength: int64(len(errorMessage)),
|
|
||||||
Header: http.Header{},
|
|
||||||
StatusCode: 404,
|
|
||||||
Body: io.NopCloser(strings.NewReader(errorMessage)),
|
|
||||||
}
|
|
||||||
response.Header.Add(actions.HeaderActionsActivityID, "activity-id")
|
|
||||||
response.Header.Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
err := actions.ParseActionsErrorFromResponse(response)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
var actionsExceptionError *actions.ActionsExceptionError
|
|
||||||
require.ErrorAs(t, err, &actionsExceptionError)
|
|
||||||
|
|
||||||
assert.Equal(t, "exception-name", actionsExceptionError.ExceptionName)
|
|
||||||
assert.Equal(t, "example error message", actionsExceptionError.Message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
package fake
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Option func(*FakeClient)
|
|
||||||
|
|
||||||
func WithGetRunnerScaleSetResult(scaleSet *actions.RunnerScaleSet, err error) Option {
|
|
||||||
return func(f *FakeClient) {
|
|
||||||
f.getRunnerScaleSetResult.RunnerScaleSet = scaleSet
|
|
||||||
f.getRunnerScaleSetResult.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithGetRunnerGroup(runnerGroup *actions.RunnerGroup, err error) Option {
|
|
||||||
return func(f *FakeClient) {
|
|
||||||
f.getRunnerGroupByNameResult.RunnerGroup = runnerGroup
|
|
||||||
f.getRunnerGroupByNameResult.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithGetRunner(runner *actions.RunnerReference, err error) Option {
|
|
||||||
return func(f *FakeClient) {
|
|
||||||
f.getRunnerResult.RunnerReference = runner
|
|
||||||
f.getRunnerResult.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithCreateRunnerScaleSet(scaleSet *actions.RunnerScaleSet, err error) Option {
|
|
||||||
return func(f *FakeClient) {
|
|
||||||
f.createRunnerScaleSetResult.RunnerScaleSet = scaleSet
|
|
||||||
f.createRunnerScaleSetResult.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithUpdateRunnerScaleSet(scaleSet *actions.RunnerScaleSet, err error) Option {
|
|
||||||
return func(f *FakeClient) {
|
|
||||||
f.updateRunnerScaleSetResult.RunnerScaleSet = scaleSet
|
|
||||||
f.updateRunnerScaleSetResult.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultRunnerScaleSet = &actions.RunnerScaleSet{
|
|
||||||
Id: 1,
|
|
||||||
Name: "testset",
|
|
||||||
RunnerGroupId: 1,
|
|
||||||
RunnerGroupName: "testgroup",
|
|
||||||
Labels: []actions.Label{{Type: "test", Name: "test"}},
|
|
||||||
RunnerSetting: actions.RunnerSetting{},
|
|
||||||
CreatedOn: time.Now(),
|
|
||||||
RunnerJitConfigUrl: "test.test.test",
|
|
||||||
Statistics: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultUpdatedRunnerScaleSet = &actions.RunnerScaleSet{
|
|
||||||
Id: 1,
|
|
||||||
Name: "testset",
|
|
||||||
RunnerGroupId: 2,
|
|
||||||
RunnerGroupName: "testgroup2",
|
|
||||||
Labels: []actions.Label{{Type: "test", Name: "test"}},
|
|
||||||
RunnerSetting: actions.RunnerSetting{},
|
|
||||||
CreatedOn: time.Now(),
|
|
||||||
RunnerJitConfigUrl: "test.test.test",
|
|
||||||
Statistics: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultRunnerGroup = &actions.RunnerGroup{
|
|
||||||
ID: 1,
|
|
||||||
Name: "testgroup",
|
|
||||||
Size: 1,
|
|
||||||
IsDefault: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var sessionID = uuid.New()
|
|
||||||
|
|
||||||
var defaultRunnerScaleSetSession = &actions.RunnerScaleSetSession{
|
|
||||||
SessionId: &sessionID,
|
|
||||||
OwnerName: "testowner",
|
|
||||||
RunnerScaleSet: defaultRunnerScaleSet,
|
|
||||||
MessageQueueUrl: "https://test.url/path",
|
|
||||||
MessageQueueAccessToken: "faketoken",
|
|
||||||
Statistics: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultAcquirableJob = &actions.AcquirableJob{
|
|
||||||
AcquireJobUrl: "https://test.url",
|
|
||||||
MessageType: "",
|
|
||||||
RunnerRequestId: 1,
|
|
||||||
RepositoryName: "testrepo",
|
|
||||||
OwnerName: "testowner",
|
|
||||||
JobWorkflowRef: "workflowref",
|
|
||||||
EventName: "testevent",
|
|
||||||
RequestLabels: []string{"test"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultAcquirableJobList = &actions.AcquirableJobList{
|
|
||||||
Count: 1,
|
|
||||||
Jobs: []actions.AcquirableJob{*defaultAcquirableJob},
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultRunnerReference = &actions.RunnerReference{
|
|
||||||
Id: 1,
|
|
||||||
Name: "testrunner",
|
|
||||||
RunnerScaleSetId: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultRunnerScaleSetMessage = &actions.RunnerScaleSetMessage{
|
|
||||||
MessageId: 1,
|
|
||||||
MessageType: "test",
|
|
||||||
Body: "{}",
|
|
||||||
Statistics: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultRunnerScaleSetJitRunnerConfig = &actions.RunnerScaleSetJitRunnerConfig{
|
|
||||||
Runner: defaultRunnerReference,
|
|
||||||
EncodedJITConfig: "test",
|
|
||||||
}
|
|
||||||
|
|
||||||
// FakeClient implements actions service
|
|
||||||
type FakeClient struct {
|
|
||||||
getRunnerScaleSetResult struct {
|
|
||||||
*actions.RunnerScaleSet
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
getRunnerScaleSetByIdResult struct {
|
|
||||||
*actions.RunnerScaleSet
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
getRunnerGroupByNameResult struct {
|
|
||||||
*actions.RunnerGroup
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
createRunnerScaleSetResult struct {
|
|
||||||
*actions.RunnerScaleSet
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
updateRunnerScaleSetResult struct {
|
|
||||||
*actions.RunnerScaleSet
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
deleteRunnerScaleSetResult struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
createMessageSessionResult struct {
|
|
||||||
*actions.RunnerScaleSetSession
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
deleteMessageSessionResult struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
refreshMessageSessionResult struct {
|
|
||||||
*actions.RunnerScaleSetSession
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
acquireJobsResult struct {
|
|
||||||
ids []int64
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
getAcquirableJobsResult struct {
|
|
||||||
*actions.AcquirableJobList
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
getMessageResult struct {
|
|
||||||
*actions.RunnerScaleSetMessage
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
deleteMessageResult struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
generateJitRunnerConfigResult struct {
|
|
||||||
*actions.RunnerScaleSetJitRunnerConfig
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
getRunnerResult struct {
|
|
||||||
*actions.RunnerReference
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
getRunnerByNameResult struct {
|
|
||||||
*actions.RunnerReference
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
removeRunnerResult struct {
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFakeClient(options ...Option) actions.ActionsService {
|
|
||||||
f := &FakeClient{}
|
|
||||||
f.applyDefaults()
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(f)
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) applyDefaults() {
|
|
||||||
f.getRunnerScaleSetResult.RunnerScaleSet = defaultRunnerScaleSet
|
|
||||||
f.getRunnerScaleSetByIdResult.RunnerScaleSet = defaultRunnerScaleSet
|
|
||||||
f.getRunnerGroupByNameResult.RunnerGroup = defaultRunnerGroup
|
|
||||||
f.createRunnerScaleSetResult.RunnerScaleSet = defaultRunnerScaleSet
|
|
||||||
f.updateRunnerScaleSetResult.RunnerScaleSet = defaultUpdatedRunnerScaleSet
|
|
||||||
f.createMessageSessionResult.RunnerScaleSetSession = defaultRunnerScaleSetSession
|
|
||||||
f.refreshMessageSessionResult.RunnerScaleSetSession = defaultRunnerScaleSetSession
|
|
||||||
f.acquireJobsResult.ids = []int64{1}
|
|
||||||
f.getAcquirableJobsResult.AcquirableJobList = defaultAcquirableJobList
|
|
||||||
f.getMessageResult.RunnerScaleSetMessage = defaultRunnerScaleSetMessage
|
|
||||||
f.generateJitRunnerConfigResult.RunnerScaleSetJitRunnerConfig = defaultRunnerScaleSetJitRunnerConfig
|
|
||||||
f.getRunnerResult.RunnerReference = defaultRunnerReference
|
|
||||||
f.getRunnerByNameResult.RunnerReference = defaultRunnerReference
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*actions.RunnerScaleSet, error) {
|
|
||||||
return f.getRunnerScaleSetResult.RunnerScaleSet, f.getRunnerScaleSetResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int) (*actions.RunnerScaleSet, error) {
|
|
||||||
return f.getRunnerScaleSetByIdResult.RunnerScaleSet, f.getRunnerScaleSetResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*actions.RunnerGroup, error) {
|
|
||||||
return f.getRunnerGroupByNameResult.RunnerGroup, f.getRunnerGroupByNameResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *actions.RunnerScaleSet) (*actions.RunnerScaleSet, error) {
|
|
||||||
return f.createRunnerScaleSetResult.RunnerScaleSet, f.createRunnerScaleSetResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) UpdateRunnerScaleSet(ctx context.Context, runnerScaleSetId int, runnerScaleSet *actions.RunnerScaleSet) (*actions.RunnerScaleSet, error) {
|
|
||||||
return f.updateRunnerScaleSetResult.RunnerScaleSet, f.updateRunnerScaleSetResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) DeleteRunnerScaleSet(ctx context.Context, runnerScaleSetId int) error {
|
|
||||||
return f.deleteRunnerScaleSetResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) CreateMessageSession(ctx context.Context, runnerScaleSetId int, owner string) (*actions.RunnerScaleSetSession, error) {
|
|
||||||
return f.createMessageSessionResult.RunnerScaleSetSession, f.createMessageSessionResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) DeleteMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) error {
|
|
||||||
return f.deleteMessageSessionResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) RefreshMessageSession(ctx context.Context, runnerScaleSetId int, sessionId *uuid.UUID) (*actions.RunnerScaleSetSession, error) {
|
|
||||||
return f.refreshMessageSessionResult.RunnerScaleSetSession, f.refreshMessageSessionResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) AcquireJobs(ctx context.Context, runnerScaleSetId int, messageQueueAccessToken string, requestIds []int64) ([]int64, error) {
|
|
||||||
return f.acquireJobsResult.ids, f.acquireJobsResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) GetAcquirableJobs(ctx context.Context, runnerScaleSetId int) (*actions.AcquirableJobList, error) {
|
|
||||||
return f.getAcquirableJobsResult.AcquirableJobList, f.getAcquirableJobsResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) GetMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, lastMessageId int64, maxCapacity int) (*actions.RunnerScaleSetMessage, error) {
|
|
||||||
return f.getMessageResult.RunnerScaleSetMessage, f.getMessageResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) DeleteMessage(ctx context.Context, messageQueueUrl, messageQueueAccessToken string, messageId int64) error {
|
|
||||||
return f.deleteMessageResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *actions.RunnerScaleSetJitRunnerSetting, scaleSetId int) (*actions.RunnerScaleSetJitRunnerConfig, error) {
|
|
||||||
return f.generateJitRunnerConfigResult.RunnerScaleSetJitRunnerConfig, f.generateJitRunnerConfigResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) GetRunner(ctx context.Context, runnerId int64) (*actions.RunnerReference, error) {
|
|
||||||
return f.getRunnerResult.RunnerReference, f.getRunnerResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) GetRunnerByName(ctx context.Context, runnerName string) (*actions.RunnerReference, error) {
|
|
||||||
return f.getRunnerByNameResult.RunnerReference, f.getRunnerByNameResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) RemoveRunner(ctx context.Context, runnerId int64) error {
|
|
||||||
return f.removeRunnerResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeClient) SetUserAgent(_ actions.UserAgentInfo) {}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package fake
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MultiClientOption func(*fakeMultiClient)
|
|
||||||
|
|
||||||
func WithDefaultClient(client actions.ActionsService, err error) MultiClientOption {
|
|
||||||
return func(f *fakeMultiClient) {
|
|
||||||
f.defaultClient = client
|
|
||||||
f.defaultErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fakeMultiClient struct {
|
|
||||||
defaultClient actions.ActionsService
|
|
||||||
defaultErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMultiClient(opts ...MultiClientOption) actions.MultiClient {
|
|
||||||
f := &fakeMultiClient{}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.defaultClient == nil {
|
|
||||||
f.defaultClient = NewFakeClient()
|
|
||||||
}
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeMultiClient) GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...actions.ClientOption) (actions.ActionsService, error) {
|
|
||||||
return f.defaultClient, f.defaultErr
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testUserAgent = actions.UserAgentInfo{
|
|
||||||
Version: "test",
|
|
||||||
CommitSHA: "test",
|
|
||||||
ScaleSetID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewGitHubAPIRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
t.Run("uses the right host/path prefix", func(t *testing.T) {
|
|
||||||
scenarios := []struct {
|
|
||||||
configURL string
|
|
||||||
path string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
configURL: "https://github.com/org/repo",
|
|
||||||
path: "/app/installations/123/access_tokens",
|
|
||||||
expected: "https://api.github.com/app/installations/123/access_tokens",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
configURL: "https://www.github.com/org/repo",
|
|
||||||
path: "/app/installations/123/access_tokens",
|
|
||||||
expected: "https://api.github.com/app/installations/123/access_tokens",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
configURL: "http://github.localhost/org/repo",
|
|
||||||
path: "/app/installations/123/access_tokens",
|
|
||||||
expected: "http://api.github.localhost/app/installations/123/access_tokens",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
configURL: "https://my-instance.com/org/repo",
|
|
||||||
path: "/app/installations/123/access_tokens",
|
|
||||||
expected: "https://my-instance.com/api/v3/app/installations/123/access_tokens",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
configURL: "http://localhost/org/repo",
|
|
||||||
path: "/app/installations/123/access_tokens",
|
|
||||||
expected: "http://localhost/api/v3/app/installations/123/access_tokens",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, scenario := range scenarios {
|
|
||||||
client, err := actions.NewClient(scenario.configURL, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req, err := client.NewGitHubAPIRequest(ctx, http.MethodGet, scenario.path, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, scenario.expected, req.URL.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("sets user agent header if present", func(t *testing.T) {
|
|
||||||
client, err := actions.NewClient("http://localhost/my-org", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
client.SetUserAgent(testUserAgent)
|
|
||||||
|
|
||||||
req, err := client.NewGitHubAPIRequest(ctx, http.MethodGet, "/app/installations/123/access_tokens", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("sets the body we pass", func(t *testing.T) {
|
|
||||||
client, err := actions.NewClient("http://localhost/my-org", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req, err := client.NewGitHubAPIRequest(
|
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
"/app/installations/123/access_tokens",
|
|
||||||
strings.NewReader("the-body"),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
b, err := io.ReadAll(req.Body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, "the-body", string(b))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewActionsServiceRequest(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
defaultCreds := &actions.ActionsAuth{Token: "token"}
|
|
||||||
|
|
||||||
t.Run("manages authentication", func(t *testing.T) {
|
|
||||||
t.Run("client is brand new", func(t *testing.T) {
|
|
||||||
token := defaultActionsToken(t)
|
|
||||||
server := testserver.New(t, nil, testserver.WithActionsToken(token))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, "Bearer "+token, req.Header.Get("Authorization"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("admin token is about to expire", func(t *testing.T) {
|
|
||||||
newToken := defaultActionsToken(t)
|
|
||||||
server := testserver.New(t, nil, testserver.WithActionsToken(newToken))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
client.ActionsServiceAdminToken = "expiring-token"
|
|
||||||
client.ActionsServiceAdminTokenExpiresAt = time.Now().Add(59 * time.Second)
|
|
||||||
|
|
||||||
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, "Bearer "+newToken, req.Header.Get("Authorization"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("admin token refresh failure", func(t *testing.T) {
|
|
||||||
newToken := defaultActionsToken(t)
|
|
||||||
errMessage := `{"message":"test"}`
|
|
||||||
unauthorizedHandler := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
w.Write([]byte(errMessage))
|
|
||||||
}
|
|
||||||
server := testserver.New(
|
|
||||||
t,
|
|
||||||
nil,
|
|
||||||
testserver.WithActionsToken("random-token"),
|
|
||||||
testserver.WithActionsToken(newToken),
|
|
||||||
testserver.WithActionsRegistrationTokenHandler(unauthorizedHandler),
|
|
||||||
)
|
|
||||||
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
expiringToken := "expiring-token"
|
|
||||||
expiresAt := time.Now().Add(59 * time.Second)
|
|
||||||
client.ActionsServiceAdminToken = expiringToken
|
|
||||||
client.ActionsServiceAdminTokenExpiresAt = expiresAt
|
|
||||||
_, err = client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), errMessage)
|
|
||||||
assert.Equal(t, client.ActionsServiceAdminToken, expiringToken)
|
|
||||||
assert.Equal(t, client.ActionsServiceAdminTokenExpiresAt, expiresAt)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("admin token refresh retry", func(t *testing.T) {
|
|
||||||
newToken := defaultActionsToken(t)
|
|
||||||
errMessage := `{"message":"test"}`
|
|
||||||
|
|
||||||
srv := "http://github.com/my-org"
|
|
||||||
resp := &actions.ActionsServiceAdminConnection{
|
|
||||||
AdminToken: &newToken,
|
|
||||||
ActionsServiceUrl: &srv,
|
|
||||||
}
|
|
||||||
failures := 0
|
|
||||||
unauthorizedHandler := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if failures < 5 {
|
|
||||||
failures++
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
w.Write([]byte(errMessage))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
_ = json.NewEncoder(w).Encode(resp)
|
|
||||||
}
|
|
||||||
server := testserver.New(t, nil, testserver.WithActionsToken("random-token"), testserver.WithActionsToken(newToken), testserver.WithActionsRegistrationTokenHandler(unauthorizedHandler))
|
|
||||||
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
expiringToken := "expiring-token"
|
|
||||||
expiresAt := time.Now().Add(59 * time.Second)
|
|
||||||
client.ActionsServiceAdminToken = expiringToken
|
|
||||||
client.ActionsServiceAdminTokenExpiresAt = expiresAt
|
|
||||||
|
|
||||||
_, err = client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, client.ActionsServiceAdminToken, newToken)
|
|
||||||
assert.Equal(t, client.ActionsServiceURL, srv)
|
|
||||||
assert.NotEqual(t, client.ActionsServiceAdminTokenExpiresAt, expiresAt)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("token is currently valid", func(t *testing.T) {
|
|
||||||
tokenThatShouldNotBeFetched := defaultActionsToken(t)
|
|
||||||
server := testserver.New(t, nil, testserver.WithActionsToken(tokenThatShouldNotBeFetched))
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
client.ActionsServiceAdminToken = "healthy-token"
|
|
||||||
client.ActionsServiceAdminTokenExpiresAt = time.Now().Add(1 * time.Hour)
|
|
||||||
|
|
||||||
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, "Bearer healthy-token", req.Header.Get("Authorization"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("builds the right URL including api version", func(t *testing.T) {
|
|
||||||
server := testserver.New(t, nil)
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "/my/path?name=banana", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
serverURL, err := url.Parse(server.URL)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
result := req.URL
|
|
||||||
assert.Equal(t, serverURL.Host, result.Host)
|
|
||||||
assert.Equal(t, "/tenant/123/my/path", result.Path)
|
|
||||||
assert.Equal(t, "banana", result.Query().Get("name"))
|
|
||||||
assert.Equal(t, "6.0-preview", result.Query().Get("api-version"))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("populates header", func(t *testing.T) {
|
|
||||||
server := testserver.New(t, nil)
|
|
||||||
|
|
||||||
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
client.SetUserAgent(testUserAgent)
|
|
||||||
|
|
||||||
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "/my/path", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent"))
|
|
||||||
assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClient_Identifier(t *testing.T) {
|
|
||||||
t.Run("configURL changes", func(t *testing.T) {
|
|
||||||
scenarios := []struct {
|
|
||||||
name string
|
|
||||||
url string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "url of a different repo",
|
|
||||||
url: "https://github.com/org/repo2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url of an org",
|
|
||||||
url: "https://github.com/org",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url of an enterprise",
|
|
||||||
url: "https://github.com/enterprises/my-enterprise",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url of a self-hosted github",
|
|
||||||
url: "https://selfhosted.com/org/repo",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
configURL := "https://github.com/org/repo"
|
|
||||||
defaultCreds := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
oldClient, err := actions.NewClient(configURL, defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, scenario := range scenarios {
|
|
||||||
t.Run(scenario.name, func(t *testing.T) {
|
|
||||||
newClient, err := actions.NewClient(scenario.url, defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotEqual(t, oldClient.Identifier(), newClient.Identifier())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("credentials change", func(t *testing.T) {
|
|
||||||
defaultTokenCreds := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
defaultAppCreds := &actions.ActionsAuth{
|
|
||||||
AppCreds: &actions.GitHubAppAuth{
|
|
||||||
AppID: "123",
|
|
||||||
AppInstallationID: 123,
|
|
||||||
AppPrivateKey: "private key",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
scenarios := []struct {
|
|
||||||
name string
|
|
||||||
old *actions.ActionsAuth
|
|
||||||
new *actions.ActionsAuth
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "different token",
|
|
||||||
old: defaultTokenCreds,
|
|
||||||
new: &actions.ActionsAuth{
|
|
||||||
Token: "new token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "changing from token to github app",
|
|
||||||
old: defaultTokenCreds,
|
|
||||||
new: defaultAppCreds,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "changing from github app to token",
|
|
||||||
old: defaultAppCreds,
|
|
||||||
new: defaultTokenCreds,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "different github app",
|
|
||||||
old: defaultAppCreds,
|
|
||||||
new: &actions.ActionsAuth{
|
|
||||||
AppCreds: &actions.GitHubAppAuth{
|
|
||||||
AppID: "456",
|
|
||||||
AppInstallationID: 456,
|
|
||||||
AppPrivateKey: "new private key",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfigURL := "https://github.com/org/repo"
|
|
||||||
|
|
||||||
for _, scenario := range scenarios {
|
|
||||||
t.Run(scenario.name, func(t *testing.T) {
|
|
||||||
oldClient, err := actions.NewClient(defaultConfigURL, scenario.old)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
newClient, err := actions.NewClient(defaultConfigURL, scenario.new)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotEqual(t, oldClient.Identifier(), newClient.Identifier())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("changes in TLS config", func(t *testing.T) {
|
|
||||||
configURL := "https://github.com/org/repo"
|
|
||||||
defaultCreds := &actions.ActionsAuth{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
noTlS, err := actions.NewClient(configURL, defaultCreds)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
poolFromCert := func(t *testing.T, path string) *x509.CertPool {
|
|
||||||
t.Helper()
|
|
||||||
f, err := os.ReadFile(path)
|
|
||||||
require.NoError(t, err)
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
require.True(t, pool.AppendCertsFromPEM(f))
|
|
||||||
return pool
|
|
||||||
}
|
|
||||||
|
|
||||||
root, err := actions.NewClient(
|
|
||||||
configURL,
|
|
||||||
defaultCreds,
|
|
||||||
actions.WithRootCAs(poolFromCert(t, filepath.Join("testdata", "rootCA.crt"))),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
chain, err := actions.NewClient(
|
|
||||||
configURL,
|
|
||||||
defaultCreds,
|
|
||||||
actions.WithRootCAs(poolFromCert(t, filepath.Join("testdata", "intermediate.crt"))),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
clients := []*actions.Client{
|
|
||||||
noTlS,
|
|
||||||
root,
|
|
||||||
chain,
|
|
||||||
}
|
|
||||||
identifiers := map[string]struct{}{}
|
|
||||||
for _, client := range clients {
|
|
||||||
identifiers[client.Identifier()] = struct{}{}
|
|
||||||
}
|
|
||||||
assert.Len(t, identifiers, len(clients), "all clients should have a unique identifier")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,102 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MultiClient interface {
|
|
||||||
GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...ClientOption) (ActionsService, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type multiClient struct {
|
|
||||||
// To lock adding and removing of individual clients.
|
|
||||||
mu sync.Mutex
|
|
||||||
clients map[ActionsClientKey]*Client
|
|
||||||
|
|
||||||
logger logr.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type GitHubAppAuth struct {
|
|
||||||
// AppID is the ID or the Client ID of the application
|
|
||||||
AppID string
|
|
||||||
AppInstallationID int64
|
|
||||||
AppPrivateKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActionsAuth struct {
|
|
||||||
// GitHub App
|
|
||||||
AppCreds *GitHubAppAuth
|
|
||||||
|
|
||||||
// GitHub PAT
|
|
||||||
Token string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ActionsClientKey struct {
|
|
||||||
Identifier string
|
|
||||||
Namespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMultiClient(logger logr.Logger) MultiClient {
|
|
||||||
return &multiClient{
|
|
||||||
mu: sync.Mutex{},
|
|
||||||
clients: make(map[ActionsClientKey]*Client),
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...ClientOption) (ActionsService, error) {
|
|
||||||
m.logger.Info("retrieve actions client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
|
||||||
|
|
||||||
if err := appConfig.Validate(); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to validate app config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var creds ActionsAuth
|
|
||||||
if len(appConfig.Token) > 0 {
|
|
||||||
creds.Token = appConfig.Token
|
|
||||||
} else {
|
|
||||||
creds.AppCreds = &GitHubAppAuth{
|
|
||||||
AppID: appConfig.AppID,
|
|
||||||
AppInstallationID: appConfig.AppInstallationID,
|
|
||||||
AppPrivateKey: appConfig.AppPrivateKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(
|
|
||||||
githubConfigURL,
|
|
||||||
&creds,
|
|
||||||
append([]ClientOption{
|
|
||||||
WithLogger(m.logger),
|
|
||||||
}, options...)...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to instantiate new client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
key := ActionsClientKey{
|
|
||||||
Identifier: client.Identifier(),
|
|
||||||
Namespace: namespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedClient, has := m.clients[key]
|
|
||||||
if has && cachedClient.rootCAs.Equal(client.rootCAs) {
|
|
||||||
m.logger.Info("using cache client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
|
||||||
return cachedClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
m.logger.Info("creating new client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
|
||||||
|
|
||||||
m.clients[key] = client
|
|
||||||
|
|
||||||
m.logger.Info("successfully created new client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testUserAgent = UserAgentInfo{
|
|
||||||
Version: "test",
|
|
||||||
CommitSHA: "test",
|
|
||||||
ScaleSetID: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiClientCaching(t *testing.T) {
|
|
||||||
logger := logr.Discard()
|
|
||||||
ctx := context.Background()
|
|
||||||
multiClient := NewMultiClient(logger).(*multiClient)
|
|
||||||
|
|
||||||
defaultNamespace := "default"
|
|
||||||
defaultConfigURL := "https://github.com/org/repo"
|
|
||||||
defaultCreds := &appconfig.AppConfig{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
defaultAuth := ActionsAuth{
|
|
||||||
Token: defaultCreds.Token,
|
|
||||||
}
|
|
||||||
client, err := NewClient(defaultConfigURL, &defaultAuth)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
multiClient.clients[ActionsClientKey{client.Identifier(), defaultNamespace}] = client
|
|
||||||
|
|
||||||
// Verify that the client is cached
|
|
||||||
cachedClient, err := multiClient.GetClientFor(
|
|
||||||
ctx,
|
|
||||||
defaultConfigURL,
|
|
||||||
defaultCreds,
|
|
||||||
defaultNamespace,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, client, cachedClient)
|
|
||||||
assert.Len(t, multiClient.clients, 1)
|
|
||||||
|
|
||||||
// Asking for a different client results in creating and caching a new client
|
|
||||||
otherNamespace := "other"
|
|
||||||
newClient, err := multiClient.GetClientFor(
|
|
||||||
ctx,
|
|
||||||
defaultConfigURL,
|
|
||||||
defaultCreds,
|
|
||||||
otherNamespace,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotEqual(t, client, newClient)
|
|
||||||
assert.Len(t, multiClient.clients, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiClientOptions(t *testing.T) {
|
|
||||||
logger := logr.Discard()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
defaultNamespace := "default"
|
|
||||||
defaultConfigURL := "https://github.com/org/repo"
|
|
||||||
|
|
||||||
t.Run("GetClientFor", func(t *testing.T) {
|
|
||||||
defaultCreds := &appconfig.AppConfig{
|
|
||||||
Token: "token",
|
|
||||||
}
|
|
||||||
|
|
||||||
multiClient := NewMultiClient(logger)
|
|
||||||
service, err := multiClient.GetClientFor(
|
|
||||||
ctx,
|
|
||||||
defaultConfigURL,
|
|
||||||
defaultCreds,
|
|
||||||
defaultNamespace,
|
|
||||||
)
|
|
||||||
service.SetUserAgent(testUserAgent)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
client := service.(*Client)
|
|
||||||
req, err := client.NewGitHubAPIRequest(ctx, "GET", "/test", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateJWT(t *testing.T) {
|
|
||||||
key := `-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQC7tgquvNIp+Ik3
|
|
||||||
rRVZ9r0zJLsSzTHqr2dA6EUUmpRiQ25MzjMqKqu0OBwvh/pZyfjSIkKrhIridNK4
|
|
||||||
DWnPfPWHE2K3Muh0X2sClxtqiiFmXsvbiTzhUm5a+zCcv0pJCWYnKi0HmyXpAXjJ
|
|
||||||
iN8mWliZN896verVYXWrod7EaAnuST4TiJeqZYW4bBBG81fPNc/UP4j6CKAW8nx9
|
|
||||||
HtcX6ApvlHeCLZUTW/qhGLO0nLKoEOr3tXCPW5VjKzlm134Dl+8PN6f1wv6wMAoA
|
|
||||||
lo7Ha5+c74jhPL6gHXg7cRaHQmuJCJrtl8qbLkFAulfkBixBw/6i11xoM/MOC64l
|
|
||||||
TWmXqrxTAgMBAAECgf9zYlxfL+rdHRXCoOm7pUeSPL0dWaPFP12d/Z9LSlDAt/h6
|
|
||||||
Pd+eqYEwhf795SAbJuzNp51Ls6LUGnzmLOdojKwfqJ51ahT1qbcBcMZNOcvtGqZ9
|
|
||||||
xwLG993oyR49C361Lf2r8mKrdrR5/fW0B1+1s6A+eRFivqFOtsOc4V4iMeHYsCVJ
|
|
||||||
hM7yMu0UfpolDJA/CzopsoGq3UuQlibUEUxKULza06aDjg/gBH3PnP+fQ1m0ovDY
|
|
||||||
h0pX6SCq5fXVJFS+Pbpu7j2ePNm3mr0qQhrUONZq0qhGN/piCbBZe1CqWApyO7nA
|
|
||||||
B95VChhL1eYs1BKvQePh12ap83woIUcW2mJF2F0CgYEA+aERTuKWEm+zVNKS9t3V
|
|
||||||
qNhecCOpayKM9OlALIK/9W6KBS+pDsjQQteQAUAItjvLiDjd5KsrtSgjbSgr66IP
|
|
||||||
b615Pakywe5sdnVGzSv+07KMzuFob9Hj6Xv9als9Y2geVhUZB2Frqve/UCjmC56i
|
|
||||||
zuQTSele5QKCSSTFBV3423cCgYEAwIBv9ChsI+mse6vPaqSPpZ2n237anThMcP33
|
|
||||||
aS0luYXqMWXZ0TQ/uSmCElY4G3xqNo8szzfy6u0HpldeUsEUsIcBNUV5kIIb8wKu
|
|
||||||
Zmgcc8gBIjJkyUJI4wuz9G/fegEUj3u6Cttmmj4iWLzCRscRJdfGpqwRIhOGyXb9
|
|
||||||
2Rur5QUCgYAGWIPaH4R1H4XNiDTYNbdyvV1ZOG7cHFq89xj8iK5cjNzRWO7RQ2WX
|
|
||||||
7WbpwTj3ePmpktiBMaDA0C5mXfkP2mTOD/jfCmgR6f+z2zNbj9zAgO93at9+yDUl
|
|
||||||
AFPm2j7rQgBTa+HhACb+h6HDZebDMNsuqzmaTWZuJ+wr89VWV5c17QKBgH3jwNNQ
|
|
||||||
mCAIUidynaulQNfTOZIe7IMC7WK7g9CBmPkx7Y0uiXr6C25hCdJKFllLTP6vNWOy
|
|
||||||
uCcQqf8LhgDiilBDifO3op9xpyuOJlWMYocJVkxx3l2L/rSU07PYcbKNAFAxXuJ4
|
|
||||||
xym51qZnkznMN5ei/CPFxVKeqHgaXDpekVStAoGAV3pSWAKDXY/42XEHixrCTqLW
|
|
||||||
kBxfaf3g7iFnl3u8+7Z/7Cb4ZqFcw0bRJseKuR9mFvBhcZxSErbMDEYrevefU9aM
|
|
||||||
APeCxEyw6hJXgbWKoG7Fw2g2HP3ytCJ4YzH0zNitHjk/1h4BG7z8cEQILCSv5mN2
|
|
||||||
etFcaQuTHEZyRhhJ4BU=
|
|
||||||
-----END PRIVATE KEY-----`
|
|
||||||
|
|
||||||
auth := &GitHubAppAuth{
|
|
||||||
AppID: "123",
|
|
||||||
AppPrivateKey: key,
|
|
||||||
}
|
|
||||||
jwt, err := createJWTForGitHubApp(auth)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(jwt)
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SessionService interface {
|
|
||||||
GetMessage(ctx context.Context, lastMessageId int64, maxCapacity int) (*RunnerScaleSetMessage, error)
|
|
||||||
DeleteMessage(ctx context.Context, messageId int64) error
|
|
||||||
AcquireJobs(ctx context.Context, requestIds []int64) ([]int64, error)
|
|
||||||
io.Closer
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AcquirableJobList struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
Jobs []AcquirableJob `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AcquirableJob struct {
|
|
||||||
AcquireJobUrl string `json:"acquireJobUrl"`
|
|
||||||
MessageType string `json:"messageType"`
|
|
||||||
RunnerRequestId int64 `json:"runnerRequestId"`
|
|
||||||
RepositoryName string `json:"repositoryName"`
|
|
||||||
OwnerName string `json:"ownerName"`
|
|
||||||
JobWorkflowRef string `json:"jobWorkflowRef"`
|
|
||||||
EventName string `json:"eventName"`
|
|
||||||
RequestLabels []string `json:"requestLabels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Int64List struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
Value []int64 `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type JobAvailable struct {
|
|
||||||
AcquireJobUrl string `json:"acquireJobUrl"`
|
|
||||||
JobMessageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
type JobAssigned struct {
|
|
||||||
JobMessageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
type JobStarted struct {
|
|
||||||
RunnerID int `json:"runnerId"`
|
|
||||||
RunnerName string `json:"runnerName"`
|
|
||||||
JobMessageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
type JobCompleted struct {
|
|
||||||
Result string `json:"result"`
|
|
||||||
RunnerId int `json:"runnerId"`
|
|
||||||
RunnerName string `json:"runnerName"`
|
|
||||||
JobMessageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
type JobMessageType struct {
|
|
||||||
MessageType string `json:"messageType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type JobMessageBase struct {
|
|
||||||
JobMessageType
|
|
||||||
RunnerRequestID int64 `json:"runnerRequestId"`
|
|
||||||
RepositoryName string `json:"repositoryName"`
|
|
||||||
OwnerName string `json:"ownerName"`
|
|
||||||
JobID string `json:"jobId"`
|
|
||||||
JobWorkflowRef string `json:"jobWorkflowRef"`
|
|
||||||
JobDisplayName string `json:"jobDisplayName"`
|
|
||||||
WorkflowRunID int64 `json:"workflowRunId"`
|
|
||||||
EventName string `json:"eventName"`
|
|
||||||
RequestLabels []string `json:"requestLabels"`
|
|
||||||
QueueTime time.Time `json:"queueTime"`
|
|
||||||
ScaleSetAssignTime time.Time `json:"scaleSetAssignTime"`
|
|
||||||
RunnerAssignTime time.Time `json:"runnerAssignTime"`
|
|
||||||
FinishTime time.Time `json:"finishTime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Label struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerGroup struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
IsDefault bool `json:"isDefaultGroup"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerGroupList struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
RunnerGroups []RunnerGroup `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerScaleSet struct {
|
|
||||||
Id int `json:"id,omitempty"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
RunnerGroupId int `json:"runnerGroupId,omitempty"`
|
|
||||||
RunnerGroupName string `json:"runnerGroupName,omitempty"`
|
|
||||||
Labels []Label `json:"labels,omitempty"`
|
|
||||||
RunnerSetting RunnerSetting `json:"RunnerSetting,omitempty"`
|
|
||||||
CreatedOn time.Time `json:"createdOn,omitempty"`
|
|
||||||
RunnerJitConfigUrl string `json:"runnerJitConfigUrl,omitempty"`
|
|
||||||
Statistics *RunnerScaleSetStatistic `json:"statistics,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerScaleSetJitRunnerSetting struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
WorkFolder string `json:"workFolder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerScaleSetMessage struct {
|
|
||||||
MessageId int64 `json:"messageId"`
|
|
||||||
MessageType string `json:"messageType"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
Statistics *RunnerScaleSetStatistic `json:"statistics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type runnerScaleSetsResponse struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
RunnerScaleSets []RunnerScaleSet `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerScaleSetSession struct {
|
|
||||||
SessionId *uuid.UUID `json:"sessionId,omitempty"`
|
|
||||||
OwnerName string `json:"ownerName,omitempty"`
|
|
||||||
RunnerScaleSet *RunnerScaleSet `json:"runnerScaleSet,omitempty"`
|
|
||||||
MessageQueueUrl string `json:"messageQueueUrl,omitempty"`
|
|
||||||
MessageQueueAccessToken string `json:"messageQueueAccessToken,omitempty"`
|
|
||||||
Statistics *RunnerScaleSetStatistic `json:"statistics,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerScaleSetStatistic struct {
|
|
||||||
TotalAvailableJobs int `json:"totalAvailableJobs"`
|
|
||||||
TotalAcquiredJobs int `json:"totalAcquiredJobs"`
|
|
||||||
TotalAssignedJobs int `json:"totalAssignedJobs"`
|
|
||||||
TotalRunningJobs int `json:"totalRunningJobs"`
|
|
||||||
TotalRegisteredRunners int `json:"totalRegisteredRunners"`
|
|
||||||
TotalBusyRunners int `json:"totalBusyRunners"`
|
|
||||||
TotalIdleRunners int `json:"totalIdleRunners"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerSetting struct {
|
|
||||||
Ephemeral bool `json:"ephemeral,omitempty"`
|
|
||||||
IsElastic bool `json:"isElastic,omitempty"`
|
|
||||||
DisableUpdate bool `json:"disableUpdate,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerReferenceList struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
RunnerReferences []RunnerReference `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerReference struct {
|
|
||||||
Id int `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
RunnerScaleSetId int `json:"runnerScaleSetId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunnerScaleSetJitRunnerConfig struct {
|
|
||||||
Runner *RunnerReference `json:"runner"`
|
|
||||||
EncodedJITConfig string `json:"encodedJITConfig"`
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package actions_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUserAgentInfoString(t *testing.T) {
|
|
||||||
userAgentInfo := actions.UserAgentInfo{
|
|
||||||
Version: "0.1.0",
|
|
||||||
CommitSHA: "1234567890abcdef",
|
|
||||||
ScaleSetID: 10,
|
|
||||||
HasProxy: true,
|
|
||||||
Subsystem: "test",
|
|
||||||
}
|
|
||||||
|
|
||||||
userAgent := userAgentInfo.String()
|
|
||||||
expectedProduct := "actions-runner-controller/0.1.0 (1234567890abcdef; test)"
|
|
||||||
assert.Contains(t, userAgent, expectedProduct)
|
|
||||||
expectedScaleSet := "ScaleSetID/10 (Proxy/enabled)"
|
|
||||||
assert.Contains(t, userAgent, expectedScaleSet)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user