Skip to content

Commit

Permalink
resolve and download actions via launch service (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristopherHX authored May 4, 2023
1 parent 0be62de commit 454b336
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 21 deletions.
121 changes: 100 additions & 21 deletions actionsdotnetactcompat/act_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/ChristopherHX/github-act-runner/actionsrunner"
rcommon "github.com/ChristopherHX/github-act-runner/common"
"github.com/ChristopherHX/github-act-runner/protocol"
"github.com/ChristopherHX/github-act-runner/protocol/launch"
"github.com/ChristopherHX/github-act-runner/protocol/logger"
"github.com/google/uuid"
"github.com/nektos/act/pkg/common"
Expand Down Expand Up @@ -326,28 +327,11 @@ func ExecWorker(rqt *protocol.AgentJobRequestMessage, wc actionsrunner.WorkerCon
return err
}
for _, v := range actionDownloadInfo.Actions {
cachedTar := filepath.Join(ngcei.Dir, "..", actionurl[0]+"."+actionurl[1]+"."+v.ResolvedSha+".tar")
var tarstream io.Reader
if fr, err := os.Open(cachedTar); err == nil {
tarstream = fr
} else {
req, _ := http.NewRequestWithContext(ctx, "GET", v.TarballUrl, nil)
rsp, err := vssConnection.Client.Do(req)
if err != nil {
return err
}
defer rsp.Body.Close()
if len(v.ResolvedSha) > 0 {
fo, _ := os.OpenFile(cachedTar, os.O_TRUNC|os.O_CREATE, 0777)
io.Copy(fo, rsp.Body)
tarstream = fo
fo.Seek(0, 0)
} else {
tarstream = rsp.Body
}
token := runnerConfig.Token
if v.Authentication != nil && v.Authentication.Token != "" {
token = v.Authentication.Token
}
gzr, _ := gzip.NewReader(tarstream)
err = container.ExtractTar(gzr, ngcei.Dir)
err := downloadAndExtractAction(ctx, ngcei.Dir, actionurl[0], actionurl[1], v.ResolvedSha, v.TarballUrl, token, vssConnection.Client)
if err != nil {
return err
}
Expand All @@ -357,6 +341,43 @@ func ExecWorker(rqt *protocol.AgentJobRequestMessage, wc actionsrunner.WorkerCon
}
if strings.EqualFold(rqt.MessageType, "RunnerJobRequest") {
runnerConfig.DownloadAction = nil
launchEndpoint, hasLaunchEndpoint := rqt.Variables["system.github.launch_endpoint"]
if hasLaunchEndpoint && launchEndpoint.Value != "" {
runnerConfig.DownloadAction = func(ngcei git.NewGitCloneExecutorInput) common.Executor {
return func(ctx context.Context) error {
actionList := &launch.ActionReferenceRequestList{}
actionurl := strings.Split(ngcei.URL, "/")
actionurl = actionurl[len(actionurl)-2:]
actionList.Actions = []launch.ActionReferenceRequest{
{Action: strings.Join(actionurl, "/"), Version: ngcei.Ref},
}
actionDownloadInfo := &launch.ActionDownloadInfoResponseCollection{}
urlBuilder := protocol.VssConnection{TenantURL: launchEndpoint.Value}
url, err := urlBuilder.BuildURL("actions/build/{planId}/jobs/{jobId}/runnerresolve/actions", map[string]string{
"jobId": rqt.JobID,
"planId": rqt.Plan.PlanID,
}, nil)
if err != nil {
return err
}
err = vssConnection.RequestWithContext2(ctx, "POST", url, "", actionList, actionDownloadInfo)
if err != nil {
return err
}
for _, v := range actionDownloadInfo.Actions {
token := runnerConfig.Token
if v.Authentication != nil && v.Authentication.Token != "" {
token = v.Authentication.Token
}
err := downloadAndExtractAction(ctx, ngcei.Dir, actionurl[0], actionurl[1], v.ResolvedSha, v.TarUrl, token, vssConnection.Client)
if err != nil {
return err
}
}
return nil
}
}
}
}
if viaGit, hasViaGit := rcommon.LookupEnvBool("GITHUB_ACT_RUNNER_DOWNLOAD_ACTIONS_VIA_GIT"); hasViaGit && viaGit {
runnerConfig.DownloadAction = nil
Expand Down Expand Up @@ -528,3 +549,61 @@ func ExecWorker(rqt *protocol.AgentJobRequestMessage, wc actionsrunner.WorkerCon
}
finishJob2(jobStatus, outputMap)
}

func downloadAndExtractAction(ctx context.Context, target string, owner string, name string, resolvedSha string, tarURL string, token string, httpClient *http.Client) (reterr error) {
cachedTar := filepath.Join(target, "..", owner+"."+name+"."+resolvedSha+".tar")
defer func() {
if reterr != nil {
os.Remove(cachedTar)
}
}()
var tarstream io.Reader
if fr, err := os.Open(cachedTar); err == nil {
tarstream = fr
defer fr.Close()
} else {
req, err := http.NewRequestWithContext(ctx, "GET", tarURL, nil)
if err != nil {
return err
}
if token != "" {
req.Header.Add("Authorization", "token "+token)
}
rsp, err := httpClient.Do(req)
if err != nil {
return err
}
defer rsp.Body.Close()
if len(resolvedSha) == len("0000000000000000000000000000000000000000") {
fo, err := os.OpenFile(cachedTar, os.O_TRUNC|os.O_CREATE, 0777)
if err != nil {
return err
}
defer fo.Close()
len, err := io.Copy(fo, rsp.Body)
if err != nil {
return err
}
if rsp.ContentLength >= 0 && len != rsp.ContentLength {
return fmt.Errorf("failed to download tar expected %v, but copied %v", rsp.ContentLength, len)
}
tarstream = fo
fo.Seek(0, 0)
} else {
tarstream = rsp.Body
}
}
if err := extractTarGz(tarstream, target); err != nil {
return err
}
return nil
}

func extractTarGz(reader io.Reader, dir string) error {
gzr, err := gzip.NewReader(reader)
if err != nil {
return err
}
defer gzr.Close()
return container.ExtractTar(gzr, dir)
}
30 changes: 30 additions & 0 deletions protocol/launch/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package launch

type ActionReferenceRequest struct {
Action string `json:"action,omitempty"`
Version string `json:"version,omitempty"`
Path string `json:"path,omitempty"`
}

type ActionReferenceRequestList struct {
Actions []ActionReferenceRequest `json:"actions,omitempty"`
}

type ActionDownloadInfoResponse struct {
Authentication *ActionDownloadAuthenticationResponse `json:"authentication,omitempty"`
Name string `json:"name,omitempty"`
ResolvedName string `json:"resolved_name,omitempty"`
ResolvedSha string `json:"resolved_sha,omitempty"`
TarUrl string `json:"tar_url,omitempty"`
Version string `json:"version,omitempty"`
ZipUrl string `json:"zip_url,omitempty"`
}

type ActionDownloadAuthenticationResponse struct {
ExpiresAt string `json:"expires_at,omitempty"`
Token string `json:"token,omitempty"`
}

type ActionDownloadInfoResponseCollection struct {
Actions map[string]ActionDownloadInfoResponse `json:"actions,omitempty"`
}

0 comments on commit 454b336

Please sign in to comment.