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 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( err = api.UpdatePage(
target, target,

View File

@ -1,10 +1,14 @@
package confluence package confluence
import ( import (
"encoding/json" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"mime/multipart"
"net/http"
"os"
"github.com/bndr/gopencils" "github.com/bndr/gopencils"
"github.com/kovetskiy/lorg" "github.com/kovetskiy/lorg"
@ -12,20 +16,6 @@ import (
"github.com/reconquest/karma-go" "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 type RestrictionOperation string
const ( const (
@ -64,6 +54,36 @@ type PageInfo struct {
} `json:"_links"` } `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 { func NewAPI(baseURL string, username string, password string) *API {
auth := &gopencils.BasicAuth{username, password} auth := &gopencils.BasicAuth{username, password}
@ -92,11 +112,10 @@ func (api *API) FindRootPage(space string) (*PageInfo, error) {
} }
if len(page.Ancestors) == 0 { if len(page.Ancestors) == 0 {
return nil, fmt.Errorf( return &PageInfo{
"page %q from space %q has no parents", ID: page.ID,
page.Title, Title: page.Title,
space, }, nil
)
} }
return &PageInfo{ return &PageInfo{
@ -139,8 +158,170 @@ func (api *API) FindPage(space string, title string) (*PageInfo, error) {
return &result.Results[0], nil return &result.Results[0], nil
} }
func (api *API) GetAttachments(pageID string) error { func (api *API) CreateAttachment(
result := map[string]interface{}{} 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{ payload := map[string]string{
"expand": "version,container", "expand": "version,container",
@ -150,19 +331,14 @@ func (api *API) GetAttachments(pageID string) error {
"content/"+pageID+"/child/attachment", &result, "content/"+pageID+"/child/attachment", &result,
).Get(payload) ).Get(payload)
if err != nil { if err != nil {
return err return nil, err
} }
if request.Raw.StatusCode != 200 { if request.Raw.StatusCode != 200 {
return newErrorStatusNotOK(request) return nil, newErrorStatusNotOK(request)
} }
{ return result.Results, nil
marshaledXXX, _ := json.MarshalIndent(result, "", " ")
fmt.Printf("result: %s\n", string(marshaledXXX))
}
return nil
} }
func (api *API) GetPageByID(pageID string) (*PageInfo, error) { func (api *API) GetPageByID(pageID string) (*PageInfo, error) {

View File

@ -1,9 +1,30 @@
package mark 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 { type Attachment struct {
Name string ID string
Name string
Filename string
Path string
Checksum string
Link string
} }
func ResolveAttachments( func ResolveAttachments(
@ -11,9 +32,144 @@ func ResolveAttachments(
page *confluence.PageInfo, page *confluence.PageInfo,
base string, base string,
names []string, names []string,
) { ) error {
err := api.GetAttachments(page.ID) 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 { if err != nil {
panic(err) 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
} }