garm/util/github/scalesets/client.go
Gabriel Adrian Samfira 39003f006a Ensure scale set exists
Github will remove inactive scale sets after 7 days. This change
ensures the scale set exists in github before spinning up the listener.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-23 18:55:08 +00:00

104 lines
3.1 KiB
Go

// Copyright 2024 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package scalesets
import (
"fmt"
"io"
"net/http"
"sync"
"github.com/google/go-github/v72/github"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
)
func NewClient(cli common.GithubClient) (*ScaleSetClient, error) {
return &ScaleSetClient{
ghCli: cli,
httpClient: &http.Client{},
}, nil
}
type ScaleSetClient struct {
ghCli common.GithubClient
httpClient *http.Client
// scale sets are aparently available through the same security
// contex that a normal runner would use. We connect to the same
// API endpoint a runner would connect to, in order to fetch jobs.
// To do this, we use a runner registration token.
runnerRegistrationToken *github.RegistrationToken
// actionsServiceInfo holds the pipeline URL and the JWT token to
// access it. The pipeline URL is the base URL where we can access
// the scale set endpoints.
actionsServiceInfo *params.ActionsServiceAdminInfoResponse
mux sync.Mutex
}
func (s *ScaleSetClient) SetGithubClient(cli common.GithubClient) {
s.mux.Lock()
defer s.mux.Unlock()
s.ghCli = cli
}
func (s *ScaleSetClient) GetGithubClient() (common.GithubClient, error) {
s.mux.Lock()
defer s.mux.Unlock()
if s.ghCli == nil {
return nil, fmt.Errorf("github client is not set in scaleset client")
}
return s.ghCli, nil
}
func (s *ScaleSetClient) Do(req *http.Request) (*http.Response, error) {
if s.httpClient == nil {
return nil, fmt.Errorf("http client is not initialized")
}
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to dispatch HTTP request: %w", err)
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return resp, nil
}
var body []byte
if resp != nil {
defer resp.Body.Close()
body, err = io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read body: %w", err)
}
}
switch resp.StatusCode {
case 404:
return nil, runnerErrors.NewNotFoundError("resource %s not found: %q", req.URL.String(), string(body))
case 400:
return nil, runnerErrors.NewBadRequestError("bad request while calling %s: %q", req.URL.String(), string(body))
case 409:
return nil, runnerErrors.NewConflictError("conflict while calling %s: %q", req.URL.String(), string(body))
case 401, 403:
return nil, runnerErrors.ErrUnauthorized
default:
return nil, fmt.Errorf("request to %s failed with status code %d: %q", req.URL.String(), resp.StatusCode, string(body))
}
}