Handle org runners
This commit is contained in:
parent
bf0a5bf147
commit
ebec0dda52
18 changed files with 1028 additions and 184 deletions
124
apiserver/controllers/controllers.go
Normal file
124
apiserver/controllers/controllers.go
Normal 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)
|
||||
}
|
||||
7
apiserver/params/params.go
Normal file
7
apiserver/params/params.go
Normal 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"`
|
||||
}
|
||||
21
apiserver/routers/routers.go
Normal file
21
apiserver/routers/routers.go
Normal 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
|
||||
}
|
||||
172
cmd/runner-manager/create.go
Normal file
172
cmd/runner-manager/create.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
195
github/github.go
Normal 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
3
go.mod
|
|
@ -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
6
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
package pool
|
||||
46
runner/pool/repository.go
Normal file
46
runner/pool/repository.go
Normal 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() {
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
27
runner/providers/providers.go
Normal file
27
runner/providers/providers.go
Normal 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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
48
util/util.go
48
util/util.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue