GitHub Enterprise support (#658)
* Add option to specify custom GitHub instance * Use correct GHE API endpoint URLs Co-authored-by: Markus Wolf <knister.peter@shadowrun-clan.de> * Extract slug from GitHub Enterprise URLs Co-authored-by: Markus Wolf <knister.peter@shadowrun-clan.de> * Use GITHUB_TOKEN for clone authenticate if provided This change will allow use authentication for cloning actions from private repositories or github enterprise instances. Co-Authored-By: Markus Wolf <knister.peter@shadowrun-clan.de> * Add section about using act on GitHub Enterprise to README Co-authored-by: Markus Wolf <knister.peter@shadowrun-clan.de> * Set GitHubInstance in runnerConfig in runner_test Co-authored-by: Markus Wolf <knister.peter@shadowrun-clan.de> Co-authored-by: hackercat <me@hackerc.at> Co-authored-by: Markus Wolf <knister.peter@shadowrun-clan.de>
This commit is contained in:
parent
710a3ac94c
commit
0c4374ec41
10
README.md
10
README.md
|
@ -129,6 +129,7 @@ It will save that information to `~/.actrc`, please refer to [Configuration](#co
|
|||
--env stringArray env to make available to actions with optional value (e.g. --e myenv=foo or -s myenv)
|
||||
--env-file string environment file to read and use as env in the containers (default ".env")
|
||||
-e, --eventpath string path to event JSON file
|
||||
--github-instance string GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server. (default "github.com")
|
||||
-g, --graph draw workflows
|
||||
-h, --help help for act
|
||||
--insecure-secrets NOT RECOMMENDED! Doesn't hide secrets while printing logs.
|
||||
|
@ -306,6 +307,15 @@ act -e pull-request.json
|
|||
|
||||
Act will properly provide `github.head_ref` and `github.base_ref` to the action as expected.
|
||||
|
||||
# GitHub Enterprise
|
||||
|
||||
Act supports using and authenticating against private GitHub Enterprise servers.
|
||||
To use your custom GHE server, set the CLI flag `--github-instance` to your hostname (e.g. `github.company.com`).
|
||||
|
||||
Please note that if your GHE server requires authentication, we will use the secret provided via `GITHUB_TOKEN`.
|
||||
|
||||
Please also see the [official documentation for GitHub actions on GHE](https://docs.github.com/en/enterprise-server@3.0/admin/github-actions/about-using-actions-in-your-enterprise) for more information on how to use actions.
|
||||
|
||||
# Support
|
||||
|
||||
Need help? Ask on [Gitter](https://gitter.im/nektos/act)!
|
||||
|
|
|
@ -29,6 +29,7 @@ type Input struct {
|
|||
containerArchitecture string
|
||||
noWorkflowRecurse bool
|
||||
useGitIgnore bool
|
||||
githubInstance string
|
||||
}
|
||||
|
||||
func (i *Input) resolve(path string) string {
|
||||
|
|
|
@ -61,6 +61,7 @@ func Execute(ctx context.Context, version string) {
|
|||
rootCmd.PersistentFlags().BoolVarP(&input.insecureSecrets, "insecure-secrets", "", false, "NOT RECOMMENDED! Doesn't hide secrets while printing logs.")
|
||||
rootCmd.PersistentFlags().StringVarP(&input.envfile, "env-file", "", ".env", "environment file to read and use as env in the containers")
|
||||
rootCmd.PersistentFlags().StringVarP(&input.containerArchitecture, "container-architecture", "", "", "Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.")
|
||||
rootCmd.PersistentFlags().StringVarP(&input.githubInstance, "github-instance", "", "github.com", "GitHub instance to use. Don't use this if you are not using GitHub Enterprise Server.")
|
||||
rootCmd.SetArgs(args())
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
|
@ -254,6 +255,7 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
|
|||
UsernsMode: input.usernsMode,
|
||||
ContainerArchitecture: input.containerArchitecture,
|
||||
UseGitIgnore: input.useGitIgnore,
|
||||
GitHubInstance: input.githubInstance,
|
||||
}
|
||||
r, err := runner.New(config)
|
||||
if err != nil {
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
git "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/go-ini/ini"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -142,12 +143,12 @@ func findGitPrettyRef(head, root, sub string) (string, error) {
|
|||
}
|
||||
|
||||
// FindGithubRepo get the repo
|
||||
func FindGithubRepo(file string) (string, error) {
|
||||
func FindGithubRepo(file string, githubInstance string) (string, error) {
|
||||
url, err := findGitRemoteURL(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, slug, err := findGitSlug(url)
|
||||
_, slug, err := findGitSlug(url, githubInstance)
|
||||
return slug, err
|
||||
}
|
||||
|
||||
|
@ -174,7 +175,7 @@ func findGitRemoteURL(file string) (string, error) {
|
|||
return url, nil
|
||||
}
|
||||
|
||||
func findGitSlug(url string) (string, string, error) {
|
||||
func findGitSlug(url string, githubInstance string) (string, string, error) {
|
||||
if matches := codeCommitHTTPRegex.FindStringSubmatch(url); matches != nil {
|
||||
return "CodeCommit", matches[2], nil
|
||||
} else if matches := codeCommitSSHRegex.FindStringSubmatch(url); matches != nil {
|
||||
|
@ -183,6 +184,14 @@ func findGitSlug(url string) (string, string, error) {
|
|||
return "GitHub", fmt.Sprintf("%s/%s", matches[1], matches[2]), nil
|
||||
} else if matches := githubSSHRegex.FindStringSubmatch(url); matches != nil {
|
||||
return "GitHub", fmt.Sprintf("%s/%s", matches[1], matches[2]), nil
|
||||
} else if githubInstance != "github.com" {
|
||||
gheHTTPRegex := regexp.MustCompile(fmt.Sprintf(`^https?://%s/(.+)/(.+?)(?:.git)?$`, githubInstance))
|
||||
gheSSHRegex := regexp.MustCompile(fmt.Sprintf(`%s[:/](.+)/(.+).git$`, githubInstance))
|
||||
if matches := gheHTTPRegex.FindStringSubmatch(url); matches != nil {
|
||||
return "GitHubEnterprise", fmt.Sprintf("%s/%s", matches[1], matches[2]), nil
|
||||
} else if matches := gheSSHRegex.FindStringSubmatch(url); matches != nil {
|
||||
return "GitHubEnterprise", fmt.Sprintf("%s/%s", matches[1], matches[2]), nil
|
||||
}
|
||||
}
|
||||
return "", url, nil
|
||||
}
|
||||
|
@ -218,9 +227,10 @@ func findGitDirectory(fromFile string) (string, error) {
|
|||
|
||||
// NewGitCloneExecutorInput the input for the NewGitCloneExecutor
|
||||
type NewGitCloneExecutorInput struct {
|
||||
URL string
|
||||
Ref string
|
||||
Dir string
|
||||
URL string
|
||||
Ref string
|
||||
Dir string
|
||||
Token string
|
||||
}
|
||||
|
||||
// CloneIfRequired ...
|
||||
|
@ -237,10 +247,24 @@ func CloneIfRequired(ctx context.Context, refName plumbing.ReferenceName, input
|
|||
progressWriter = os.Stdout
|
||||
}
|
||||
|
||||
r, err = git.PlainCloneContext(ctx, input.Dir, false, &git.CloneOptions{
|
||||
URL: input.URL,
|
||||
Progress: progressWriter,
|
||||
})
|
||||
var cloneOptions git.CloneOptions
|
||||
if input.Token != "" {
|
||||
cloneOptions = git.CloneOptions{
|
||||
URL: input.URL,
|
||||
Progress: progressWriter,
|
||||
Auth: &http.BasicAuth{
|
||||
Username: "token",
|
||||
Password: input.Token,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
cloneOptions = git.CloneOptions{
|
||||
URL: input.URL,
|
||||
Progress: progressWriter,
|
||||
}
|
||||
}
|
||||
|
||||
r, err = git.PlainCloneContext(ctx, input.Dir, false, &cloneOptions)
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to clone %v %s: %v", input.URL, refName, err)
|
||||
return nil, err
|
||||
|
|
|
@ -34,7 +34,7 @@ func TestFindGitSlug(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tt := range slugTests {
|
||||
provider, slug, err := findGitSlug(tt.url)
|
||||
provider, slug, err := findGitSlug(tt.url, "github.com")
|
||||
|
||||
assert.NoError(err)
|
||||
assert.Equal(tt.provider, provider)
|
||||
|
|
|
@ -503,7 +503,7 @@ func (rc *RunContext) getGithubContext() *githubContext {
|
|||
}
|
||||
|
||||
repoPath := rc.Config.Workdir
|
||||
repo, err := common.FindGithubRepo(repoPath)
|
||||
repo, err := common.FindGithubRepo(repoPath, rc.Config.GitHubInstance)
|
||||
if err != nil {
|
||||
log.Warningf("unable to get git repo: %v", err)
|
||||
} else {
|
||||
|
@ -644,6 +644,11 @@ func (rc *RunContext) withGithubEnv(env map[string]string) map[string]string {
|
|||
env["GITHUB_SERVER_URL"] = "https://github.com"
|
||||
env["GITHUB_API_URL"] = "https://api.github.com"
|
||||
env["GITHUB_GRAPHQL_URL"] = "https://api.github.com/graphql"
|
||||
if rc.Config.GitHubInstance != "github.com" {
|
||||
env["GITHUB_SERVER_URL"] = fmt.Sprintf("https://%s", rc.Config.GitHubInstance)
|
||||
env["GITHUB_API_URL"] = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance)
|
||||
env["GITHUB_GRAPHQL_URL"] = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance)
|
||||
}
|
||||
|
||||
job := rc.Run.Job()
|
||||
if job.RunsOn() != nil {
|
||||
|
|
|
@ -38,6 +38,7 @@ type Config struct {
|
|||
UsernsMode string // user namespace to use
|
||||
ContainerArchitecture string // Desired OS/architecture platform for running containers
|
||||
UseGitIgnore bool // controls if paths in .gitignore should not be copied into container, default true
|
||||
GitHubInstance string // GitHub instance to use, default "github.com"
|
||||
}
|
||||
|
||||
// Resolves the equivalent host path inside the container
|
||||
|
|
|
@ -56,6 +56,7 @@ func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo, sec
|
|||
ReuseContainers: false,
|
||||
ContainerArchitecture: tjfi.containerArchitecture,
|
||||
Secrets: secrets,
|
||||
GitHubInstance: "github.com",
|
||||
}
|
||||
|
||||
runner, err := New(runnerConfig)
|
||||
|
|
|
@ -70,7 +70,11 @@ func (sc *StepContext) Executor() common.Executor {
|
|||
if remoteAction == nil {
|
||||
return common.NewErrorExecutor(formatError(step.Uses))
|
||||
}
|
||||
if remoteAction.IsCheckout() && rc.getGithubContext().isLocalCheckout(step) {
|
||||
|
||||
remoteAction.URL = rc.Config.GitHubInstance
|
||||
|
||||
github := rc.getGithubContext()
|
||||
if remoteAction.IsCheckout() && github.isLocalCheckout(step) {
|
||||
return func(ctx context.Context) error {
|
||||
common.Logger(ctx).Debugf("Skipping actions/checkout")
|
||||
return nil
|
||||
|
@ -80,9 +84,10 @@ func (sc *StepContext) Executor() common.Executor {
|
|||
actionDir := fmt.Sprintf("%s/%s", rc.ActionCacheDir(), strings.ReplaceAll(step.Uses, "/", "-"))
|
||||
return common.NewPipelineExecutor(
|
||||
common.NewGitCloneExecutor(common.NewGitCloneExecutorInput{
|
||||
URL: remoteAction.CloneURL(),
|
||||
Ref: remoteAction.Ref,
|
||||
Dir: actionDir,
|
||||
URL: remoteAction.CloneURL(),
|
||||
Ref: remoteAction.Ref,
|
||||
Dir: actionDir,
|
||||
Token: github.Token,
|
||||
}),
|
||||
sc.setupAction(actionDir, remoteAction.Path),
|
||||
sc.runAction(actionDir, remoteAction.Path),
|
||||
|
@ -568,6 +573,7 @@ func (sc *StepContext) runAction(actionDir string, actionPath string) common.Exe
|
|||
}
|
||||
|
||||
type remoteAction struct {
|
||||
URL string
|
||||
Org string
|
||||
Repo string
|
||||
Path string
|
||||
|
@ -575,7 +581,7 @@ type remoteAction struct {
|
|||
}
|
||||
|
||||
func (ra *remoteAction) CloneURL() string {
|
||||
return fmt.Sprintf("https://github.com/%s/%s", ra.Org, ra.Repo)
|
||||
return fmt.Sprintf("https://%s/%s/%s", ra.URL, ra.Org, ra.Repo)
|
||||
}
|
||||
|
||||
func (ra *remoteAction) IsCheckout() bool {
|
||||
|
@ -601,6 +607,7 @@ func newRemoteAction(action string) *remoteAction {
|
|||
Repo: matches[2],
|
||||
Path: matches[4],
|
||||
Ref: matches[6],
|
||||
URL: "github.com",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue