Skip to content

Commit

Permalink
feat: Add data source github_users (integrations#900)
Browse files Browse the repository at this point in the history
  • Loading branch information
pascal-hofmann authored Oct 17, 2021
1 parent df3db7b commit cd0c688
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 0 deletions.
99 changes: 99 additions & 0 deletions github/data_source_github_users.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package github

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/shurcooL/githubv4"
"log"
"reflect"
"strings"
)

func dataSourceGithubUsers() *schema.Resource {
return &schema.Resource{
Read: dataSourceGithubUsersRead,

Schema: map[string]*schema.Schema{
"usernames": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Required: true,
},
"logins": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Computed: true,
},
"node_ids": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Computed: true,
},
"unknown_logins": {
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Computed: true,
},
},
}
}

func dataSourceGithubUsersRead(d *schema.ResourceData, meta interface{}) error {
usernames := expandStringList(d.Get("usernames").([]interface{}))

// Create GraphQL variables and query struct
type (
UserFragment struct {
Id string
Login string
}
)
var fields []reflect.StructField
variables := make(map[string]interface{})
for idx, username := range usernames {
label := fmt.Sprintf("User%d", idx)
variables[label] = githubv4.String(username)
fields = append(fields, reflect.StructField{
Name: label, Type: reflect.TypeOf(UserFragment{}), Tag: reflect.StructTag(fmt.Sprintf("graphql:\"%[1]s: user(login: $%[1]s)\"", label)),
})
}
query := reflect.New(reflect.StructOf(fields)).Elem()

if len(usernames) > 0 {
log.Printf("[INFO] Refreshing GitHub Users: %s", strings.Join(usernames, ", "))
ctx := context.WithValue(context.Background(), ctxId, d.Id())
client := meta.(*Owner).v4client
err := client.Query(ctx, query.Addr().Interface(), variables)
if err != nil && !strings.Contains(err.Error(), "Could not resolve to a User with the login of") {
return err
}
}

var logins, nodeIDs, unknownLogins []string
for idx, username := range usernames {
label := fmt.Sprintf("User%d", idx)
user := query.FieldByName(label).Interface().(UserFragment)
if user.Login != "" {
logins = append(logins, user.Login)
nodeIDs = append(nodeIDs, user.Id)
} else {
unknownLogins = append(unknownLogins, username)
}
}

d.SetId(buildChecksumID(usernames))
d.Set("logins", logins)
d.Set("node_ids", nodeIDs)
d.Set("unknown_logins", unknownLogins)

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

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccGithubUsersDataSource(t *testing.T) {

t.Run("queries multiple accounts", func(t *testing.T) {

config := fmt.Sprintf(`
data "github_users" "test" {
usernames = ["%[1]s", "!%[1]s"]
}
`, testOwnerFunc())

check := resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.github_users.test", "logins.#", "1"),
resource.TestCheckResourceAttr("data.github_users.test", "logins.0", testOwnerFunc()),
resource.TestCheckResourceAttr("data.github_users.test", "node_ids.#", "1"),
resource.TestCheckResourceAttr("data.github_users.test", "unknown_logins.#", "1"),
resource.TestCheckResourceAttr("data.github_users.test", "unknown_logins.0", fmt.Sprintf("!%s", testOwnerFunc())),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
testCase(t, individual)
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})

})

t.Run("does not fail if called with empty list of usernames", func(t *testing.T) {

config := `
data "github_users" "test" {
usernames = []
}
`

check := resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.github_users.test", "logins.#", "0"),
resource.TestCheckResourceAttr("data.github_users.test", "node_ids.#", "0"),
resource.TestCheckResourceAttr("data.github_users.test", "unknown_logins.#", "0"),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
testCase(t, individual)
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})

})
}
1 change: 1 addition & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ func Provider() terraform.ResourceProvider {
"github_repository_pull_requests": dataSourceGithubRepositoryPullRequests(),
"github_team": dataSourceGithubTeam(),
"github_user": dataSourceGithubUser(),
"github_users": dataSourceGithubUsers(),
},
}

Expand Down
13 changes: 13 additions & 0 deletions github/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package github

import (
"context"
"crypto/md5"
"errors"
"fmt"
"regexp"
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -78,6 +80,17 @@ func buildThreePartID(a, b, c string) string {
return fmt.Sprintf("%s:%s:%s", a, b, c)
}

func buildChecksumID(v []string) string {
sort.Strings(v)

h := md5.New()
// Hash.Write never returns an error. See https://pkg.go.dev/hash#Hash
_, _ = h.Write([]byte(strings.Join(v, "")))
bs := h.Sum(nil)

return fmt.Sprintf("%x", bs)
}

func expandStringList(configured []interface{}) []string {
vs := make([]string, 0, len(configured))
for _, v := range configured {
Expand Down
37 changes: 37 additions & 0 deletions website/docs/d/users.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
layout: "github"
page_title: "GitHub: github_users"
description: |-
Get information about multiple GitHub users.
---

# github\_users

Use this data source to retrieve information about multiple GitHub users at once.

## Example Usage

```hcl
# Retrieve information about multiple GitHub users.
data "github_users" "example" {
usernames = ["example1", "example2", "example3"]
}
output "valid_users" {
value = "${data.github_user.example.logins}"
}
output "invalid_users" {
value = "${data.github_user.example.unknown_logins}"
}
```

## Argument Reference

* `usernames` - (Required) List of usernames.

## Attributes Reference

* `node_ids` - list of Node IDs of users that could be found.
* `logins` - list of logins of users that could be found.
* `unknown_logins` - list of logins without matching user.
3 changes: 3 additions & 0 deletions website/github.erb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
<li>
<a href="/docs/providers/github/d/user.html">github_user</a>
</li>
<li>
<a href="/docs/providers/github/d/users.html">github_users</a>
</li>
</ul>
</li>

Expand Down

0 comments on commit cd0c688

Please sign in to comment.