Handle org runners

This commit is contained in:
Gabriel Adrian Samfira 2022-04-22 14:46:27 +00:00
parent bf0a5bf147
commit ebec0dda52
18 changed files with 1028 additions and 184 deletions

View file

@ -0,0 +1,124 @@
package controllers
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"runner-manager/apiserver/params"
gErrors "runner-manager/errors"
"runner-manager/github"
"runner-manager/runner"
"github.com/pkg/errors"
)
func NewAPIController(r *runner.Runner) (*APIController, error) {
return &APIController{
r: r,
}, nil
}
type APIController struct {
r *runner.Runner
}
func handleError(w http.ResponseWriter, err error) {
w.Header().Add("Content-Type", "application/json")
origErr := errors.Cause(err)
apiErr := params.APIErrorResponse{
Details: origErr.Error(),
}
switch origErr.(type) {
case *gErrors.NotFoundError:
w.WriteHeader(http.StatusNotFound)
apiErr.Error = "Not Found"
case *gErrors.UnauthorizedError:
w.WriteHeader(http.StatusUnauthorized)
apiErr.Error = "Not Authorized"
case *gErrors.BadRequestError:
w.WriteHeader(http.StatusBadRequest)
apiErr.Error = "Bad Request"
case *gErrors.DuplicateUserError, *gErrors.ConflictError:
w.WriteHeader(http.StatusConflict)
apiErr.Error = "Conflict"
default:
w.WriteHeader(http.StatusInternalServerError)
apiErr.Error = "Server error"
}
json.NewEncoder(w).Encode(apiErr)
}
func (a *APIController) authenticateHook(body []byte, headers http.Header) error {
// signature := headers.Get("X-Hub-Signature-256")
hookType := headers.Get("X-Github-Hook-Installation-Target-Type")
var workflowJob github.WorkflowJob
if err := json.Unmarshal(body, &workflowJob); err != nil {
return gErrors.NewBadRequestError("invalid post body: %s", err)
}
switch hookType {
case "repository":
case "organization":
default:
return gErrors.NewBadRequestError("invalid hook type: %s", hookType)
}
return nil
}
func (a *APIController) handleWorkflowJobEvent(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
handleError(w, gErrors.NewBadRequestError("invalid post body: %s", err))
return
}
signature := r.Header.Get("X-Hub-Signature-256")
hookType := r.Header.Get("X-Github-Hook-Installation-Target-Type")
fmt.Printf(">>> Signature: %s\n", signature)
fmt.Printf(">>> HookType: %s\n", hookType)
var workflowJob github.WorkflowJob
if err := json.Unmarshal(body, &workflowJob); err != nil {
handleError(w, gErrors.ErrBadRequest)
return
}
// entity := workflowJob.Repository.Owner.Login
asJs, err := json.MarshalIndent(workflowJob, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s\n", string(asJs))
}
func (a *APIController) CatchAll(w http.ResponseWriter, r *http.Request) {
headers := r.Header.Clone()
for key, val := range headers {
fmt.Printf("%s --> %v\n", key, val)
}
event := github.Event(headers.Get("X-Github-Event"))
switch event {
case github.WorkflowJobEvent:
a.handleWorkflowJobEvent(w, r)
default:
log.Printf("ignoring unknown event %s", event)
return
}
}
// NotFoundHandler is returned when an invalid URL is acccessed
func (a *APIController) NotFoundHandler(w http.ResponseWriter, r *http.Request) {
apiErr := params.APIErrorResponse{
Details: "Resource not found",
Error: "Not found",
}
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(apiErr)
}

View file

@ -0,0 +1,7 @@
package params
// APIErrorResponse holds information about an error, returned by the API
type APIErrorResponse struct {
Error string `json:"error"`
Details string `json:"details"`
}

View file

@ -0,0 +1,21 @@
package routers
import (
"io"
"net/http"
gorillaHandlers "github.com/gorilla/handlers"
"github.com/gorilla/mux"
"runner-manager/apiserver/controllers"
)
func NewAPIRouter(han *controllers.APIController, logWriter io.Writer) *mux.Router {
router := mux.NewRouter()
log := gorillaHandlers.CombinedLoggingHandler
apiRouter := router.PathPrefix("").Subrouter()
apiRouter.PathPrefix("/").Handler(log(logWriter, http.HandlerFunc(han.CatchAll)))
return router
}

View file

@ -0,0 +1,172 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"os/signal"
"runner-manager/config"
"runner-manager/params"
"runner-manager/runner/providers/lxd"
"runner-manager/util"
"github.com/google/go-github/v43/github"
"golang.org/x/oauth2"
"gopkg.in/yaml.v3"
)
var (
conf = flag.String("config", config.DefaultConfigFilePath, "runner-manager config file")
version = flag.Bool("version", false, "prints version")
)
var Version string
// var token = "super secret token"
func main() {
flag.Parse()
if *version {
fmt.Println(Version)
return
}
ctx, stop := signal.NotifyContext(context.Background(), signals...)
defer stop()
fmt.Println(ctx)
cfg, err := config.NewConfig(*conf)
if err != nil {
log.Fatalf("Fetching config: %+v", err)
}
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: cfg.Github.OAuth2Token},
)
tc := oauth2.NewClient(ctx, ts)
ghClient := github.NewClient(tc)
// // list all repositories for the authenticated user
// repos, _, err := client.Repositories.List(ctx, "", nil)
// fmt.Println(repos, err)
logWriter, err := util.GetLoggingWriter(cfg)
if err != nil {
log.Fatalf("fetching log writer: %+v", err)
}
log.SetOutput(logWriter)
// controller, err := controllers.NewAPIController()
// if err != nil {
// log.Fatalf("failed to create controller: %+v", err)
// }
// router := routers.NewAPIRouter(controller, logWriter)
// tlsCfg, err := cfg.APIServer.APITLSConfig()
// if err != nil {
// log.Fatalf("failed to get TLS config: %q", err)
// }
// srv := &http.Server{
// Addr: cfg.APIServer.BindAddress(),
// TLSConfig: tlsCfg,
// // Pass our instance of gorilla/mux in.
// Handler: router,
// }
// listener, err := net.Listen("tcp", srv.Addr)
// if err != nil {
// log.Fatalf("creating listener: %q", err)
// }
// go func() {
// if err := srv.Serve(listener); err != nil {
// log.Fatalf("Listening: %+v", err)
// }
// }()
// <-ctx.Done()
// runner, err := runner.NewRunner(ctx, *cfg)
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(runner)
controllerID := "026d374d-6a8a-4241-8ed9-a246fff6762f"
provider, err := lxd.NewProvider(ctx, &cfg.Providers[0], controllerID)
if err != nil {
log.Fatal(err)
}
// if err := provider.RemoveAllInstances(ctx); err != nil {
// log.Fatal(err)
// }
// fmt.Println(provider)
// if err := provider.DeleteInstance(ctx, "runner-manager-2fbe5354-be28-4e00-95a8-11479912368d"); err != nil {
// log.Fatal(err)
// }
// instances, err := provider.ListInstances(ctx)
// asJs, err := json.MarshalIndent(instances, "", " ")
// fmt.Println(string(asJs), err)
log.Print("Fetching tools")
tools, _, err := ghClient.Actions.ListOrganizationRunnerApplicationDownloads(ctx, cfg.Organizations[0].Name)
// tools, _, err := ghClient.Actions.ListRunnerApplicationDownloads(ctx, cfg.Repositories[0].Owner, cfg.Repositories[0].Name)
if err != nil {
log.Fatal(err)
}
tk, _, err := ghClient.Actions.CreateOrganizationRegistrationToken(ctx, "gsamfira")
if err != nil {
log.Fatalf("fetching org token: %+v", err)
}
fmt.Printf("Org token is: %v\n", *tk)
toolsAsYaml, err := yaml.Marshal(tools)
if err != nil {
log.Fatal(err)
}
log.Printf("got tools:\n%s\n", string(toolsAsYaml))
log.Print("fetching runner token")
ghRunnerToken, _, err := ghClient.Actions.CreateRegistrationToken(ctx, cfg.Repositories[0].Owner, cfg.Repositories[0].Name)
if err != nil {
log.Fatal(err)
}
log.Printf("got token %v", ghRunnerToken)
bootstrapArgs := params.BootstrapInstance{
Tools: tools,
RepoURL: cfg.Organizations[0].String(),
GithubRunnerAccessToken: *tk.Token,
RunnerType: cfg.Repositories[0].Pool.Runners[0].Name,
CallbackURL: "",
InstanceToken: "",
OSArch: config.Amd64,
Flavor: cfg.Organizations[0].Pool.Runners[0].Flavor,
Image: cfg.Organizations[0].Pool.Runners[0].Image,
Labels: cfg.Organizations[0].Pool.Runners[0].Labels,
SSHKeys: []string{
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC2oT7j/+elHY9U2ibgk2RYJgCvqIwewYKJTtHslTQFDWlHLeDam93BBOFlQJm9/wKX/qjC8d26qyzjeeeVf2EEAztp+jQfEq9OU+EtgQUi589jxtVmaWuYED8KVNbzLuP79SrBtEZD4xqgmnNotPhRshh3L6eYj4XzLWDUuOD6kzNdsJA2QOKeMOIFpBN6urKJHRHYD+oUPUX1w5QMv1W1Srlffl4m5uE+0eJYAMr02980PG4+jS4bzM170wYdWwUI0pSZsEDC8Fn7jef6QARU2CgHJYlaTem+KWSXislOUTaCpR0uhakP1ezebW20yuuc3bdRNgSlZi9B7zAPALGZpOshVqwF+KmLDi6XiFwG+NnwAFa6zaQfhOxhw/rF5Jk/wVjHIHkNNvYewycZPbKui0E3QrdVtR908N3VsPtLhMQ59BEMl3xlURSi0fiOU3UjnwmOkOoFDy/WT8qk//gFD93tUxlf4eKXDgNfME3zNz8nVi2uCPvG5NT/P/VWR8NMqW6tZcmWyswM/GgL6Y84JQ3ESZq/7WvAetdc1gVIDQJ2ejYbSHBcQpWvkocsiuMTCwiEvQ0sr+UE5jmecQvLPUyXOhuMhw43CwxnLk1ZSeYeCorxbskyqIXH71o8zhbPoPiEbwgB+i9WEoq02u7c8CmCmO8Y9aOnh8MzTKxIgQ==",
},
}
instance, err := provider.CreateInstance(ctx, bootstrapArgs)
if err != nil {
log.Fatal(err)
}
fmt.Println(instance)
}

View file

@ -1,127 +1,160 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"os/signal"
// import (
// "context"
// "flag"
// "fmt"
// "log"
// "net"
// "net/http"
// "os/signal"
"runner-manager/config"
"runner-manager/runner"
"runner-manager/runner/providers/lxd"
"runner-manager/util"
// "github.com/google/go-github/v43/github"
// "golang.org/x/oauth2"
// "gopkg.in/yaml.v3"
)
// "runner-manager/apiserver/controllers"
// "runner-manager/apiserver/routers"
// "runner-manager/config"
// "runner-manager/util"
// // "github.com/google/go-github/v43/github"
// // "golang.org/x/oauth2"
// // "gopkg.in/yaml.v3"
// )
var (
conf = flag.String("config", config.DefaultConfigFilePath, "runner-manager config file")
version = flag.Bool("version", false, "prints version")
)
// var (
// conf = flag.String("config", config.DefaultConfigFilePath, "runner-manager config file")
// version = flag.Bool("version", false, "prints version")
// )
var Version string
// var Version string
// var token = "super secret token"
// // var token = "super secret token"
func main() {
flag.Parse()
if *version {
fmt.Println(Version)
return
}
// func main() {
// flag.Parse()
// if *version {
// fmt.Println(Version)
// return
// }
// ctx, stop := signal.NotifyContext(context.Background(), signals...)
// defer stop()
// fmt.Println(ctx)
ctx, stop := signal.NotifyContext(context.Background(), signals...)
defer stop()
fmt.Println(ctx)
// cfg, err := config.NewConfig(*conf)
// if err != nil {
// log.Fatalf("Fetching config: %+v", err)
// }
cfg, err := config.NewConfig(*conf)
if err != nil {
log.Fatal(err)
}
// // ts := oauth2.StaticTokenSource(
// // &oauth2.Token{AccessToken: cfg.Github.OAuth2Token},
// // )
// ts := oauth2.StaticTokenSource(
// &oauth2.Token{AccessToken: cfg.Github.OAuth2Token},
// )
// // tc := oauth2.NewClient(ctx, ts)
// tc := oauth2.NewClient(ctx, ts)
// // ghClient := github.NewClient(tc)
// ghClient := github.NewClient(tc)
// // // list all repositories for the authenticated user
// // repos, _, err := client.Repositories.List(ctx, "", nil)
// // list all repositories for the authenticated user
// repos, _, err := client.Repositories.List(ctx, "", nil)
// // fmt.Println(repos, err)
// fmt.Println(repos, err)
// logWriter, err := util.GetLoggingWriter(cfg)
// if err != nil {
// log.Fatalf("fetching log writer: %+v", err)
// }
// log.SetOutput(logWriter)
logWriter, err := util.GetLoggingWriter(cfg)
if err != nil {
log.Fatal(err)
}
log.SetOutput(logWriter)
// controller, err := controllers.NewAPIController()
// if err != nil {
// log.Fatalf("failed to create controller: %+v", err)
// }
runner, err := runner.NewRunner(ctx, cfg)
if err != nil {
log.Fatal(err)
}
// router := routers.NewAPIRouter(controller, logWriter)
fmt.Println(runner)
controllerID := "026d374d-6a8a-4241-8ed9-a246fff6762f"
provider, err := lxd.NewProvider(ctx, &cfg.Providers[0], &cfg.Repositories[0].Pool, controllerID)
if err != nil {
log.Fatal(err)
}
// tlsCfg, err := cfg.APIServer.APITLSConfig()
// if err != nil {
// log.Fatalf("failed to get TLS config: %q", err)
// }
if err := provider.RemoveAllInstances(ctx); err != nil {
log.Fatal(err)
}
// srv := &http.Server{
// Addr: cfg.APIServer.BindAddress(),
// TLSConfig: tlsCfg,
// // Pass our instance of gorilla/mux in.
// Handler: router,
// }
// fmt.Println(provider)
// listener, err := net.Listen("tcp", srv.Addr)
// if err != nil {
// log.Fatalf("creating listener: %q", err)
// }
// if err := provider.DeleteInstance(ctx, "runner-manager-2fbe5354-be28-4e00-95a8-11479912368d"); err != nil {
// log.Fatal(err)
// }
// go func() {
// if err := srv.Serve(listener); err != nil {
// log.Fatalf("Listening: %+v", err)
// }
// }()
// instances, err := provider.ListInstances(ctx)
// <-ctx.Done()
// asJs, err := json.MarshalIndent(instances, "", " ")
// fmt.Println(string(asJs), err)
// // runner, err := runner.NewRunner(ctx, *cfg)
// // if err != nil {
// // log.Fatal(err)
// // }
// log.Print("Fetching tools")
// tools, _, err := ghClient.Actions.ListRunnerApplicationDownloads(ctx, cfg.Repositories[0].Owner, cfg.Repositories[0].Name)
// if err != nil {
// log.Fatal(err)
// }
// // fmt.Println(runner)
// // controllerID := "026d374d-6a8a-4241-8ed9-a246fff6762f"
// // provider, err := lxd.NewProvider(ctx, &cfg.Providers[0], controllerID)
// // if err != nil {
// // log.Fatal(err)
// // }
// toolsAsYaml, err := yaml.Marshal(tools)
// if err != nil {
// log.Fatal(err)
// }
// log.Printf("got tools:\n%s\n", string(toolsAsYaml))
// // if err := provider.RemoveAllInstances(ctx); err != nil {
// // log.Fatal(err)
// // }
// log.Print("fetching runner token")
// ghRunnerToken, _, err := ghClient.Actions.CreateRegistrationToken(ctx, cfg.Repositories[0].Owner, cfg.Repositories[0].Name)
// if err != nil {
// log.Fatal(err)
// }
// log.Printf("got token %v", ghRunnerToken)
// // fmt.Println(provider)
// bootstrapArgs := params.BootstrapInstance{
// Tools: tools,
// RepoURL: cfg.Repositories[0].String(),
// GithubRunnerAccessToken: *ghRunnerToken.Token,
// RunnerType: cfg.Repositories[0].Pool.Runners[0].Name,
// CallbackURL: "",
// InstanceToken: "",
// SSHKeys: []string{
// "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC2oT7j/+elHY9U2ibgk2RYJgCvqIwewYKJTtHslTQFDWlHLeDam93BBOFlQJm9/wKX/qjC8d26qyzjeeeVf2EEAztp+jQfEq9OU+EtgQUi589jxtVmaWuYED8KVNbzLuP79SrBtEZD4xqgmnNotPhRshh3L6eYj4XzLWDUuOD6kzNdsJA2QOKeMOIFpBN6urKJHRHYD+oUPUX1w5QMv1W1Srlffl4m5uE+0eJYAMr02980PG4+jS4bzM170wYdWwUI0pSZsEDC8Fn7jef6QARU2CgHJYlaTem+KWSXislOUTaCpR0uhakP1ezebW20yuuc3bdRNgSlZi9B7zAPALGZpOshVqwF+KmLDi6XiFwG+NnwAFa6zaQfhOxhw/rF5Jk/wVjHIHkNNvYewycZPbKui0E3QrdVtR908N3VsPtLhMQ59BEMl3xlURSi0fiOU3UjnwmOkOoFDy/WT8qk//gFD93tUxlf4eKXDgNfME3zNz8nVi2uCPvG5NT/P/VWR8NMqW6tZcmWyswM/GgL6Y84JQ3ESZq/7WvAetdc1gVIDQJ2ejYbSHBcQpWvkocsiuMTCwiEvQ0sr+UE5jmecQvLPUyXOhuMhw43CwxnLk1ZSeYeCorxbskyqIXH71o8zhbPoPiEbwgB+i9WEoq02u7c8CmCmO8Y9aOnh8MzTKxIgQ==",
// },
// }
// // if err := provider.DeleteInstance(ctx, "runner-manager-2fbe5354-be28-4e00-95a8-11479912368d"); err != nil {
// // log.Fatal(err)
// // }
// instance, err := provider.CreateInstance(ctx, bootstrapArgs)
// if err != nil {
// log.Fatal(err)
// }
// // instances, err := provider.ListInstances(ctx)
// fmt.Println(instance)
}
// // asJs, err := json.MarshalIndent(instances, "", " ")
// // fmt.Println(string(asJs), err)
// // log.Print("Fetching tools")
// // tools, _, err := ghClient.Actions.ListRunnerApplicationDownloads(ctx, cfg.Repositories[0].Owner, cfg.Repositories[0].Name)
// // if err != nil {
// // log.Fatal(err)
// // }
// // toolsAsYaml, err := yaml.Marshal(tools)
// // if err != nil {
// // log.Fatal(err)
// // }
// // log.Printf("got tools:\n%s\n", string(toolsAsYaml))
// // log.Print("fetching runner token")
// // ghRunnerToken, _, err := ghClient.Actions.CreateRegistrationToken(ctx, cfg.Repositories[0].Owner, cfg.Repositories[0].Name)
// // if err != nil {
// // log.Fatal(err)
// // }
// // log.Printf("got token %v", ghRunnerToken)
// // bootstrapArgs := params.BootstrapInstance{
// // Tools: tools,
// // RepoURL: cfg.Repositories[0].String(),
// // GithubRunnerAccessToken: *ghRunnerToken.Token,
// // RunnerType: cfg.Repositories[0].Pool.Runners[0].Name,
// // CallbackURL: "",
// // InstanceToken: "",
// // SSHKeys: []string{
// // "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC2oT7j/+elHY9U2ibgk2RYJgCvqIwewYKJTtHslTQFDWlHLeDam93BBOFlQJm9/wKX/qjC8d26qyzjeeeVf2EEAztp+jQfEq9OU+EtgQUi589jxtVmaWuYED8KVNbzLuP79SrBtEZD4xqgmnNotPhRshh3L6eYj4XzLWDUuOD6kzNdsJA2QOKeMOIFpBN6urKJHRHYD+oUPUX1w5QMv1W1Srlffl4m5uE+0eJYAMr02980PG4+jS4bzM170wYdWwUI0pSZsEDC8Fn7jef6QARU2CgHJYlaTem+KWSXislOUTaCpR0uhakP1ezebW20yuuc3bdRNgSlZi9B7zAPALGZpOshVqwF+KmLDi6XiFwG+NnwAFa6zaQfhOxhw/rF5Jk/wVjHIHkNNvYewycZPbKui0E3QrdVtR908N3VsPtLhMQ59BEMl3xlURSi0fiOU3UjnwmOkOoFDy/WT8qk//gFD93tUxlf4eKXDgNfME3zNz8nVi2uCPvG5NT/P/VWR8NMqW6tZcmWyswM/GgL6Y84JQ3ESZq/7WvAetdc1gVIDQJ2ejYbSHBcQpWvkocsiuMTCwiEvQ0sr+UE5jmecQvLPUyXOhuMhw43CwxnLk1ZSeYeCorxbskyqIXH71o8zhbPoPiEbwgB+i9WEoq02u7c8CmCmO8Y9aOnh8MzTKxIgQ==",
// // },
// // }
// // instance, err := provider.CreateInstance(ctx, bootstrapArgs)
// // if err != nil {
// // log.Fatal(err)
// // }
// // fmt.Println(instance)
// }

View file

@ -84,14 +84,15 @@ type Config struct {
// ConfigDir is the folder where the runner may save any aditional files
// or configurations it may need. Things like auto-generated SSH keys that
// may be used to access the runner instances.
ConfigDir string `toml:"config_dir" json:"config-dir"`
APIServer APIServer `toml:"apiserver" json:"apiserver"`
Database Database `toml:"database" json:"database"`
Repositories []Repository `toml:"repository" json:"repository"`
Providers []Provider `toml:"provider" json:"provider"`
Github Github `toml:"github"`
ConfigDir string `toml:"config_dir,omitempty" json:"config-dir,omitempty"`
APIServer APIServer `toml:"apiserver,omitempty" json:"apiserver,omitempty"`
Database Database `toml:"database,omitempty" json:"database,omitempty"`
Repositories []Repository `toml:"repository,omitempty" json:"repository,omitempty"`
Organizations []Organization `toml:"organization,omitempty" json:"organization,omitempty"`
Providers []Provider `toml:"provider,omitempty" json:"provider,omitempty"`
Github Github `toml:"github,omitempty"`
// LogFile is the location of the log file.
LogFile string `toml:"log_file"`
LogFile string `toml:"log_file,omitempty"`
}
// Validate validates the config
@ -134,9 +135,59 @@ func (c *Config) Validate() error {
}
}
for _, org := range c.Organizations {
if err := org.Validate(); err != nil {
return errors.Wrap(err, "validating organization")
}
// We also need to validate that the provider used for this
// repo, has been defined in the providers section. Multiple
// repos can use the same provider.
found := false
for _, provider := range c.Providers {
if provider.Name == org.Pool.ProviderName {
found = true
break
}
}
if !found {
return fmt.Errorf("provider %s defined in org %s is not defined", org.Pool.ProviderName, org.Name)
}
}
return nil
}
// Organization represents a Github organization for which we can manage runners.
type Organization struct {
// Name is the name of the organization.
Name string `toml:"name" json:"name"`
// WebsocketSecret is the shared secret used to create the hash of
// the webhook body. We use this to validate that the webhook message
// came in from the correct repo.
WebhookSecret string `toml:"webhook_secret" json:"webhook-secret"`
// Pool is the pool defined for this repository.
Pool Pool `toml:"pool" json:"pool"`
}
func (o *Organization) Validate() error {
if o.Name == "" {
return fmt.Errorf("missing org name")
}
if err := o.Pool.Validate(); err != nil {
return errors.Wrap(err, "validating org pool")
}
return nil
}
func (o *Organization) String() string {
return fmt.Sprintf("https://github.com/%s", o.Name)
}
// Github hold configuration options specific to interacting with github.
// Currently that is just a OAuth2 personal token.
type Github struct {
@ -452,6 +503,19 @@ type APIServer struct {
CORSOrigins []string `toml:"cors_origins" json:"cors-origins"`
}
func (a *APIServer) APITLSConfig() (*tls.Config, error) {
if !a.UseTLS {
return nil, nil
}
return a.TLSConfig.TLSConfig()
}
// BindAddress returns a host:port string.
func (a *APIServer) BindAddress() string {
return fmt.Sprintf("%s:%d", a.Bind, a.Port)
}
// Validate validates the API server config
func (a *APIServer) Validate() error {
if a.UseTLS {

View file

@ -3,5 +3,93 @@ package errors
import "fmt"
var (
ErrNotFound = fmt.Errorf("Not found")
// ErrUnauthorized is returned when a user does not have
// authorization to perform a request
ErrUnauthorized = NewUnauthorizedError("Unauthorized")
// ErrNotFound is returned if an object is not found in
// the database.
ErrNotFound = NewNotFoundError("not found")
// ErrDuplicateUser is returned when creating a user, if the
// user already exists.
ErrDuplicateEntity = NewDuplicateUserError("duplicate")
// ErrBadRequest is returned is a malformed request is sent
ErrBadRequest = NewBadRequestError("invalid request")
)
type baseError struct {
msg string
}
func (b *baseError) Error() string {
return b.msg
}
// NewUnauthorizedError returns a new UnauthorizedError
func NewUnauthorizedError(msg string) error {
return &UnauthorizedError{
baseError{
msg: msg,
},
}
}
// UnauthorizedError is returned when a request is unauthorized
type UnauthorizedError struct {
baseError
}
// NewNotFoundError returns a new NotFoundError
func NewNotFoundError(msg string) error {
return &NotFoundError{
baseError{
msg: msg,
},
}
}
// NotFoundError is returned when a resource is not found
type NotFoundError struct {
baseError
}
// NewDuplicateUserError returns a new DuplicateUserError
func NewDuplicateUserError(msg string) error {
return &DuplicateUserError{
baseError{
msg: msg,
},
}
}
// DuplicateUserError is returned when a duplicate user is requested
type DuplicateUserError struct {
baseError
}
// NewBadRequestError returns a new BadRequestError
func NewBadRequestError(msg string, a ...interface{}) error {
return &BadRequestError{
baseError{
msg: fmt.Sprintf(msg, a...),
},
}
}
// BadRequestError is returned when a malformed request is received
type BadRequestError struct {
baseError
}
// NewConflictError returns a new ConflictError
func NewConflictError(msg string, a ...interface{}) error {
return &ConflictError{
baseError{
msg: fmt.Sprintf(msg, a...),
},
}
}
// ConflictError is returned when a conflicting request is made
type ConflictError struct {
baseError
}

195
github/github.go Normal file
View file

@ -0,0 +1,195 @@
package github
import "time"
type Event string
const (
// WorkflowJobEvent is the event set in the webhook payload from github
// when a workflow_job hook is sent.
WorkflowJobEvent Event = "workflow_job"
)
// WorkflowJob holds the payload sent by github when a workload_job is sent.
type WorkflowJob struct {
Action string `json:"action"`
WorkflowJob struct {
ID int64 `json:"id"`
RunID int64 `json:"run_id"`
RunURL string `json:"run_url"`
RunAttempt int64 `json:"run_attempt"`
NodeID string `json:"node_id"`
HeadSha string `json:"head_sha"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
Status string `json:"status"`
Conclusion string `json:"conclusion"`
StartedAt time.Time `json:"started_at"`
CompletedAt time.Time `json:"completed_at"`
Name string `json:"name"`
Steps []struct {
Name string `json:"name"`
Status string `json:"status"`
Conclusion string `json:"conclusion"`
Number int64 `json:"number"`
StartedAt time.Time `json:"started_at"`
CompletedAt time.Time `json:"completed_at"`
} `json:"steps"`
CheckRunURL string `json:"check_run_url"`
Labels []string `json:"labels"`
RunnerID int64 `json:"runner_id"`
RunnerName string `json:"runner_name"`
RunnerGroupID int64 `json:"runner_group_id"`
RunnerGroupName string `json:"runner_group_name"`
} `json:"workflow_job"`
Repository struct {
ID int64 `json:"id"`
NodeID string `json:"node_id"`
Name string `json:"name"`
FullName string `json:"full_name"`
Private bool `json:"private"`
Owner struct {
Login string `json:"login"`
ID int64 `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"owner"`
HTMLURL string `json:"html_url"`
Description string `json:"description"`
Fork bool `json:"fork"`
URL string `json:"url"`
ForksURL string `json:"forks_url"`
KeysURL string `json:"keys_url"`
CollaboratorsURL string `json:"collaborators_url"`
TeamsURL string `json:"teams_url"`
HooksURL string `json:"hooks_url"`
IssueEventsURL string `json:"issue_events_url"`
EventsURL string `json:"events_url"`
AssigneesURL string `json:"assignees_url"`
BranchesURL string `json:"branches_url"`
TagsURL string `json:"tags_url"`
BlobsURL string `json:"blobs_url"`
GitTagsURL string `json:"git_tags_url"`
GitRefsURL string `json:"git_refs_url"`
TreesURL string `json:"trees_url"`
StatusesURL string `json:"statuses_url"`
LanguagesURL string `json:"languages_url"`
StargazersURL string `json:"stargazers_url"`
ContributorsURL string `json:"contributors_url"`
SubscribersURL string `json:"subscribers_url"`
SubscriptionURL string `json:"subscription_url"`
CommitsURL string `json:"commits_url"`
GitCommitsURL string `json:"git_commits_url"`
CommentsURL string `json:"comments_url"`
IssueCommentURL string `json:"issue_comment_url"`
ContentsURL string `json:"contents_url"`
CompareURL string `json:"compare_url"`
MergesURL string `json:"merges_url"`
ArchiveURL string `json:"archive_url"`
DownloadsURL string `json:"downloads_url"`
IssuesURL string `json:"issues_url"`
PullsURL string `json:"pulls_url"`
MilestonesURL string `json:"milestones_url"`
NotificationsURL string `json:"notifications_url"`
LabelsURL string `json:"labels_url"`
ReleasesURL string `json:"releases_url"`
DeploymentsURL string `json:"deployments_url"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
PushedAt time.Time `json:"pushed_at"`
GitURL string `json:"git_url"`
SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"`
SvnURL string `json:"svn_url"`
Homepage *string `json:"homepage"`
Size int64 `json:"size"`
StargazersCount int64 `json:"stargazers_count"`
WatchersCount int64 `json:"watchers_count"`
Language *string `json:"language"`
HasIssues bool `json:"has_issues"`
HasProjects bool `json:"has_projects"`
HasDownloads bool `json:"has_downloads"`
HasWiki bool `json:"has_wiki"`
HasPages bool `json:"has_pages"`
ForksCount int64 `json:"forks_count"`
MirrorURL *string `json:"mirror_url"`
Archived bool `json:"archived"`
Disabled bool `json:"disabled"`
OpenIssuesCount int64 `json:"open_issues_count"`
License struct {
Key string `json:"key"`
Name string `json:"name"`
SpdxID string `json:"spdx_id"`
URL string `json:"url"`
NodeID string `json:"node_id"`
} `json:"license"`
AllowForking bool `json:"allow_forking"`
IsTemplate bool `json:"is_template"`
// Topics []interface{} `json:"topics"`
Visibility string `json:"visibility"`
Forks int64 `json:"forks"`
OpenIssues int64 `json:"open_issues"`
Watchers int64 `json:"watchers"`
DefaultBranch string `json:"default_branch"`
} `json:"repository"`
Organization struct {
Login string `json:"login"`
ID int64 `json:"id"`
NodeID string `json:"node_id"`
URL string `json:"url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
HooksURL string `json:"hooks_url"`
IssuesURL string `json:"issues_url"`
MembersURL string `json:"members_url"`
PublicMembersURL string `json:"public_members_url"`
AvatarURL string `json:"avatar_url"`
Description string `json:"description"`
} `json:"organization"`
Enterprise struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
// Description interface{} `json:"description"`
// WebsiteURL interface{} `json:"website_url"`
HTMLURL string `json:"html_url"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
} `json:"enterprise"`
Sender struct {
Login string `json:"login"`
ID int64 `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
} `json:"sender"`
}

3
go.mod
View file

@ -5,6 +5,8 @@ go 1.18
require (
github.com/BurntSushi/toml v0.3.1
github.com/google/go-github/v43 v43.0.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/lxc/lxd v0.0.0-20220415052741-1170f2806124
github.com/pborman/uuid v1.2.1
github.com/pkg/errors v0.9.1
@ -15,6 +17,7 @@ require (
)
require (
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 // indirect
github.com/go-macaroon-bakery/macaroonpb v1.0.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect

6
go.sum
View file

@ -9,6 +9,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 h1:fmFk0Wt3bBxxwZnu48jqMdaOR/IZ4vdtJFuaFV8MpIE=
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3/go.mod h1:bJWSKrZyQvfTnb2OudyUjurSG4/edverV7n82+K3JiM=
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
@ -52,6 +54,10 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/juju/mgotest v1.0.1/go.mod h1:vTaDufYul+Ps8D7bgseHjq87X8eu0ivlKLp9mVc/Bfc=

View file

@ -24,7 +24,7 @@ type Instance struct {
// OSVersion is the version of the operating system.
OSVersion string `json:"os-version,omitempty"`
// OSArch is the operating system architecture.
OSArch string `json:"os-arch,omitempty"`
OSArch config.OSArch `json:"os-arch,omitempty"`
// Addresses is a list of IP addresses the provider reports
// for this instance.
Addresses []string `json:"ip-addresses,omitempty"`
@ -53,4 +53,9 @@ type BootstrapInstance struct {
// SSHKeys are the ssh public keys we may want to inject inside the runners, if the
// provider supports it.
SSHKeys []string `json:"ssh-keys"`
OSArch config.OSArch `json:"arch"`
Flavor string `json:"flavor"`
Image string `json:"image"`
Labels []string `json:"labels"`
}

View file

@ -1 +0,0 @@
package pool

46
runner/pool/repository.go Normal file
View file

@ -0,0 +1,46 @@
package pool
import (
"context"
"runner-manager/config"
"runner-manager/params"
"runner-manager/runner/common"
"github.com/google/go-github/v43/github"
)
func NewRepositoryRunnerPool(ctx context.Context, cfg config.Repository, ghcli *github.Client, provider common.Provider) (common.PoolManager, error) {
return &Repository{
ctx: ctx,
cfg: cfg,
ghcli: ghcli,
provider: provider,
}, nil
}
type Repository struct {
ctx context.Context
cfg config.Repository
ghcli *github.Client
provider common.Provider
}
func (r *Repository) getGithubRunners() ([]github.Runner, error) {
return nil, nil
}
func (r *Repository) getProviderInstances() ([]params.Instance, error) {
return nil, nil
}
func (r *Repository) Start() error {
return nil
}
func (r *Repository) Stop() error {
return nil
}
func (r *Repository) loop() {
}

View file

@ -3,9 +3,7 @@ package lxd
import (
"context"
"fmt"
"strings"
"runner-manager/cloudconfig"
"runner-manager/config"
runnerErrors "runner-manager/errors"
"runner-manager/params"
@ -47,6 +45,12 @@ var (
config.Arm64: "aarch64",
config.Arm: "armv7l",
}
lxdToConfigArch map[string]config.OSArch = map[string]config.OSArch{
"x86_64": config.Amd64,
"aarch64": config.Arm64,
"armv7l": config.Arm,
}
)
const (
@ -54,22 +58,15 @@ const (
DefaultProjectName = "runner-manager-project"
)
func NewProvider(ctx context.Context, cfg *config.Provider, pool *config.Pool, controllerID string) (common.Provider, error) {
func NewProvider(ctx context.Context, cfg *config.Provider, controllerID string) (common.Provider, error) {
if err := cfg.Validate(); err != nil {
return nil, errors.Wrap(err, "validating provider config")
}
if err := pool.Validate(); err != nil {
return nil, errors.Wrap(err, "validating pool")
}
if cfg.ProviderType != config.LXDProvider {
return nil, fmt.Errorf("invalid provider type %s, expected %s", cfg.ProviderType, config.LXDProvider)
}
if cfg.Name != pool.ProviderName {
return nil, fmt.Errorf("provider %s is not responsible for pool", cfg.Name)
}
cli, err := getClientFromConfig(ctx, &cfg.LXD)
if err != nil {
return nil, errors.Wrap(err, "creating LXD client")
@ -84,7 +81,6 @@ func NewProvider(ctx context.Context, cfg *config.Provider, pool *config.Pool, c
provider := &LXD{
ctx: ctx,
cfg: cfg,
pool: pool,
cli: cli,
controllerID: controllerID,
imageManager: &image{
@ -99,9 +95,6 @@ func NewProvider(ctx context.Context, cfg *config.Provider, pool *config.Pool, c
type LXD struct {
// cfg is the provider config for this provider.
cfg *config.Provider
// pool holds the config for the pool this provider is
// responsible for.
pool *config.Pool
// ctx is the context.
ctx context.Context
// cli is the LXD client.
@ -112,7 +105,7 @@ type LXD struct {
controllerID string
}
func (l *LXD) getProfiles(runner config.Runner) ([]string, error) {
func (l *LXD) getProfiles(flavor string) ([]string, error) {
ret := []string{}
if l.cfg.LXD.IncludeDefaultProfile {
ret = append(ret, "default")
@ -128,45 +121,14 @@ func (l *LXD) getProfiles(runner config.Runner) ([]string, error) {
set[profile] = struct{}{}
}
if _, ok := set[runner.Flavor]; !ok {
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "looking for profile %s", runner.Flavor)
if _, ok := set[flavor]; !ok {
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "looking for profile %s", flavor)
}
ret = append(ret, runner.Flavor)
ret = append(ret, flavor)
return ret, nil
}
func (l *LXD) getCloudConfig(runner config.Runner, bootstrapParams params.BootstrapInstance, tools github.RunnerApplicationDownload, runnerName string) (string, error) {
cloudCfg := cloudconfig.NewDefaultCloudInitConfig()
installRunnerParams := cloudconfig.InstallRunnerParams{
FileName: *tools.Filename,
DownloadURL: *tools.DownloadURL,
GithubToken: bootstrapParams.GithubRunnerAccessToken,
RunnerUsername: config.DefaultUser,
RunnerGroup: config.DefaultUser,
RepoURL: bootstrapParams.RepoURL,
RunnerName: runnerName,
RunnerLabels: strings.Join(runner.Labels, ","),
}
installScript, err := cloudconfig.InstallRunnerScript(installRunnerParams)
if err != nil {
return "", errors.Wrap(err, "generating script")
}
cloudCfg.AddSSHKey(bootstrapParams.SSHKeys...)
cloudCfg.AddFile(installScript, "/install_runner.sh", "root:root", "755")
cloudCfg.AddRunCmd("/install_runner.sh")
cloudCfg.AddRunCmd("rm -f /install_runner.sh")
asStr, err := cloudCfg.Serialize()
if err != nil {
return "", errors.Wrap(err, "creating cloud config")
}
return asStr, nil
}
func (l *LXD) getTools(image *api.Image, tools []*github.RunnerApplicationDownload) (github.RunnerApplicationDownload, error) {
if image == nil {
return github.RunnerApplicationDownload{}, fmt.Errorf("nil image received")
@ -211,17 +173,6 @@ func (l *LXD) getTools(image *api.Image, tools []*github.RunnerApplicationDownlo
return github.RunnerApplicationDownload{}, fmt.Errorf("failed to find tools for OS %s and arch %s", osType, image.Architecture)
}
func (l *LXD) resolveArchitecture(runner config.Runner) (string, error) {
if string(runner.OSArch) == "" {
return configToLXDArchMap[config.Amd64], nil
}
arch, ok := configToLXDArchMap[runner.OSArch]
if !ok {
return "", fmt.Errorf("architecture %s is not supported", runner.OSArch)
}
return arch, nil
}
// sadly, the security.secureboot flag is a string encoded boolean.
func (l *LXD) secureBootEnabled() string {
if l.cfg.LXD.SecureBoot {
@ -232,23 +183,18 @@ func (l *LXD) secureBootEnabled() string {
func (l *LXD) getCreateInstanceArgs(bootstrapParams params.BootstrapInstance) (api.InstancesPost, error) {
name := fmt.Sprintf("runner-manager-%s", uuid.New())
runner, err := util.FindRunnerType(bootstrapParams.RunnerType, l.pool.Runners)
if err != nil {
return api.InstancesPost{}, errors.Wrap(err, "fetching runner")
}
profiles, err := l.getProfiles(runner)
profiles, err := l.getProfiles(bootstrapParams.Flavor)
if err != nil {
return api.InstancesPost{}, errors.Wrap(err, "fetching profiles")
}
arch, err := l.resolveArchitecture(runner)
arch, err := resolveArchitecture(bootstrapParams.OSArch)
if err != nil {
return api.InstancesPost{}, errors.Wrap(err, "fetching archictecture")
}
image, err := l.imageManager.EnsureImage(runner.Image, config.LXDImageVirtualMachine, arch)
image, err := l.imageManager.EnsureImage(bootstrapParams.Image, config.LXDImageVirtualMachine, arch)
if err != nil {
return api.InstancesPost{}, errors.Wrap(err, "getting image details")
}
@ -258,7 +204,7 @@ func (l *LXD) getCreateInstanceArgs(bootstrapParams params.BootstrapInstance) (a
return api.InstancesPost{}, errors.Wrap(err, "getting tools")
}
cloudCfg, err := l.getCloudConfig(runner, bootstrapParams, tools, name)
cloudCfg, err := util.GetCloudConfig(bootstrapParams, tools, name)
if err != nil {
return api.InstancesPost{}, errors.Wrap(err, "generating cloud-config")
}

View file

@ -2,6 +2,7 @@ package lxd
import (
"context"
"fmt"
"io/ioutil"
"log"
"runner-manager/config"
@ -41,8 +42,14 @@ func lxdInstanceToAPIInstance(instance *api.InstanceFull) params.Instance {
}
}
}
instanceArch, ok := lxdToConfigArch[instance.Architecture]
if !ok {
log.Printf("failed to find OS architecture")
}
return params.Instance{
OSArch: instance.Architecture,
OSArch: instanceArch,
ProviderID: instance.Name,
Name: instance.Name,
OSType: osType,
@ -103,3 +110,14 @@ func projectName(cfg config.LXD) string {
}
return DefaultProjectName
}
func resolveArchitecture(osArch config.OSArch) (string, error) {
if string(osArch) == "" {
return configToLXDArchMap[config.Amd64], nil
}
arch, ok := configToLXDArchMap[osArch]
if !ok {
return "", fmt.Errorf("architecture %s is not supported", osArch)
}
return arch, nil
}

View file

@ -0,0 +1,27 @@
package providers
import (
"context"
"runner-manager/config"
"runner-manager/runner/common"
"runner-manager/runner/providers/lxd"
"github.com/pkg/errors"
)
// LoadProvidersFromConfig loads all providers from the config and populates
// a map with them.
func LoadProvidersFromConfig(ctx context.Context, cfg config.Config, controllerID string) (map[string]common.Provider, error) {
providers := map[string]common.Provider{}
for _, providerCfg := range cfg.Providers {
switch providerCfg.ProviderType {
case config.LXDProvider:
provider, err := lxd.NewProvider(ctx, &providerCfg, controllerID)
if err != nil {
return nil, errors.Wrap(err, "creating provider")
}
providers[providerCfg.Name] = provider
}
}
return providers, nil
}

View file

@ -2,11 +2,14 @@ package runner
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runner-manager/config"
gErrors "runner-manager/errors"
"runner-manager/runner/common"
"runner-manager/runner/providers"
"runner-manager/util"
"sync"
@ -15,10 +18,22 @@ import (
"golang.org/x/crypto/ssh"
)
func NewRunner(ctx context.Context, cfg *config.Config) (*Runner, error) {
func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) {
ghc, err := util.GithubClientFromConfig(ctx, cfg.Github)
if err != nil {
return nil, errors.Wrap(err, "getting github client")
}
providers, err := providers.LoadProvidersFromConfig(ctx, cfg, "")
if err != nil {
return nil, errors.Wrap(err, "loading providers")
}
runner := &Runner{
ctx: ctx,
config: cfg,
ctx: ctx,
config: cfg,
ghc: ghc,
providers: providers,
}
if err := runner.ensureSSHKeys(); err != nil {
@ -34,8 +49,35 @@ type Runner struct {
ctx context.Context
ghc *github.Client
config *config.Config
pools []common.PoolManager
controllerID string
config config.Config
repositories map[string]common.PoolManager
organizations map[string]common.PoolManager
providers map[string]common.Provider
}
func (r *Runner) getRepoSecret(repoName string) (string, error) {
return "", nil
}
func (r *Runner) getOrgSecret(orgName string) (string, error) {
return "", nil
}
func (r *Runner) ValidateHookBody(hookTargetType, signature, entity string, body []byte) error {
var secret string
var err error
switch hookTargetType {
case "repository":
secret, err = r.getRepoSecret(entity)
case "organization":
secret, err = r.getOrgSecret(entity)
default:
return gErrors.NewBadRequestError("invalid hook type: %s", hookTargetType)
}
fmt.Println(secret, err)
return nil
}
func (r *Runner) sshDir() string {

View file

@ -2,6 +2,7 @@ package util
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@ -14,12 +15,16 @@ import (
"path"
"strings"
"github.com/google/go-github/v43/github"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
"golang.org/x/oauth2"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
"runner-manager/cloudconfig"
"runner-manager/config"
runnerErrors "runner-manager/errors"
"runner-manager/params"
)
var (
@ -110,3 +115,46 @@ func OSToOSType(os string) (config.OSType, error) {
}
return osType, nil
}
func GithubClientFromConfig(ctx context.Context, cfg config.Github) (*github.Client, error) {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: cfg.OAuth2Token},
)
tc := oauth2.NewClient(ctx, ts)
ghClient := github.NewClient(tc)
return ghClient, nil
}
func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.RunnerApplicationDownload, runnerName string) (string, error) {
cloudCfg := cloudconfig.NewDefaultCloudInitConfig()
installRunnerParams := cloudconfig.InstallRunnerParams{
FileName: *tools.Filename,
DownloadURL: *tools.DownloadURL,
GithubToken: bootstrapParams.GithubRunnerAccessToken,
RunnerUsername: config.DefaultUser,
RunnerGroup: config.DefaultUser,
RepoURL: bootstrapParams.RepoURL,
RunnerName: runnerName,
RunnerLabels: strings.Join(bootstrapParams.Labels, ","),
}
installScript, err := cloudconfig.InstallRunnerScript(installRunnerParams)
if err != nil {
return "", errors.Wrap(err, "generating script")
}
cloudCfg.AddSSHKey(bootstrapParams.SSHKeys...)
cloudCfg.AddFile(installScript, "/install_runner.sh", "root:root", "755")
cloudCfg.AddRunCmd("/install_runner.sh")
cloudCfg.AddRunCmd("rm -f /install_runner.sh")
asStr, err := cloudCfg.Serialize()
if err != nil {
return "", errors.Wrap(err, "creating cloud config")
}
return asStr, nil
}