Skip to content

Commit

Permalink
internal/tools/release: upload command (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmcloughlin committed Apr 5, 2021
1 parent e800013 commit e5cc968
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 27 deletions.
13 changes: 10 additions & 3 deletions internal/tools/release/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
type check struct {
cli.Command

zenodo Zenodo
varsfile VarsFile
httpclient HTTPClient
zenodo Zenodo
varsfile VarsFile
}

func (*check) Name() string { return "check" }
Expand All @@ -29,6 +30,7 @@ Perform some final checks to confirm release can be tagged.
}

func (cmd *check) SetFlags(f *flag.FlagSet) {
cmd.httpclient.SetFlags(f)
cmd.zenodo.SetFlags(f)
cmd.varsfile.SetFlags(f)
}
Expand All @@ -55,7 +57,12 @@ func (cmd *check) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}
}

// Check that a Zenodo deposit has been allocated.
client, err := cmd.zenodo.Client()
httpclient, err := cmd.httpclient.Client()
if err != nil {
return cmd.Error(err)
}

client, err := cmd.zenodo.Client(httpclient)
if err != nil {
return cmd.Error(err)
}
Expand Down
35 changes: 14 additions & 21 deletions internal/tools/release/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"io/ioutil"
"net/http"
"os"
"path/filepath"
"runtime"

"github.com/mmcloughlin/addchain/internal/metavars"
"github.com/mmcloughlin/addchain/internal/zenodo"
Expand All @@ -29,12 +27,7 @@ func (v *VarsFile) SetFlags(f *flag.FlagSet) {
// DefaultPath returns the path to the default variables file in the meta
// package. Returns the empty string if the path cannot be determined.
func (v *VarsFile) DefaultPath() string {
_, self, _, ok := runtime.Caller(0)
if !ok {
return ""
}
path := filepath.Join(filepath.Dir(self), "../../meta/vars.go")
return filepath.Clean(path)
return RepoPath("internal/meta/vars.go")
}

// Get variable from the meta variables file.
Expand Down Expand Up @@ -72,18 +65,18 @@ func (v *VarsFile) Set(name, value string) error {

// Zenodo configures a zenodo client.
type Zenodo struct {
base string
token string
httpclient HTTPClient
base string
sandbox bool
token string
}

const zenodoTokenEnvVar = "ZENODO_TOKEN"

// SetFlags registers command-line flags to configure the zenodo client.
func (z *Zenodo) SetFlags(f *flag.FlagSet) {
f.StringVar(&z.base, "url", zenodo.BaseURL, "zenodo api base url")
f.BoolVar(&z.sandbox, "sandbox", false, "use zenodo sandbox")
f.StringVar(&z.token, "token", "", fmt.Sprintf("zenodo token (uses %q environment variable if empty)", zenodoTokenEnvVar))
z.httpclient.SetFlags(f)
}

// Token returns the configured zenodo token, either from the command-line or
Expand All @@ -98,24 +91,24 @@ func (z *Zenodo) Token() (string, error) {
return "", errors.New("zenodo token not specified")
}

// Client builds the configured client.
func (z *Zenodo) Client() (*zenodo.Client, error) {
// HTTP Client.
client, err := z.httpclient.Client()
if err != nil {
return nil, err
// URL to connect to.
func (z *Zenodo) URL() string {
if z.sandbox {
return zenodo.SandboxBaseURL
}
return z.base
}

// Client builds the configured client.
func (z *Zenodo) Client(c *http.Client) (*zenodo.Client, error) {
// Token.
token, err := z.Token()
if err != nil {
return nil, err
}

// Zenodo client.
c := zenodo.NewClient(client, z.base, token)

return c, nil
return zenodo.NewClient(c, z.URL(), token), nil
}

// HTTPClient configures a HTTP client.
Expand Down
1 change: 1 addition & 0 deletions internal/tools/release/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func main() {
subcommands.Register(&bump{Command: base}, "")
subcommands.Register(&reservedoi{Command: base}, "")
subcommands.Register(&check{Command: base}, "")
subcommands.Register(&upload{Command: base}, "")
subcommands.Register(subcommands.HelpCommand(), "")

flag.Parse()
Expand Down
13 changes: 10 additions & 3 deletions internal/tools/release/reservedoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
type reservedoi struct {
cli.Command

zenodo Zenodo
varsfile VarsFile
httpclient HTTPClient
zenodo Zenodo
varsfile VarsFile
}

func (*reservedoi) Name() string { return "reservedoi" }
Expand All @@ -29,13 +30,19 @@ Reserve DOI on Zenodo for a new release.
}

func (cmd *reservedoi) SetFlags(f *flag.FlagSet) {
cmd.httpclient.SetFlags(f)
cmd.zenodo.SetFlags(f)
cmd.varsfile.SetFlags(f)
}

func (cmd *reservedoi) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// Zenodo client.
c, err := cmd.zenodo.Client()
httpclient, err := cmd.httpclient.Client()
if err != nil {
return cmd.Error(err)
}

c, err := cmd.zenodo.Client(httpclient)
if err != nil {
return cmd.Error(err)
}
Expand Down
162 changes: 162 additions & 0 deletions internal/tools/release/upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package main

import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"

"github.com/google/subcommands"

"github.com/mmcloughlin/addchain/internal/cli"
"github.com/mmcloughlin/addchain/internal/zenodo"
)

// upload subcommand.
type upload struct {
cli.Command

httpclient HTTPClient
zenodo Zenodo
varsfile VarsFile
metadata string
publish bool
}

func (*upload) Name() string { return "upload" }
func (*upload) Synopsis() string { return "upload version" }
func (*upload) Usage() string {
return `Usage: upload <version>
Bump version and update related files.
`
}

func (cmd *upload) SetFlags(f *flag.FlagSet) {
cmd.httpclient.SetFlags(f)
cmd.zenodo.SetFlags(f)
cmd.varsfile.SetFlags(f)
f.StringVar(&cmd.metadata, "metadata", RepoPath(".zenodo.json"), "path to zenodo metadata")
f.BoolVar(&cmd.publish, "publish", false, "publish the zenodo record")
}

func (cmd *upload) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// Fetch version.
version, err := cmd.varsfile.Get("releaseversion")
if err != nil {
return cmd.Error(err)
}

// HTTP Client.
httpclient, err := cmd.httpclient.Client()
if err != nil {
return cmd.Error(err)
}

// Download archive from github.
archive := bytes.NewBuffer(nil)
url := "https://api.github.com/repos/mmcloughlin/addchain/zipball/v" + version
if err := Download(ctx, archive, httpclient, url); err != nil {
return cmd.Error(err)
}

cmd.Log.Printf("downloaded zip archive bytes %d", archive.Len())

// Zenodo client.
client, err := cmd.zenodo.Client(httpclient)
if err != nil {
return cmd.Error(err)
}

// Load zenodo metadata.
metadata, err := LoadZenodoMetadata(cmd.metadata)
if err != nil {
return cmd.Error(err)
}

cmd.Log.Printf("loaded zenodo metadata from %q", cmd.metadata)

// Set metadata.
id, err := cmd.varsfile.Get("zenodoid")
if err != nil {
return cmd.Error(err)
}

d, err := client.DepositionUpdate(ctx, id, metadata)
if err != nil {
return cmd.Error(err)
}

cmd.Log.Printf("updated zenodo metadata")

// Clear Zenodo files.
if err := client.DepositionFilesDeleteAll(ctx, id); err != nil {
return cmd.Error(err)
}

cmd.Log.Printf("cleared all files from zenodo deposit")

// Upload new Zenodo file.
archivefilename := fmt.Sprintf("addchain_%s.zip", version)
file, err := client.DepositionFilesCreate(ctx, id, archivefilename, "application/zip", archive)
if err != nil {
return cmd.Error(err)
}

cmd.Log.Printf("uploaded file %q", file.Filename)

// Optionally publish zenodo deposit.
if cmd.publish {
if _, err := client.DepositionPublish(ctx, id); err != nil {
return cmd.Error(err)
}
cmd.Log.Printf("published zenodo record")
} else {
cmd.Log.Printf("publish skipped: review and publish at %q", d.Links["html"])
}

return subcommands.ExitSuccess
}

// Download a file over HTTP.
func Download(ctx context.Context, w io.Writer, c *http.Client, url string) (err error) {
// Issue GET request.
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}

res, err := c.Do(req)
if err != nil {
return err
}
defer func() {
if errc := res.Body.Close(); errc != nil && err == nil {
err = errc
}
}()

// Copy to writer.
_, err = io.Copy(w, res.Body)
return
}

// LoadZenodoMetadata reads and parses a Zenodo metadata file.
func LoadZenodoMetadata(filename string) (*zenodo.DepositionMetadata, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}

m := new(zenodo.DepositionMetadata)
if err := json.Unmarshal(b, m); err != nil {
return nil, err
}

return m, nil
}
13 changes: 13 additions & 0 deletions internal/tools/release/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package main

import (
"errors"
"path/filepath"
"regexp"
"runtime"
)

// ValidateVersion checks if version is of the form "MAJOR.MINOR.PATCH". That
Expand All @@ -15,3 +17,14 @@ func ValidateVersion(version string) error {
}

var versionrx = regexp.MustCompile(`^\d+\.\d+\.\d+$`)

// RepoPath returns a full file path given a path relative to the repository
// root. Returns empty string if it cannot be determined.
func RepoPath(rel string) string {
_, self, _, ok := runtime.Caller(0)
if !ok {
return ""
}
path := filepath.Join(filepath.Dir(self), "../../..", rel)
return filepath.Clean(path)
}
17 changes: 17 additions & 0 deletions internal/zenodo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,23 @@ func (c *Client) DepositionFilesDelete(ctx context.Context, did, fid string) err
return c.request(req, nil)
}

// DepositionFilesDeleteAll deletes all files from a deposit. This requires
// multiple API calls.
func (c *Client) DepositionFilesDeleteAll(ctx context.Context, id string) error {
fs, err := c.DepositionFilesList(ctx, id)
if err != nil {
return fmt.Errorf("list files: %w", err)
}

for _, f := range fs {
if err := c.DepositionFilesDelete(ctx, id, f.ID); err != nil {
return fmt.Errorf("delete file: %w", err)
}
}

return nil
}

func (c *Client) requestjson(ctx context.Context, method, path string, data, payload interface{}) error {
// Encode request data.
body, err := json.Marshal(data)
Expand Down

0 comments on commit e5cc968

Please sign in to comment.