From d06020da681892385725a51d7b631767b3d19767 Mon Sep 17 00:00:00 2001 From: David Zager Date: Fri, 13 Oct 2023 14:24:51 -0400 Subject: [PATCH] :sparkles: Manage milestones Signed-off-by: David Zager --- .github/workflows/manage-milestone.yaml | 48 +++++++++++ cmd/labels/action.yml | 2 +- cmd/labels/main.go | 32 +------- cmd/milestones/action.yml | 53 ++++++++++++ cmd/milestones/main.go | 103 ++++++++++++++++++++++++ pkg/action/client.go | 38 +++++++++ 6 files changed, 244 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/manage-milestone.yaml create mode 100644 cmd/milestones/action.yml create mode 100644 cmd/milestones/main.go create mode 100644 pkg/action/client.go diff --git a/.github/workflows/manage-milestone.yaml b/.github/workflows/manage-milestone.yaml new file mode 100644 index 0000000..d26b57f --- /dev/null +++ b/.github/workflows/manage-milestone.yaml @@ -0,0 +1,48 @@ +name: Manage Milestone + +on: + workflow_dispatch: + inputs: + title: + description: Title of the milestone + required: true + type: string + state: + description: The state (open|closed) of the milestone + required: false + default: "open" + type: string + description: + description: Description of the milestone + required: false + default: "" + type: string + due: + description: Due date (DateOnly format https://pkg.go.dev/time#pkg-constants) + required: false + default: "" + type: string + +jobs: + sync-labels: + name: Synchronize Labels + runs-on: ubuntu-latest + strategy: + matrix: + repos: + - org: konveyor + repo: release-tools + fail-fast: true + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Manage Milestone + uses: ./cmd/milestones + with: + github_token: ${{ secrets.GH_TOKEN }} + organization: ${{ matrix.repos.org }} + repository: ${{ matrix.repos.repo }} + title: ${{ inputs.title }} + state: ${{ inputs.state }} + description: ${{ inputs.description }} + due: ${{ inputs.due }} diff --git a/cmd/labels/action.yml b/cmd/labels/action.yml index 8ba4cac..287aaba 100644 --- a/cmd/labels/action.yml +++ b/cmd/labels/action.yml @@ -21,7 +21,7 @@ runs: - name: See environment run: env shell: bash - - name: Run verify + - name: Run action env: GITHUB_TOKEN: ${{ inputs.github_token }} run: | diff --git a/cmd/labels/main.go b/cmd/labels/main.go index 3789ce4..570092e 100644 --- a/cmd/labels/main.go +++ b/cmd/labels/main.go @@ -8,14 +8,11 @@ import ( "flag" "fmt" "log" - "net/http" - "net/url" "os" "strings" "github.com/google/go-github/v55/github" "github.com/konveyor/release-tools/pkg/action" - "golang.org/x/oauth2" "sigs.k8s.io/yaml" ) @@ -88,33 +85,6 @@ func (label *Label) Print() { fmt.Println("------------------------") } -func GetClient() *github.Client { - baseURL, err := url.Parse("https://api.github.com") - if err != nil { - action.ErrorCommand("Bad endpoint") - os.Exit(1) - } - if !strings.HasSuffix(baseURL.Path, "/") { - baseURL.Path += "/" - } - token := os.Getenv("GITHUB_TOKEN") - if token == "" { - action.ErrorCommand("GITHUB_TOKEN environment variable not specified") - os.Exit(1) - } - - httpClient := &http.Client{ - Transport: &oauth2.Transport{ - Base: http.DefaultTransport, - Source: oauth2.ReuseTokenSource(nil, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})), - }, - } - client := github.NewClient(httpClient) - client.BaseURL = baseURL - - return client -} - func main() { orgPtr := flag.String("org", "", "The organization for the repo") repoPtr := flag.String("repo", "", "The repository") @@ -151,7 +121,7 @@ func main() { fmt.Println() // Instantiate the client and get the current labels on the repo - client := GetClient() + client := action.GetClient() opt := &github.ListOptions{ PerPage: 100, } diff --git a/cmd/milestones/action.yml b/cmd/milestones/action.yml new file mode 100644 index 0000000..e7eb076 --- /dev/null +++ b/cmd/milestones/action.yml @@ -0,0 +1,53 @@ +name: Manage Milestone +description: Manage a Milestone in a Repository +inputs: + github_token: + description: "the github_token provided by the actions runner" + required: true + type: string + organization: + description: "The organization" + required: true + type: string + repository: + description: "The repository" + required: true + type: string + title: + description: Title of the milestone + required: true + type: string + state: + description: The state (open|closed) of the milestone + required: true + type: string + description: + description: Description of the milestone + required: true + type: string + due: + description: Due date (DateOnly format https://pkg.go.dev/time#pkg-constants) + required: true + type: string +runs: + using: composite + steps: + - name: Set up Go + uses: actions/setup-go@v3 + - name: See environment + run: env + shell: bash + - name: Run action + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + ORGANIZATION: ${{ inputs.organization }} + REPOSITORY: ${{ inputs.repository }} + TITLE: ${{ inputs.title }} + STATE: ${{ inputs.state }} + DESCRIPTION: ${{ inputs.description }} + DUE: ${{ inputs.due }} + run: | + cd ${GITHUB_ACTION_PATH} + go mod download + go run main.go + shell: bash diff --git a/cmd/milestones/main.go b/cmd/milestones/main.go new file mode 100644 index 0000000..c3b68eb --- /dev/null +++ b/cmd/milestones/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/google/go-github/v55/github" + "github.com/konveyor/release-tools/pkg/action" +) + +func main() { + org := os.Getenv("ORGANIZATION") + repo := os.Getenv("REPOSITORY") + title := os.Getenv("TITLE") + state := os.Getenv("STATE") + desc := os.Getenv("DESCRIPTION") + due := os.Getenv("DUE") + + if org == "" { + action.ErrorCommand("input 'organization' not defined") + os.Exit(1) + } + if repo == "" { + action.ErrorCommand("input 'repository' not defined") + os.Exit(1) + } + if title == "" { + action.ErrorCommand("input 'title' not defined") + os.Exit(1) + } + if state != "open" && state != "closed" { + action.ErrorCommand("input 'state' must be 'open' or 'closed'") + os.Exit(1) + } + + var dueTime *github.Timestamp + if due != "" { + parsedTime, err := time.Parse(time.DateOnly, due) + if err != nil { + fmt.Println("Error: " + err.Error()) + action.ErrorCommand("input 'due' not parseable as DateOnly time") + os.Exit(1) + } + dueOn := github.Timestamp{Time: parsedTime} + dueTime = &dueOn + } + + desiredMilestone := github.Milestone{ + Title: &title, + State: &state, + Description: &desc, + DueOn: dueTime, + } + + // Instantiate the client and get milestones + client := action.GetClient() + opt := &github.MilestoneListOptions{ + ListOptions: github.ListOptions{ + PerPage: 100, + }, + } + + // Make sure we get all the milestones + var currentMilestones []*github.Milestone + for { + labels, resp, err := client.Issues.ListMilestones(context.Background(), org, repo, opt) + if err != nil { + action.ErrorCommand("Failed to get repo labels") + log.Fatal(err) + } + currentMilestones = append(currentMilestones, labels...) + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + + // Grab the milestone number if it already exists + for _, milestone := range currentMilestones { + if *milestone.Title == *desiredMilestone.Title { + desiredMilestone.Number = milestone.Number + } + } + + if desiredMilestone.Number == nil { + _, _, err := client.Issues.CreateMilestone(context.Background(), org, repo, &desiredMilestone) + if err != nil { + action.ErrorCommand("Error creating milestone") + log.Fatal(err) + } + return + } + _, _, err := client.Issues.EditMilestone(context.Background(), org, repo, *desiredMilestone.Number, &desiredMilestone) + if err != nil { + action.ErrorCommand("Error editingmilestone") + log.Fatal(err) + } + + action.NoticeCommand("Yay") +} diff --git a/pkg/action/client.go b/pkg/action/client.go new file mode 100644 index 0000000..f513f84 --- /dev/null +++ b/pkg/action/client.go @@ -0,0 +1,38 @@ +package action + +import ( + "net/http" + "net/url" + "os" + "strings" + + "github.com/google/go-github/v55/github" + "golang.org/x/oauth2" +) + +func GetClient() *github.Client { + baseURL, err := url.Parse("https://api.github.com") + if err != nil { + ErrorCommand("Bad endpoint") + os.Exit(1) + } + if !strings.HasSuffix(baseURL.Path, "/") { + baseURL.Path += "/" + } + token := os.Getenv("GITHUB_TOKEN") + if token == "" { + ErrorCommand("GITHUB_TOKEN environment variable not specified") + os.Exit(1) + } + + httpClient := &http.Client{ + Transport: &oauth2.Transport{ + Base: http.DefaultTransport, + Source: oauth2.ReuseTokenSource(nil, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})), + }, + } + client := github.NewClient(httpClient) + client.BaseURL = baseURL + + return client +}