From 2a04fa265136dc8127fe963d4237967e9a024797 Mon Sep 17 00:00:00 2001 From: Masayuki Morita Date: Wed, 16 Oct 2019 22:37:28 +0900 Subject: [PATCH] Get the latest release version from GitHub Release --- command/release.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 9 +++++++ main.go | 5 ++++ release/github.go | 52 +++++++++++++++++++++++++++++++++++++ release/release.go | 32 +++++++++++++++++++++++ 6 files changed, 164 insertions(+) create mode 100644 command/release.go create mode 100644 release/github.go create mode 100644 release/release.go diff --git a/command/release.go b/command/release.go new file mode 100644 index 0000000..7a59c87 --- /dev/null +++ b/command/release.go @@ -0,0 +1,65 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/minamijoyo/tfupdate/release" + flag "github.com/spf13/pflag" +) + +// ReleaseCommand is a command which gets the latest release version. +type ReleaseCommand struct { + Meta + url string +} + +// Run runs the procedure of this command. +func (c *ReleaseCommand) Run(args []string) int { + cmdFlags := flag.NewFlagSet("release", flag.ContinueOnError) + + if err := cmdFlags.Parse(args); err != nil { + c.UI.Error(fmt.Sprintf("failed to parse arguments: %s", err)) + return 1 + } + + if len(cmdFlags.Args()) != 1 { + c.UI.Error(fmt.Sprintf("The command expects 1 argument, but got %#v", cmdFlags.Args())) + c.UI.Error(c.Help()) + return 1 + } + + c.url = cmdFlags.Arg(0) + + r, err := release.NewRelease("github", c.url) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + + v, err := release.ResolveVersionAlias(r, "latest") + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + + c.UI.Output(v) + return 0 +} + +// Help returns long-form help text. +func (c *ReleaseCommand) Help() string { + helpText := ` +Usage: tfupdate release [options] + +Arguments + URL A URL of the release repository + (e.g. https://github.com/terraform-providers/terraform-provider-aws) +` + return strings.TrimSpace(helpText) +} + +// Synopsis returns one-line help text. +func (c *ReleaseCommand) Synopsis() string { + return "Get the latest release version" +} diff --git a/go.mod b/go.mod index 16af6b3..ef81b14 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/minamijoyo/tfupdate go 1.12 require ( + github.com/google/go-github/v28 v28.1.1 github.com/hashicorp/hcl/v2 v2.0.0 github.com/hashicorp/logutils v1.0.0 github.com/mitchellh/cli v1.0.0 diff --git a/go.sum b/go.sum index e097b08..7b90821 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,13 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= @@ -59,10 +64,14 @@ github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6Ac github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/main.go b/main.go index 0531884..ca8c05a 100644 --- a/main.go +++ b/main.go @@ -88,6 +88,11 @@ func initCommands() map[string]cli.CommandFactory { Meta: meta, }, nil }, + "release": func() (cli.Command, error) { + return &command.ReleaseCommand{ + Meta: meta, + }, nil + }, } return commands diff --git a/release/github.go b/release/github.go new file mode 100644 index 0000000..68d38c1 --- /dev/null +++ b/release/github.go @@ -0,0 +1,52 @@ +package release + +import ( + "context" + "fmt" + "regexp" + + "github.com/google/go-github/v28/github" + "github.com/pkg/errors" +) + +// GitHubRelease is a release implementation which provides version information with GitHub Release. +type GitHubRelease struct { + client *github.Client + owner string + repo string +} + +// NewGitHubRelease is a factory method which returns an GitHubRelease instance. +func NewGitHubRelease(url string) (Release, error) { + re := regexp.MustCompile(`https://github.com/(.+)/(.+)`) + matched := re.FindStringSubmatch(url) + if len(matched) != 3 { + return nil, errors.Errorf("failed to parse url: %s, matched: %#v", url, matched) + } + owner := matched[1] + repo := matched[2] + + return &GitHubRelease{ + client: github.NewClient(nil), + owner: owner, + repo: repo, + }, nil +} + +// Latest returns a latest version. +func (r *GitHubRelease) Latest() (string, error) { + release, _, err := r.client.Repositories.GetLatestRelease(context.Background(), r.owner, r.repo) + + if err != nil { + return "", fmt.Errorf("failed to get the latest release from github.com/%s/%s: %s", r.owner, r.repo, err) + } + + name := *release.Name + + // if a name starts with `v`, remove it. + if name[0] == 'v' { + return name[1:], nil + } + + return name, nil +} diff --git a/release/release.go b/release/release.go new file mode 100644 index 0000000..ef52ab2 --- /dev/null +++ b/release/release.go @@ -0,0 +1,32 @@ +package release + +import ( + "github.com/pkg/errors" +) + +// Release is an interface which provides version information. +type Release interface { + // Latest returns a latest version. + Latest() (string, error) +} + +// NewRelease is a factory method which returns a Release implementation. +func NewRelease(releaseType string, url string) (Release, error) { + switch releaseType { + case "github": + return NewGitHubRelease(url) + default: + return nil, errors.Errorf("failed to new release. unknown type: %s", releaseType) + } +} + +// ResolveVersionAlias resolves a version alias. +func ResolveVersionAlias(r Release, alias string) (string, error) { + switch alias { + case "latest": + return r.Latest() + default: + // if an alias does not match keywords, just return alias as a version. + return alias, nil + } +}