Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-144 - New Data Source: Github release #356

Merged
merged 10 commits into from
Feb 20, 2020
150 changes: 150 additions & 0 deletions github/data_source_github_release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package github

import (
"context"
"fmt"
"log"
"strconv"
"strings"

"github.com/google/go-github/v28/github"
"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceGithubRelease() *schema.Resource {
return &schema.Resource{
Read: dataSourceGithubReleaseRead,

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
},
"owner": {
Type: schema.TypeString,
Required: true,
},
"retrieve_by": {
Type: schema.TypeString,
Required: true,
},
"release_tag": {
Type: schema.TypeString,
Optional: true,
},
"release_id": {
Type: schema.TypeInt,
Optional: true,
},
"target_commitish": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
},
"body": {
Type: schema.TypeString,
Computed: true,
},
"draft": {
Type: schema.TypeBool,
Computed: true,
},
"prerelease": {
Type: schema.TypeBool,
Computed: true,
},
"created_at": {
Type: schema.TypeString,
Computed: true,
},
"published_at": {
Type: schema.TypeString,
Computed: true,
},
"url": {
Type: schema.TypeString,
Computed: true,
},
"html_url": {
Type: schema.TypeString,
Computed: true,
},
"asserts_url": {
Type: schema.TypeString,
Computed: true,
},
"upload_url": {
Type: schema.TypeString,
Computed: true,
},
"zipball_url": {
Type: schema.TypeString,
Computed: true,
},
"tarball_url": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func dataSourceGithubReleaseRead(d *schema.ResourceData, meta interface{}) error {
repository := d.Get("repository").(string)
owner := d.Get("owner").(string)

client := meta.(*Organization).client
ctx := context.Background()

var err error
var release *github.RepositoryRelease

switch retrieveBy := strings.ToLower(d.Get("retrieve_by").(string)); retrieveBy {
case "latest":
log.Printf("[INFO] Refreshing GitHub latest release from repository %s", repository)
release, _, err = client.Repositories.GetLatestRelease(ctx, owner, repository)
case "id":
releaseID := int64(d.Get("release_id").(int))
if releaseID == 0 {
return fmt.Errorf("`release_id` must be set when `retrieve_by` = `id`")
}

log.Printf("[INFO] Refreshing GitHub release by id %d from repository %s", releaseID, repository)
release, _, err = client.Repositories.GetRelease(ctx, owner, repository, releaseID)
case "tag":
tag := d.Get("release_tag").(string)
if tag == "" {
return fmt.Errorf("`release_tag` must be set when `retrieve_by` = `tag`")
}

log.Printf("[INFO] Refreshing GitHub release by tag %s from repository %s", tag, repository)
release, _, err = client.Repositories.GetReleaseByTag(ctx, owner, repository, tag)
default:
return fmt.Errorf("one of: `latest`, `id`, `tag` must be set for `retrieve_by`")
}

if err != nil {
return err
}

d.SetId(strconv.FormatInt(release.GetID(), 10))
d.Set("release_tag", release.GetTagName())
d.Set("target_commitish", release.GetTargetCommitish())
d.Set("name", release.GetName())
d.Set("body", release.GetBody())
d.Set("draft", release.GetDraft())
d.Set("prerelease", release.GetPrerelease())
d.Set("created_at", release.GetCreatedAt())
d.Set("published_at", release.GetPublishedAt())
d.Set("url", release.GetURL())
d.Set("html_url", release.GetHTMLURL())
d.Set("asserts_url", release.GetAssetsURL())
d.Set("upload_url", release.GetUploadURL())
d.Set("zipball_url", release.GetZipballURL())
d.Set("tarball_url", release.GetTarballURL())

return nil
}
160 changes: 160 additions & 0 deletions github/data_source_github_release_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package github

import (
"fmt"
"regexp"
"strconv"
"testing"

"github.com/hashicorp/terraform/helper/resource"
)

func TestAccGithubReleaseDataSource_fetchByLatestNoReleaseReturnsError(t *testing.T) {
repo := "nonExistentRepo"
owner := "no-user"
retrieveBy := "latest"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubReleaseDataSourceConfig(repo, owner, retrieveBy, "", 0),
ExpectError: regexp.MustCompile(`Not Found`),
},
},
})
}

func TestAccGithubReleaseDataSource_latestExisting(t *testing.T) {
repo := "terraform"
owner := "hashicorp"
retrieveBy := "latest"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubReleaseDataSourceConfig(repo, owner, retrieveBy, "", 0),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("data.github_release.test", "url", regexp.MustCompile(`hashicorp/terraform`)),
resource.TestMatchResourceAttr("data.github_release.test", "tarball_url", regexp.MustCompile(`hashicorp/terraform/tarball`)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: to keep this within some margins, I would extract the third arguments here into their own variables above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good shout, will update this.

),
},
},
})

}

func TestAccGithubReleaseDataSource_fetchByIdWithNoIdReturnsError(t *testing.T) {
retrieveBy := "id"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubReleaseDataSourceConfig("", "", retrieveBy, "", 0),
ExpectError: regexp.MustCompile("release_id` must be set when `retrieve_by` = `id`"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a leading character here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I'll add that in

},
},
})
}

func TestAccGithubReleaseDataSource_fetchByIdExisting(t *testing.T) {
repo := "terraform"
owner := "hashicorp"
retrieveBy := "id"
id := int64(23055013)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubReleaseDataSourceConfig(repo, owner, retrieveBy, "", id),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.github_release.test", "release_id", strconv.FormatInt(id, 10)),
resource.TestMatchResourceAttr("data.github_release.test", "url", regexp.MustCompile(`hashicorp/terraform`)),
resource.TestMatchResourceAttr("data.github_release.test", "tarball_url", regexp.MustCompile(`hashicorp/terraform/tarball`)),
),
},
},
})
}

func TestAccGithubReleaseDataSource_fetchByTagNoTagReturnsError(t *testing.T) {
repo := "terraform"
owner := "hashicorp"
retrieveBy := "tag"
id := int64(23055013)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubReleaseDataSourceConfig(repo, owner, retrieveBy, "", id),
ExpectError: regexp.MustCompile("`release_tag` must be set when `retrieve_by` = `tag`"),
},
},
})
}

func TestAccGithubReleaseDataSource_fetchByTagExisting(t *testing.T) {
repo := "terraform"
owner := "hashicorp"
retrieveBy := "tag"
tag := "v0.12.20"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubReleaseDataSourceConfig(repo, owner, retrieveBy, tag, 0),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.github_release.test", "release_tag", tag),
resource.TestMatchResourceAttr("data.github_release.test", "url", regexp.MustCompile(`hashicorp/terraform`)),
resource.TestMatchResourceAttr("data.github_release.test", "tarball_url", regexp.MustCompile(`hashicorp/terraform/tarball`)),
),
},
},
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convention within this repository I've observed so far has been to not hard-code values such as the repository and tag as was done in this test. I think this test is stable but would like to see an attempt to obtain these values from the environment before proceeding with the hard-coded defaults. See here for prior art.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for referring to this. I was unsure how to proceed since we don't have an associated resource to create / delete a release. Have a look at my changes - adding another environment variable with the tag and sourcing it from the template repo.

In future, if added, I think it would be good to manage this release test object via a release resource as well, making it fully isolated.


func TestAccGithubReleaseDataSource_invalidRetrieveMethodReturnsError(t *testing.T) {
retrieveBy := "not valid"
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckGithubReleaseDataSourceConfig("", "", retrieveBy, "", 0),
ExpectError: regexp.MustCompile("one of: `latest`, `id`, `tag` must be set for `retrieve_by`"),
},
},
})

}

func testAccCheckGithubReleaseDataSourceConfig(repo, owner, retrieveBy, tag string, id int64) string {
return fmt.Sprintf(`
data "github_release" "test" {
repository = "%s"
owner = "%s"
retrieve_by = "%s"
release_tag = "%s"
release_id = %d
}
`, repo, owner, retrieveBy, tag, id)
}
1 change: 1 addition & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func Provider() terraform.ResourceProvider {
DataSourcesMap: map[string]*schema.Resource{
"github_collaborators": dataSourceGithubCollaborators(),
"github_ip_ranges": dataSourceGithubIpRanges(),
"github_release": dataSourceGithubRelease(),
"github_repositories": dataSourceGithubRepositories(),
"github_repository": dataSourceGithubRepository(),
"github_team": dataSourceGithubTeam(),
Expand Down
69 changes: 69 additions & 0 deletions website/docs/d/release.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
layout: "github"
page_title: "GitHub: github_release"
description: |-
Get information on a GitHub release.
---

# github\_user

Use this data source to retrieve information about a GitHub release in a specified repository.

## Example Usage
To retrieve the latest release that is present in a repository:
```hcl
data "github_release" "example" {
repository = "example-repository"
owner = "example-owner"
retrieve_by = "latest"
}
```
To retrieve a specific release from a repository based on it's ID:
```hcl
data "github_release" "example" {
repository = "example-repository"
owner = "example-owner"
retrieve_by = "id"
id = 12345
}
```
Finally, to retrieve a release based on it's tag:
```hcl
data "github_release" "example" {
repository = "example-repository"
owner = "example-owner"
retrieve_by = "tag"
release_tag = "v1.0.0"
}
```

## Argument Reference

* `repository` - (Required) Name of the repository to retrieve the release from.

* `owner` - (Required) Owner of the repository.

* `retrieve_by` - (Required) Describes how to fetch the release. Valid values are `id`, `tag`, `latest`.

* `release_id` - (Optional) ID of the release to retrieve. Must be specified when `retrieve_by` = `id`.

* `release_tag` - (Optional) Tag of the release to retrieve. Must be specified when `retrieve_by` = `tag`.


## Attributes Reference

* `release_tag` - Tag of release
* `release_id` - ID of release
* `target_commitish` - Commitish value that determines where the Git release is created from
* `name` - Name of release
* `body` - Contents of the description (body) of a release
* `draft` - (`Boolean`) indicates whether the release is a draft
* `prerelease` - (`Boolean`) indicates whether the release is a prerelease
* `created_at` - Date of release creation
* `published_at` - Date of release publishing
* `url` - Base URL of the release
* `html_url` - URL directing to detailed information on the release
* `asserts_url` - URL of any associated assets with the release
* `upload_url` - URL that can be used to upload Assets to the release
* `zipball_url` - Download URL of a specific release in `zip` format
* `tarball_url` - Download URL of a specific release in `tar.gz` format