Skip to content

Commit 87f5694

Browse files
authored
generator release-v2: add feature of automatically create release pull request from release request (Azure#18455)
* Auto SDK Release * add comment * goimports -w * format * resolve comment * Hi * recover * Use the 'attention' label to determine that the PR has been generated * attention label replace to 'PR ready' label * execute .ps1 * add issue 'PR ready' label * add issue lable * fix * resolve comment * Handle multi-package releasee * fix * fix * remove isPrReady func * fix * issue label * rename to Invoke-MgmtTestGen.ps1 * fix * git push msg * fix GetRemoteUserName func * fix get pullrequest html_url * fix * fix html_url
1 parent 8ebd724 commit 87f5694

File tree

11 files changed

+723
-40
lines changed

11 files changed

+723
-40
lines changed

eng/tools/generator/cmd/issue/issueCmd.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,22 +179,43 @@ func (c *commandContext) listSpecifiedIssues(ids []int) ([]*github.Issue, error)
179179
return issues, nil
180180
}
181181

182-
func issueHasLabel(issue *github.Issue, label string) bool {
182+
func issueHasLabel(issue *github.Issue, label IssueLabel) bool {
183183
if issue == nil {
184184
return false
185185
}
186186

187187
for _, l := range issue.Labels {
188-
if l.GetName() == label {
188+
if IssueLabel(l.GetName()) == label {
189189
return true
190190
}
191191
}
192192

193193
return false
194194
}
195195

196+
type IssueLabel string
197+
198+
const (
199+
GoLabel IssueLabel = "GO"
200+
AutoLinkLabel IssueLabel = "auto-link"
201+
PRreadyLabel IssueLabel = "PRready"
202+
InconsistentTagLabel IssueLabel = "Inconsistent tag"
203+
)
204+
196205
func isGoReleaseRequest(issue *github.Issue) bool {
197-
return issueHasLabel(issue, "Go")
206+
return issueHasLabel(issue, GoLabel)
207+
}
208+
209+
func isAutoLink(issue *github.Issue) bool {
210+
return issueHasLabel(issue, AutoLinkLabel)
211+
}
212+
213+
func isPRReady(issue *github.Issue) bool {
214+
return issueHasLabel(issue, PRreadyLabel)
215+
}
216+
217+
func isInconsistentTag(issue *github.Issue) bool {
218+
return issueHasLabel(issue, InconsistentTagLabel)
198219
}
199220

200221
func (c *commandContext) parseIssues(issues []*github.Issue) ([]request.Request, error) {
@@ -204,6 +225,18 @@ func (c *commandContext) parseIssues(issues []*github.Issue) ([]request.Request,
204225
if issue == nil {
205226
continue
206227
}
228+
if isPRReady(issue) {
229+
continue
230+
}
231+
if !isAutoLink(issue) {
232+
continue
233+
}
234+
if isInconsistentTag(issue) {
235+
log.Printf("[ERROR] %s Readme tag is inconsistent with default tag\n", issue.GetHTMLURL())
236+
errResult = multierror.Append(errResult, fmt.Errorf("%s: readme tag is inconsistent with default tag", issue.GetHTMLURL()))
237+
continue
238+
}
239+
207240
log.Printf("Parsing issue %s (%s)", issue.GetHTMLURL(), issue.GetTitle())
208241
req, err := request.ParseIssue(c.ctx, c.client, *issue, request.ParsingOptions{
209242
IncludeDataPlaneRequests: c.flags.IncludeDataPlaneRequests,

eng/tools/generator/cmd/issue/link/consts.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@ const (
1818
FilePrefix = "blob/"
1919
// CommitPrefix ...
2020
CommitPrefix = "commit/"
21+
// SDKRepo ...
22+
SDKRepo = "azure-sdk-for-go"
23+
// ReleaseIssueRepo SDK Release Request Repo ...
24+
ReleaseIssueRepo = "sdk-release-request"
2125
)

eng/tools/generator/cmd/issue/request/handlers.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ func handlePRNotMerged(_ context.Context, _ *query.Client, reqIssue ReleaseReque
4040

4141
func getTrack(issue ReleaseRequestIssue) Track {
4242
for _, l := range issue.Labels {
43-
if l != nil && l.GetName() == "Track2" {
44-
return Track2
43+
if l != nil && l.GetName() == "Track1" {
44+
return Track1
4545
}
4646
}
4747

48-
return Track1
48+
return Track2
4949
}

eng/tools/generator/cmd/v2/common/cmdProcessor.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"fmt"
88
"log"
99
"os/exec"
10+
"regexp"
11+
"strings"
1012
)
1113

1214
// execute `go generate` command and fetch result
@@ -23,7 +25,7 @@ func ExecuteGoGenerate(path string) error {
2325

2426
// execute `pwsh Invoke-MgmtTestgen` command and fetch result
2527
func ExecuteExampleGenerate(path, packagePath string) error {
26-
cmd := exec.Command("pwsh", "../../../../eng/scripts/Invoke-MgmtTestGen.ps1", "-skipBuild", "-cleanGenerated", "-format", "-tidy", "-generateExample", packagePath)
28+
cmd := exec.Command("pwsh", "../../../../eng/scripts/Invoke-MgmtTestgen.ps1", "-skipBuild", "-cleanGenerated", "-format", "-tidy", "-generateExample", packagePath)
2729
cmd.Dir = path
2830
output, err := cmd.CombinedOutput()
2931
log.Printf("Result of `pwsh Invoke-MgmtTestgen` execution: \n%s", string(output))
@@ -51,3 +53,57 @@ func ExecuteGoimports(path string) error {
5153
}
5254
return nil
5355
}
56+
57+
func ExecuteGitPush(path, remoteName, branchName string) (string, error) {
58+
refName := fmt.Sprintf(branchName + ":" + branchName)
59+
cmd := exec.Command("git", "push", remoteName, refName)
60+
cmd.Dir = path
61+
msg, err := cmd.CombinedOutput()
62+
if err != nil {
63+
return string(msg), err
64+
}
65+
return "", nil
66+
}
67+
68+
func ExecuteCreatePullRequest(path, repoOwner, repoName, prOwner, prBranch, prTitle, prBody, authToken string) (string, error) {
69+
cmd := exec.Command("pwsh", "./eng/common/scripts/Submit-PullRequest.ps1", "-RepoOwner", repoOwner, "-RepoName", repoName, "-BaseBranch", "main", "-PROwner", prOwner, "-PRBranch", prBranch, "-AuthToken", authToken, "-PRTitle", prTitle, "-PRBody", prBody)
70+
cmd.Dir = path
71+
output, err := cmd.CombinedOutput()
72+
log.Printf("Result of `pwsh Submit-PullRequest` execution: \n%s", string(output))
73+
if err != nil {
74+
return "", fmt.Errorf("failed to execute `pwsh Submit-PullRequest` '%s': %+v", string(output), err)
75+
}
76+
77+
match := regexp.MustCompile(`html_url[ ]*:.*`).FindString(string(output))
78+
_, after, _ := strings.Cut(match, ":")
79+
index := strings.Index(after, "https")
80+
return after[index:], nil
81+
}
82+
83+
func ExecuteAddIssueComment(path, repoOwner, repoName, issueNumber, comment, authToken string) error {
84+
cmd := exec.Command("pwsh", "./eng/common/scripts/Add-IssueComment.ps1", "-RepoOwner", repoOwner, "-RepoName", repoName, "-IssueNumber", issueNumber, "-Comment", comment, "-AuthToken", authToken)
85+
cmd.Dir = path
86+
output, err := cmd.CombinedOutput()
87+
log.Printf("Result of `pwsh Add-IssueComment` execution: \n%s", string(output))
88+
if err != nil {
89+
return fmt.Errorf("failed to execute `pwsh Add-IssueComment` '%s': %+v", string(output), err)
90+
}
91+
return nil
92+
}
93+
94+
func ExecuteAddIssueLabels(path, repoOwner, repoName, issueNumber, authToken string, labels []string) error {
95+
var l string
96+
if len(labels) == 1 {
97+
l = labels[0]
98+
} else {
99+
l = strings.Join(labels, ",")
100+
}
101+
cmd := exec.Command("pwsh", "./eng/common/scripts/Add-IssueLabels.ps1", "-RepoOwner", repoOwner, "-RepoName", repoName, "-IssueNumber", issueNumber, "-Labels", l, "-AuthToken", authToken)
102+
cmd.Dir = path
103+
output, err := cmd.CombinedOutput()
104+
log.Printf("Result of `pwsh Add-IssueLabels` execution: \n%s", string(output))
105+
if err != nil {
106+
return fmt.Errorf("failed to execute `pwsh Add-IssueLabels` '%s': %+v", string(output), err)
107+
}
108+
return nil
109+
}

eng/tools/generator/cmd/v2/common/fileProcessor.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ var (
3939
)
4040

4141
type PackageInfo struct {
42-
Name string
43-
Config string
42+
Name string
43+
Config string
44+
SpecName string
45+
RequestLink string
4446
}
4547

4648
// reads from readme.go.md, parses the `track2` section to get module and package name
@@ -78,6 +80,10 @@ func ReadV2ModuleNameToGetNamespace(path string) (map[string][]PackageInfo, erro
7880
return nil, fmt.Errorf("last `track2` section does not properly end")
7981
}
8082

83+
s := strings.ReplaceAll(path, "\\", "/")
84+
s1 := strings.Split(s, "/")
85+
specName := s1[len(s1)-3]
86+
8187
for i := range start {
8288
// get the content of the `track2` section
8389
section := lines[start[i]+1 : end[i]]
@@ -95,7 +101,7 @@ func ReadV2ModuleNameToGetNamespace(path string) (map[string][]PackageInfo, erro
95101
for _, matchResult := range matchResults {
96102
packageConfig = matchResult[1] + ": true"
97103
}
98-
result[modules[2]] = append(result[modules[2]], PackageInfo{Name: namespaceName, Config: packageConfig})
104+
result[modules[2]] = append(result[modules[2]], PackageInfo{Name: namespaceName, Config: packageConfig, SpecName: specName})
99105
}
100106
}
101107
}

eng/tools/generator/cmd/v2/release/releaseCmd.go

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,39 @@
44
package release
55

66
import (
7+
"errors"
78
"fmt"
89
"log"
10+
"path"
11+
"strings"
912
"time"
1013

14+
"github.com/Azure/azure-sdk-for-go/eng/tools/generator/cmd/issue/link"
1115
"github.com/Azure/azure-sdk-for-go/eng/tools/generator/cmd/v2/common"
16+
"github.com/Azure/azure-sdk-for-go/eng/tools/generator/config"
17+
"github.com/Azure/azure-sdk-for-go/eng/tools/generator/config/validate"
1218
"github.com/Azure/azure-sdk-for-go/eng/tools/generator/flags"
19+
"github.com/Azure/azure-sdk-for-go/eng/tools/generator/repo"
20+
"github.com/go-git/go-git/v5/plumbing"
1321
"github.com/spf13/cobra"
1422
"github.com/spf13/pflag"
1523
)
1624

1725
var (
1826
releaseBranchNamePattern = "release-%s-%s-%s-%v"
27+
confirmComment = "Release [PR](%s) is ready. Please check whether the package works well ([doc for testing with the generated Go SDK](https://github.com/Azure/azure-rest-api-specs/blob/main/documentation/code-gen/configure-go-sdk.md#test-with-the-generated-go-sdk)). If you are not a Golang user, you can mainly check whether the changelog meets your requirements."
1928
)
2029

2130
// Release command
2231
func Command() *cobra.Command {
2332
releaseCmd := &cobra.Command{
24-
Use: "release-v2 <azure-sdk-for-go directory/commitid> <azure-rest-api-specs directory/commitid> <rp-name> [namespaceName]",
33+
Use: "release-v2 <azure-sdk-for-go directory/commitid> <azure-rest-api-specs directory/commitid> <rp-name/release-request-json-config> [namespaceName]",
2534
Short: "Generate a v2 release of azure-sdk-for-go",
2635
Long: `This command will generate a new v2 release for azure-sdk-for-go with given rp name and namespace name.
2736
2837
azure-sdk-for-go directory/commitid: the directory path of the azure-sdk-for-go with git control or just a commitid for remote repo
2938
azure-rest-api-specs directory: the directory path of the azure-rest-api-specs with git control or just a commitid for remote repo
30-
rp-name: name of resource provider to be released, same for the swagger folder name
39+
rp-name/release-request-json-config: name of resource provider to be released (same for the swagger folder name) or release request json config file path generated by 'generator issue'
3140
namespaceName: name of namespace to be released, default value is arm+rp-name
3241
3342
`,
@@ -64,6 +73,7 @@ type Flags struct {
6473
SkipGenerateExample bool
6574
PackageConfig string
6675
GoVersion string
76+
Token string
6777
}
6878

6979
func BindFlags(flagSet *pflag.FlagSet) {
@@ -77,6 +87,7 @@ func BindFlags(flagSet *pflag.FlagSet) {
7787
flagSet.Bool("skip-generate-example", false, "Skip generate example for SDK in the same time")
7888
flagSet.String("package-config", "", "Additional config for package")
7989
flagSet.String("go-version", "1.18", "Go version")
90+
flagSet.StringP("token", "t", "", "Specify the personal access token of Github")
8091
}
8192

8293
func ParseFlags(flagSet *pflag.FlagSet) Flags {
@@ -91,6 +102,7 @@ func ParseFlags(flagSet *pflag.FlagSet) Flags {
91102
SkipGenerateExample: flags.GetBool(flagSet, "skip-generate-example"),
92103
PackageConfig: flags.GetString(flagSet, "package-config"),
93104
GoVersion: flags.GetString(flagSet, "go-version"),
105+
Token: flags.GetString(flagSet, "token"),
94106
}
95107
}
96108

@@ -111,6 +123,14 @@ func (c *commandContext) execute(sdkRepoParam, specRepoParam string) error {
111123
return err
112124
}
113125

126+
if path.Ext(c.rpName) == ".json" {
127+
return c.generateFromRequest(sdkRepo, specRepoParam, specCommitHash)
128+
} else {
129+
return c.generate(sdkRepo, specCommitHash)
130+
}
131+
}
132+
133+
func (c *commandContext) generate(sdkRepo repo.SDKRepository, specCommitHash string) error {
114134
log.Printf("Release generation for rp: %s, namespace: %s", c.rpName, c.namespaceName)
115135
generateCtx := common.GenerateContext{
116136
SDKPath: sdkRepo.Root(),
@@ -155,3 +175,103 @@ func (c *commandContext) execute(sdkRepoParam, specRepoParam string) error {
155175

156176
return nil
157177
}
178+
179+
func (c *commandContext) generateFromRequest(sdkRepo repo.SDKRepository, specRepoParam, specCommitHash string) error {
180+
var pullRequestUrls = make(map[string]string)
181+
var pushBranch = make(map[string]string)
182+
forkRemote, err := repo.GetForkRemote(sdkRepo)
183+
if err != nil {
184+
return err
185+
}
186+
187+
log.Printf("track2 parsing the config...")
188+
cfg, err := config.ParseConfig(c.rpName)
189+
if err != nil {
190+
return fmt.Errorf("parse config err: %v", err)
191+
}
192+
log.Printf("Configuration: %s", cfg.String())
193+
armServices, err := validate.ParseTrack2(cfg, specRepoParam)
194+
if err != nil {
195+
return err
196+
}
197+
for arm, packageInfos := range armServices {
198+
for _, info := range packageInfos {
199+
originalHead, err := sdkRepo.Head()
200+
if err != nil {
201+
return err
202+
}
203+
204+
// run generator
205+
c.rpName = arm
206+
c.namespaceName = info.Name
207+
c.flags.SpecRPName = info.SpecName
208+
err = c.generate(sdkRepo, specCommitHash)
209+
if err != nil {
210+
return err
211+
}
212+
213+
// get current branch name
214+
generateHead, err := sdkRepo.Head()
215+
if err != nil {
216+
return err
217+
}
218+
pushBranch[generateHead.Name().Short()] = info.RequestLink
219+
220+
log.Printf("git checkout %v", originalHead.Name().Short())
221+
if err := sdkRepo.Checkout(&repo.CheckoutOptions{
222+
Branch: plumbing.ReferenceName(originalHead.Name().Short()),
223+
Force: true,
224+
}); err != nil {
225+
return err
226+
}
227+
}
228+
}
229+
230+
for branch := range pushBranch {
231+
log.Printf("Fixes: %s\n", branch)
232+
}
233+
234+
if c.flags.Token != "" {
235+
for branchName, issue := range pushBranch {
236+
log.Printf("git push fork %s\n", branchName)
237+
msg, err := common.ExecuteGitPush(sdkRepo.Root(), forkRemote.Config().Name, branchName)
238+
if err != nil {
239+
return fmt.Errorf("git push fork error:%v,msg:%s", err, msg)
240+
}
241+
242+
githubUserName := repo.GetRemoteUserName(forkRemote)
243+
if githubUserName == "" {
244+
return errors.New("github user name not exist")
245+
}
246+
247+
log.Printf("%s: create pull request...\n", branchName)
248+
pullRequestUrl, err := common.ExecuteCreatePullRequest(sdkRepo.Root(), link.SpecOwner, link.SDKRepo, githubUserName, branchName, repo.ReleaseTitle(branchName), issue, c.flags.Token)
249+
if err != nil {
250+
return err
251+
}
252+
pullRequestUrls[branchName] = pullRequestUrl
253+
254+
log.Printf("Leave a comment in %s...\n", issue)
255+
issueNumber := strings.Split(issue, "/")
256+
err = common.ExecuteAddIssueComment(sdkRepo.Root(), link.SpecOwner, link.ReleaseIssueRepo, issueNumber[len(issueNumber)-1], fmt.Sprintf(confirmComment, pullRequestUrl), c.flags.Token)
257+
if err != nil {
258+
return err
259+
}
260+
261+
log.Printf("Add Labels...\n")
262+
err = common.ExecuteAddIssueLabels(sdkRepo.Root(), link.SpecOwner, link.ReleaseIssueRepo, issueNumber[len(issueNumber)-1], c.flags.Token, []string{"PRready"})
263+
if err != nil {
264+
return err
265+
}
266+
}
267+
}
268+
269+
if len(pullRequestUrls) != 0 {
270+
log.Println("Fixes:")
271+
for branch, url := range pullRequestUrls {
272+
log.Printf("%s : %s", branch, url)
273+
}
274+
}
275+
276+
return nil
277+
}

0 commit comments

Comments
 (0)