Skip to content

Commit

Permalink
Add tag protection (#2424)
Browse files Browse the repository at this point in the history
  • Loading branch information
jfengupgrade authored Aug 13, 2022
1 parent 7a7c657 commit aa96e8d
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 0 deletions.
76 changes: 76 additions & 0 deletions example/tagprotection/main.go
Original file line number Diff line number Diff line change
@@ -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))
}
16 changes: 16 additions & 0 deletions github/github-accessors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions github/github-accessors_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions github/repos_tags.go
Original file line number Diff line number Diff line change
@@ -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)
}
132 changes: 132 additions & 0 deletions github/repos_tags_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}

0 comments on commit aa96e8d

Please sign in to comment.