Add github membership
parent
d9b4ca5104
commit
ba8f950aad
|
@ -23,3 +23,4 @@ read:org # Used to list your organization teams
|
|||
|
||||
- [x] github_repository
|
||||
- [x] github_team
|
||||
- [x] github_membership
|
||||
|
|
|
@ -61,5 +61,6 @@ func Deserializers() []deserializer.CTYDeserializer {
|
|||
|
||||
ghdeserializer.NewGithubRepositoryDeserializer(),
|
||||
ghdeserializer.NewGithubTeamDeserializer(),
|
||||
ghdeserializer.NewGithubMembershipDeserializer(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,6 +157,7 @@ func TestTerraformStateReader_Github_Resources(t *testing.T) {
|
|||
}{
|
||||
{name: "github repository", dirName: "github_repository", wantErr: false},
|
||||
{name: "github team", dirName: "github_team", wantErr: false},
|
||||
{name: "github membership", dirName: "github_membership", wantErr: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"Etag": "W/\"2d1cae5b1a58be39bd09bad91fb225b3b589ddc47ff101cf4b8a86d3d8b6f65d\"",
|
||||
"Id": "driftctl-test:driftctl-acceptance-tester",
|
||||
"Role": "admin",
|
||||
"Username": "driftctl-acceptance-tester"
|
||||
},
|
||||
{
|
||||
"Etag": "W/\"d2a4eb901334f41629f5ead98a4b6e02489ae2f13b2152630f00e4dfa6488586\"",
|
||||
"Id": "driftctl-test:eliecharra",
|
||||
"Role": "admin",
|
||||
"Username": "eliecharra"
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"version": 4,
|
||||
"terraform_version": "0.14.4",
|
||||
"serial": 180,
|
||||
"lineage": "9fb78851-b86b-b53a-f625-c5b3407eb935",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "github_membership",
|
||||
"name": "driftctl-acceptance-tester",
|
||||
"provider": "provider[\"registry.terraform.io/hashicorp/github\"]",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"etag": "W/\"2d1cae5b1a58be39bd09bad91fb225b3b589ddc47ff101cf4b8a86d3d8b6f65d\"",
|
||||
"id": "driftctl-test:driftctl-acceptance-tester",
|
||||
"role": "admin",
|
||||
"username": "driftctl-acceptance-tester"
|
||||
},
|
||||
"sensitive_attributes": [],
|
||||
"private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ=="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "github_membership",
|
||||
"name": "eliecharra",
|
||||
"provider": "provider[\"registry.terraform.io/hashicorp/github\"]",
|
||||
"instances": [
|
||||
{
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"etag": "W/\"d2a4eb901334f41629f5ead98a4b6e02489ae2f13b2152630f00e4dfa6488586\"",
|
||||
"id": "driftctl-test:eliecharra",
|
||||
"role": "admin",
|
||||
"username": "eliecharra"
|
||||
},
|
||||
"sensitive_attributes": [],
|
||||
"private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ=="
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"github.com/cloudskiff/driftctl/pkg/remote/deserializer"
|
||||
remoteerror "github.com/cloudskiff/driftctl/pkg/remote/error"
|
||||
"github.com/cloudskiff/driftctl/pkg/resource"
|
||||
resourcegithub "github.com/cloudskiff/driftctl/pkg/resource/github"
|
||||
ghdeserializer "github.com/cloudskiff/driftctl/pkg/resource/github/deserializer"
|
||||
"github.com/cloudskiff/driftctl/pkg/terraform"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type GithubMembershipSupplier struct {
|
||||
reader terraform.ResourceReader
|
||||
deserializer deserializer.CTYDeserializer
|
||||
repository GithubRepository
|
||||
runner *terraform.ParallelResourceReader
|
||||
}
|
||||
|
||||
func NewGithubMembershipSupplier(provider *GithubTerraformProvider, repository GithubRepository) *GithubMembershipSupplier {
|
||||
return &GithubMembershipSupplier{
|
||||
provider,
|
||||
ghdeserializer.NewGithubMembershipDeserializer(),
|
||||
repository,
|
||||
terraform.NewParallelResourceReader(provider.Runner().SubRunner()),
|
||||
}
|
||||
}
|
||||
|
||||
func (s GithubMembershipSupplier) Resources() ([]resource.Resource, error) {
|
||||
|
||||
resourceList, err := s.repository.ListMembership()
|
||||
|
||||
if err != nil {
|
||||
return nil, remoteerror.NewResourceEnumerationError(err, resourcegithub.GithubMembershipResourceType)
|
||||
}
|
||||
|
||||
for _, id := range resourceList {
|
||||
id := id
|
||||
s.runner.Run(func() (cty.Value, error) {
|
||||
completeResource, err := s.reader.ReadResource(terraform.ReadResourceArgs{
|
||||
Ty: resourcegithub.GithubMembershipResourceType,
|
||||
ID: id,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("Error reading %s[%s]: %+v", id, resourcegithub.GithubMembershipResourceType, err)
|
||||
return cty.NilVal, err
|
||||
}
|
||||
return *completeResource, nil
|
||||
})
|
||||
}
|
||||
|
||||
results, err := s.runner.Wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.deserializer.Deserialize(results)
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudskiff/driftctl/pkg/parallel"
|
||||
"github.com/cloudskiff/driftctl/pkg/resource"
|
||||
ghdeserializer "github.com/cloudskiff/driftctl/pkg/resource/github/deserializer"
|
||||
"github.com/cloudskiff/driftctl/pkg/terraform"
|
||||
"github.com/cloudskiff/driftctl/test"
|
||||
"github.com/cloudskiff/driftctl/test/goldenfile"
|
||||
dritftctlmocks "github.com/cloudskiff/driftctl/test/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func TestGithubMembershipupplier_Resources(t *testing.T) {
|
||||
cases := []struct {
|
||||
test string
|
||||
dirName string
|
||||
mocks func(client *MockGithubRepository)
|
||||
err error
|
||||
}{
|
||||
{
|
||||
test: "no members",
|
||||
dirName: "github_membership_empty",
|
||||
mocks: func(client *MockGithubRepository) {
|
||||
client.On("ListMembership").Return([]string{}, nil)
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
test: "Multiple membership with admin and member roles",
|
||||
dirName: "github_membership_multiple",
|
||||
mocks: func(client *MockGithubRepository) {
|
||||
client.On("ListMembership").Return([]string{
|
||||
"driftctl-test:driftctl-acceptance-tester",
|
||||
"driftctl-test:eliecharra",
|
||||
}, nil)
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
shouldUpdate := c.dirName == *goldenfile.Update
|
||||
|
||||
providerLibrary := terraform.NewProviderLibrary()
|
||||
supplierLibrary := resource.NewSupplierLibrary()
|
||||
|
||||
mockedRepo := MockGithubRepository{}
|
||||
c.mocks(&mockedRepo)
|
||||
|
||||
if shouldUpdate {
|
||||
provider, err := InitTestGithubProvider(providerLibrary)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
supplierLibrary.AddSupplier(NewGithubMembershipSupplier(provider, &mockedRepo))
|
||||
}
|
||||
|
||||
t.Run(c.test, func(tt *testing.T) {
|
||||
provider := dritftctlmocks.NewMockedGoldenTFProvider(c.dirName, providerLibrary.Provider(terraform.GITHUB), shouldUpdate)
|
||||
GithubMembershipDeserializer := ghdeserializer.NewGithubMembershipDeserializer()
|
||||
s := &GithubMembershipSupplier{
|
||||
provider,
|
||||
GithubMembershipDeserializer,
|
||||
&mockedRepo,
|
||||
terraform.NewParallelResourceReader(parallel.NewParallelRunner(context.TODO(), 10)),
|
||||
}
|
||||
got, err := s.Resources()
|
||||
assert.Equal(tt, c.err, err)
|
||||
|
||||
mock.AssertExpectationsForObjects(tt)
|
||||
test.CtyTestDiff(got, c.dirName, provider, GithubMembershipDeserializer, shouldUpdate, tt)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ func Init(alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary,
|
|||
|
||||
supplierLibrary.AddSupplier(NewGithubRepositorySupplier(provider, repository))
|
||||
supplierLibrary.AddSupplier(NewGithubTeamSupplier(provider, repository))
|
||||
supplierLibrary.AddSupplier(NewGithubMembershipSupplier(provider, repository))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,29 @@ type MockGithubRepository struct {
|
|||
mock.Mock
|
||||
}
|
||||
|
||||
// ListMembership provides a mock function with given fields:
|
||||
func (_m *MockGithubRepository) ListMembership() ([]string, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 []string
|
||||
if rf, ok := ret.Get(0).(func() []string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ListRepositories provides a mock function with given fields:
|
||||
func (_m *MockGithubRepository) ListRepositories() ([]string, error) {
|
||||
ret := _m.Called()
|
||||
|
|
|
@ -2,6 +2,7 @@ package github
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/shurcooL/githubv4"
|
||||
"golang.org/x/oauth2"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
type GithubRepository interface {
|
||||
ListRepositories() ([]string, error)
|
||||
ListTeams() ([]int, error)
|
||||
ListMembership() ([]string, error)
|
||||
}
|
||||
|
||||
type GithubGraphQLClient interface {
|
||||
|
@ -159,3 +161,43 @@ func (r githubRepository) ListTeams() ([]int, error) {
|
|||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type listMembership struct {
|
||||
Organization struct {
|
||||
MembersWithRole struct {
|
||||
Nodes []struct {
|
||||
Login string
|
||||
}
|
||||
PageInfo struct {
|
||||
EndCursor githubv4.String
|
||||
HasNextPage bool
|
||||
}
|
||||
} `graphql:"membersWithRole(first: 100, after: $cursor)"`
|
||||
} `graphql:"organization(login: $login)"`
|
||||
}
|
||||
|
||||
func (r *githubRepository) ListMembership() ([]string, error) {
|
||||
query := listMembership{}
|
||||
results := make([]string, 0)
|
||||
if r.config.Organization == "" {
|
||||
return results, nil
|
||||
}
|
||||
variables := map[string]interface{}{
|
||||
"cursor": (*githubv4.String)(nil),
|
||||
"login": (githubv4.String)(r.config.Organization),
|
||||
}
|
||||
for {
|
||||
err := r.client.Query(r.ctx, &query, variables)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, membership := range query.Organization.MembersWithRole.Nodes {
|
||||
results = append(results, fmt.Sprintf("%s:%s", r.config.Organization, membership.Login))
|
||||
}
|
||||
if !query.Organization.MembersWithRole.PageInfo.HasNextPage {
|
||||
break
|
||||
}
|
||||
variables["cursor"] = githubv4.NewString(query.Organization.MembersWithRole.PageInfo.EndCursor)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
|
|
@ -300,3 +300,111 @@ func TestListTeams(t *testing.T) {
|
|||
|
||||
assert.Equal([]int{1, 2, 3, 4}, teams)
|
||||
}
|
||||
|
||||
func TestListMembership_WithError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mockedClient := mocks.GithubGraphQLClient{}
|
||||
expectedError := errors.New("test error from graphql")
|
||||
mockedClient.On("Query", mock.Anything, mock.Anything, mock.Anything).Return(expectedError)
|
||||
|
||||
r := githubRepository{
|
||||
client: &mockedClient,
|
||||
config: githubConfig{
|
||||
Organization: "testorg",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := r.ListMembership()
|
||||
assert.Equal(expectedError, err)
|
||||
}
|
||||
|
||||
func TestListMembership_WithoutOrganization(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
r := githubRepository{}
|
||||
|
||||
teams, err := r.ListMembership()
|
||||
assert.Nil(err)
|
||||
assert.Equal([]string{}, teams)
|
||||
}
|
||||
|
||||
func TestListMembership(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mockedClient := mocks.GithubGraphQLClient{}
|
||||
mockedClient.On("Query",
|
||||
mock.Anything,
|
||||
mock.MatchedBy(func(query interface{}) bool {
|
||||
q, ok := query.(*listMembership)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
q.Organization.MembersWithRole.Nodes = []struct {
|
||||
Login string
|
||||
}{
|
||||
{
|
||||
Login: "user-admin",
|
||||
},
|
||||
{
|
||||
Login: "user-non-admin-1",
|
||||
},
|
||||
}
|
||||
q.Organization.MembersWithRole.PageInfo = pageInfo{
|
||||
EndCursor: "next",
|
||||
HasNextPage: true,
|
||||
}
|
||||
return true
|
||||
}),
|
||||
map[string]interface{}{
|
||||
"login": (githubv4.String)("testorg"),
|
||||
"cursor": (*githubv4.String)(nil),
|
||||
}).Return(nil)
|
||||
|
||||
mockedClient.On("Query",
|
||||
mock.Anything,
|
||||
mock.MatchedBy(func(query interface{}) bool {
|
||||
q, ok := query.(*listMembership)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
q.Organization.MembersWithRole.Nodes = []struct {
|
||||
Login string
|
||||
}{
|
||||
{
|
||||
Login: "user-non-admin-2",
|
||||
},
|
||||
{
|
||||
Login: "user-non-admin-3",
|
||||
},
|
||||
}
|
||||
q.Organization.MembersWithRole.PageInfo = pageInfo{
|
||||
HasNextPage: false,
|
||||
}
|
||||
return true
|
||||
}),
|
||||
map[string]interface{}{
|
||||
"login": (githubv4.String)("testorg"),
|
||||
"cursor": githubv4.NewString("next"),
|
||||
}).Return(nil)
|
||||
|
||||
r := githubRepository{
|
||||
client: &mockedClient,
|
||||
ctx: context.TODO(),
|
||||
config: githubConfig{
|
||||
Organization: "testorg",
|
||||
},
|
||||
}
|
||||
|
||||
teams, err := r.ListMembership()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal([]string{
|
||||
"testorg:user-admin",
|
||||
"testorg:user-non-admin-1",
|
||||
"testorg:user-non-admin-2",
|
||||
"testorg:user-non-admin-3",
|
||||
}, teams)
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
[]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"Typ": "WyJvYmplY3QiLHsiZXRhZyI6InN0cmluZyIsImlkIjoic3RyaW5nIiwicm9sZSI6InN0cmluZyIsInVzZXJuYW1lIjoic3RyaW5nIn1d",
|
||||
"Val": "eyJldGFnIjoiVy9cImVhZGY4NzgyYjA2ZmRjYzNmY2NjNjM4NTYzOWFlMDY3YWRkMzE2ZWFkMTc0MjMzNjU4OTU1NWUzNjI3YzcwZDlcIiIsImlkIjoiZHJpZnRjdGwtdGVzdDpkcmlmdGN0bC1hY2NlcHRhbmNlLXRlc3RlciIsInJvbGUiOiJtZW1iZXIiLCJ1c2VybmFtZSI6ImRyaWZ0Y3RsLWFjY2VwdGFuY2UtdGVzdGVyIn0=",
|
||||
"Err": null
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"Typ": "WyJvYmplY3QiLHsiZXRhZyI6InN0cmluZyIsImlkIjoic3RyaW5nIiwicm9sZSI6InN0cmluZyIsInVzZXJuYW1lIjoic3RyaW5nIn1d",
|
||||
"Val": "eyJldGFnIjoiVy9cImQyYTRlYjkwMTMzNGY0MTYyOWY1ZWFkOThhNGI2ZTAyNDg5YWUyZjEzYjIxNTI2MzBmMDBlNGRmYTY0ODg1ODZcIiIsImlkIjoiZHJpZnRjdGwtdGVzdDplbGllY2hhcnJhIiwicm9sZSI6ImFkbWluIiwidXNlcm5hbWUiOiJlbGllY2hhcnJhIn0=",
|
||||
"Err": null
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"etag": "W/\"d2a4eb901334f41629f5ead98a4b6e02489ae2f13b2152630f00e4dfa6488586\"",
|
||||
"id": "driftctl-test:eliecharra",
|
||||
"role": "admin",
|
||||
"username": "eliecharra"
|
||||
},
|
||||
{
|
||||
"etag": "W/\"eadf8782b06fdcc3fccc6385639ae067add316ead1742336589555e3627c70d9\"",
|
||||
"id": "driftctl-test:driftctl-acceptance-tester",
|
||||
"role": "member",
|
||||
"username": "driftctl-acceptance-tester"
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,41 @@
|
|||
package deserializer
|
||||
|
||||
import (
|
||||
"github.com/cloudskiff/driftctl/pkg/resource"
|
||||
"github.com/cloudskiff/driftctl/pkg/resource/github"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
type GithubMembershipDeserializer struct {
|
||||
}
|
||||
|
||||
func NewGithubMembershipDeserializer() *GithubMembershipDeserializer {
|
||||
return &GithubMembershipDeserializer{}
|
||||
}
|
||||
|
||||
func (s GithubMembershipDeserializer) HandledType() resource.ResourceType {
|
||||
return github.GithubMembershipResourceType
|
||||
}
|
||||
|
||||
func (s GithubMembershipDeserializer) Deserialize(rawResourceList []cty.Value) ([]resource.Resource, error) {
|
||||
resources := make([]resource.Resource, 0)
|
||||
for _, raw := range rawResourceList {
|
||||
raw := raw
|
||||
res, err := decodeMembership(&raw)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error when deserializing github_membership %+v : %+v", raw, err)
|
||||
}
|
||||
resources = append(resources, res)
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func decodeMembership(res *cty.Value) (resource.Resource, error) {
|
||||
var decoded github.GithubMembership
|
||||
if err := gocty.FromCtyValue(*res, &decoded); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &decoded, nil
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// GENERATED, DO NOT EDIT THIS FILE
|
||||
package github
|
||||
|
||||
const GithubMembershipResourceType = "github_membership"
|
||||
|
||||
type GithubMembership struct {
|
||||
Etag *string `cty:"etag" computed:"true" diff:"-"`
|
||||
Id string `cty:"id" computed:"true"`
|
||||
Role *string `cty:"role"`
|
||||
Username *string `cty:"username"`
|
||||
}
|
||||
|
||||
func (r *GithubMembership) TerraformId() string {
|
||||
return r.Id
|
||||
}
|
||||
|
||||
func (r *GithubMembership) TerraformType() string {
|
||||
return GithubMembershipResourceType
|
||||
}
|
Loading…
Reference in New Issue