create/update attachment

This commit is contained in:
Egor Kovetskiy 2019-04-19 17:35:05 +03:00
parent 76ddde31ab
commit 2abc2f122b
3 changed files with 370 additions and 35 deletions

View File

@ -187,7 +187,10 @@ func main() {
target = page
}
mark.ResolveAttachments(api, target, ".", meta.Attachments)
err = mark.ResolveAttachments(api, target, ".", meta.Attachments)
if err != nil {
log.Fatalf(err, "unable to create/update attachments")
}
err = api.UpdatePage(
target,

View File

@ -1,10 +1,14 @@
package confluence
import (
"encoding/json"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"github.com/bndr/gopencils"
"github.com/kovetskiy/lorg"
@ -12,20 +16,6 @@ import (
"github.com/reconquest/karma-go"
)
func discarder() *lorg.Log {
stderr := lorg.NewLog()
stderr.SetOutput(ioutil.Discard)
return stderr
}
var (
log = cog.NewLogger(discarder())
)
func SetLogger(logger *cog.Logger) {
log = logger
}
type RestrictionOperation string
const (
@ -64,6 +54,36 @@ type PageInfo struct {
} `json:"_links"`
}
type AttachmentInfo struct {
Filename string `json:"title"`
ID string `json:"id"`
Metadata struct {
Comment string `json:"comment"`
} `json:"metadata"`
Links struct {
Download string `json:"download"`
} `json:"_links"`
}
func discarder() *lorg.Log {
stderr := lorg.NewLog()
stderr.SetOutput(ioutil.Discard)
return stderr
}
var (
log = cog.NewLogger(discarder())
)
func SetLogger(logger *cog.Logger) {
log = logger
}
type form struct {
buffer io.Reader
writer *multipart.Writer
}
func NewAPI(baseURL string, username string, password string) *API {
auth := &gopencils.BasicAuth{username, password}
@ -92,11 +112,10 @@ func (api *API) FindRootPage(space string) (*PageInfo, error) {
}
if len(page.Ancestors) == 0 {
return nil, fmt.Errorf(
"page %q from space %q has no parents",
page.Title,
space,
)
return &PageInfo{
ID: page.ID,
Title: page.Title,
}, nil
}
return &PageInfo{
@ -139,8 +158,170 @@ func (api *API) FindPage(space string, title string) (*PageInfo, error) {
return &result.Results[0], nil
}
func (api *API) GetAttachments(pageID string) error {
result := map[string]interface{}{}
func (api *API) CreateAttachment(
pageID string,
name string,
comment string,
path string,
) (AttachmentInfo, error) {
var info AttachmentInfo
form, err := getAttachmentPayload(name, comment, path)
if err != nil {
return AttachmentInfo{}, err
}
var result struct {
Results []AttachmentInfo `json:"results"`
}
resource := api.rest.Res(
"content/"+pageID+"/child/attachment", &result,
)
resource.Payload = form.buffer
resource.Headers = http.Header{}
resource.SetHeader("Content-Type", form.writer.FormDataContentType())
resource.SetHeader("X-Atlassian-Token", "no-check")
request, err := resource.Post()
if err != nil {
return info, err
}
if request.Raw.StatusCode != 200 {
return info, newErrorStatusNotOK(request)
}
if len(result.Results) == 0 {
return info, errors.New(
"Confluence REST API for creating attachments returned " +
"0 json objects, expected at least 1",
)
}
info = result.Results[0]
return info, nil
}
func (api *API) UpdateAttachment(
pageID string,
attachID string,
name string,
comment string,
path string,
) (AttachmentInfo, error) {
var info AttachmentInfo
form, err := getAttachmentPayload(name, comment, path)
if err != nil {
return AttachmentInfo{}, err
}
var result struct {
Results []AttachmentInfo `json:"results"`
}
resource := api.rest.Res(
"content/"+pageID+"/child/attachment/"+attachID+"/data", &result,
)
resource.Payload = form.buffer
resource.Headers = http.Header{}
resource.SetHeader("Content-Type", form.writer.FormDataContentType())
resource.SetHeader("X-Atlassian-Token", "no-check")
request, err := resource.Post()
if err != nil {
return info, err
}
//if request.Raw.StatusCode != 200 {
return info, newErrorStatusNotOK(request)
//}
if len(result.Results) == 0 {
return info, errors.New(
"Confluence REST API for creating attachments returned " +
"0 json objects, expected at least 1",
)
}
info = result.Results[0]
return info, nil
}
func getAttachmentPayload(name, comment, path string) (*form, error) {
var (
payload = bytes.NewBuffer(nil)
writer = multipart.NewWriter(payload)
)
file, err := os.Open(path)
if err != nil {
return nil, karma.Format(
err,
"unable to open file: %q",
path,
)
}
defer file.Close()
content, err := writer.CreateFormFile("file", name)
if err != nil {
return nil, karma.Format(
err,
"unable to create form file",
)
}
_, err = io.Copy(content, file)
if err != nil {
return nil, karma.Format(
err,
"unable to copy i/o between form-file and file",
)
}
commentWriter, err := writer.CreateFormField("comment")
if err != nil {
return nil, karma.Format(
err,
"unable to create form field for comment",
)
}
_, err = commentWriter.Write([]byte(comment))
if err != nil {
return nil, karma.Format(
err,
"unable to write comment in form-field",
)
}
err = writer.Close()
if err != nil {
return nil, karma.Format(
err,
"unable to close form-writer",
)
}
return &form{
buffer: payload,
writer: writer,
}, nil
}
func (api *API) GetAttachments(pageID string) ([]AttachmentInfo, error) {
result := struct {
Results []AttachmentInfo `json:"results"`
}{}
payload := map[string]string{
"expand": "version,container",
@ -150,19 +331,14 @@ func (api *API) GetAttachments(pageID string) error {
"content/"+pageID+"/child/attachment", &result,
).Get(payload)
if err != nil {
return err
return nil, err
}
if request.Raw.StatusCode != 200 {
return newErrorStatusNotOK(request)
return nil, newErrorStatusNotOK(request)
}
{
marshaledXXX, _ := json.MarshalIndent(result, "", " ")
fmt.Printf("result: %s\n", string(marshaledXXX))
}
return nil
return result.Results, nil
}
func (api *API) GetPageByID(pageID string) (*PageInfo, error) {

View File

@ -1,9 +1,30 @@
package mark
import "github.com/kovetskiy/mark/pkg/confluence"
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/reconquest/karma-go"
)
const (
AttachmentChecksumPrefix = `mark:checksum: `
)
type Attachment struct {
Name string
ID string
Name string
Filename string
Path string
Checksum string
Link string
}
func ResolveAttachments(
@ -11,9 +32,144 @@ func ResolveAttachments(
page *confluence.PageInfo,
base string,
names []string,
) {
err := api.GetAttachments(page.ID)
) error {
attachs := []Attachment{}
for _, name := range names {
attach := Attachment{
Name: name,
Filename: strings.ReplaceAll(name, "/", "_"),
Path: filepath.Join(base, name),
}
checksum, err := getChecksum(attach.Path)
if err != nil {
return karma.Format(
err,
"unable to get checksum for attachment: %q", attach.Name,
)
}
attach.Checksum = checksum
attachs = append(attachs, attach)
}
remotes, err := api.GetAttachments(page.ID)
if err != nil {
panic(err)
}
existing := []Attachment{}
creating := []Attachment{}
updating := []Attachment{}
for _, attach := range attachs {
var found bool
var same bool
for _, remote := range remotes {
if remote.Filename == attach.Filename {
same = attach.Checksum == strings.TrimPrefix(
remote.Metadata.Comment,
AttachmentChecksumPrefix,
)
attach.ID = remote.ID
attach.Link = remote.Links.Download
found = true
break
}
}
if found {
if same {
existing = append(existing, attach)
} else {
updating = append(updating, attach)
}
} else {
creating = append(creating, attach)
}
}
{
marshaledXXX, _ := json.MarshalIndent(existing, "", " ")
fmt.Printf("existing: %s\n", string(marshaledXXX))
}
{
marshaledXXX, _ := json.MarshalIndent(creating, "", " ")
fmt.Printf("creating: %s\n", string(marshaledXXX))
}
{
marshaledXXX, _ := json.MarshalIndent(updating, "", " ")
fmt.Printf("updating: %s\n", string(marshaledXXX))
}
for i, attach := range creating {
log.Infof(nil, "creating attachment: %q", attach.Name)
info, err := api.CreateAttachment(
page.ID,
attach.Filename,
AttachmentChecksumPrefix+attach.Checksum,
attach.Path,
)
if err != nil {
return karma.Format(
err,
"unable to create attachment %q",
attach.Name,
)
}
attach.ID = info.ID
attach.Link = info.Links.Download
creating[i] = attach
}
for i, attach := range updating {
log.Infof(nil, "updating attachment: %q", attach.Name)
info, err := api.UpdateAttachment(
page.ID,
attach.ID,
attach.Name,
AttachmentChecksumPrefix+attach.Checksum,
attach.Path,
)
if err != nil {
return karma.Format(
err,
"unable to update attachment %q",
attach.Name,
)
}
attach.Link = info.Links.Download
updating[i] = attach
}
return nil
}
func getChecksum(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", karma.Format(
err,
"unable to open file",
)
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
}