Added support for other than public GitHub URL (#146)

Refactoring a bit
This commit is contained in:
Juho Saarinen 2020-10-28 15:15:53 +02:00 committed by GitHub
parent 99a53a6e79
commit 40c5050978
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 101 additions and 82 deletions

View File

@ -50,7 +50,7 @@ jobs:
--platform linux/amd64,linux/arm64 \ --platform linux/amd64,linux/arm64 \
--tag ${DOCKERHUB_USERNAME}/actions-runner-dind:v${RUNNER_VERSION} \ --tag ${DOCKERHUB_USERNAME}/actions-runner-dind:v${RUNNER_VERSION} \
--tag ${DOCKERHUB_USERNAME}/actions-runner-dind:latest \ --tag ${DOCKERHUB_USERNAME}/actions-runner-dind:latest \
-f Dockerfile.dindrunner . -f dindrunner.Dockerfile .
- name: Login to GitHub Docker Registry - name: Login to GitHub Docker Registry
run: echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin run: echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
@ -76,4 +76,4 @@ jobs:
--platform linux/amd64,linux/arm64 \ --platform linux/amd64,linux/arm64 \
--tag ${DOCKERHUB_USERNAME}/actions-runner-dind:v${RUNNER_VERSION} \ --tag ${DOCKERHUB_USERNAME}/actions-runner-dind:v${RUNNER_VERSION} \
--tag ${DOCKERHUB_USERNAME}/actions-runner-dind:latest \ --tag ${DOCKERHUB_USERNAME}/actions-runner-dind:latest \
-f Dockerfile.dindrunner . --push -f dindrunner.Dockerfile . --push

View File

@ -20,6 +20,16 @@ Install the custom resource and actions-runner-controller itself. This will crea
$ kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/latest/download/actions-runner-controller.yaml $ kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/latest/download/actions-runner-controller.yaml
``` ```
### Github Enterprise support
If you use either Github Enterprise Cloud or Server (and have recent enought version supporting Actions), you can use **actions-runner-controller** with those, too. Authentication works same way as with public Github (repo and organization level).
```
$ kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL>
```
[Enterprise level](https://docs.github.com/en/enterprise-server@2.22/actions/hosting-your-own-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-an-enterprise) runners are not working yet as there's no API definition for those.
## Setting up authentication with GitHub API ## Setting up authentication with GitHub API
There are two ways for actions-runner-controller to authenticate with the GitHub API: There are two ways for actions-runner-controller to authenticate with the GitHub API:

View File

@ -16,7 +16,10 @@ import (
) )
func newGithubClient(server *httptest.Server) *github.Client { func newGithubClient(server *httptest.Server) *github.Client {
client, err := github.NewClientWithAccessToken("token") c := github.Config{
Token: "token",
}
client, err := c.NewClient()
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -317,6 +317,10 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
Name: "DOCKERD_IN_RUNNER", Name: "DOCKERD_IN_RUNNER",
Value: fmt.Sprintf("%v", dockerdInRunner), Value: fmt.Sprintf("%v", dockerdInRunner),
}, },
{
Name: "GITHUB_URL",
Value: r.GitHubClient.GithubBaseURL,
},
} }
env = append(env, runner.Spec.Env...) env = append(env, runner.Spec.Env...)

View File

@ -9,43 +9,66 @@ import (
"time" "time"
"github.com/bradleyfalzon/ghinstallation" "github.com/bradleyfalzon/ghinstallation"
"github.com/go-logr/logr"
"github.com/google/go-github/v32/github" "github.com/google/go-github/v32/github"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
// Config contains configuration for Github client
type Config struct {
Log logr.Logger
EnterpriseURL string `split_words:"true"`
AppID int64 `split_words:"true"`
AppInstallationID int64 `split_words:"true"`
AppPrivateKey string `split_words:"true"`
Token string
}
// Client wraps GitHub client with some additional // Client wraps GitHub client with some additional
type Client struct { type Client struct {
*github.Client *github.Client
regTokens map[string]*github.RegistrationToken regTokens map[string]*github.RegistrationToken
mu sync.Mutex mu sync.Mutex
// GithubBaseURL to Github without API suffix.
GithubBaseURL string
} }
// NewClient returns a client authenticated as a GitHub App. func (c *Config) NewClient() (*Client, error) {
func NewClient(appID, installationID int64, privateKeyPath string) (*Client, error) { var (
tr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, appID, installationID, privateKeyPath) httpClient *http.Client
client *github.Client
)
githubBaseURL := "https://github.com/"
if len(c.Token) > 0 {
httpClient = oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: c.Token},
))
} else {
tr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, c.AppID, c.AppInstallationID, c.AppPrivateKey)
if err != nil { if err != nil {
c.Log.Error(err, "Authentication failed")
return nil, fmt.Errorf("authentication failed: %v", err) return nil, fmt.Errorf("authentication failed: %v", err)
} }
httpClient = &http.Client{Transport: tr}
gh := github.NewClient(&http.Client{Transport: tr})
return &Client{
Client: gh,
regTokens: map[string]*github.RegistrationToken{},
mu: sync.Mutex{},
}, nil
} }
// NewClientWithAccessToken returns a client authenticated with personal access token. if len(c.EnterpriseURL) > 0 {
func NewClientWithAccessToken(token string) (*Client, error) { var err error
tc := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource( client, err = github.NewEnterpriseClient(c.EnterpriseURL, c.EnterpriseURL, httpClient)
&oauth2.Token{AccessToken: token}, if err != nil {
)) c.Log.Error(err, "Enterprise client creation failed")
return nil, fmt.Errorf("enterprise client creation failed: %v", err)
}
githubBaseURL = fmt.Sprintf("%s://%s%s", client.BaseURL.Scheme, client.BaseURL.Host, strings.TrimSuffix(client.BaseURL.Path, "api/v3/"))
} else {
client = github.NewClient(httpClient)
}
return &Client{ return &Client{
Client: github.NewClient(tc), Client: client,
regTokens: map[string]*github.RegistrationToken{}, regTokens: map[string]*github.RegistrationToken{},
mu: sync.Mutex{}, mu: sync.Mutex{},
GithubBaseURL: githubBaseURL,
}, nil }, nil
} }

View File

@ -14,7 +14,10 @@ import (
var server *httptest.Server var server *httptest.Server
func newTestClient() *Client { func newTestClient() *Client {
client, err := NewClientWithAccessToken("token") c := Config{
Token: "token",
}
client, err := c.NewClient()
if err != nil { if err != nil {
panic(err) panic(err)
} }

1
go.mod
View File

@ -9,6 +9,7 @@ require (
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04 github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04
github.com/google/go-querystring v1.0.0 github.com/google/go-querystring v1.0.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/onsi/ginkgo v1.8.0 github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega v1.5.0 github.com/onsi/gomega v1.5.0
github.com/stretchr/testify v1.4.0 // indirect github.com/stretchr/testify v1.4.0 // indirect

2
go.sum
View File

@ -158,6 +158,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=

65
main.go
View File

@ -20,9 +20,9 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"strconv"
"time" "time"
"github.com/kelseyhightower/envconfig"
actionsv1alpha1 "github.com/summerwind/actions-runner-controller/api/v1alpha1" actionsv1alpha1 "github.com/summerwind/actions-runner-controller/api/v1alpha1"
"github.com/summerwind/actions-runner-controller/controllers" "github.com/summerwind/actions-runner-controller/controllers"
"github.com/summerwind/actions-runner-controller/github" "github.com/summerwind/actions-runner-controller/github"
@ -62,68 +62,29 @@ func main() {
runnerImage string runnerImage string
dockerImage string dockerImage string
ghToken string
ghAppID int64
ghAppInstallationID int64
ghAppPrivateKey string
) )
var c github.Config
err = envconfig.Process("github", &c)
if err != nil {
fmt.Fprintln(os.Stderr, "Error: Environment variable read failed.")
}
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
flag.StringVar(&runnerImage, "runner-image", defaultRunnerImage, "The image name of self-hosted runner container.") flag.StringVar(&runnerImage, "runner-image", defaultRunnerImage, "The image name of self-hosted runner container.")
flag.StringVar(&dockerImage, "docker-image", defaultDockerImage, "The image name of docker sidecar container.") flag.StringVar(&dockerImage, "docker-image", defaultDockerImage, "The image name of docker sidecar container.")
flag.StringVar(&ghToken, "github-token", "", "The personal access token of GitHub.") flag.StringVar(&c.Token, "github-token", c.Token, "The personal access token of GitHub.")
flag.Int64Var(&ghAppID, "github-app-id", 0, "The application ID of GitHub App.") flag.Int64Var(&c.AppID, "github-app-id", c.AppID, "The application ID of GitHub App.")
flag.Int64Var(&ghAppInstallationID, "github-app-installation-id", 0, "The installation ID of GitHub App.") flag.Int64Var(&c.AppInstallationID, "github-app-installation-id", c.AppInstallationID, "The installation ID of GitHub App.")
flag.StringVar(&ghAppPrivateKey, "github-app-private-key", "", "The path of a private key file to authenticate as a GitHub App") flag.StringVar(&c.AppPrivateKey, "github-app-private-key", c.AppPrivateKey, "The path of a private key file to authenticate as a GitHub App")
flag.DurationVar(&syncPeriod, "sync-period", 10*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled. When you use autoscaling, set to a lower value like 10 minute, because this corresponds to the minimum time to react on demand change") flag.DurationVar(&syncPeriod, "sync-period", 10*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled. When you use autoscaling, set to a lower value like 10 minute, because this corresponds to the minimum time to react on demand change")
flag.Parse() flag.Parse()
if ghToken == "" { ghClient, err = c.NewClient()
ghToken = os.Getenv("GITHUB_TOKEN")
}
if ghAppID == 0 {
appID, err := strconv.ParseInt(os.Getenv("GITHUB_APP_ID"), 10, 64)
if err == nil {
ghAppID = appID
}
}
if ghAppInstallationID == 0 {
appInstallationID, err := strconv.ParseInt(os.Getenv("GITHUB_APP_INSTALLATION_ID"), 10, 64)
if err == nil {
ghAppInstallationID = appInstallationID
}
}
if ghAppPrivateKey == "" {
ghAppPrivateKey = os.Getenv("GITHUB_APP_PRIVATE_KEY")
}
if ghAppID != 0 {
if ghAppInstallationID == 0 {
fmt.Fprintln(os.Stderr, "Error: The installation ID must be specified.")
os.Exit(1)
}
if ghAppPrivateKey == "" {
fmt.Fprintln(os.Stderr, "Error: The path of a private key file must be specified.")
os.Exit(1)
}
ghClient, err = github.NewClient(ghAppID, ghAppInstallationID, ghAppPrivateKey)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: Failed to create GitHub client: %v\n", err) fmt.Fprintln(os.Stderr, "Error: Client creation failed.", err)
os.Exit(1)
}
} else if ghToken != "" {
ghClient, err = github.NewClientWithAccessToken(ghToken)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: Failed to create GitHub client: %v\n", err)
os.Exit(1)
}
} else {
fmt.Fprintln(os.Stderr, "Error: GitHub App credentials or personal access token must be specified.")
os.Exit(1) os.Exit(1)
} }

View File

@ -24,7 +24,7 @@ endif
docker-build: docker-build:
docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${NAME}:${TAG} -t ${NAME}:v${RUNNER_VERSION} . docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${NAME}:${TAG} -t ${NAME}:v${RUNNER_VERSION} .
docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${DIND_RUNNER_NAME}:${TAG} -t ${DIND_RUNNER_NAME}:v${RUNNER_VERSION} -f Dockerfile.dindrunner . docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${DIND_RUNNER_NAME}:${TAG} -t ${DIND_RUNNER_NAME}:v${RUNNER_VERSION} -f dindrunner.Dockerfile .
docker-push: docker-push:
@ -48,5 +48,5 @@ docker-buildx:
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \ --build-arg RUNNER_VERSION=${RUNNER_VERSION} \
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \ --build-arg DOCKER_VERSION=${DOCKER_VERSION} \
-t "${DIND_RUNNER_NAME}:latest" \ -t "${DIND_RUNNER_NAME}:latest" \
-f Dockerfile.dindrunner \ -f dindrunner.Dockerfile \
. ${PUSH_ARG} . ${PUSH_ARG}

View File

@ -56,6 +56,7 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
echo >&2 "error: failed to download 'docker-${DOCKER_VERSION}' from '${DOCKER_CHANNEL}' for '${ARCH}'"; \ echo >&2 "error: failed to download 'docker-${DOCKER_VERSION}' from '${DOCKER_CHANNEL}' for '${ARCH}'"; \
exit 1; \ exit 1; \
fi; \ fi; \
echo "Downloaded Docker from https://download.docker.com/linux/static/${DOCKER_CHANNEL}/${ARCH}/docker-${DOCKER_VERSION}.tgz"; \
tar --extract \ tar --extract \
--file docker.tgz \ --file docker.tgz \
--strip-components 1 \ --strip-components 1 \

View File

@ -1,5 +1,16 @@
#!/bin/bash #!/bin/bash
if [ -z "${GITHUB_URL}" ]; then
echo "Working with public GitHub" 1>&2
GITHUB_URL="https://github.com/"
else
length=${#GITHUB_URL}
last_char=${GITHUB_URL:length-1:1}
[[ $last_char != "/" ]] && GITHUB_URL="$GITHUB_URL/"; :
echo "Github endpoint URL ${GITHUB_URL}"
fi
if [ -z "${RUNNER_NAME}" ]; then if [ -z "${RUNNER_NAME}" ]; then
echo "RUNNER_NAME must be set" 1>&2 echo "RUNNER_NAME must be set" 1>&2
exit 1 exit 1
@ -26,7 +37,7 @@ if [ -z "${RUNNER_TOKEN}" ]; then
fi fi
cd /runner cd /runner
./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "https://github.com/${ATTACH}" --token "${RUNNER_TOKEN}" ${LABEL_ARG} ./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "${GITHUB_URL}${ATTACH}" --token "${RUNNER_TOKEN}" ${LABEL_ARG}
for f in runsvc.sh RunnerService.js; do for f in runsvc.sh RunnerService.js; do
diff {bin,patched}/${f} || : diff {bin,patched}/${f} || :