Harness move (#237)

* (feat) initial commit for harness code
* (feat) harness, add update delete create file
pull/239/head
TP Honey 1 year ago committed by GitHub
parent 4428a9bf64
commit ca3776c0f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -146,6 +146,7 @@ const (
DriverCoding
DriverGitee
DriverAzure
DriverHarness
)
// String returns the string representation of Driver.
@ -169,6 +170,8 @@ func (d Driver) String() (s string) {
return "gitee"
case DriverAzure:
return "azure"
case DriverHarness:
return "harness"
default:
return "unknown"
}

@ -0,0 +1,234 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"encoding/base64"
"fmt"
"time"
"github.com/drone/go-scm/scm"
)
type contentService struct {
client *wrapper
}
func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm.Content, *scm.Response, error) {
repo = buildHarnessURI(s.client.account, s.client.organization, s.client.project, repo)
endpoint := fmt.Sprintf("api/v1/repos/%s/content/%s?%s&include_commit=true", repo, path, ref)
out := new(fileContent)
res, err := s.client.do(ctx, "GET", endpoint, nil, out)
// decode raw output content
raw, _ := base64.StdEncoding.DecodeString(out.Content.Data)
return &scm.Content{
Path: path,
Sha: out.LatestCommit.Sha,
BlobID: out.Sha,
Data: raw,
}, res, err
}
func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) {
repo = buildHarnessURI(s.client.account, s.client.organization, s.client.project, repo)
endpoint := fmt.Sprintf("api/v1/repos/%s/commits", repo)
a := action{
Action: "CREATE",
Path: path,
Payload: string(params.Data),
Encoding: "string",
}
in := editFile{
Branch: params.Branch,
Message: params.Message,
Title: params.Message,
Actions: []action{a},
}
res, err := s.client.do(ctx, "POST", endpoint, in, nil)
return res, err
}
func (s *contentService) Update(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) {
repo = buildHarnessURI(s.client.account, s.client.organization, s.client.project, repo)
endpoint := fmt.Sprintf("api/v1/repos/%s/commits", repo)
a := action{
Action: "UPDATE",
Path: path,
Payload: string(params.Data),
Encoding: "string",
}
in := editFile{
Branch: params.Branch,
Message: params.Message,
Title: params.Message,
Actions: []action{a},
}
res, err := s.client.do(ctx, "POST", endpoint, in, nil)
return res, err
}
func (s *contentService) Delete(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) {
repo = buildHarnessURI(s.client.account, s.client.organization, s.client.project, repo)
endpoint := fmt.Sprintf("api/v1/repos/%s/commits", repo)
a := action{
Action: "DELETE",
Path: path,
Encoding: "string",
}
in := editFile{
Branch: params.Branch,
Message: params.Message,
Title: params.Message,
Actions: []action{a},
}
res, err := s.client.do(ctx, "POST", endpoint, in, nil)
return res, err
}
func buildHarnessURI(account, organization, project, repo string) (uri string) {
if account != "" {
return fmt.Sprintf("%s/%s/%s/%s/+", account, organization, project, repo)
}
return repo
}
func (s *contentService) List(ctx context.Context, repo, path, ref string, _ scm.ListOptions) ([]*scm.ContentInfo, *scm.Response, error) {
repo = buildHarnessURI(s.client.account, s.client.organization, s.client.project, repo)
endpoint := fmt.Sprintf("api/v1/repos/%s/content/%s?%s&include_commit=true", repo, path, ref)
out := new(contentList)
res, err := s.client.do(ctx, "GET", endpoint, nil, &out)
return convertContentInfoList(out.Content.Entries), res, err
}
type editFile struct {
Actions []action `json:"actions"`
Branch string `json:"branch"`
Message string `json:"message"`
NewBranch string `json:"new_branch"`
Title string `json:"title"`
}
type action struct {
Action string `json:"action"`
Encoding string `json:"encoding"`
Path string `json:"path"`
Payload string `json:"payload"`
Sha string `json:"sha"`
}
type fileContent struct {
Type string `json:"type"`
Sha string `json:"sha"`
Name string `json:"name"`
Path string `json:"path"`
LatestCommit struct {
Sha string `json:"sha"`
Title string `json:"title"`
Message string `json:"message"`
Author struct {
Identity struct {
Name string `json:"name"`
Email string `json:"email"`
} `json:"identity"`
When time.Time `json:"when"`
} `json:"author"`
Committer struct {
Identity struct {
Name string `json:"name"`
Email string `json:"email"`
} `json:"identity"`
When time.Time `json:"when"`
} `json:"committer"`
} `json:"latest_commit"`
Content struct {
Encoding string `json:"encoding"`
Data string `json:"data"`
Size int `json:"size"`
} `json:"content"`
}
type contentList struct {
Type string `json:"type"`
Sha string `json:"sha"`
Name string `json:"name"`
Path string `json:"path"`
LatestCommit struct {
Sha string `json:"sha"`
Title string `json:"title"`
Message string `json:"message"`
Author struct {
Identity struct {
Name string `json:"name"`
Email string `json:"email"`
} `json:"identity"`
When time.Time `json:"when"`
} `json:"author"`
Committer struct {
Identity struct {
Name string `json:"name"`
Email string `json:"email"`
} `json:"identity"`
When time.Time `json:"when"`
} `json:"committer"`
} `json:"latest_commit"`
Content struct {
Entries []fileEntry `json:"entries"`
} `json:"content"`
}
type fileEntry struct {
Type string `json:"type"`
Sha string `json:"sha"`
Name string `json:"name"`
Path string `json:"path"`
LatestCommit struct {
Sha string `json:"sha"`
Title string `json:"title"`
Message string `json:"message"`
Author struct {
Identity struct {
Name string `json:"name"`
Email string `json:"email"`
} `json:"identity"`
When time.Time `json:"when"`
} `json:"author"`
Committer struct {
Identity struct {
Name string `json:"name"`
Email string `json:"email"`
} `json:"identity"`
When time.Time `json:"when"`
} `json:"committer"`
} `json:"latest_commit"`
}
func convertContentInfoList(from []fileEntry) []*scm.ContentInfo {
to := []*scm.ContentInfo{}
for _, v := range from {
to = append(to, convertContentInfo(v))
}
return to
}
func convertContentInfo(from fileEntry) *scm.ContentInfo {
to := &scm.ContentInfo{
Path: from.Path,
Sha: from.LatestCommit.Sha,
BlobID: from.Sha,
}
switch from.Type {
case "file":
to.Kind = scm.ContentKindFile
case "dir":
to.Kind = scm.ContentKindDirectory
default:
to.Kind = scm.ContentKindUnsupported
}
return to
}

@ -0,0 +1,208 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/drone/go-scm/scm"
"github.com/drone/go-scm/scm/transport"
"github.com/google/go-cmp/cmp"
"github.com/h2non/gock"
)
const (
gockOrigin = "https://qa.harness.io/gateway/code"
harnessOrg = "px7xd_BFRCi-pfWPYXVjvw"
harnessAccount = "default"
harnessProject = "codeciintegration"
harnessRepo = "demo"
harnessPAT = ""
)
func TestContentFind(t *testing.T) {
defer gock.Off()
gock.New(gockOrigin).
Get("/gateway/code/api/v1/repos/px7xd_BFRCi-pfWPYXVjvw/default/codeciintegration/demo/+/content/README.md").
Reply(200).
Type("plain/text").
File("testdata/content.json")
client, _ := New(gockOrigin, harnessOrg, harnessAccount, harnessProject)
client.Client = &http.Client{
Transport: &transport.Custom{
Before: func(r *http.Request) {
r.Header.Set("x-api-key", harnessPAT)
},
},
}
result, _, err := client.Contents.Find(
context.Background(),
harnessRepo,
"README.md",
"98189d5cf2a751a6246c24a72945ba70839f1b20",
)
if err != nil {
t.Error(err)
}
if got, want := result.Path, "README.md"; got != want {
t.Errorf("Want file Path %q, got %q", want, got)
}
if !strings.Contains(string(result.Data), "demo") {
t.Errorf("Want file Data %q, must contain 'demo'", result.Data)
}
}
func TestContentCreate(t *testing.T) {
defer gock.Off()
gock.New(gockOrigin).
Post("/gateway/code/api/v1/repos/px7xd_BFRCi-pfWPYXVjvw/default/codeciintegration/demo/+/commits").
Reply(200).
Type("plain/text").
BodyString("{\"commit_id\":\"20ecde1f8c277da0e91750bef9f3b88f228d86db\"}")
client, _ := New(gockOrigin, harnessOrg, harnessAccount, harnessProject)
client.Client = &http.Client{
Transport: &transport.Custom{
Before: func(r *http.Request) {
r.Header.Set("x-api-key", harnessPAT)
},
},
}
result, err := client.Contents.Create(
context.Background(),
harnessRepo,
"README.2",
&scm.ContentParams{
Data: []byte("hello world"),
Message: "create README.2",
Branch: "main",
},
)
if err != nil {
t.Error(err)
}
if result.Status != 200 {
t.Errorf("Unexpected Results")
}
}
func TestContentUpdate(t *testing.T) {
defer gock.Off()
gock.New(gockOrigin).
Post("/gateway/code/api/v1/repos/px7xd_BFRCi-pfWPYXVjvw/default/codeciintegration/demo/+/commits").
Reply(200).
Type("plain/text").
BodyString("{\"commit_id\":\"20ecde1f8c277da0e91750bef9f3b88f228d86db\"}")
client, _ := New(gockOrigin, harnessOrg, harnessAccount, harnessProject)
client.Client = &http.Client{
Transport: &transport.Custom{
Before: func(r *http.Request) {
r.Header.Set("x-api-key", harnessPAT)
},
},
}
result, err := client.Contents.Update(
context.Background(),
harnessRepo,
"README.2",
&scm.ContentParams{
Data: []byte("hello world 2"),
Message: "update README.2",
Branch: "main",
},
)
if err != nil {
t.Error(err)
}
if result.Status != 200 {
t.Errorf("Unexpected Results")
}
}
func TestContentDelete(t *testing.T) {
defer gock.Off()
gock.New(gockOrigin).
Post("/gateway/code/api/v1/repos/px7xd_BFRCi-pfWPYXVjvw/default/codeciintegration/demo/+/commits").
Reply(200).
Type("plain/text").
BodyString("{\"commit_id\":\"20ecde1f8c277da0e91750bef9f3b88f228d86db\"}")
client, _ := New(gockOrigin, harnessOrg, harnessAccount, harnessProject)
client.Client = &http.Client{
Transport: &transport.Custom{
Before: func(r *http.Request) {
r.Header.Set("x-api-key", harnessPAT)
},
},
}
result, err := client.Contents.Delete(
context.Background(),
harnessRepo,
"README.2",
&scm.ContentParams{
Message: "delete README.2",
Branch: "main",
},
)
if err != nil {
t.Error(err)
}
if result.Status != 200 {
t.Errorf("Unexpected Results")
}
}
func TestContentList(t *testing.T) {
defer gock.Off()
gock.New(gockOrigin).
Get("/gateway/code/api/v1/repos/px7xd_BFRCi-pfWPYXVjvw/default/codeciintegration/demo/+/content/docker").
Reply(200).
Type("application/json").
File("testdata/content_list.json")
client, _ := New(gockOrigin, harnessOrg, harnessAccount, harnessProject)
client.Client = &http.Client{
Transport: &transport.Custom{
Before: func(r *http.Request) {
r.Header.Set("x-api-key", harnessPAT)
},
},
}
got, _, err := client.Contents.List(
context.Background(),
harnessRepo,
"docker",
"",
scm.ListOptions{},
)
if err != nil {
t.Error(err)
}
want := []*scm.ContentInfo{}
raw, _ := ioutil.ReadFile("testdata/content_list.json.golden")
json.Unmarshal(raw, &want)
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("Unexpected Results")
t.Log(diff)
}
}

@ -0,0 +1,187 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"fmt"
"net/url"
"time"
"github.com/drone/go-scm/scm"
)
type gitService struct {
client *wrapper
}
func (s *gitService) CreateBranch(ctx context.Context, repo string, params *scm.ReferenceInput) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *gitService) FindBranch(ctx context.Context, repo, name string) (*scm.Reference, *scm.Response, error) {
path := fmt.Sprintf("api/v1/repos/%s/branches/%s", repo, name)
out := new(branch)
res, err := s.client.do(ctx, "GET", path, nil, out)
return convertBranch(out), res, err
}
func (s *gitService) FindCommit(ctx context.Context, repo, ref string) (*scm.Commit, *scm.Response, error) {
ref = scm.TrimRef(ref)
path := fmt.Sprintf("api/v1/repos/%s/git/commits/%s", repo, url.PathEscape(ref))
out := new(commitInfo)
res, err := s.client.do(ctx, "GET", path, nil, out)
return convertCommitInfo(out), res, err
}
func (s *gitService) FindTag(ctx context.Context, repo, name string) (*scm.Reference, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *gitService) ListBranches(ctx context.Context, repo string, opts scm.ListOptions) ([]*scm.Reference, *scm.Response, error) {
path := fmt.Sprintf("api/v1/repos/%s/branches?%s", repo, encodeListOptions(opts))
out := []*branch{}
res, err := s.client.do(ctx, "GET", path, nil, &out)
return convertBranchList(out), res, err
}
func (s *gitService) ListCommits(ctx context.Context, repo string, _ scm.CommitListOptions) ([]*scm.Commit, *scm.Response, error) {
path := fmt.Sprintf("api/v1/repos/%s/commits", repo)
out := []*commitInfo{}
res, err := s.client.do(ctx, "GET", path, nil, &out)
return convertCommitList(out), res, err
}
func (s *gitService) ListTags(ctx context.Context, repo string, _ scm.ListOptions) ([]*scm.Reference, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *gitService) ListChanges(ctx context.Context, repo, ref string, _ scm.ListOptions) ([]*scm.Change, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *gitService) CompareChanges(ctx context.Context, repo, source, target string, _ scm.ListOptions) ([]*scm.Change, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
//
// native data structures
//
type (
// gitea branch object.
branch struct {
Name string `json:"name"`
Commit commit `json:"commit"`
}
// gitea commit object.
commit struct {
ID string `json:"id"`
Sha string `json:"sha"`
Message string `json:"message"`
URL string `json:"url"`
Author signature `json:"author"`
Committer signature `json:"committer"`
Timestamp time.Time `json:"timestamp"`
}
// gitea commit info object.
commitInfo struct {
Sha string `json:"sha"`
Commit commit `json:"commit"`
Author user `json:"author"`
Committer user `json:"committer"`
}
// gitea signature object.
signature struct {
Name string `json:"name"`
Email string `json:"email"`
Username string `json:"username"`
}
// gitea tag object
tag struct {
Ref string `json:"ref"`
URL string `json:"url"`
Object struct {
Type string `json:"type"`
Sha string `json:"sha"`
URL string `json:"url"`
} `json:"object"`
}
)
//
// native data structure conversion
//
func convertBranchList(src []*branch) []*scm.Reference {
dst := []*scm.Reference{}
for _, v := range src {
dst = append(dst, convertBranch(v))
}
return dst
}
func convertBranch(src *branch) *scm.Reference {
return &scm.Reference{
Name: scm.TrimRef(src.Name),
Path: scm.ExpandRef(src.Name, "refs/heads/"),
Sha: src.Commit.ID,
}
}
func convertCommitList(src []*commitInfo) []*scm.Commit {
dst := []*scm.Commit{}
for _, v := range src {
dst = append(dst, convertCommitInfo(v))
}
return dst
}
func convertCommitInfo(src *commitInfo) *scm.Commit {
return &scm.Commit{
Sha: src.Sha,
Link: src.Commit.URL,
Message: src.Commit.Message,
Author: convertUserSignature(src.Author),
Committer: convertUserSignature(src.Committer),
}
}
func convertSignature(src signature) scm.Signature {
return scm.Signature{
Login: src.Username,
Email: src.Email,
Name: src.Name,
}
}
func convertUserSignature(src user) scm.Signature {
return scm.Signature{
Login: userLogin(&src),
Email: src.Email,
Name: src.Fullname,
Avatar: src.Avatar,
}
}
func convertTagList(src []*tag) []*scm.Reference {
var dst []*scm.Reference
for _, v := range src {
dst = append(dst, convertTag(v))
}
return dst
}
func convertTag(src *tag) *scm.Reference {
return &scm.Reference{
Name: scm.TrimRef(src.Ref),
Path: src.Ref,
Sha: src.Object.Sha,
}
}

@ -0,0 +1,5 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness

@ -0,0 +1,108 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/drone/go-scm/scm"
)
// New returns a new gitness API client.
func New(uri, account, organization, project string) (*scm.Client, error) {
if !((organization == "" && account == "" && project == "") || (organization != "" && account != "" && project != "")) {
return nil, fmt.Errorf("harness account, organization and project are required")
}
base, err := url.Parse(uri)
if err != nil {
return nil, err
}
if !strings.HasSuffix(base.Path, "/") {
base.Path = base.Path + "/"
}
client := &wrapper{new(scm.Client), account, organization, project}
client.BaseURL = base
// initialize services
client.Driver = scm.DriverGitea
client.Linker = &linker{base.String()}
client.Contents = &contentService{client}
client.Git = &gitService{client}
client.Issues = &issueService{client}
client.Milestones = &milestoneService{client}
client.Organizations = &organizationService{client}
client.PullRequests = &pullService{client}
client.Repositories = &repositoryService{client}
client.Releases = &releaseService{client}
client.Reviews = &reviewService{client}
client.Users = &userService{client}
client.Webhooks = &webhookService{client}
return client.Client, nil
}
// wraper wraps the Client to provide high level helper functions
// for making http requests and unmarshaling the response.
type wrapper struct {
*scm.Client
account string
organization string
project string
}
// do wraps the Client.Do function by creating the Request and
// unmarshalling the response.
func (c *wrapper) do(ctx context.Context, method, path string, in, out interface{}) (*scm.Response, error) {
req := &scm.Request{
Method: method,
Path: path,
}
// if we are posting or putting data, we need to
// write it to the body of the request.
if in != nil {
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(in)
req.Header = map[string][]string{
"Content-Type": {"application/json"},
}
req.Body = buf
}
// execute the http request
res, err := c.Client.Do(ctx, req)
if err != nil {
return nil, err
}
defer res.Body.Close()
// if an error is encountered, unmarshal and return the
// error response.
if res.Status > 300 {
return res, errors.New(
http.StatusText(res.Status),
)
}
if out == nil {
return res, nil
}
// if raw output is expected, copy to the provided
// buffer and exit.
if w, ok := out.(io.Writer); ok {
io.Copy(w, res.Body)
return res, nil
}
// if a json response is expected, parse and return
// the json response.
return res, json.NewDecoder(res.Body).Decode(out)
}

@ -0,0 +1,47 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// package gitea implements a Gogs client.
package harness
import (
"fmt"
"testing"
"github.com/drone/go-scm/scm"
)
func TestClient(t *testing.T) {
client, err := New(gockOrigin, "", "", "")
if err != nil {
t.Error(err)
}
if got, want := client.BaseURL.String(), fmt.Sprintf("%s/", gockOrigin); got != want {
t.Errorf("Want Client URL %q, got %q", want, got)
}
}
func TestClient_Error(t *testing.T) {
_, err := New("http://a b.com/", "", "", "")
if err == nil {
t.Errorf("Expect error when invalid URL")
}
}
func testPage(res *scm.Response) func(t *testing.T) {
return func(t *testing.T) {
if got, want := res.Page.Next, 2; got != want {
t.Errorf("Want next page %d, got %d", want, got)
}
if got, want := res.Page.Prev, 1; got != want {
t.Errorf("Want prev page %d, got %d", want, got)
}
if got, want := res.Page.First, 1; got != want {
t.Errorf("Want first page %d, got %d", want, got)
}
if got, want := res.Page.Last, 5; got != want {
t.Errorf("Want last page %d, got %d", want, got)
}
}
}

@ -0,0 +1,147 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"time"
"github.com/drone/go-scm/scm"
)
type issueService struct {
client *wrapper
}
func (s *issueService) Find(ctx context.Context, repo string, number int) (*scm.Issue, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *issueService) FindComment(ctx context.Context, repo string, index, id int) (*scm.Comment, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *issueService) List(ctx context.Context, repo string, opts scm.IssueListOptions) ([]*scm.Issue, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *issueService) ListComments(ctx context.Context, repo string, index int, opts scm.ListOptions) ([]*scm.Comment, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *issueService) Create(ctx context.Context, repo string, input *scm.IssueInput) (*scm.Issue, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *issueService) CreateComment(ctx context.Context, repo string, index int, input *scm.CommentInput) (*scm.Comment, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *issueService) DeleteComment(ctx context.Context, repo string, index, id int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *issueService) Close(ctx context.Context, repo string, number int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *issueService) Lock(ctx context.Context, repo string, number int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *issueService) Unlock(ctx context.Context, repo string, number int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
//
// native data structures
//
type (
// gitea issue response object.
issue struct {
ID int `json:"id"`
Number int `json:"number"`
User user `json:"user"`
Title string `json:"title"`
Body string `json:"body"`
State string `json:"state"`
Labels []string `json:"labels"`
Comments int `json:"comments"`
Created time.Time `json:"created_at"`
Updated time.Time `json:"updated_at"`
PullRequest *struct {
Merged bool `json:"merged"`
MergedAt interface{} `json:"merged_at"`
} `json:"pull_request"`
}
// gitea issue request object.
issueInput struct {
Title string `json:"title"`
Body string `json:"body"`
}
// gitea issue comment response object.
issueComment struct {
ID int `json:"id"`
HTMLURL string `json:"html_url"`
User user `json:"user"`
Body string `json:"body"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// gitea issue comment request object.
issueCommentInput struct {
Body string `json:"body"`
}
)
//
// native data structure conversion
//
func convertIssueList(from []*issue) []*scm.Issue {
to := []*scm.Issue{}
for _, v := range from {
to = append(to, convertIssue(v))
}
return to
}
func convertIssue(from *issue) *scm.Issue {
return &scm.Issue{
Number: from.Number,
Title: from.Title,
Body: from.Body,
Link: "", // TODO construct the link to the issue.
Closed: from.State == "closed",
Author: *convertUser(&from.User),
Created: from.Created,
Updated: from.Updated,
}
}
func convertIssueCommentList(from []*issueComment) []*scm.Comment {
to := []*scm.Comment{}
for _, v := range from {
to = append(to, convertIssueComment(v))
}
return to
}
func convertIssueComment(from *issueComment) *scm.Comment {
return &scm.Comment{
ID: from.ID,
Body: from.Body,
Author: *convertUser(&from.User),
Created: from.CreatedAt,
Updated: from.UpdatedAt,
}
}

@ -0,0 +1,5 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness

@ -0,0 +1,27 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"github.com/drone/go-scm/scm"
)
type linker struct {
base string
}
// Resource returns a link to the resource.
func (l *linker) Resource(ctx context.Context, repo string, ref scm.Reference) (string, error) {
return "", scm.ErrNotSupported
}
// Diff returns a link to the diff.
func (l *linker) Diff(ctx context.Context, repo string, source, target scm.Reference) (string, error) {
return "", scm.ErrNotSupported
}

@ -0,0 +1,5 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness

@ -0,0 +1,90 @@
package harness
import (
"context"
"github.com/drone/go-scm/scm"
"github.com/drone/go-scm/scm/driver/internal/null"
)
type milestoneService struct {
client *wrapper
}
func (s *milestoneService) Find(ctx context.Context, repo string, id int) (*scm.Milestone, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *milestoneService) List(ctx context.Context, repo string, opts scm.MilestoneListOptions) ([]*scm.Milestone, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *milestoneService) Create(ctx context.Context, repo string, input *scm.MilestoneInput) (*scm.Milestone, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *milestoneService) Delete(ctx context.Context, repo string, id int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *milestoneService) Update(ctx context.Context, repo string, id int, input *scm.MilestoneInput) (*scm.Milestone, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
// stateType issue state type
type stateType string
const (
// stateOpen pr/issue is open
stateOpen stateType = "open"
// stateClosed pr/issue is closed
stateClosed stateType = "closed"
// stateAll is all
stateAll stateType = "all"
)
type milestone struct {
ID int64 `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
State stateType `json:"state"`
OpenIssues int `json:"open_issues"`
ClosedIssues int `json:"closed_issues"`
Created null.Time `json:"created_at"`
Updated null.Time `json:"updated_at"`
Closed null.Time `json:"closed_at"`
Deadline null.Time `json:"due_on"`
}
type milestoneInput struct {
Title string `json:"title"`
Description string `json:"description"`
State stateType `json:"state"`
Deadline null.Time `json:"due_on"`
}
func convertMilestoneList(src []*milestone) []*scm.Milestone {
var dst []*scm.Milestone
for _, v := range src {
dst = append(dst, convertMilestone(v))
}
return dst
}
func convertMilestone(src *milestone) *scm.Milestone {
if src == nil || src.Deadline.IsZero() {
return nil
}
return &scm.Milestone{
Number: int(src.ID),
ID: int(src.ID),
Title: src.Title,
Description: src.Description,
State: string(src.State),
DueDate: src.Deadline.ValueOrZero(),
}
}

@ -0,0 +1,57 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"github.com/drone/go-scm/scm"
)
type organizationService struct {
client *wrapper
}
func (s *organizationService) Find(ctx context.Context, name string) (*scm.Organization, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *organizationService) FindMembership(ctx context.Context, name, username string) (*scm.Membership, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *organizationService) List(ctx context.Context, opts scm.ListOptions) ([]*scm.Organization, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
//
// native data structures
//
type org struct {
Name string `json:"username"`
Avatar string `json:"avatar_url"`
}
//
// native data structure conversion
//
func convertOrgList(from []*org) []*scm.Organization {
to := []*scm.Organization{}
for _, v := range from {
to = append(to, convertOrg(v))
}
return to
}
func convertOrg(from *org) *scm.Organization {
return &scm.Organization{
Name: from.Name,
Avatar: from.Avatar,
}
}

@ -0,0 +1,5 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness

@ -0,0 +1,157 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"fmt"
"time"
"github.com/drone/go-scm/scm"
)
type pullService struct {
client *wrapper
}
func (s *pullService) Find(ctx context.Context, repo string, index int) (*scm.PullRequest, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *pullService) FindComment(context.Context, string, int, int) (*scm.Comment, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *pullService) List(ctx context.Context, repo string, opts scm.PullRequestListOptions) ([]*scm.PullRequest, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *pullService) ListComments(context.Context, string, int, scm.ListOptions) ([]*scm.Comment, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *pullService) ListCommits(context.Context, string, int, scm.ListOptions) ([]*scm.Commit, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *pullService) ListChanges(context.Context, string, int, scm.ListOptions) ([]*scm.Change, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *pullService) Create(ctx context.Context, repo string, input *scm.PullRequestInput) (*scm.PullRequest, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *pullService) CreateComment(context.Context, string, int, *scm.CommentInput) (*scm.Comment, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *pullService) DeleteComment(context.Context, string, int, int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *pullService) Merge(ctx context.Context, repo string, index int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *pullService) Close(context.Context, string, int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
//
// native data structures
//
type pr struct {
ID int `json:"id"`
Number int `json:"number"`
User user `json:"user"`
Title string `json:"title"`
Body string `json:"body"`
State string `json:"state"`
HeadBranch string `json:"head_branch"`
HeadRepo repository `json:"head_repo"`
Head reference `json:"head"`
BaseBranch string `json:"base_branch"`
BaseRepo repository `json:"base_repo"`
Base reference `json:"base"`
HTMLURL string `json:"html_url"`
DiffURL string `json:"diff_url"`
Mergeable bool `json:"mergeable"`
Merged bool `json:"merged"`
Created time.Time `json:"created_at"`
Updated time.Time `json:"updated_at"`
Labels []struct {
Name string `json:"name"`
Color string `json:"color"`
} `json:"labels"`
}
type reference struct {
Repo repository `json:"repo"`
Name string `json:"ref"`
Sha string `json:"sha"`
}
type prInput struct {
Title string `json:"title"`
Body string `json:"body"`
Head string `json:"head"`
Base string `json:"base"`
}
//
// native data structure conversion
//
func convertPullRequests(src []*pr) []*scm.PullRequest {
dst := []*scm.PullRequest{}
for _, v := range src {
dst = append(dst, convertPullRequest(v))
}
return dst
}
func convertPullRequest(src *pr) *scm.PullRequest {
var labels []scm.Label
for _, label := range src.Labels {
labels = append(labels, scm.Label{
Name: label.Name,
Color: label.Color,
})
}
return &scm.PullRequest{
Number: src.Number,
Title: src.Title,
Body: src.Body,
Sha: src.Head.Sha,
Source: src.Head.Name,
Target: src.Base.Name,
Link: src.HTMLURL,
Diff: src.DiffURL,
Fork: src.Base.Repo.FullName,
Ref: fmt.Sprintf("refs/pull/%d/head", src.Number),
Closed: src.State == "closed",
Author: *convertUser(&src.User),
Merged: src.Merged,
Created: src.Created,
Updated: src.Updated,
Labels: labels,
}
}
func convertPullRequestFromIssue(src *issue) *scm.PullRequest {
return &scm.PullRequest{
Number: src.Number,
Title: src.Title,
Body: src.Body,
Closed: src.State == "closed",
Author: *convertUser(&src.User),
Merged: src.PullRequest.Merged,
Created: src.Created,
Updated: src.Updated,
}
}

@ -0,0 +1,5 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness

@ -0,0 +1,112 @@
package harness
import (
"context"
"github.com/drone/go-scm/scm"
"github.com/drone/go-scm/scm/driver/internal/null"
)
type releaseService struct {
client *wrapper
}
func (s *releaseService) Find(ctx context.Context, repo string, id int) (*scm.Release, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *releaseService) FindByTag(ctx context.Context, repo string, tag string) (*scm.Release, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *releaseService) List(ctx context.Context, repo string, opts scm.ReleaseListOptions) ([]*scm.Release, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *releaseService) Create(ctx context.Context, repo string, input *scm.ReleaseInput) (*scm.Release, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *releaseService) Delete(ctx context.Context, repo string, id int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *releaseService) DeleteByTag(ctx context.Context, repo string, tag string) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}
func (s *releaseService) Update(ctx context.Context, repo string, id int, input *scm.ReleaseInput) (*scm.Release, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *releaseService) UpdateByTag(ctx context.Context, repo string, tag string, input *scm.ReleaseInput) (*scm.Release, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
type ReleaseInput struct {
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
}
// release represents a repository release
type release struct {
ID int64 `json:"id"`
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"name"`
Note string `json:"body"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
TarURL string `json:"tarball_url"`
ZipURL string `json:"zipball_url"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
CreatedAt null.Time `json:"created_at"`
PublishedAt null.Time `json:"published_at"`
Publisher *user `json:"author"`
Attachments []*Attachment `json:"assets"`
}
type Attachment struct {
ID int64 `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
DownloadCount int64 `json:"download_count"`
Created null.Time `json:"created_at"`
UUID string `json:"uuid"`
DownloadURL string `json:"browser_download_url"`
}
func convertRelease(src *release) *scm.Release {
return &scm.Release{
ID: int(src.ID),
Title: src.Title,
Description: src.Note,
Link: convertAPIURLToHTMLURL(src.URL, src.TagName),
Tag: src.TagName,
Commitish: src.Target,
Draft: src.IsDraft,
Prerelease: src.IsPrerelease,
Created: src.CreatedAt.ValueOrZero(),
Published: src.PublishedAt.ValueOrZero(),
}
}
func convertReleaseList(src []*release) []*scm.Release {
var dst []*scm.Release
for _, v := range src {
dst = append(dst, convertRelease(v))
}
return dst
}
func releaseListOptionsToGiteaListOptions(in scm.ReleaseListOptions) ListOptions {
return ListOptions{
Page: in.Page,
PageSize: in.Size,
}
}

@ -0,0 +1,273 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"fmt"
"net/url"
"strconv"
"time"
"github.com/drone/go-scm/scm"
)
type repositoryService struct {
client *wrapper
}
func (s *repositoryService) Find(ctx context.Context, repo string) (*scm.Repository, *scm.Response, error) {
path := fmt.Sprintf("api/v1/repos/%s", repo)
out := new(repository)
res, err := s.client.do(ctx, "GET", path, nil, out)
return convertRepository(out), res, err
}
func (s *repositoryService) FindHook(ctx context.Context, repo string, id string) (*scm.Hook, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *repositoryService) FindPerms(ctx context.Context, repo string) (*scm.Perm, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *repositoryService) List(ctx context.Context, opts scm.ListOptions) ([]*scm.Repository, *scm.Response, error) {
path := fmt.Sprintf("api/v1/user/repos?%s", encodeListOptions(opts))
out := []*repository{}
res, err := s.client.do(ctx, "GET", path, nil, &out)
return convertRepositoryList(out), res, err
}
func (s *repositoryService) ListHooks(ctx context.Context, repo string, opts scm.ListOptions) ([]*scm.Hook, *scm.Response, error) {
path := fmt.Sprintf("api/v1/repos/%s/hooks?%s", repo, encodeListOptions(opts))
out := []*hook{}
res, err := s.client.do(ctx, "GET", path, nil, &out)
return convertHookList(out), res, err
}
func (s *repositoryService) ListStatus(ctx context.Context, repo string, ref string, opts scm.ListOptions) ([]*scm.Status, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *repositoryService) CreateHook(ctx context.Context, repo string, input *scm.HookInput) (*scm.Hook, *scm.Response, error) {
target, err := url.Parse(input.Target)
if err != nil {
return nil, nil, err
}
params := target.Query()
params.Set("secret", input.Secret)
target.RawQuery = params.Encode()
path := fmt.Sprintf("api/v1/repos/%s/hooks", repo)
in := new(hook)
in.Type = "gitea"
in.Active = true
in.Config.Secret = input.Secret
in.Config.ContentType = "json"
in.Config.URL = target.String()
in.Events = append(
input.NativeEvents,
convertHookEvent(input.Events)...,
)
out := new(hook)
res, err := s.client.do(ctx, "POST", path, in, out)
return convertHook(out), res, err
}
func (s *repositoryService) CreateStatus(ctx context.Context, repo string, ref string, input *scm.StatusInput) (*scm.Status, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *repositoryService) UpdateHook(ctx context.Context, repo, id string, input *scm.HookInput) (*scm.Hook, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *repositoryService) DeleteHook(ctx context.Context, repo string, id string) (*scm.Response, error) {
path := fmt.Sprintf("api/v1/repos/%s/hooks/%s", repo, id)
return s.client.do(ctx, "DELETE", path, nil, nil)
}
//
// native data structures
//
type (
// gitea repository resource.
repository struct {
ID int `json:"id"`
Owner user `json:"owner"`
Name string `json:"name"`
FullName string `json:"full_name"`
Private bool `json:"private"`
Fork bool `json:"fork"`
HTMLURL string `json:"html_url"`
SSHURL string `json:"ssh_url"`
CloneURL string `json:"clone_url"`
DefaultBranch string `json:"default_branch"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Permissions perm `json:"permissions"`
Archived bool `json:"archived"`
}
// gitea permissions details.
perm struct {
Admin bool `json:"admin"`
Push bool `json:"push"`
Pull bool `json:"pull"`
}
// gitea hook resource.
hook struct {
ID int `json:"id"`
Type string `json:"type"`
Events []string `json:"events"`
Active bool `json:"active"`
Config hookConfig `json:"config"`
}
// gitea hook configuration details.
hookConfig struct {
URL string `json:"url"`
ContentType string `json:"content_type"`
Secret string `json:"secret"`
}
// gitea status resource.
status struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
State string `json:"status"`
TargetURL string `json:"target_url"`
Description string `json:"description"`
Context string `json:"context"`
}
// gitea status creation request.
statusInput struct {
State string `json:"state"`
TargetURL string `json:"target_url"`
Description string `json:"description"`
Context string `json:"context"`
}
)
//
// native data structure conversion
//
func convertRepositoryList(src []*repository) []*scm.Repository {
var dst []*scm.Repository
for _, v := range src {
dst = append(dst, convertRepository(v))
}
return dst
}
func convertRepository(src *repository) *scm.Repository {
return &scm.Repository{
ID: strconv.Itoa(src.ID),
Namespace: userLogin(&src.Owner),
Name: src.Name,
Perm: convertPerm(src.Permissions),
Branch: src.DefaultBranch,
Private: src.Private,
Clone: src.CloneURL,
CloneSSH: src.SSHURL,
Link: src.HTMLURL,
Archived: src.Archived,
}
}
func convertPerm(src perm) *scm.Perm {
return &scm.Perm{
Push: src.Push,
Pull: src.Pull,
Admin: src.Admin,
}
}
func convertHookList(src []*hook) []*scm.Hook {
var dst []*scm.Hook
for _, v := range src {
dst = append(dst, convertHook(v))
}
return dst
}
func convertHook(from *hook) *scm.Hook {
return &scm.Hook{
ID: strconv.Itoa(from.ID),
Active: from.Active,
Target: from.Config.URL,
Events: from.Events,
}
}
func convertHookEvent(from scm.HookEvents) []string {
var events []string
if from.PullRequest {
events = append(events, "pull_request")
}
if from.Issue {
events = append(events, "issues")
}
if from.IssueComment || from.PullRequestComment {
events = append(events, "issue_comment")
}
if from.Branch || from.Tag {
events = append(events, "create")
events = append(events, "delete")
}
if from.Push {
events = append(events, "push")
}
return events
}
func convertStatusList(src []*status) []*scm.Status {
var dst []*scm.Status
for _, v := range src {
dst = append(dst, convertStatus(v))
}
return dst
}
func convertStatus(from *status) *scm.Status {
return &scm.Status{
State: convertState(from.State),
Label: from.Context,
Desc: from.Description,
Target: from.TargetURL,
}
}
func convertState(from string) scm.State {
switch from {
case "error":
return scm.StateError
case "failure":
return scm.StateFailure
case "pending":
return scm.StatePending
case "success":
return scm.StateSuccess
default:
return scm.StateUnknown
}
}
func convertFromState(from scm.State) string {
switch from {
case scm.StatePending, scm.StateRunning:
return "pending"
case scm.StateSuccess:
return "success"
case scm.StateFailure:
return "failure"
default:
return "error"
}
}

@ -0,0 +1,9 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
//
// repository sub-tests
//

@ -0,0 +1,31 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"github.com/drone/go-scm/scm"
)
type reviewService struct {
client *wrapper
}
func (s *reviewService) Find(ctx context.Context, repo string, number, id int) (*scm.Review, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *reviewService) List(ctx context.Context, repo string, number int, opts scm.ListOptions) ([]*scm.Review, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *reviewService) Create(ctx context.Context, repo string, number int, input *scm.ReviewInput) (*scm.Review, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *reviewService) Delete(ctx context.Context, repo string, number, id int) (*scm.Response, error) {
return nil, scm.ErrNotSupported
}

@ -0,0 +1,5 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness

@ -0,0 +1,30 @@
{
"type": "file",
"sha": "7839122fc2ec4af9d5a6c14c779a82a05c5769b5",
"name": "README.md",
"path": "README.md",
"latest_commit": {
"sha": "98189d5cf2a751a6246c24a72945ba70839f1b20",
"title": "initial commit",
"message": "initial commit",
"author": {
"identity": {
"name": "gitness",
"email": "system@gitness"
},
"when": "2023-02-02T14:12:13Z"
},
"committer": {
"identity": {
"name": "gitness",
"email": "system@gitness"
},
"when": "2023-02-02T14:12:13Z"
}
},
"content": {
"encoding": "base64",
"data": "ZGVtbw0KdGVzdCBzdHJpbmc=",
"size": 23
}
}

@ -0,0 +1,79 @@
{
"type": "dir",
"sha": "4fdc5acb2f9074a6d6ec826f01518fb27fe20a06",
"name": "",
"path": "",
"latest_commit": {
"sha": "4381eecb9dd29c34972087c4185f392c20d9f7c0",
"title": "move to version 1.1.2",
"message": "move to version 1.1.2",
"author": {
"identity": {
"name": "Johannes Batzill",
"email": "johannes.batzill@harness.io"
},
"when": "2023-02-01T02:27:42Z"
},
"committer": {
"identity": {
"name": "Johannes Batzill",
"email": "johannes.batzill@harness.io"
},
"when": "2023-02-01T02:27:42Z"
}
},
"content": {
"entries": [
{
"type": "file",
"sha": "41fdf994e79e42bd4136c7e3004367f6d21d800d",
"name": ".gitignore",
"path": ".gitignore",
"latest_commit": {
"sha": "f8de11b6f3e4499889e16b989977c66c84ad02c9",
"title": "add initial version of demo application",
"message": "add initial version of demo application",
"author": {
"identity": {
"name": "Johannes Batzill",
"email": "johannes.batzill@harness.io"
},
"when": "2023-02-01T00:08:20Z"
},
"committer": {
"identity": {
"name": "Johannes Batzill",
"email": "johannes.batzill@harness.io"
},
"when": "2023-02-01T00:08:20Z"
}
}
},
{
"type": "file",
"sha": "261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64",
"name": "LICENSE",
"path": "LICENSE",
"latest_commit": {
"sha": "a2a1bb3664f096e69f4b15b80f4650cc9f8ae913",
"title": "Initial commit",
"message": "Initial commit",
"author": {
"identity": {
"name": "Johannes Batzill",
"email": "johannes.batzill@harness.io"
},
"when": "2023-01-31T23:54:24Z"
},
"committer": {
"identity": {
"name": "GitHub",
"email": "noreply@github.com"
},
"when": "2023-01-31T23:54:24Z"
}
}
}
]
}
}

@ -0,0 +1,14 @@
[
{
"Path": ".gitignore",
"Sha": "f8de11b6f3e4499889e16b989977c66c84ad02c9",
"BlobID": "41fdf994e79e42bd4136c7e3004367f6d21d800d",
"kind": "file"
},
{
"Path": "LICENSE",
"Sha": "a2a1bb3664f096e69f4b15b80f4650cc9f8ae913",
"BlobID": "261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64",
"kind": "file"
}
]

@ -0,0 +1,172 @@
{
"secret": "12345",
"action": "opened",
"number": 1,
"pull_request": {
"id": 473,
"url": "",
"number": 1,
"user": {
"id": 6641,
"login": "jcitizen",
"full_name": "",
"email": "jane@example.com",
"avatar_url": "https://secure.gravatar.com/avatar/66f07ff48e6a9cb393de7a34e03bb52a?d=identicon",
"language": "en-US",
"username": "jcitizen"
},
"title": "Add License File",
"body": "Using a BSD License",
"labels": [],
"milestone": null,
"assignee": null,
"assignees": null,
"state": "open",
"comments": 0,
"html_url": "https://try.gitea.io/jcitizen/my-repo/pulls/1",
"diff_url": "https://try.gitea.io/jcitizen/my-repo/pulls/1.diff",
"patch_url": "https://try.gitea.io/jcitizen/my-repo/pulls/1.patch",
"mergeable": true,
"merged": false,
"merged_at": null,
"merge_commit_sha": null,
"merged_by": null,
"base": {
"label": "master",
"ref": "master",
"sha": "39af58f1eff02aa308e16913e887c8d50362b474",
"repo_id": 6589,
"repo": {
"id": 6589,
"owner": {
"id": 6641,
"login": "jcitizen",
"full_name": "",
"email": "jane@example.com",
"avatar_url": "https://secure.gravatar.com/avatar/66f07ff48e6a9cb393de7a34e03bb52a?d=identicon",
"language": "en-US",
"username": "jcitizen"
},
"name": "my-repo",
"full_name": "jcitizen/my-repo",
"description": "",
"empty": false,
"private": false,
"fork": false,
"parent": null,
"mirror": false,
"size": 64,
"html_url": "https://try.gitea.io/jcitizen/my-repo",
"ssh_url": "git@try.gitea.io:jcitizen/my-repo.git",
"clone_url": "https://try.gitea.io/jcitizen/my-repo.git",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"default_branch": "master",
"created_at": "2018-07-06T00:08:02Z",
"updated_at": "2018-07-06T01:06:56Z",
"permissions": {
"admin": false,
"push": false,
"pull": false
}
}
},
"head": {
"label": "feature",
"ref": "feature",
"sha": "2eba238e33607c1fa49253182e9fff42baafa1eb",
"repo_id": 6589,
"repo": {
"id": 6589,
"owner": {
"id": 6641,
"login": "jcitizen",
"full_name": "",
"email": "jane@example.com",
"avatar_url": "https://secure.gravatar.com/avatar/66f07ff48e6a9cb393de7a34e03bb52a?d=identicon",
"language": "en-US",
"username": "jcitizen"
},
"name": "my-repo",
"full_name": "jcitizen/my-repo",
"description": "",
"empty": false,
"private": false,
"fork": false,
"parent": null,
"mirror": false,
"size": 64,
"html_url": "https://try.gitea.io/jcitizen/my-repo",
"ssh_url": "git@try.gitea.io:jcitizen/my-repo.git",
"clone_url": "https://try.gitea.io/jcitizen/my-repo.git",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"default_branch": "master",
"created_at": "2018-07-06T00:08:02Z",
"updated_at": "2018-07-06T01:06:56Z",
"permissions": {
"admin": false,
"push": false,
"pull": false
}
}
},
"merge_base": "39af58f1eff02aa308e16913e887c8d50362b474",
"due_date": null,
"created_at": "2018-07-06T00:37:47Z",
"updated_at": "2018-07-06T00:37:47Z",
"closed_at": null
},
"repository": {
"id": 6589,
"owner": {
"id": 6641,
"login": "jcitizen",
"full_name": "",
"email": "jane@example.com",
"avatar_url": "https://secure.gravatar.com/avatar/66f07ff48e6a9cb393de7a34e03bb52a?d=identicon",
"language": "en-US",
"username": "jcitizen"
},
"name": "my-repo",
"full_name": "jcitizen/my-repo",
"description": "",
"empty": false,
"private": false,
"fork": false,
"parent": null,
"mirror": false,
"size": 64,
"html_url": "https://try.gitea.io/jcitizen/my-repo",
"ssh_url": "git@try.gitea.io:jcitizen/my-repo.git",
"clone_url": "https://try.gitea.io/jcitizen/my-repo.git",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"default_branch": "master",
"created_at": "2018-07-06T00:08:02Z",
"updated_at": "2018-07-06T01:06:56Z",
"permissions": {
"admin": false,
"push": false,
"pull": false
}
},
"sender": {
"id": 6641,
"login": "jcitizen",
"full_name": "",
"email": "jane@example.com",
"avatar_url": "https://secure.gravatar.com/avatar/66f07ff48e6a9cb393de7a34e03bb52a?d=identicon",
"language": "en-US",
"username": "jcitizen"
}
}

@ -0,0 +1,47 @@
{
"Action": "opened",
"Repo": {
"ID": "6589",
"Namespace": "jcitizen",
"Name": "my-repo",
"Perm": {
"Pull": false,
"Push": false,
"Admin": false
},
"Branch": "master",
"Private": false,
"Clone": "https://try.gitea.io/jcitizen/my-repo.git",
"CloneSSH": "git@try.gitea.io:jcitizen/my-repo.git",
"Link": "https://try.gitea.io/jcitizen/my-repo",
"Created": "0001-01-01T00:00:00Z",
"Updated": "0001-01-01T00:00:00Z"
},
"PullRequest": {
"Number": 1,
"Title": "Add License File",
"Body": "Using a BSD License",
"Sha": "2eba238e33607c1fa49253182e9fff42baafa1eb",
"Ref": "refs/pull/1/head",
"Source": "feature",
"Target": "master",
"Fork": "jcitizen/my-repo",
"Link": "https://try.gitea.io/jcitizen/my-repo/pulls/1",
"Closed": false,
"Merged": false,
"Author": {
"Login": "jcitizen",
"Name": "",
"Email": "jane@example.com",
"Avatar": "https://secure.gravatar.com/avatar/66f07ff48e6a9cb393de7a34e03bb52a?d=identicon"
},
"Created": "0001-01-01T00:00:00Z",
"Updated": "0001-01-01T00:00:00Z"
},
"Sender": {
"Login": "jcitizen",
"Name": "",
"Email": "jane@example.com",
"Avatar": "https://secure.gravatar.com/avatar/66f07ff48e6a9cb393de7a34e03bb52a?d=identicon"
}
}

@ -0,0 +1,66 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"context"
"github.com/drone/go-scm/scm"
)
type userService struct {
client *wrapper
}
func (s *userService) Find(ctx context.Context) (*scm.User, *scm.Response, error) {
out := new(user)
res, err := s.client.do(ctx, "GET", "api/v1/user", nil, out)
return convertUser(out), res, err
}
func (s *userService) FindLogin(ctx context.Context, login string) (*scm.User, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
func (s *userService) FindEmail(ctx context.Context) (string, *scm.Response, error) {
return "", nil, scm.ErrNotSupported
}
func (s *userService) ListEmail(context.Context, scm.ListOptions) ([]*scm.Email, *scm.Response, error) {
return nil, nil, scm.ErrNotSupported
}
//
// native data structures
//
type user struct {
ID int `json:"id"`
Login string `json:"login"`
Username string `json:"username"`
Fullname string `json:"full_name"`
Email string `json:"email"`
Avatar string `json:"avatar_url"`
}
//
// native data structure conversion
//
func convertUser(src *user) *scm.User {
return &scm.User{
Login: userLogin(src),
Avatar: src.Avatar,
Email: src.Email,
Name: src.Fullname,
}
}
func userLogin(src *user) string {
if src.Username != "" {
return src.Username
}
return src.Login
}

@ -0,0 +1,5 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness

@ -0,0 +1,103 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/drone/go-scm/scm"
)
func encodeListOptions(opts scm.ListOptions) string {
params := url.Values{}
if opts.Page != 0 {
params.Set("page", strconv.Itoa(opts.Page))
}
if opts.Size != 0 {
params.Set("limit", strconv.Itoa(opts.Size))
}
return params.Encode()
}
func encodeIssueListOptions(opts scm.IssueListOptions) string {
params := url.Values{}
if opts.Page != 0 {
params.Set("page", strconv.Itoa(opts.Page))
}
if opts.Size != 0 {
params.Set("limit", strconv.Itoa(opts.Size))
}
if opts.Open && opts.Closed {
params.Set("state", "all")
} else if opts.Closed {
params.Set("state", "closed")
}
return params.Encode()
}
func encodePullRequestListOptions(opts scm.PullRequestListOptions) string {
params := url.Values{}
if opts.Page != 0 {
params.Set("page", strconv.Itoa(opts.Page))
}
if opts.Size != 0 {
params.Set("limit", strconv.Itoa(opts.Size))
}
if opts.Open && opts.Closed {
params.Set("state", "all")
} else if opts.Closed {
params.Set("state", "closed")
}
return params.Encode()
}
// convertAPIURLToHTMLURL converts an release API endpoint into a html endpoint
func convertAPIURLToHTMLURL(apiURL string, tagName string) string {
// "url": "https://try.gitea.com/api/v1/repos/octocat/Hello-World/123",
// "html_url": "https://try.gitea.com/octocat/Hello-World/releases/tag/v1.0.0",
// the url field is the API url, not the html url, so until go-sdk v0.13.3, build it ourselves
link, err := url.Parse(apiURL)
if err != nil {
return ""
}
pathParts := strings.Split(link.Path, "/")
if len(pathParts) != 7 {
return ""
}
link.Path = fmt.Sprintf("/%s/%s/releases/tag/%s", pathParts[4], pathParts[5], tagName)
return link.String()
}
func encodeMilestoneListOptions(opts scm.MilestoneListOptions) string {
params := url.Values{}
if opts.Page != 0 {
params.Set("page", strconv.Itoa(opts.Page))
}
if opts.Size != 0 {
params.Set("per_page", strconv.Itoa(opts.Size))
}
if opts.Open && opts.Closed {
params.Set("state", "all")
} else if opts.Closed {
params.Set("state", "closed")
}
return params.Encode()
}
type ListOptions struct {
Page int
PageSize int
}
func encodeReleaseListOptions(o ListOptions) string {
query := make(url.Values)
query.Add("page", fmt.Sprintf("%d", o.Page))
query.Add("limit", fmt.Sprintf("%d", o.PageSize))
return query.Encode()
}

@ -0,0 +1,81 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"testing"
"github.com/drone/go-scm/scm"
)
func Test_encodeListOptions(t *testing.T) {
opts := scm.ListOptions{
Page: 10,
Size: 30,
}
want := "limit=30&page=10"
got := encodeListOptions(opts)
if got != want {
t.Errorf("Want encoded list options %q, got %q", want, got)
}
}
func Test_encodeIssueListOptions(t *testing.T) {
opts := scm.IssueListOptions{
Page: 10,
Size: 30,
Open: true,
Closed: true,
}
want := "limit=30&page=10&state=all"
got := encodeIssueListOptions(opts)
if got != want {
t.Errorf("Want encoded issue list options %q, got %q", want, got)
}
}
func Test_encodeIssueListOptions_Closed(t *testing.T) {
opts := scm.IssueListOptions{
Page: 10,
Size: 30,
Open: false,
Closed: true,
}
want := "limit=30&page=10&state=closed"
got := encodeIssueListOptions(opts)
if got != want {
t.Errorf("Want encoded issue list options %q, got %q", want, got)
}
}
func Test_encodePullRequestListOptions(t *testing.T) {
t.Parallel()
opts := scm.PullRequestListOptions{
Page: 10,
Size: 30,
Open: true,
Closed: true,
}
want := "limit=30&page=10&state=all"
got := encodePullRequestListOptions(opts)
if got != want {
t.Errorf("Want encoded pr list options %q, got %q", want, got)
}
}
func Test_encodePullRequestListOptions_Closed(t *testing.T) {
t.Parallel()
opts := scm.PullRequestListOptions{
Page: 10,
Size: 30,
Open: false,
Closed: true,
}
want := "limit=30&page=10&state=closed"
got := encodePullRequestListOptions(opts)
if got != want {
t.Errorf("Want encoded pr list options %q, got %q", want, got)
}
}

@ -0,0 +1,361 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/drone/go-scm/scm"
"github.com/drone/go-scm/scm/driver/internal/hmac"
)
type webhookService struct {
client *wrapper
}
func (s *webhookService) Parse(req *http.Request, fn scm.SecretFunc) (scm.Webhook, error) {
data, err := ioutil.ReadAll(
io.LimitReader(req.Body, 10000000),
)
if err != nil {
return nil, err
}
var hook scm.Webhook
switch req.Header.Get("X-Gitea-Event") {
case "push":
hook, err = s.parsePushHook(data)
case "create":
hook, err = s.parseCreateHook(data)
case "delete":
hook, err = s.parseDeleteHook(data)
case "issues":
hook, err = s.parseIssueHook(data)
case "issue_comment":
hook, err = s.parseIssueCommentHook(data)
case "pull_request":
hook, err = s.parsePullRequestHook(data)
default:
return nil, scm.ErrUnknownEvent
}
if err != nil {
return nil, err
}
// get the gitea signature key to verify the payload
// signature. If no key is provided, no validation
// is performed.
key, err := fn(hook)
if err != nil {
return hook, err
} else if key == "" {
return hook, nil
}
secret := req.FormValue("secret")
signature := req.Header.Get("X-Gitea-Signature")
// fail if no signature passed
if signature == "" && secret == "" {
return hook, scm.ErrSignatureInvalid
}
// test signature if header not set and secret is in payload
if signature == "" && secret != "" && secret != key {
return hook, scm.ErrSignatureInvalid
}
// test signature using header
if signature != "" && !hmac.Validate(sha256.New, data, []byte(key), signature) {
return hook, scm.ErrSignatureInvalid
}
return hook, nil
}
func (s *webhookService) parsePushHook(data []byte) (scm.Webhook, error) {
dst := new(pushHook)
err := json.Unmarshal(data, dst)
return convertPushHook(dst), err
}
func (s *webhookService) parseCreateHook(data []byte) (scm.Webhook, error) {
dst := new(createHook)
err := json.Unmarshal(data, dst)
switch dst.RefType {
case "tag":
return convertTagHook(dst, scm.ActionCreate), err
case "branch":
return convertBranchHook(dst, scm.ActionCreate), err
default:
return nil, scm.ErrUnknownEvent
}
}
func (s *webhookService) parseDeleteHook(data []byte) (scm.Webhook, error) {
dst := new(createHook)
err := json.Unmarshal(data, dst)
switch dst.RefType {
case "tag":
return convertTagHook(dst, scm.ActionDelete), err
case "branch":
return convertBranchHook(dst, scm.ActionDelete), err
default:
return nil, scm.ErrUnknownEvent
}
}
func (s *webhookService) parseIssueHook(data []byte) (scm.Webhook, error) {
dst := new(issueHook)
err := json.Unmarshal(data, dst)
return convertIssueHook(dst), err
}
func (s *webhookService) parseIssueCommentHook(data []byte) (scm.Webhook, error) {
dst := new(issueHook)
err := json.Unmarshal(data, dst)
if dst.Issue.PullRequest != nil {
return convertPullRequestCommentHook(dst), err
}
return convertIssueCommentHook(dst), err
}
func (s *webhookService) parsePullRequestHook(data []byte) (scm.Webhook, error) {
dst := new(pullRequestHook)
err := json.Unmarshal(data, dst)
return convertPullRequestHook(dst), err
}
//
// native data structures
//
type (
// gitea push webhook payload
pushHook struct {
Ref string `json:"ref"`
Before string `json:"before"`
After string `json:"after"`
Compare string `json:"compare_url"`
Commits []commit `json:"commits"`
Repository repository `json:"repository"`
Pusher user `json:"pusher"`
Sender user `json:"sender"`
}
// gitea create webhook payload
createHook struct {
Ref string `json:"ref"`
RefType string `json:"ref_type"`
Sha string `json:"sha"`
DefaultBranch string `json:"default_branch"`
Repository repository `json:"repository"`
Sender user `json:"sender"`
}
// gitea issue webhook payload
issueHook struct {
Action string `json:"action"`
Issue issue `json:"issue"`
Comment issueComment `json:"comment"`
Repository repository `json:"repository"`
Sender user `json:"sender"`
}
// gitea pull request webhook payload
pullRequestHook struct {
Action string `json:"action"`
Number int `json:"number"`
PullRequest pr `json:"pull_request"`
Repository repository `json:"repository"`
Sender user `json:"sender"`
}
)
//
// native data structure conversion
//
func convertTagHook(dst *createHook, action scm.Action) *scm.TagHook {
return &scm.TagHook{
Action: action,
Ref: scm.Reference{
Name: dst.Ref,
Sha: dst.Sha,
},
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertBranchHook(dst *createHook, action scm.Action) *scm.BranchHook {
return &scm.BranchHook{
Action: action,
Ref: scm.Reference{
Name: dst.Ref,
},
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertPushHook(dst *pushHook) *scm.PushHook {
if len(dst.Commits) > 0 {
var commits []scm.Commit
for _, c := range dst.Commits {
commits = append(commits,
scm.Commit{
Sha: c.ID,
Message: c.Message,
Link: c.URL,
Author: scm.Signature{
Login: c.Author.Username,
Email: c.Author.Email,
Name: c.Author.Name,
Date: c.Timestamp,
},
Committer: scm.Signature{
Login: c.Committer.Username,
Email: c.Committer.Email,
Name: c.Committer.Name,
Date: c.Timestamp,
},
})
}
return &scm.PushHook{
Ref: dst.Ref,
Before: dst.Before,
Commit: scm.Commit{
Sha: dst.After,
Message: dst.Commits[0].Message,
Link: dst.Compare,
Author: scm.Signature{
Login: dst.Commits[0].Author.Username,
Email: dst.Commits[0].Author.Email,
Name: dst.Commits[0].Author.Name,
Date: dst.Commits[0].Timestamp,
},
Committer: scm.Signature{
Login: dst.Commits[0].Committer.Username,
Email: dst.Commits[0].Committer.Email,
Name: dst.Commits[0].Committer.Name,
Date: dst.Commits[0].Timestamp,
},
},
Commits: commits,
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
return &scm.PushHook{
Ref: dst.Ref,
Commit: scm.Commit{
Sha: dst.After,
Link: dst.Compare,
Author: scm.Signature{
Login: dst.Pusher.Login,
Email: dst.Pusher.Email,
Name: dst.Pusher.Fullname,
},
Committer: scm.Signature{
Login: dst.Pusher.Login,
Email: dst.Pusher.Email,
Name: dst.Pusher.Fullname,
},
},
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertPullRequestHook(dst *pullRequestHook) *scm.PullRequestHook {
return &scm.PullRequestHook{
Action: convertAction(dst.Action),
PullRequest: scm.PullRequest{
Number: dst.PullRequest.Number,
Title: dst.PullRequest.Title,
Body: dst.PullRequest.Body,
Closed: dst.PullRequest.State == "closed",
Author: scm.User{
Login: dst.PullRequest.User.Login,
Email: dst.PullRequest.User.Email,
Avatar: dst.PullRequest.User.Avatar,
},
Merged: dst.PullRequest.Merged,
// Created: nil,
// Updated: nil,
Source: dst.PullRequest.Head.Name,
Target: dst.PullRequest.Base.Name,
Fork: dst.PullRequest.Head.Repo.FullName,
Link: dst.PullRequest.HTMLURL,
Ref: fmt.Sprintf("refs/pull/%d/head", dst.PullRequest.Number),
Sha: dst.PullRequest.Head.Sha,
},
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertPullRequestCommentHook(dst *issueHook) *scm.PullRequestCommentHook {
return &scm.PullRequestCommentHook{
Action: convertAction(dst.Action),
PullRequest: *convertPullRequestFromIssue(&dst.Issue),
Comment: *convertIssueComment(&dst.Comment),
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertIssueHook(dst *issueHook) *scm.IssueHook {
return &scm.IssueHook{
Action: convertAction(dst.Action),
Issue: *convertIssue(&dst.Issue),
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertIssueCommentHook(dst *issueHook) *scm.IssueCommentHook {
return &scm.IssueCommentHook{
Action: convertAction(dst.Action),
Issue: *convertIssue(&dst.Issue),
Comment: *convertIssueComment(&dst.Comment),
Repo: *convertRepository(&dst.Repository),
Sender: *convertUser(&dst.Sender),
}
}
func convertAction(src string) (action scm.Action) {
switch src {
case "create", "created":
return scm.ActionCreate
case "delete", "deleted":
return scm.ActionDelete
case "update", "updated", "edit", "edited":
return scm.ActionUpdate
case "open", "opened":
return scm.ActionOpen
case "reopen", "reopened":
return scm.ActionReopen
case "close", "closed":
return scm.ActionClose
case "label", "labeled":
return scm.ActionLabel
case "unlabel", "unlabeled":
return scm.ActionUnlabel
case "merge", "merged":
return scm.ActionMerge
case "synchronize", "synchronized":
return scm.ActionSync
default:
return
}
}

@ -0,0 +1,5 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package harness
Loading…
Cancel
Save