diff --git a/cmd/agola/cmd/projectcreate.go b/cmd/agola/cmd/projectcreate.go index 9a5f1d2..a2b73cf 100644 --- a/cmd/agola/cmd/projectcreate.go +++ b/cmd/agola/cmd/projectcreate.go @@ -41,6 +41,7 @@ type projectCreateOptions struct { remoteSourceName string skipSSHHostKeyCheck bool visibility string + passVarsToForkedPR bool } var projectCreateOpts projectCreateOptions @@ -54,6 +55,7 @@ func init() { flags.BoolVarP(&projectCreateOpts.skipSSHHostKeyCheck, "skip-ssh-host-key-check", "s", false, "skip ssh host key check") flags.StringVar(&projectCreateOpts.parentPath, "parent", "", `parent project group path (i.e "org/org01" for root project group in org01, "user/user01/group01/subgroub01") or project group id where the project should be created`) flags.StringVar(&projectCreateOpts.visibility, "visibility", "public", `project visibility (public or private)`) + flags.BoolVar(&projectCreateOpts.passVarsToForkedPR, "pass-vars-to-forked-pr", false, `pass variables to run even if triggered by PR from forked repo`) if err := cmdProjectCreate.MarkFlagRequired("name"); err != nil { log.Fatal(err) @@ -96,6 +98,7 @@ func projectCreate(cmd *cobra.Command, args []string) error { RepoPath: projectCreateOpts.repoPath, RemoteSourceName: projectCreateOpts.remoteSourceName, SkipSSHHostKeyCheck: projectCreateOpts.skipSSHHostKeyCheck, + PassVarsToForkedPR: projectCreateOpts.passVarsToForkedPR, } log.Infof("creating project") diff --git a/cmd/agola/cmd/projectupdate.go b/cmd/agola/cmd/projectupdate.go index 959bb7b..aafe1e1 100644 --- a/cmd/agola/cmd/projectupdate.go +++ b/cmd/agola/cmd/projectupdate.go @@ -37,9 +37,10 @@ var cmdProjectUpdate = &cobra.Command{ type projectUpdateOptions struct { ref string - name string - parentPath string - visibility string + name string + parentPath string + visibility string + passVarsToForkedPR bool } var projectUpdateOpts projectUpdateOptions @@ -51,6 +52,7 @@ func init() { flags.StringVarP(&projectUpdateOpts.name, "name", "n", "", "project name") flags.StringVar(&projectUpdateOpts.parentPath, "parent", "", `parent project group path (i.e "org/org01" for root project group in org01, "user/user01/group01/subgroub01") or project group id where the project should be moved`) flags.StringVar(&projectUpdateOpts.visibility, "visibility", "public", `project visibility (public or private)`) + flags.BoolVar(&projectUpdateOpts.passVarsToForkedPR, "pass-vars-to-forked-pr", false, `pass variables to run even if triggered by PR from forked repo`) if err := cmdProjectUpdate.MarkFlagRequired("ref"); err != nil { log.Fatal(err) @@ -78,6 +80,9 @@ func projectUpdate(cmd *cobra.Command, args []string) error { visibility := gwapitypes.Visibility(projectUpdateOpts.visibility) req.Visibility = &visibility } + if flags.Changed("pass-vars-to-forked-pr") { + req.PassVarsToForkedPR = &projectUpdateOpts.passVarsToForkedPR + } log.Infof("updating project") project, _, err := gwclient.UpdateProject(context.TODO(), projectUpdateOpts.ref, req) diff --git a/internal/gitsources/gitea/parse.go b/internal/gitsources/gitea/parse.go index 34e779c..9814b9d 100644 --- a/internal/gitsources/gitea/parse.go +++ b/internal/gitsources/gitea/parse.go @@ -156,6 +156,10 @@ func webhookDataFromPullRequest(hook *pullRequestHook) *types.WebhookData { if sender == "" { sender = hook.Sender.Login } + prFromSameRepo := false + if hook.PullRequest.Base.Repo.URL == hook.PullRequest.Head.Repo.URL { + prFromSameRepo = true + } whd := &types.WebhookData{ Event: types.WebhookEventPullRequest, CommitSHA: hook.PullRequest.Head.Sha, @@ -166,11 +170,13 @@ func webhookDataFromPullRequest(hook *pullRequestHook) *types.WebhookData { Sender: sender, PullRequestID: strconv.FormatInt(hook.PullRequest.ID, 10), PullRequestLink: hook.PullRequest.URL, + PRFromSameRepo: prFromSameRepo, Repo: types.WebhookDataRepo{ Path: path.Join(hook.Repo.Owner.Username, hook.Repo.Name), WebURL: hook.Repo.URL, }, } + return whd } diff --git a/internal/gitsources/github/parse.go b/internal/gitsources/github/parse.go index ad53717..83c052f 100644 --- a/internal/gitsources/github/parse.go +++ b/internal/gitsources/github/parse.go @@ -116,6 +116,10 @@ func webhookDataFromPullRequest(hook *github.PullRequestEvent) (*types.WebhookDa if sender == nil { sender = hook.Sender.Login } + prFromSameRepo := false + if hook.PullRequest.Base.Repo.URL == hook.PullRequest.Head.Repo.URL { + prFromSameRepo = true + } whd := &types.WebhookData{ Event: types.WebhookEventPullRequest, @@ -127,6 +131,7 @@ func webhookDataFromPullRequest(hook *github.PullRequestEvent) (*types.WebhookDa Sender: *sender, PullRequestID: strconv.Itoa(*hook.PullRequest.Number), PullRequestLink: *hook.PullRequest.HTMLURL, + PRFromSameRepo: prFromSameRepo, Repo: types.WebhookDataRepo{ Path: path.Join(*hook.Repo.Owner.Login, *hook.Repo.Name), diff --git a/internal/gitsources/gitlab/parse.go b/internal/gitsources/gitlab/parse.go index daedb21..c43e4d9 100644 --- a/internal/gitsources/gitlab/parse.go +++ b/internal/gitsources/gitlab/parse.go @@ -140,7 +140,12 @@ func webhookDataFromPullRequest(hook *pullRequestHook) *types.WebhookData { if sender == "" { sender = hook.User.Username } - build := &types.WebhookData{ + prFromSameRepo := false + if hook.ObjectAttributes.Source.URL == hook.ObjectAttributes.Target.URL { + prFromSameRepo = true + } + + whd := &types.WebhookData{ Event: types.WebhookEventPullRequest, CommitSHA: hook.ObjectAttributes.LastCommit.ID, SSHURL: hook.Project.SSHURL, @@ -150,11 +155,12 @@ func webhookDataFromPullRequest(hook *pullRequestHook) *types.WebhookData { Sender: sender, PullRequestID: strconv.Itoa(hook.ObjectAttributes.Iid), PullRequestLink: hook.ObjectAttributes.URL, + PRFromSameRepo: prFromSameRepo, Repo: types.WebhookDataRepo{ Path: hook.Project.PathWithNamespace, WebURL: hook.Project.WebURL, }, } - return build + return whd } diff --git a/internal/services/gateway/action/project.go b/internal/services/gateway/action/project.go index 5c035e8..3f62d81 100644 --- a/internal/services/gateway/action/project.go +++ b/internal/services/gateway/action/project.go @@ -57,6 +57,7 @@ type CreateProjectRequest struct { RemoteSourceName string RepoPath string SkipSSHHostKeyCheck bool + PassVarsToForkedPR bool } func (h *ActionHandler) CreateProject(ctx context.Context, req *CreateProjectRequest) (*csapitypes.Project, error) { @@ -150,6 +151,7 @@ func (h *ActionHandler) CreateProject(ctx context.Context, req *CreateProjectReq RepositoryPath: req.RepoPath, SkipSSHHostKeyCheck: req.SkipSSHHostKeyCheck, SSHPrivateKey: string(privateKey), + PassVarsToForkedPR: req.PassVarsToForkedPR, } h.log.Infof("creating project") @@ -183,7 +185,8 @@ type UpdateProjectRequest struct { Name *string ParentRef *string - Visibility *cstypes.Visibility + Visibility *cstypes.Visibility + PassVarsToForkedPR *bool } func (h *ActionHandler) UpdateProject(ctx context.Context, projectRef string, req *UpdateProjectRequest) (*csapitypes.Project, error) { @@ -209,6 +212,9 @@ func (h *ActionHandler) UpdateProject(ctx context.Context, projectRef string, re if req.Visibility != nil { p.Visibility = *req.Visibility } + if req.PassVarsToForkedPR != nil { + p.PassVarsToForkedPR = *req.PassVarsToForkedPR + } h.log.Infof("updating project") rp, resp, err := h.configstoreClient.UpdateProject(ctx, p.ID, p.Project) diff --git a/internal/services/gateway/action/run.go b/internal/services/gateway/action/run.go index ec7d662..801469b 100644 --- a/internal/services/gateway/action/run.go +++ b/internal/services/gateway/action/run.go @@ -338,6 +338,7 @@ type CreateRunRequest struct { Tag string Ref string PullRequestID string + PRFromSameRepo bool SSHPrivKey string SSHHostKey string SkipSSHHostKeyCheck bool @@ -429,10 +430,12 @@ func (h *ActionHandler) CreateRuns(ctx context.Context, req *CreateRunRequest) e var variables map[string]string if req.RunType == itypes.RunTypeProject { - var err error - variables, err = h.genRunVariables(ctx, req) - if err != nil { - return err + if req.RefType != itypes.RunRefTypePullRequest || req.PRFromSameRepo || req.Project.PassVarsToForkedPR { + var err error + variables, err = h.genRunVariables(ctx, req) + if err != nil { + return err + } } } else { variables = req.Variables diff --git a/internal/services/gateway/api/project.go b/internal/services/gateway/api/project.go index f68eef9..9e8d4f4 100644 --- a/internal/services/gateway/api/project.go +++ b/internal/services/gateway/api/project.go @@ -55,6 +55,7 @@ func (h *CreateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) RepoPath: req.RepoPath, RemoteSourceName: req.RemoteSourceName, SkipSSHHostKeyCheck: req.SkipSSHHostKeyCheck, + PassVarsToForkedPR: req.PassVarsToForkedPR, } project, err := h.ah.CreateProject(ctx, areq) @@ -101,9 +102,10 @@ func (h *UpdateProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) } areq := &action.UpdateProjectRequest{ - Name: req.Name, - ParentRef: req.ParentRef, - Visibility: visibility, + Name: req.Name, + ParentRef: req.ParentRef, + Visibility: visibility, + PassVarsToForkedPR: req.PassVarsToForkedPR, } project, err := h.ah.UpdateProject(ctx, projectRef, areq) if httpError(w, err) { @@ -235,12 +237,13 @@ func (h *ProjectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func createProjectResponse(r *csapitypes.Project) *gwapitypes.ProjectResponse { res := &gwapitypes.ProjectResponse{ - ID: r.ID, - Name: r.Name, - Path: r.Path, - ParentPath: r.ParentPath, - Visibility: gwapitypes.Visibility(r.Visibility), - GlobalVisibility: string(r.GlobalVisibility), + ID: r.ID, + Name: r.Name, + Path: r.Path, + ParentPath: r.ParentPath, + Visibility: gwapitypes.Visibility(r.Visibility), + GlobalVisibility: string(r.GlobalVisibility), + PassVarsToForkedPR: r.PassVarsToForkedPR, } return res diff --git a/internal/services/gateway/api/webhook.go b/internal/services/gateway/api/webhook.go index 05a5bb9..ba916a9 100644 --- a/internal/services/gateway/api/webhook.go +++ b/internal/services/gateway/api/webhook.go @@ -127,6 +127,7 @@ func (h *webhooksHandler) handleWebhook(r *http.Request) error { Branch: webhookData.Branch, Tag: webhookData.Tag, PullRequestID: webhookData.PullRequestID, + PRFromSameRepo: webhookData.PRFromSameRepo, Ref: webhookData.Ref, SSHPrivKey: sshPrivKey, SSHHostKey: sshHostKey, diff --git a/internal/services/types/webhook.go b/internal/services/types/webhook.go index 1655685..bc00594 100644 --- a/internal/services/types/webhook.go +++ b/internal/services/types/webhook.go @@ -43,6 +43,7 @@ type WebhookData struct { // use a string if on some platform (current or future) some PRs id will not be numbers PullRequestID string `json:"pull_request_id,omitempty"` PullRequestLink string `json:"link,omitempty"` // Link to pull request + PRFromSameRepo bool `json:"pr_from_same_repo,omitempty"` Repo WebhookDataRepo `json:"repo,omitempty"` } diff --git a/services/configstore/types/types.go b/services/configstore/types/types.go index 15a840f..dd1c937 100644 --- a/services/configstore/types/types.go +++ b/services/configstore/types/types.go @@ -307,6 +307,8 @@ type Project struct { // Webhooksecret is the secret passed to git sources that support a // secret/token for signing or verifying the webhook payload WebhookSecret string `json:"webhook_secret,omitempty"` + + PassVarsToForkedPR bool `json:"pass_vars_to_forked_pr,omitempty"` } type SecretType string diff --git a/services/gateway/api/types/project.go b/services/gateway/api/types/project.go index 0dcdc8a..d940397 100644 --- a/services/gateway/api/types/project.go +++ b/services/gateway/api/types/project.go @@ -21,21 +21,24 @@ type CreateProjectRequest struct { RepoPath string `json:"repo_path,omitempty"` RemoteSourceName string `json:"remote_source_name,omitempty"` SkipSSHHostKeyCheck bool `json:"skip_ssh_host_key_check,omitempty"` + PassVarsToForkedPR bool `json:"pass_vars_to_forked_pr,omitempty"` } type UpdateProjectRequest struct { - Name *string `json:"name,omitempty"` - ParentRef *string `json:"parent_ref,omitempty"` - Visibility *Visibility `json:"visibility,omitempty"` + Name *string `json:"name,omitempty"` + ParentRef *string `json:"parent_ref,omitempty"` + Visibility *Visibility `json:"visibility,omitempty"` + PassVarsToForkedPR *bool `json:"pass_vars_to_forked_pr,omitempty"` } type ProjectResponse struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Path string `json:"path,omitempty"` - ParentPath string `json:"parent_path,omitempty"` - Visibility Visibility `json:"visibility,omitempty"` - GlobalVisibility string `json:"global_visibility,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + ParentPath string `json:"parent_path,omitempty"` + Visibility Visibility `json:"visibility,omitempty"` + GlobalVisibility string `json:"global_visibility,omitempty"` + PassVarsToForkedPR bool `json:"pass_vars_to_forked_pr,omitempty"` } type ProjectCreateRunRequest struct { diff --git a/tests/setup_test.go b/tests/setup_test.go index 2017aac..7b64d55 100644 --- a/tests/setup_test.go +++ b/tests/setup_test.go @@ -49,6 +49,7 @@ import ( "gopkg.in/src-d/go-billy.v4/osfs" "gopkg.in/src-d/go-git.v4" gitconfig "gopkg.in/src-d/go-git.v4/config" + "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/cache" "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/transport/http" @@ -58,6 +59,8 @@ import ( const ( giteaUser01 = "user01" + giteaUser02 = "user02" + agolaUser01 = "user01" ) @@ -447,9 +450,64 @@ func TestCreateProject(t *testing.T) { createProject(ctx, t, giteaClient, gwClient) } +func TestUpdateProject(t *testing.T) { + + tests := []struct { + name string + passVarsToForkedPR bool + expected_pre bool + expected_post bool + }{ + { + name: "test project update with pass-vars-to-forked-pr true", + passVarsToForkedPR: true, + expected_pre: false, + expected_post: true, + }, + { + name: "test project update with pass-vars-to-forked-pr false", + passVarsToForkedPR: false, + expected_pre: false, + expected_post: false, + }, + } + + for _, tt := range tests { + dir, err := ioutil.TempDir("", "agola") + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + defer os.RemoveAll(dir) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tetcd, tgitea, c := setup(ctx, t, dir) + defer shutdownGitea(tgitea) + defer shutdownEtcd(tetcd) + + giteaAPIURL := fmt.Sprintf("http://%s:%s", tgitea.HTTPListenAddress, tgitea.HTTPPort) + + giteaToken, token := createLinkedAccount(ctx, t, tgitea, c) + + giteaClient := gitea.NewClient(giteaAPIURL, giteaToken) + gwClient := gwclient.NewClient(c.Gateway.APIExposedURL, token) + + _, project := createProject(ctx, t, giteaClient, gwClient) + if project.PassVarsToForkedPR != tt.expected_pre { + t.Fatalf("expected PassVarsToForkedPR %v, got %v (pre-update)", tt.expected_pre, project.PassVarsToForkedPR) + } + project = updateProject(ctx, t, giteaClient, gwClient, project.ID, tt.passVarsToForkedPR) + if project.PassVarsToForkedPR != tt.expected_post { + t.Fatalf("expected PassVarsToForkedPR %v, got %v (port-update)", tt.expected_post, project.PassVarsToForkedPR) + } + } +} + func createProject(ctx context.Context, t *testing.T, giteaClient *gitea.Client, gwClient *gwclient.Client) (*gitea.Repository, *gwapitypes.ProjectResponse) { giteaRepo, err := giteaClient.CreateRepo(gitea.CreateRepoOption{ - Name: "repo01", + Name: "repo01", + Private: false, }) if err != nil { t.Fatalf("unexpected err: %v", err) @@ -470,7 +528,18 @@ func createProject(ctx context.Context, t *testing.T, giteaClient *gitea.Client, return giteaRepo, project } -func push(t *testing.T, config, cloneURL, remoteToken, message string) { +func updateProject(ctx context.Context, t *testing.T, giteaClient *gitea.Client, gwClient *gwclient.Client, projectRef string, passVarsToForkedPR bool) *gwapitypes.ProjectResponse { + project, _, err := gwClient.UpdateProject(ctx, projectRef, &gwapitypes.UpdateProjectRequest{ + PassVarsToForkedPR: util.BoolP(passVarsToForkedPR), + }) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + return project +} + +func push(t *testing.T, config, cloneURL, remoteToken, message string, pushNewBranch bool) { gitfs := memfs.New() f, err := gitfs.Create(".agola/config.jsonnet") if err != nil { @@ -521,6 +590,52 @@ func push(t *testing.T, config, cloneURL, remoteToken, message string) { t.Fatalf("unexpected err: %v", err) } + if pushNewBranch { + // change worktree and push to a new branch + headRef, err := r.Head() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + ref := plumbing.NewHashReference("refs/heads/new-branch", headRef.Hash()) + err = r.Storer.SetReference(ref) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + f, err = gitfs.Create("file1") + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if _, err = f.Write([]byte("my file content")); err != nil { + t.Fatalf("unexpected err: %v", err) + } + if _, err := wt.Add("file1"); err != nil { + t.Fatalf("unexpected err: %v", err) + } + _, err = wt.Commit("add file1", &git.CommitOptions{ + Author: &object.Signature{ + Name: "user01", + Email: "user01@example.com", + When: time.Now(), + }, + }) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + if err := r.Push(&git.PushOptions{ + RemoteName: "origin", + RefSpecs: []gitconfig.RefSpec{ + gitconfig.RefSpec("refs/heads/new-branch:refs/heads/new-branch"), + }, + Auth: &http.BasicAuth{ + Username: giteaUser01, + Password: remoteToken, + }, + }); err != nil { + t.Fatalf("unexpected err: %v", err) + } + } } func TestPush(t *testing.T) { @@ -686,7 +801,7 @@ func TestPush(t *testing.T) { giteaRepo, project := createProject(ctx, t, giteaClient, gwClient) - push(t, tt.config, giteaRepo.CloneURL, giteaToken, tt.message) + push(t, tt.config, giteaRepo.CloneURL, giteaToken, tt.message, false) _ = testutil.Wait(30*time.Second, func() (bool, error) { runs, _, err := gwClient.GetRuns(ctx, nil, nil, []string{path.Join("/project", project.ID)}, nil, "", 0, false) @@ -1282,3 +1397,286 @@ func TestDirectRunLogs(t *testing.T) { }) } } + +func TestPullRequest(t *testing.T) { + config := ` + { + runs: [ + { + name: 'run01', + tasks: [ + { + name: 'task01', + runtime: { + containers: [ + { + image: 'alpine/git', + }, + ], + }, + environment: { + MYPASSWORD: { from_variable: 'mypassword' }, + }, + steps: [ + { type: 'clone' }, + { type: 'run', command: 'echo -n $MYPASSWORD' }, + ], + }, + ], + when: { + ref: '#refs/pull/\\d+/head#', + }, + }, + ], + } + ` + + tests := []struct { + name string + passVarsToForkedPR bool + prFromSameRepo bool + expected string + }{ + { + name: "test PR from same repowith PassVarsToForkedPR set to false", + passVarsToForkedPR: false, + prFromSameRepo: true, + expected: "mysupersecretpassword", + }, + { + name: "test PR from same repo with PassVarsToForkedPR set to true", + passVarsToForkedPR: true, + prFromSameRepo: true, + expected: "mysupersecretpassword", + }, + { + name: "test PR from forked repo with PassVarsToForkedPR set to false", + passVarsToForkedPR: false, + prFromSameRepo: false, + expected: "", + }, + { + name: "test PR from forked repo with PassVarsToForkedPR set to true", + passVarsToForkedPR: true, + prFromSameRepo: false, + expected: "mysupersecretpassword", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + dir, err := ioutil.TempDir("", "agola") + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + defer os.RemoveAll(dir) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tetcd, tgitea, c := setup(ctx, t, dir) + defer shutdownGitea(tgitea) + defer shutdownEtcd(tetcd) + + giteaAPIURL := fmt.Sprintf("http://%s:%s", tgitea.HTTPListenAddress, tgitea.HTTPPort) + + giteaToken, token := createLinkedAccount(ctx, t, tgitea, c) + + giteaClient := gitea.NewClient(giteaAPIURL, giteaToken) + gwClient := gwclient.NewClient(c.Gateway.APIExposedURL, token) + + giteaRepo, project := createProject(ctx, t, giteaClient, gwClient) + project = updateProject(ctx, t, giteaClient, gwClient, project.ID, tt.passVarsToForkedPR) + + //create project secret + secretData := map[string]string{"mypassword": "mysupersecretpassword"} + sreq := &gwapitypes.CreateSecretRequest{ + Name: "mysecret", + Type: gwapitypes.SecretTypeInternal, + Data: secretData, + } + + secret, _, err := gwClient.CreateProjectSecret(context.TODO(), project.ID, sreq) + if err != nil { + t.Fatal("failed to create project secret: %w", err) + } + + // create project variable + rvalues := []gwapitypes.VariableValueRequest{} + rvalues = append(rvalues, gwapitypes.VariableValueRequest{ + SecretName: secret.Name, + SecretVar: "mypassword", + }) + + vreq := &gwapitypes.CreateVariableRequest{ + Name: "mypassword", + Values: rvalues, + } + + _, _, err = gwClient.CreateProjectVariable(context.TODO(), project.ID, vreq) + if err != nil { + t.Fatal("failed to create project variable: %w", err) + } + + if tt.prFromSameRepo { + // create PR from branch on same repo + push(t, config, giteaRepo.CloneURL, giteaToken, "commit", true) + + prOpts := gitea.CreatePullRequestOption{ + Head: "new-branch", + Base: "master", + Title: "add file1 from new-branch on same repo", + } + _, err = giteaClient.CreatePullRequest(giteaUser01, "repo01", prOpts) + if err != nil { + t.Fatal("failed to create pull request: %w", err) + } + } else { + // create PR from forked repo + push(t, config, giteaRepo.CloneURL, giteaToken, "commit", false) + + userOpts := gitea.CreateUserOption{ + Username: giteaUser02, + Password: "password", + Email: "user02@example.com", + MustChangePassword: util.BoolP(false), + } + _, err := giteaClient.AdminCreateUser(userOpts) + if err != nil { + t.Fatal("failed to create user02: %w", err) + } + + giteaUser02Token, err := giteaClient.CreateAccessToken(giteaUser02, "password", gitea.CreateAccessTokenOption{Name: "token01"}) + if err != nil { + t.Fatalf("failed to create token for user02: %v", err) + } + + giteaUser02Client := gitea.NewClient(giteaAPIURL, giteaUser02Token.Token) + giteaForkedRepo, err := giteaUser02Client.CreateFork(giteaUser01, "repo01", gitea.CreateForkOption{}) + if err != nil { + t.Fatal("failed to fork repo01: %w", err) + } + + gitfs := memfs.New() + r, err := git.Clone(memory.NewStorage(), gitfs, &git.CloneOptions{ + Auth: &http.BasicAuth{ + Username: giteaUser02, + Password: giteaUser02Token.Token, + }, + URL: giteaForkedRepo.CloneURL, + }) + if err != nil { + t.Fatalf("failed to clone forked repo: %v", err) + } + + wt, err := r.Worktree() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + f, err := gitfs.Create("file2") + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if _, err = f.Write([]byte("file2 content")); err != nil { + t.Fatalf("unexpected err: %v", err) + } + if _, err := wt.Add("file2"); err != nil { + t.Fatalf("unexpected err: %v", err) + } + _, err = wt.Commit("commit from user02", &git.CommitOptions{ + Author: &object.Signature{ + Name: giteaUser02, + Email: "user02@example.com", + When: time.Now(), + }, + }) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + if err := r.Push(&git.PushOptions{ + RemoteName: "origin", + RefSpecs: []gitconfig.RefSpec{ + gitconfig.RefSpec("refs/heads/master:refs/heads/master"), + }, + Auth: &http.BasicAuth{ + Username: giteaUser02, + Password: giteaUser02Token.Token, + }, + }); err != nil { + t.Fatalf("unexpected err: %v", err) + } + + prOpts := gitea.CreatePullRequestOption{ + Head: "user02:master", + Base: "master", + Title: "add file1 from master on forked repo", + } + _, err = giteaUser02Client.CreatePullRequest(giteaUser01, "repo01", prOpts) + if err != nil { + t.Fatal("failed to create pull request: %w", err) + } + } + _ = testutil.Wait(30*time.Second, func() (bool, error) { + runs, _, err := gwClient.GetRuns(ctx, nil, nil, []string{path.Join("/project", project.ID)}, nil, "", 0, false) + if err != nil { + return false, nil + } + + if len(runs) == 0 { + return false, nil + } + run := runs[0] + if run.Phase != rstypes.RunPhaseFinished { + return false, nil + } + + return true, nil + }) + + runs, _, err := gwClient.GetRuns(ctx, nil, nil, []string{path.Join("/project", project.ID)}, nil, "", 0, false) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + t.Logf("runs: %s", util.Dump(runs)) + + run, _, err := gwClient.GetRun(ctx, runs[0].ID) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + var task *gwapitypes.RunResponseTask + for _, t := range run.Tasks { + if t.Name == "task01" { + task = t + break + } + } + + if len(runs) > 0 { + run := runs[0] + if run.Phase != rstypes.RunPhaseFinished { + t.Fatalf("expected run phase %q, got %q", rstypes.RunPhaseFinished, run.Phase) + } + if run.Result != rstypes.RunResultSuccess { + t.Fatalf("expected run result %q, got %q", rstypes.RunResultSuccess, run.Result) + } + resp, err := gwClient.GetLogs(ctx, run.ID, task.ID, false, 1, false) + if err != nil { + t.Fatalf("failed to get log: %v", err) + } + defer resp.Body.Close() + + mypassword, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("failed to read log: %v", err) + } + if tt.expected != string(mypassword) { + t.Fatalf("expected mypassword %q, got %q", tt.expected, string(mypassword)) + } + } + }) + } +}