From cd0c68804e664ac3f82719f48d40283f0f76736f Mon Sep 17 00:00:00 2001 From: Pascal Hofmann Date: Sun, 17 Oct 2021 17:02:57 +0200 Subject: [PATCH] feat: Add data source github_users (#900) --- github/data_source_github_users.go | 99 +++++++++++++++++++++++++ github/data_source_github_users_test.go | 95 ++++++++++++++++++++++++ github/provider.go | 1 + github/util.go | 13 ++++ website/docs/d/users.html.markdown | 37 +++++++++ website/github.erb | 3 + 6 files changed, 248 insertions(+) create mode 100644 github/data_source_github_users.go create mode 100644 github/data_source_github_users_test.go create mode 100644 website/docs/d/users.html.markdown diff --git a/github/data_source_github_users.go b/github/data_source_github_users.go new file mode 100644 index 0000000000..f9363e3bfc --- /dev/null +++ b/github/data_source_github_users.go @@ -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 +} diff --git a/github/data_source_github_users_test.go b/github/data_source_github_users_test.go new file mode 100644 index 0000000000..8f80f2cf62 --- /dev/null +++ b/github/data_source_github_users_test.go @@ -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) + }) + + }) +} diff --git a/github/provider.go b/github/provider.go index 10805b4690..e2c117501e 100644 --- a/github/provider.go +++ b/github/provider.go @@ -135,6 +135,7 @@ func Provider() terraform.ResourceProvider { "github_repository_pull_requests": dataSourceGithubRepositoryPullRequests(), "github_team": dataSourceGithubTeam(), "github_user": dataSourceGithubUser(), + "github_users": dataSourceGithubUsers(), }, } diff --git a/github/util.go b/github/util.go index cad1d106dd..6b8307e7cc 100644 --- a/github/util.go +++ b/github/util.go @@ -2,9 +2,11 @@ package github import ( "context" + "crypto/md5" "errors" "fmt" "regexp" + "sort" "strconv" "strings" @@ -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 { diff --git a/website/docs/d/users.html.markdown b/website/docs/d/users.html.markdown new file mode 100644 index 0000000000..02424e1d23 --- /dev/null +++ b/website/docs/d/users.html.markdown @@ -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. diff --git a/website/github.erb b/website/github.erb index 1d900f1000..c8ab0aeccc 100644 --- a/website/github.erb +++ b/website/github.erb @@ -58,6 +58,9 @@
  • github_user
  • +
  • + github_users +