diff --git a/example/tagprotection/main.go b/example/tagprotection/main.go new file mode 100644 index 00000000000..6f347b97ed6 --- /dev/null +++ b/example/tagprotection/main.go @@ -0,0 +1,76 @@ +// Copyright 2022 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The tagprotection command demonstrates the functionality that +// prompts the user for GitHub owner, repo, tag protection pattern and token, +// then creates a new tag protection if the user entered a pattern at the prompt. +// Otherwise, it will just list all existing tag protections. +package main + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "log" + "os" + "strings" + "syscall" + + "github.com/google/go-github/v45/github" + "golang.org/x/crypto/ssh/terminal" + "golang.org/x/oauth2" +) + +func main() { + // read github owner, repo, token from standard input + r := bufio.NewReader(os.Stdin) + fmt.Print("GitHub Org/User name: ") + owner, _ := r.ReadString('\n') + owner = strings.TrimSpace(owner) + + fmt.Print("GitHub repo name: ") + repo, _ := r.ReadString('\n') + repo = strings.TrimSpace(repo) + + fmt.Print("Tag pattern(leave blank to not create new tag protection): ") + pattern, _ := r.ReadString('\n') + pattern = strings.TrimSpace(pattern) + + fmt.Print("GitHub Token: ") + byteToken, _ := terminal.ReadPassword(int(syscall.Stdin)) + println() + token := string(byteToken) + + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := github.NewClient(tc) + + // create new tag protection + if pattern != "" { + tagProtection, _, err := client.Repositories.CreateTagProtection(ctx, owner, repo, pattern) + if err != nil { + log.Fatalf("Error: %v\n", err) + } + println() + fmt.Printf("New tag protection created in github.com/%v/%v\n", owner, repo) + tp, _ := json.Marshal(tagProtection) + fmt.Println(string(tp)) + } + + // list all tag protection + println() + fmt.Printf("List all tag protection in github.com/%v/%v\n", owner, repo) + tagProtections, _, err := client.Repositories.ListTagProtection(ctx, owner, repo) + if err != nil { + log.Fatalf("Error: %v\n", err) + } + results, _ := json.Marshal(tagProtections) + fmt.Println(string(results)) +} diff --git a/github/github-accessors.go b/github/github-accessors.go index 334ff395100..af6928ec231 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -17302,6 +17302,22 @@ func (t *Tag) GetVerification() *SignatureVerification { return t.Verification } +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (t *TagProtection) GetID() int64 { + if t == nil || t.ID == nil { + return 0 + } + return *t.ID +} + +// GetPattern returns the Pattern field if it's non-nil, zero value otherwise. +func (t *TagProtection) GetPattern() string { + if t == nil || t.Pattern == nil { + return "" + } + return *t.Pattern +} + // GetCompletedAt returns the CompletedAt field if it's non-nil, zero value otherwise. func (t *TaskStep) GetCompletedAt() Timestamp { if t == nil || t.CompletedAt == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 4339a879f65..907892a9e59 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -20182,6 +20182,26 @@ func TestTag_GetVerification(tt *testing.T) { t.GetVerification() } +func TestTagProtection_GetID(tt *testing.T) { + var zeroValue int64 + t := &TagProtection{ID: &zeroValue} + t.GetID() + t = &TagProtection{} + t.GetID() + t = nil + t.GetID() +} + +func TestTagProtection_GetPattern(tt *testing.T) { + var zeroValue string + t := &TagProtection{Pattern: &zeroValue} + t.GetPattern() + t = &TagProtection{} + t.GetPattern() + t = nil + t.GetPattern() +} + func TestTaskStep_GetCompletedAt(tt *testing.T) { var zeroValue Timestamp t := &TaskStep{CompletedAt: &zeroValue} diff --git a/github/repos_tags.go b/github/repos_tags.go new file mode 100644 index 00000000000..ff46d90c731 --- /dev/null +++ b/github/repos_tags.go @@ -0,0 +1,76 @@ +// Copyright 2022 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// TagProtection represents a repository tag protection. +type TagProtection struct { + ID *int64 `json:"id"` + Pattern *string `json:"pattern"` +} + +// tagProtectionRequest represents a request to create tag protection. +type tagProtectionRequest struct { + // An optional glob pattern to match against when enforcing tag protection. + Pattern string `json:"pattern"` +} + +// ListTagProtection lists tag protection of the specified repository. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/tags#list-tag-protection-states-for-a-repository +func (s *RepositoriesService) ListTagProtection(ctx context.Context, owner, repo string) ([]*TagProtection, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/tags/protection", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var tagProtections []*TagProtection + resp, err := s.client.Do(ctx, req, &tagProtections) + if err != nil { + return nil, resp, err + } + + return tagProtections, resp, nil +} + +// CreateTagProtection creates the tag protection of the specified repository. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/tags#create-a-tag-protection-state-for-a-repository +func (s *RepositoriesService) CreateTagProtection(ctx context.Context, owner, repo, pattern string) (*TagProtection, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/tags/protection", owner, repo) + r := &tagProtectionRequest{Pattern: pattern} + req, err := s.client.NewRequest("POST", u, r) + if err != nil { + return nil, nil, err + } + + tagProtection := new(TagProtection) + resp, err := s.client.Do(ctx, req, tagProtection) + if err != nil { + return nil, resp, err + } + + return tagProtection, resp, nil +} + +// DeleteTagProtection deletes a tag protection from the specified repository. +// +// GitHub API docs: https://docs.github.com/en/rest/repos/tags#delete-a-tag-protection-state-for-a-repository +func (s *RepositoriesService) DeleteTagProtection(ctx context.Context, owner, repo string, tagProtectionID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/tags/protection/%v", owner, repo, tagProtectionID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/github/repos_tags_test.go b/github/repos_tags_test.go new file mode 100644 index 00000000000..e5e15a693c0 --- /dev/null +++ b/github/repos_tags_test.go @@ -0,0 +1,132 @@ +// Copyright 2022 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestRepositoriesService_ListTagProtection(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/tags/protection", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `[{"id":1, "pattern":"tag1"},{"id":2, "pattern":"tag2"}]`) + }) + + ctx := context.Background() + tagProtections, _, err := client.Repositories.ListTagProtection(ctx, "o", "r") + if err != nil { + t.Errorf("Repositories.ListTagProtection returned error: %v", err) + } + + want := []*TagProtection{{ID: Int64(1), Pattern: String("tag1")}, {ID: Int64(2), Pattern: String("tag2")}} + if !cmp.Equal(tagProtections, want) { + t.Errorf("Repositories.ListTagProtection returned %+v, want %+v", tagProtections, want) + } + + const methodName = "ListTagProtection" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Repositories.ListTagProtection(ctx, "\n", "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Repositories.ListTagProtection(ctx, "o", "r") + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestRepositoriesService_ListTagProtection_invalidOwner(t *testing.T) { + client, _, _, teardown := setup() + defer teardown() + + ctx := context.Background() + _, _, err := client.Repositories.ListTagProtection(ctx, "%", "r") + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateTagProtection(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + pattern := "tag*" + + mux.HandleFunc("/repos/o/r/tags/protection", func(w http.ResponseWriter, r *http.Request) { + v := new(tagProtectionRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + want := &tagProtectionRequest{Pattern: "tag*"} + if !cmp.Equal(v, want) { + t.Errorf("Request body = %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{"id":1,"pattern":"tag*"}`) + }) + + ctx := context.Background() + got, _, err := client.Repositories.CreateTagProtection(ctx, "o", "r", pattern) + if err != nil { + t.Errorf("Repositories.CreateTagProtection returned error: %v", err) + } + + want := &TagProtection{ID: Int64(1), Pattern: String("tag*")} + if !cmp.Equal(got, want) { + t.Errorf("Repositories.CreateTagProtection returned %+v, want %+v", got, want) + } + + const methodName = "CreateTagProtection" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Repositories.CreateTagProtection(ctx, "\n", "\n", pattern) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Repositories.CreateTagProtection(ctx, "o", "r", pattern) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestRepositoriesService_DeleteTagProtection(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/tags/protection/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + ctx := context.Background() + _, err := client.Repositories.DeleteTagProtection(ctx, "o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteTagProtection returned error: %v", err) + } + + const methodName = "DeleteTagProtection" + testBadOptions(t, methodName, func() (err error) { + _, err = client.Repositories.DeleteTagProtection(ctx, "\n", "\n", 1) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Repositories.DeleteTagProtection(ctx, "o", "r", 1) + }) +}