Skip to content

Commit

Permalink
Merge pull request #101 from drone/bitbucket_create
Browse files Browse the repository at this point in the history
(feat) normalise sha in content, add bitbucket create/update
  • Loading branch information
TP Honey committed Apr 7, 2021
2 parents 8301645 + b514328 commit 773965b
Show file tree
Hide file tree
Showing 28 changed files with 1,350 additions and 49 deletions.
3 changes: 1 addition & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/h2non/gock v1.0.9 h1:17gCehSo8ZOgEsFKpQgqHiR7VLyjxdAG3lkhVvO9QZU=
github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE=
github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE=
12 changes: 5 additions & 7 deletions scm/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ import "context"
type (
// Content stores the contents of a repository file.
Content struct {
Path string
Data []byte

// the hash of the blob, sometimes referred to
// as the blob id or blob sha. this is the equivalent
// to running the git hash-object command.
Hash string
Path string
Data []byte
Sha string
BlobID string
}

// ContentParams provide parameters for creating and
Expand All @@ -26,6 +23,7 @@ type (
Message string
Data []byte
Sha string
BlobID string
Signature Signature
}

Expand Down
43 changes: 38 additions & 5 deletions scm/driver/bitbucket/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"encoding/json"
"io"
"mime/multipart"
"net/url"
"strings"

Expand Down Expand Up @@ -65,12 +66,44 @@ func (c *wrapper) do(ctx context.Context, method, path string, in, out interface
// 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"},
// create or update content
switch content := in.(type) {
case *contentCreateUpdate:
// add the content to the multipart
myReader := strings.NewReader(string(content.Content))
var b bytes.Buffer
w := multipart.NewWriter(&b)
var fw io.Writer
fw, _ = w.CreateFormFile(content.Files, "")
_, _ = io.Copy(fw, myReader)
// add the other fields
if content.Message != "" {
_ = w.WriteField("message", content.Message)
}
if content.Branch != "" {
_ = w.WriteField("branch", content.Branch)
}
if content.Sha != "" {
_ = w.WriteField("parents", content.Sha)
}
if content.Author != "" {
_ = w.WriteField("author", content.Author)
}
w.Close()
// write the multipart response to the body
req.Body = &b
// write the content type that contains the length of the multipart
req.Header = map[string][]string{
"Content-Type": {w.FormDataContentType()},
}
default:
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(in)
req.Header = map[string][]string{
"Content-Type": {"application/json"},
}
req.Body = buf
}
req.Body = buf
}

// execute the http request
Expand Down
57 changes: 53 additions & 4 deletions scm/driver/bitbucket/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,51 @@ func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm
endpoint := fmt.Sprintf("/2.0/repositories/%s/src/%s/%s", repo, ref, path)
out := new(bytes.Buffer)
res, err := s.client.do(ctx, "GET", endpoint, nil, out)
return &scm.Content{
content := &scm.Content{
Path: path,
Data: out.Bytes(),
}, res, err
}
if err != nil {
return content, res, err
}
metaEndpoint := fmt.Sprintf("/2.0/repositories/%s/src/%s/%s?format=meta", repo, ref, path)
metaOut := new(metaContent)
metaRes, metaErr := s.client.do(ctx, "GET", metaEndpoint, nil, metaOut)
if metaErr == nil {
content.Sha = metaOut.Commit.Hash
return content, metaRes, metaErr
} else {
// do not risk that returning an error if getting the meta fails.
return content, res, err
}
}

func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) {
return nil, scm.ErrNotSupported
endpoint := fmt.Sprintf("/2.0/repositories/%s/src", repo)
in := &contentCreateUpdate{
Files: path,
Message: params.Message,
Branch: params.Branch,
Content: params.Data,
Author: fmt.Sprintf("%s <%s>", params.Signature.Name, params.Signature.Email),
}
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) {
return nil, scm.ErrNotSupported
// https://jira.atlassian.com/browse/BCLOUD-20424?error=login_required&error_description=Login+required&state=196d85f7-a181-4b63-babe-0b567858d8f5 ugh :(
endpoint := fmt.Sprintf("/2.0/repositories/%s/src", repo)
in := &contentCreateUpdate{
Files: path,
Message: params.Message,
Branch: params.Branch,
Content: params.Data,
Sha: params.Sha,
Author: fmt.Sprintf("%s <%s>", params.Signature.Name, params.Signature.Email),
}
res, err := s.client.do(ctx, "POST", endpoint, in, nil)
return res, err
}

func (s *contentService) Delete(ctx context.Context, repo, path, ref string) (*scm.Response, error) {
Expand All @@ -57,6 +90,22 @@ type content struct {
Attributes []string `json:"attributes"`
}

type metaContent struct {
Path string `json:"path"`
Commit struct {
Hash string `json:"hash"`
} `json:"commit"`
}

type contentCreateUpdate struct {
Files string `json:"files"`
Branch string `json:"branch"`
Message string `json:"message"`
Content []byte `json:"content"`
Sha string `json:"sha"`
Author string `json:"author"`
}

func convertContentInfoList(from *contents) []*scm.ContentInfo {
to := []*scm.ContentInfo{}
for _, v := range from.Values {
Expand Down
141 changes: 133 additions & 8 deletions scm/driver/bitbucket/content_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ func TestContentFind(t *testing.T) {
Type("text/plain").
File("testdata/content.txt")

gock.New("https://api.bitbucket.org").
MatchParam("format", "meta").
Get("/2.0/repositories/atlassian/atlaskit/src/425863f9dbe56d70c8dcdbf2e4e0805e85591fcc/README").
Reply(200).
Type("application/json").
File("testdata/content.json")

client, _ := New("https://api.bitbucket.org")
got, _, err := client.Contents.Find(context.Background(), "atlassian/atlaskit", "README", "425863f9dbe56d70c8dcdbf2e4e0805e85591fcc")
if err != nil {
Expand All @@ -41,19 +48,137 @@ func TestContentFind(t *testing.T) {
}
}

func TestContentFindNoMeta(t *testing.T) {
defer gock.Off()

gock.New("https://api.bitbucket.org").
Get("/2.0/repositories/atlassian/atlaskit/src/425863f9dbe56d70c8dcdbf2e4e0805e85591fcc/README").
Reply(200).
Type("text/plain").
File("testdata/content.txt")

gock.New("https://api.bitbucket.org").
MatchParam("format", "meta").
Get("/2.0/repositories/atlassian/atlaskit/src/425863f9dbe56d70c8dcdbf2e4e0805e85591fcc/README").
Reply(404).
Type("application/json").
File("testdata/content_fail.json")

client, _ := New("https://api.bitbucket.org")
got, _, err := client.Contents.Find(context.Background(), "atlassian/atlaskit", "README", "425863f9dbe56d70c8dcdbf2e4e0805e85591fcc")
if err != nil {
t.Error(err)
}

want := new(scm.Content)
raw, _ := ioutil.ReadFile("testdata/content.json.fail")
json.Unmarshal(raw, want)

if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("Unexpected Results")
t.Log(diff)
}
}

func TestContentCreate(t *testing.T) {
content := new(contentService)
_, err := content.Create(context.Background(), "atlassian/atlaskit", "README", nil)
if err != scm.ErrNotSupported {
t.Errorf("Expect Not Supported error")
defer gock.Off()

gock.New("https://api.bitbucket.org").
Post("/2.0/repositories/atlassian/atlaskit/src").
Reply(201).
Type("application/json")

params := &scm.ContentParams{
Message: "my commit message",
Data: []byte("bXkgbmV3IGZpbGUgY29udGVudHM="),
Signature: scm.Signature{
Name: "Monalisa Octocat",
Email: "octocat@github.com",
},
}

client := NewDefault()
res, err := client.Contents.Create(
context.Background(),
"atlassian/atlaskit",
"test/hello",
params,
)

if err != nil {
t.Error(err)
return
}

if res.Status != 201 {
t.Errorf("Unexpected Results")
}

}

func TestContentUpdate(t *testing.T) {
content := new(contentService)
_, err := content.Update(context.Background(), "atlassian/atlaskit", "README", nil)
if err != scm.ErrNotSupported {
t.Errorf("Expect Not Supported error")
defer gock.Off()

gock.New("https://api.bitbucket.org").
Post("/2.0/repositories/atlassian/atlaskit/src").
Reply(201).
Type("application/json")

params := &scm.ContentParams{
Message: "my commit message",
Data: []byte("bXkgbmV3IGZpbGUgY29udGVudHM="),
Signature: scm.Signature{
Name: "Monalisa Octocat",
Email: "octocat@github.com",
},
}

client := NewDefault()
res, err := client.Contents.Update(
context.Background(),
"atlassian/atlaskit",
"test/hello",
params,
)

if err != nil {
t.Error(err)
return
}

if res.Status != 201 {
t.Errorf("Unexpected Results")
}
}

func TestContentUpdateBadCommitID(t *testing.T) {
defer gock.Off()

gock.New("https://api.bitbucket.org").
Post("/2.0/repositories/atlassian/atlaskit/src").
Reply(400).
Type("application/json").
File("testdata/content_update.json.fail")

params := &scm.ContentParams{
Message: "my commit message",
Data: []byte("bXkgbmV3IGZpbGUgY29udGVudHM="),
Sha: "bad commit",
Signature: scm.Signature{
Name: "Monalisa Octocat",
Email: "octocat@github.com",
},
}

client := NewDefault()
_, err := client.Contents.Update(
context.Background(),
"atlassian/atlaskit",
"test/hello",
params,
)
if err.Error() != "parents: Commit not found: 1a7eba6c-d4fe-47b7-b767-859abc660efc" {
t.Errorf("Expecting 'parents: Commit not found: 1a7eba6c-d4fe-47b7-b767-859abc660efc'")
}
}

Expand Down
31 changes: 31 additions & 0 deletions scm/driver/bitbucket/testdata/content.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"mimetype": null,
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/tphoney/scm-test/src/0846a192175701903ddd9264ece31922d8437c1a/README.md"
},
"meta": {
"href": "https://api.bitbucket.org/2.0/repositories/tphoney/scm-test/src/0846a192175701903ddd9264ece31922d8437c1a/README.md?format=meta"
},
"history": {
"href": "https://api.bitbucket.org/2.0/repositories/tphoney/scm-test/filehistory/0846a192175701903ddd9264ece31922d8437c1a/README.md"
}
},
"escaped_path": "README.md",
"path": "README.md",
"commit": {
"type": "commit",
"hash": "0846a192175701903ddd9264ece31922d8437c1a",
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/tphoney/scm-test/commit/0846a192175701903ddd9264ece31922d8437c1a"
},
"html": {
"href": "https://bitbucket.org/tphoney/scm-test/commits/0846a192175701903ddd9264ece31922d8437c1a"
}
}
},
"attributes": [],
"type": "commit_file",
"size": 39
}
4 changes: 4 additions & 0 deletions scm/driver/bitbucket/testdata/content.json.fail
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"Path": "README",
"Data": "SEVMTE8gV09STEQK"
}
5 changes: 3 additions & 2 deletions scm/driver/bitbucket/testdata/content.json.golden
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"Path": "README",
"Data": "SEVMTE8gV09STEQK"
}
"Data": "SEVMTE8gV09STEQK",
"Sha": "0846a192175701903ddd9264ece31922d8437c1a"
}
6 changes: 6 additions & 0 deletions scm/driver/bitbucket/testdata/content_fail.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "error",
"error": {
"message": "No such file or directory: README.md.asdas"
}
}
Loading

0 comments on commit 773965b

Please sign in to comment.