diff --git a/Makefile b/Makefile index b815eafd..6992166d 100644 --- a/Makefile +++ b/Makefile @@ -51,5 +51,12 @@ fmtcheck: fmt: gofmt -w $(GOFMT_FILES) - -.PHONY: bin default generate test vet bootstrap fmt fmtcheck +update-resources: + pushd $(CURDIR)/plugin/iamutil && \ + go build -o generate ./internal && \ + ./generate && \ + rm generate && \ + popd + + +.PHONY: bin default generate test vet bootstrap fmt fmtcheck update-resources diff --git a/go.sum b/go.sum index 1cb7c8c5..3f02d81d 100644 --- a/go.sum +++ b/go.sum @@ -228,6 +228,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -251,6 +253,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -283,6 +286,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qd golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= diff --git a/plugin/backend.go b/plugin/backend.go index e498fcfc..114f61b4 100644 --- a/plugin/backend.go +++ b/plugin/backend.go @@ -33,7 +33,7 @@ type backend struct { // cache directly. cache *cache.Cache - iamResources iamutil.IamResourceParser + resources iamutil.ResourceParser rolesetLock sync.Mutex } @@ -49,8 +49,8 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, func Backend() *backend { var b = &backend{ - cache: cache.New(), - iamResources: iamutil.GetEnabledIamResources(), + cache: cache.New(), + resources: iamutil.GetEnabledResources(), } b.Backend = &framework.Backend{ diff --git a/plugin/iamutil/api_handle.go b/plugin/iamutil/api_handle.go new file mode 100644 index 00000000..748015ed --- /dev/null +++ b/plugin/iamutil/api_handle.go @@ -0,0 +1,117 @@ +package iamutil + +import ( + "context" + "encoding/json" + "fmt" + "github.com/hashicorp/errwrap" + "google.golang.org/api/googleapi" + "io" + "net/http" + "strings" +) + +type ApiHandle struct { + c *http.Client + userAgent string +} + +func GetApiHandle(client *http.Client, userAgent string) *ApiHandle { + return &ApiHandle{ + c: client, + userAgent: userAgent, + } +} + +func (h *ApiHandle) DoGetRequest(ctx context.Context, r Resource, out interface{}) (err error) { + config := r.GetConfig() + req, err := constructRequest(r, &config.GetMethod, nil) + if err != nil { + return errwrap.Wrapf("Unable to construct Get request: {{err}}", err) + } + return h.doRequest(ctx, req, out) +} + +func (h *ApiHandle) DoSetRequest(ctx context.Context, r Resource, data io.Reader, out interface{}) error { + config := r.GetConfig() + req, err := constructRequest(r, &config.SetMethod, data) + if err != nil { + return errwrap.Wrapf("Unable to construct Set request: {{err}}", err) + } + return h.doRequest(ctx, req, out) +} + +func (h *ApiHandle) doRequest(ctx context.Context, req *http.Request, out interface{}) error { + if req.Header == nil { + req.Header = make(http.Header) + } + if h.userAgent != "" { + req.Header.Set("User-Agent", h.userAgent) + } + + resp, err := h.c.Do(req.WithContext(ctx)) + if err != nil { + return err + } + defer googleapi.CloseBody(resp) + + if err := googleapi.CheckResponse(resp); err != nil { + return err + } + + if err := json.NewDecoder(resp.Body).Decode(out); err != nil { + return errwrap.Wrapf("unable to decode JSON resp to output interface: {{err}}", err) + } + return nil +} + +func constructRequest(r Resource, restMethod *RestMethod, data io.Reader) (*http.Request, error) { + config := r.GetConfig() + if data == nil && config != nil && config.Service == "cloudresourcemanager" { + // In order to support Resource Manager policies with conditional bindings, + // we need to request the policy version of 3. This request parameter is backwards compatible + // and will return version 1 policies if they are not yet updated to version 3. + requestPolicyVersion3 := `{"options": {"requestedPolicyVersion": 3}}` + data = strings.NewReader(requestPolicyVersion3) + } + req, err := http.NewRequest( + restMethod.HttpMethod, + googleapi.ResolveRelative(restMethod.BaseURL, restMethod.Path), + data) + if err != nil { + return nil, err + } + + if req.Header == nil { + req.Header = make(http.Header) + } + if data != nil { + req.Header.Set("Content-Type", "application/json") + } + + relId := r.GetRelativeId() + replacementMap := make(map[string]string) + + if strings.Contains(restMethod.Path, "{+resource}") { + // +resource is used to represent full relative resource name + if len(config.Parameters) == 1 && config.Parameters[0] == "resource" { + relName := "" + tkns := strings.Split(config.TypeKey, "/") + for _, colId := range tkns { + relName += fmt.Sprintf("%s/%s/", colId, relId.IdTuples[colId]) + } + replacementMap["resource"] = strings.Trim(relName, "/") + } + } else { + for colId, resId := range relId.IdTuples { + rId, ok := config.CollectionReplacementKeys[colId] + if !ok { + return nil, fmt.Errorf("expected value for collection id %s", colId) + } + replacementMap[rId] = resId + } + } + + googleapi.Expand(req.URL, replacementMap) + return req, nil +} diff --git a/plugin/iamutil/iam_handle_test.go b/plugin/iamutil/api_handle_test.go similarity index 84% rename from plugin/iamutil/iam_handle_test.go rename to plugin/iamutil/api_handle_test.go index a296af73..41170fce 100644 --- a/plugin/iamutil/iam_handle_test.go +++ b/plugin/iamutil/api_handle_test.go @@ -14,8 +14,8 @@ import ( "google.golang.org/api/option" ) -func TestIamHandle_ServiceAccount(t *testing.T) { - createServiceAccount := func(t *testing.T, httpC *http.Client) *parsedIamResource { +func TestIamResource_ServiceAccount(t *testing.T) { + createServiceAccount := func(t *testing.T, httpC *http.Client) *IamResource { iamAdmin, err := iam.NewService(context.Background(), option.WithHTTPClient(httpC)) if err != nil { t.Fatal(err) @@ -38,15 +38,15 @@ func TestIamHandle_ServiceAccount(t *testing.T) { t.Fatal(err) } - rConfig := generatedIamResources["projects/serviceAccounts"]["iam"]["v1"] + rConfig := generatedResources["projects/serviceAccounts"]["iam"]["v1"] - return &parsedIamResource{ + return &IamResource{ relativeId: relId, config: &rConfig, } } - deleteServiceAccount := func(t *testing.T, httpC *http.Client, r *parsedIamResource) { + deleteServiceAccount := func(t *testing.T, httpC *http.Client, r *IamResource) { saName := fmt.Sprintf("projects/%s/serviceAccounts/%s", r.relativeId.IdTuples["projects"], r.relativeId.IdTuples["serviceAccounts"]) @@ -64,8 +64,8 @@ func TestIamHandle_ServiceAccount(t *testing.T) { } func verifyIamResource_GetSetPolicy(t *testing.T, resourceType string, - getF func(*testing.T, *http.Client) *parsedIamResource, - cleanupF func(*testing.T, *http.Client, *parsedIamResource)) { + getF func(*testing.T, *http.Client) *IamResource, + cleanupF func(*testing.T, *http.Client, *IamResource)) { _, creds := util.GetTestCredentials(t) httpC, err := gcputil.GetHttpClient(creds, iam.CloudPlatformScope) @@ -76,9 +76,9 @@ func verifyIamResource_GetSetPolicy(t *testing.T, resourceType string, r := getF(t, httpC) defer cleanupF(t, httpC, r) - h := GetIamHandle(httpC, "") + h := GetApiHandle(httpC, "") - p, err := h.GetIamPolicy(context.Background(), r) + p, err := r.GetIamPolicy(context.Background(), h) if err != nil { t.Fatalf("could not get IAM Policy for resource type '%s': %v", resourceType, err) } @@ -92,12 +92,12 @@ func verifyIamResource_GetSetPolicy(t *testing.T, resourceType string, t.Fatalf("could not get IAM Policy for resource type '%s': %v", resourceType, err) } - changedP, err := h.SetIamPolicy(context.Background(), r, newP) + changedP, err := r.SetIamPolicy(context.Background(), h, newP) if err != nil { t.Fatalf("could not set IAM Policy for resource type '%s': %v", resourceType, err) } - actualP, err := h.GetIamPolicy(context.Background(), r) + actualP, err := r.GetIamPolicy(context.Background(), h) if err != nil { t.Fatalf("could not get updated IAM Policy for resource type '%s': %v", resourceType, err) } diff --git a/plugin/iamutil/bigquery_access.go b/plugin/iamutil/bigquery_access.go deleted file mode 100644 index 7804b5ba..00000000 --- a/plugin/iamutil/bigquery_access.go +++ /dev/null @@ -1,71 +0,0 @@ -package iamutil - -import ( - "fmt" - "strings" -) - -// NOTE: BigQuery does not conform to the typical REST for IAM policies -// instead it has an access array with bindings on the dataset -// object. https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#Dataset -type AccessBinding struct { - Role string `json:"role,omitempty"` - UserByEmail string `json:"userByEmail,omitempty"` -} - -type Dataset struct { - Access []*AccessBinding `json:"access,omitempty"` -} - -func (p *Policy) AsDataset() Dataset { - dataset := Dataset{} - if p == nil { - return dataset - } - for _, binding := range p.Bindings { - for _, member := range binding.Members { - var email string - memberSplit := strings.Split(member, ":") - if len(memberSplit) == 2 { - email = memberSplit[1] - } else { - email = member - } - - if email != "" { - dataset.Access = append(dataset.Access, &AccessBinding{ - Role: binding.Role, - UserByEmail: email, - }) - } - } - } - return dataset -} - -func (ds *Dataset) AsPolicy() Policy { - policy := Policy{} - if ds == nil { - return policy - } - bindingMap := make(map[string]*Binding) - for _, accessBinding := range ds.Access { - email := fmt.Sprintf("serviceAccount:%s", accessBinding.UserByEmail) - if binding, ok := bindingMap[accessBinding.Role]; ok { - binding.Members = append(binding.Members, email) - } else { - bindingMap[accessBinding.Role] = &Binding{ - Role: accessBinding.Role, - Members: []string{email}, - } - } - } - for k := range bindingMap { - policy.Bindings = append(policy.Bindings, bindingMap[k]) - } - return policy -} - -func (r *parsedIamResource) IsBigqueryResource() bool { - return r.config.TypeKey == "projects/datasets" && r.config.Service == "bigquery" -} diff --git a/plugin/iamutil/dataset_resource.go b/plugin/iamutil/dataset_resource.go new file mode 100644 index 00000000..6a630f52 --- /dev/null +++ b/plugin/iamutil/dataset_resource.go @@ -0,0 +1,116 @@ +package iamutil + +import ( + "context" + "encoding/json" + "fmt" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/go-gcp-common/gcputil" + "strings" +) + +// NOTE: BigQuery does not conform to the typical REST for IAM policies +// instead it has an access array with bindings on the dataset +// object. https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets#Dataset +type AccessBinding struct { + Role string `json:"role,omitempty"` + UserByEmail string `json:"userByEmail,omitempty"` +} + +type Dataset struct { + Access []*AccessBinding `json:"access,omitempty"` +} + +// NOTE: DatasetResource implements IamResource. +// This is because bigquery datasets have their own +// ACLs instead of an IAM policy +type DatasetResource struct { + relativeId *gcputil.RelativeResourceName + config *RestResource +} + +func (r *DatasetResource) GetConfig() *RestResource { + return r.config +} + +func (r *DatasetResource) GetRelativeId() *gcputil.RelativeResourceName { + return r.relativeId +} + +func (r *DatasetResource) GetIamPolicy(ctx context.Context, h *ApiHandle) (*Policy, error) { + var dataset Dataset + if err := h.DoGetRequest(ctx, r, &dataset); err != nil { + return nil, errwrap.Wrapf("unable to get BigQuery Dataset ACL: {{err}}", err) + } + p := dataset.AsPolicy() + return &p, nil +} + +func (r *DatasetResource) SetIamPolicy(ctx context.Context, h *ApiHandle, p *Policy) (*Policy, error) { + var jsonP []byte + jsonP, err := json.Marshal(p.AsDataset()) + if err != nil { + return nil, err + } + reqJson := fmt.Sprintf(r.config.SetMethod.RequestFormat, jsonP) + if !json.Valid([]byte(reqJson)) { + return nil, fmt.Errorf("request format from generated BigQuery Dataset config invalid JSON: %s", reqJson) + } + + var dataset Dataset + if err := h.DoSetRequest(ctx, r, strings.NewReader(reqJson), &dataset); err != nil { + return nil, errwrap.Wrapf("unable to set BigQuery Dataset ACL: {{err}}", err) + } + policy := dataset.AsPolicy() + + return &policy, nil +} + +func (p *Policy) AsDataset() Dataset { + ds := Dataset{} + if p == nil { + return ds + } + for _, binding := range p.Bindings { + for _, member := range binding.Members { + var email string + memberSplit := strings.Split(member, ":") + if len(memberSplit) == 2 { + email = memberSplit[1] + } else { + email = member + } + + if email != "" { + ds.Access = append(ds.Access, &AccessBinding{ + Role: binding.Role, + UserByEmail: email, + }) + } + } + } + return ds +} + +func (ds *Dataset) AsPolicy() Policy { + policy := Policy{} + if ds == nil { + return policy + } + bindingMap := make(map[string]*Binding) + for _, accessBinding := range ds.Access { + email := fmt.Sprintf("serviceAccount:%s", accessBinding.UserByEmail) + if binding, ok := bindingMap[accessBinding.Role]; ok { + binding.Members = append(binding.Members, email) + } else { + bindingMap[accessBinding.Role] = &Binding{ + Role: accessBinding.Role, + Members: []string{email}, + } + } + } + for k := range bindingMap { + policy.Bindings = append(policy.Bindings, bindingMap[k]) + } + return policy +} diff --git a/plugin/iamutil/bigquery_access_test.go b/plugin/iamutil/dataset_resource_test.go similarity index 93% rename from plugin/iamutil/bigquery_access_test.go rename to plugin/iamutil/dataset_resource_test.go index 99754fe6..2c6dedb9 100644 --- a/plugin/iamutil/bigquery_access_test.go +++ b/plugin/iamutil/dataset_resource_test.go @@ -22,12 +22,12 @@ func TestPolicyToDataset(t *testing.T) { } func TestDatasetToPolicy(t *testing.T) { - expectedPolicy, dataset := getTestFixtures() + expectedPolicy, ds := getTestFixtures() expectedPolicyBytes, err := json.Marshal(expectedPolicy) if err != nil { t.Fatal(err) } - actualPolicy := dataset.AsPolicy() + actualPolicy := ds.AsPolicy() actualPolicyBytes, err := json.Marshal(actualPolicy) if err != nil { t.Fatal(err) @@ -55,7 +55,7 @@ func getTestFixtures() (*Policy, *Dataset) { }, }, } - dataset := &Dataset{ + ds := &Dataset{ Access: []*AccessBinding{ &AccessBinding{ Role: "roles/bigquery.dataViewer", @@ -71,5 +71,5 @@ func getTestFixtures() (*Policy, *Dataset) { }, }, } - return policy, dataset + return policy, ds } diff --git a/plugin/iamutil/iam_handle.go b/plugin/iamutil/iam_handle.go deleted file mode 100644 index ee87efaf..00000000 --- a/plugin/iamutil/iam_handle.go +++ /dev/null @@ -1,86 +0,0 @@ -package iamutil - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/hashicorp/errwrap" - "google.golang.org/api/googleapi" -) - -type IamHandle struct { - c *http.Client - userAgent string -} - -func GetIamHandle(client *http.Client, userAgent string) *IamHandle { - return &IamHandle{ - c: client, - userAgent: userAgent, - } -} - -func (h *IamHandle) GetIamPolicy(ctx context.Context, r IamResource) (*Policy, error) { - req, err := r.GetIamPolicyRequest() - if err != nil { - return nil, errwrap.Wrapf("unable to construct GetIamPolicy request: {{err}}", err) - } - var p Policy - if r.IsBigqueryResource() { - var dataset Dataset - if err := h.doRequest(ctx, req, &dataset); err != nil { - return nil, errwrap.Wrapf("unable to get BigQuery Dataset ACL: {{err}}", err) - } - p = dataset.AsPolicy() - } else { - if err := h.doRequest(ctx, req, &p); err != nil { - return nil, errwrap.Wrapf("unable to get policy: {{err}}", err) - } - } - return &p, nil -} - -func (h *IamHandle) SetIamPolicy(ctx context.Context, r IamResource, p *Policy) (*Policy, error) { - req, err := r.SetIamPolicyRequest(p) - if err != nil { - return nil, errwrap.Wrapf("unable to construct SetIamPolicy request: {{err}}", err) - } - var out Policy - if r.IsBigqueryResource() { - var dataset Dataset - if err := h.doRequest(ctx, req, &dataset); err != nil { - return nil, errwrap.Wrapf("unable to set BigQuery Dataset ACL: {{err}}", err) - } - out = dataset.AsPolicy() - } else { - if err := h.doRequest(ctx, req, &out); err != nil { - return nil, errwrap.Wrapf("unable to set policy: {{err}}", err) - } - } - return &out, nil -} - -func (h *IamHandle) doRequest(ctx context.Context, req *http.Request, out interface{}) error { - if req.Header == nil { - req.Header = make(http.Header) - } - if h.userAgent != "" { - req.Header.Set("User-Agent", h.userAgent) - } - - resp, err := h.c.Do(req.WithContext(ctx)) - if err != nil { - return err - } - defer googleapi.CloseBody(resp) - - if err := googleapi.CheckResponse(resp); err != nil { - return err - } - - if err := json.NewDecoder(resp.Body).Decode(out); err != nil { - return errwrap.Wrapf("unable to decode JSON resp to output interface: {{err}}", err) - } - return nil -} diff --git a/plugin/iamutil/iam_resource.go b/plugin/iamutil/iam_resource.go index 0d273c26..811245d5 100644 --- a/plugin/iamutil/iam_resource.go +++ b/plugin/iamutil/iam_resource.go @@ -1,73 +1,38 @@ package iamutil import ( + "context" "encoding/json" "fmt" + "github.com/hashicorp/errwrap" "github.com/hashicorp/go-gcp-common/gcputil" - "google.golang.org/api/googleapi" - "io" - "net/http" "strings" ) -// IamResource handles constructing HTTP requests for getting and -// setting IAM policies. -type IamResource interface { - GetIamPolicyRequest() (*http.Request, error) - SetIamPolicyRequest(*Policy) (req *http.Request, err error) - IsBigqueryResource() bool -} - -// parsedIamResource implements IamResource. -type parsedIamResource struct { +// IamResource implements Resource. +type IamResource struct { relativeId *gcputil.RelativeResourceName - config *IamRestResource + config *RestResource } -type IamRestResource struct { - // Name is the base name of the resource - // i.e. for a GCE instance: "instance" - Name string - - // Type Key is the identifying path for the resource, or - // the RESTful resource identifier without resource IDs - // i.e. For a GCE instance: "projects/zones/instances" - TypeKey string - - // Service Information - // Service is the name of the service this resource belongs to. - Service string - - // IsPreferredVersion is true if this version of the API/resource is preferred. - IsPreferredVersion bool - - // IsPreferredVersion is true if this version of the API/resource is preferred. - GetMethod RestMethod - - // IsPreferredVersion is true if this version of the API/resource is preferred. - SetMethod RestMethod - - // Ordered parameters to be replaced in method paths - Parameters []string - - // collection Id --> parameter to be replaced {} name - CollectionReplacementKeys map[string]string +func (r *IamResource) GetConfig() *RestResource { + return r.config } -type RestMethod struct { - HttpMethod string - BaseURL string - Path string - RequestFormat string +func (r *IamResource) GetRelativeId() *gcputil.RelativeResourceName { + return r.relativeId } -func (r *parsedIamResource) SetIamPolicyRequest(p *Policy) (req *http.Request, err error) { - var jsonP []byte - if r.IsBigqueryResource() { - jsonP, err = json.Marshal(p.AsDataset()) - } else { - jsonP, err = json.Marshal(p) +func (r *IamResource) GetIamPolicy(ctx context.Context, h *ApiHandle) (*Policy, error) { + var p Policy + if err := h.DoGetRequest(ctx, r, &p); err != nil { + return nil, errwrap.Wrapf("unable to get policy: {{err}}", err) } + return &p, nil +} + +func (r *IamResource) SetIamPolicy(ctx context.Context, h *ApiHandle, p *Policy) (*Policy, error) { + jsonP, err := json.Marshal(p) if err != nil { return nil, err } @@ -76,60 +41,9 @@ func (r *parsedIamResource) SetIamPolicyRequest(p *Policy) (req *http.Request, e return nil, fmt.Errorf("request format from generated IAM config invalid JSON: %s", reqJson) } - return r.constructRequest(&r.config.SetMethod, strings.NewReader(reqJson)) -} - -var requestPolicyVersion3 = `{"options": {"requestedPolicyVersion": 3}}` - -func (r *parsedIamResource) GetIamPolicyRequest() (*http.Request, error) { - // In order to support Resource Manager policies with conditional bindings, - // we need to request the policy version of 3. This request parameter is backwards compatible - // and will return version 1 policies if they are not yet updated to version 3. - if r.config != nil && r.config.Service == "cloudresourcemanager" { - return r.constructRequest(&r.config.GetMethod, strings.NewReader(requestPolicyVersion3)) - } - return r.constructRequest(&r.config.GetMethod, nil) -} - -func (r *parsedIamResource) constructRequest(restMethod *RestMethod, data io.Reader) (*http.Request, error) { - req, err := http.NewRequest( - restMethod.HttpMethod, - googleapi.ResolveRelative(restMethod.BaseURL, restMethod.Path), - data) - if err != nil { - return nil, err + var policy Policy + if err := h.DoSetRequest(ctx, r, strings.NewReader(reqJson), &policy); err != nil { + return nil, errwrap.Wrapf("unable to set policy: {{err}}", err) } - - if req.Header == nil { - req.Header = make(http.Header) - } - if data != nil { - req.Header.Set("Content-Type", "application/json") - } - - relId := r.relativeId - replacementMap := make(map[string]string) - - if strings.Contains(restMethod.Path, "{+resource}") { - // +resource is used to represent full relative resource name - if len(r.config.Parameters) == 1 && r.config.Parameters[0] == "resource" { - relName := "" - tkns := strings.Split(r.config.TypeKey, "/") - for _, colId := range tkns { - relName += fmt.Sprintf("%s/%s/", colId, relId.IdTuples[colId]) - } - replacementMap["resource"] = strings.Trim(relName, "/") - } - } else { - for colId, resId := range relId.IdTuples { - rId, ok := r.config.CollectionReplacementKeys[colId] - if !ok { - return nil, fmt.Errorf("expected value for collection id %s", colId) - } - replacementMap[rId] = resId - } - } - - googleapi.Expand(req.URL, replacementMap) - return req, nil + return &policy, nil } diff --git a/plugin/iamutil/iam_resource_test.go b/plugin/iamutil/iam_resource_test.go index f37f65fa..d7caa063 100644 --- a/plugin/iamutil/iam_resource_test.go +++ b/plugin/iamutil/iam_resource_test.go @@ -2,13 +2,15 @@ package iamutil import ( "encoding/json" + "fmt" "github.com/hashicorp/go-gcp-common/gcputil" "io/ioutil" + "strings" "testing" ) -func TestParsedIamResource(t *testing.T) { - r := &parsedIamResource{ +func TestIamResource(t *testing.T) { + r := &IamResource{ relativeId: &gcputil.RelativeResourceName{ Name: "b", TypeKey: "f/b", @@ -18,7 +20,7 @@ func TestParsedIamResource(t *testing.T) { }, OrderedCollectionIds: []string{"f", "b"}, }, - config: &IamRestResource{ + config: &RestResource{ Name: "b", TypeKey: "f/b", Service: "agcpservice", @@ -44,7 +46,7 @@ func TestParsedIamResource(t *testing.T) { }, } - getR, err := r.GetIamPolicyRequest() + getR, err := constructRequest(r, &r.config.GetMethod, nil) if err != nil { t.Fatalf("Could not construct GetIamPolicyRequest: %v", err) } @@ -73,7 +75,18 @@ func TestParsedIamResource(t *testing.T) { }, }, } - setR, err := r.SetIamPolicyRequest(expectedP) + + jsonP, err := json.Marshal(expectedP) + if err != nil { + t.Fatalf("Could not json marshal expected policy: %v", err) + } + + reqJson := fmt.Sprintf(r.config.SetMethod.RequestFormat, jsonP) + if !json.Valid([]byte(reqJson)) { + t.Fatalf("Could not formate expected policy: %v", err) + } + + setR, err := constructRequest(r, &r.config.SetMethod, strings.NewReader(reqJson)) if err != nil { t.Fatalf("Could not construct SetIamPolicyRequest: %v", err) } @@ -126,7 +139,7 @@ func TestParsedIamResource(t *testing.T) { } func TestConditionalIamResource(t *testing.T) { - r := &parsedIamResource{ + r := &IamResource{ relativeId: &gcputil.RelativeResourceName{ Name: "projects", TypeKey: "cloudresourcemanager/projects", @@ -135,7 +148,7 @@ func TestConditionalIamResource(t *testing.T) { }, OrderedCollectionIds: []string{"cloudresourcemanager", "projects"}, }, - config: &IamRestResource{ + config: &RestResource{ Name: "projects", TypeKey: "cloudresourcemanager/projects", Service: "cloudresourcemanager", @@ -157,7 +170,7 @@ func TestConditionalIamResource(t *testing.T) { }, } - getR, err := r.GetIamPolicyRequest() + getR, err := constructRequest(r, &r.config.GetMethod, nil) if err != nil { t.Fatalf("Could not construct GetIamPolicyRequest: %v", err) } @@ -218,7 +231,7 @@ func TestConditionalIamResource(t *testing.T) { Condition: &Condition{ Title: "test", Description: "", - Expression: "a==b", + Expression: "a==b", }, }, { @@ -227,7 +240,18 @@ func TestConditionalIamResource(t *testing.T) { }, }, } - setR, err := r.SetIamPolicyRequest(expectedP) + + jsonP, err := json.Marshal(expectedP) + if err != nil { + t.Fatalf("Could not json marshal expected policy: %v", err) + } + + reqJson := fmt.Sprintf(r.config.SetMethod.RequestFormat, jsonP) + if !json.Valid([]byte(reqJson)) { + t.Fatalf("Could not formate expected policy: %v", err) + } + + setR, err := constructRequest(r, &r.config.SetMethod, strings.NewReader(reqJson)) if err != nil { t.Fatalf("Could not construct SetIamPolicyRequest: %v", err) } diff --git a/plugin/iamutil/internal/generate_iam.go b/plugin/iamutil/internal/generate_resources.go similarity index 84% rename from plugin/iamutil/internal/generate_iam.go rename to plugin/iamutil/internal/generate_resources.go index 5c208f48..e6b3a17f 100644 --- a/plugin/iamutil/internal/generate_iam.go +++ b/plugin/iamutil/internal/generate_resources.go @@ -21,7 +21,7 @@ import ( const ( templateFile = "resource_config_template" - outputFile = "iam_resources_generated.go" + outputFile = "resources_generated.go" ) var sanizitedCollectionIds = map[string]string{ @@ -86,7 +86,7 @@ func checkResource(name string, fullPath string, resource *discovery.RestResourc return fmt.Errorf("unable to get schema for setIamPolicy request, could not find policy in schema '%s'", setM.Request.Ref) } - r := iamutil.IamRestResource{ + r := iamutil.RestResource{ Name: name, TypeKey: typeKey, Service: doc.Name, @@ -157,6 +157,9 @@ func parseTypeKeyFromPattern(pattern string) string { typeKey := "" re := regexp.MustCompile("^[a-zA-Z]*[a-z]$") ptn := strings.Trim(pattern, "^$/") + // In a few resources, the Discovery API hardcodes "global" which if set + // as the TypeKey breaks the common pattern in tests. + ptn = strings.ReplaceAll(ptn, "global/", "") tkns := strings.Split(ptn, "/") for _, tkn := range tkns { if re.MatchString(tkn) { @@ -181,13 +184,13 @@ func getPolicyReplacementString(sch *discovery.JsonSchema) string { return "" } -func addToConfig(resourceKey, service, version string, r iamutil.IamRestResource, config iamutil.GeneratedResources) { +func addToConfig(resourceKey, service, version string, r iamutil.RestResource, config iamutil.GeneratedResources) { log.Printf("adding [%s %s %s]", resourceKey, service, version) if _, ok := config[resourceKey]; !ok { - config[resourceKey] = make(map[string]map[string]iamutil.IamRestResource) + config[resourceKey] = make(map[string]map[string]iamutil.RestResource) } if _, ok := config[resourceKey][service]; !ok { - config[resourceKey][service] = make(map[string]iamutil.IamRestResource) + config[resourceKey][service] = make(map[string]iamutil.RestResource) } config[resourceKey][service][version] = r } @@ -236,11 +239,9 @@ func generateConfig() error { return err } - // Inject BigQuery here since it doesn't have a IAM setter/getter - config["projects/datasets"] = map[string]map[string]iamutil.IamRestResource{ - "bigquery": { - "v2": bqResource(), - }, + // Inject overrides that use ACLs instead of IAM policies + for k, v := range resourceOverrides { + config[k] = v } if err := writeConfig(config); err != nil { @@ -250,31 +251,6 @@ func generateConfig() error { return nil } -func bqResource() iamutil.IamRestResource { - return iamutil.IamRestResource{ - Name: "datasets", - TypeKey: "projects/datasets", - Service: "bigquery", - IsPreferredVersion: true, - Parameters: []string{"resource"}, - CollectionReplacementKeys: map[string]string{}, - GetMethod: iamutil.RestMethod{ - HttpMethod: "GET", - BaseURL: "https://bigquery.googleapis.com", - Path: "bigquery/v2/{+resource}", - }, - SetMethod: iamutil.RestMethod{ - HttpMethod: "PATCH", - BaseURL: "https://bigquery.googleapis.com", - // NOTE: the bigquery portion of the path needs to be in - // the version since googleapis removes it from the - // BaseURL when resolving - Path: "bigquery/v2/{+resource}", - RequestFormat: "%s", - }, - } -} - func writeConfig(config iamutil.GeneratedResources) error { tpl, err := template.ParseFiles(fmt.Sprintf("internal/%s", templateFile)) diff --git a/plugin/iamutil/internal/resource_config_template b/plugin/iamutil/internal/resource_config_template index 947cbf47..98ae74f8 100644 --- a/plugin/iamutil/internal/resource_config_template +++ b/plugin/iamutil/internal/resource_config_template @@ -2,17 +2,17 @@ // THIS FILE IS AUTOGENERATED USING go generate. DO NOT EDIT. package iamutil -func GetEnabledIamResources() GeneratedResources { - return generatedIamResources +func GetEnabledResources() GeneratedResources { + return generatedResources } -var generatedIamResources = map[string]map[string]map[string]IamRestResource { +var generatedResources = map[string]map[string]map[string]RestResource { {{ range $typeKey,$serviceMap := . -}} "{{$typeKey}}": { {{ range $service, $versionMap := . -}} "{{$service}}": { {{ range $version, $resource := $versionMap -}} - "{{$version}}": {{template "iam_rest_resource" $resource}}, + "{{$version}}": {{template "rest_resource" $resource}}, {{ end -}} }, {{ end -}} @@ -22,8 +22,8 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource { {{end}} -{{ define "iam_rest_resource" -}} -IamRestResource{ +{{ define "rest_resource" -}} +RestResource{ Name: "{{.Name}}", TypeKey: "{{.TypeKey}}", Service: "{{.Service}}", @@ -54,4 +54,4 @@ RestMethod { Path: "{{ .Path }}", RequestFormat: `{{ .RequestFormat }}`, } -{{- end}} \ No newline at end of file +{{- end}} diff --git a/plugin/iamutil/internal/resource_overrides.go b/plugin/iamutil/internal/resource_overrides.go new file mode 100644 index 00000000..f0096170 --- /dev/null +++ b/plugin/iamutil/internal/resource_overrides.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/hashicorp/vault-plugin-secrets-gcp/plugin/iamutil" +) + +var resourceOverrides = map[string]map[string]map[string]iamutil.RestResource{ + "projects/datasets": { + "bigquery": { + "v2": iamutil.RestResource{ + Name: "datasets", + TypeKey: "projects/datasets", + Service: "bigquery", + IsPreferredVersion: true, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: iamutil.RestMethod{ + HttpMethod: "GET", + BaseURL: "https://bigquery.googleapis.com", + Path: "bigquery/v2/{+resource}", + }, + SetMethod: iamutil.RestMethod{ + HttpMethod: "PATCH", + BaseURL: "https://bigquery.googleapis.com", + // NOTE: the bigquery portion of the path needs to be in + // the version since googleapis removes it from the + // BaseURL when resolving + Path: "bigquery/v2/{+resource}", + RequestFormat: "%s", + }, + }, + }, + }, +} diff --git a/plugin/iamutil/resource.go b/plugin/iamutil/resource.go new file mode 100644 index 00000000..70cbd621 --- /dev/null +++ b/plugin/iamutil/resource.go @@ -0,0 +1,52 @@ +package iamutil + +import ( + "context" + "github.com/hashicorp/go-gcp-common/gcputil" +) + +// IamResource handles constructing HTTP requests for getting and +// setting IAM policies. +type Resource interface { + GetIamPolicy(context.Context, *ApiHandle) (*Policy, error) + SetIamPolicy(context.Context, *ApiHandle, *Policy) (*Policy, error) + GetConfig() *RestResource + GetRelativeId() *gcputil.RelativeResourceName +} + +type RestResource struct { + // Name is the base name of the resource + // i.e. for a GCE instance: "instance" + Name string + + // Type Key is the identifying path for the resource, or + // the RESTful resource identifier without resource IDs + // i.e. For a GCE instance: "projects/zones/instances" + TypeKey string + + // Service Information + // Service is the name of the service this resource belongs to. + Service string + + // IsPreferredVersion is true if this version of the API/resource is preferred. + IsPreferredVersion bool + + // IsPreferredVersion is true if this version of the API/resource is preferred. + GetMethod RestMethod + + // IsPreferredVersion is true if this version of the API/resource is preferred. + SetMethod RestMethod + + // Ordered parameters to be replaced in method paths + Parameters []string + + // collection Id --> parameter to be replaced {} name + CollectionReplacementKeys map[string]string +} + +type RestMethod struct { + HttpMethod string + BaseURL string + Path string + RequestFormat string +} diff --git a/plugin/iamutil/iam_resource_parser.go b/plugin/iamutil/resource_parser.go similarity index 83% rename from plugin/iamutil/iam_resource_parser.go rename to plugin/iamutil/resource_parser.go index 08d20dfc..f0f3bb24 100644 --- a/plugin/iamutil/iam_resource_parser.go +++ b/plugin/iamutil/resource_parser.go @@ -14,17 +14,17 @@ const ( errorMultipleVersions = `please provide a self-link with version instead; multiple versions of this resource exist, all non-preferred` ) -// IamResourceParser handles parsing resource ID and REST +// ResourceParser handles parsing resource ID and REST // config from a given resource ID or name. -type IamResourceParser interface { - Parse(string) (IamResource, error) +type ResourceParser interface { + Parse(string) (Resource, error) } -// GeneratedResources implements IamResourceParser - a value +// GeneratedResources implements ResourceParser - a value // is generated using internal/generate_iam.go -type GeneratedResources map[string]map[string]map[string]IamRestResource +type GeneratedResources map[string]map[string]map[string]RestResource -func getResourceFromVersions(rawName string, versionMap map[string]IamRestResource) (*IamRestResource, error) { +func getResourceFromVersions(rawName string, versionMap map[string]RestResource) (*RestResource, error) { possibleVer := make([]string, 0, len(versionMap)) for v, config := range versionMap { if config.IsPreferredVersion || len(versionMap) == 1 { @@ -45,10 +45,10 @@ func getResourceFromVersions(rawName string, versionMap map[string]IamRestResour return nil, fmt.Errorf(resourceParsingErrorTmpl, rawName, errorMultipleVersions) } -func (apis GeneratedResources) GetRestConfig(rawName string, fullName *gcputil.FullResourceName, prefix string) (*IamRestResource, error) { +func (apis GeneratedResources) GetRestConfig(rawName string, fullName *gcputil.FullResourceName, prefix string) (*RestResource, error) { relName := fullName.RelativeResourceName if relName == nil { - return nil, fmt.Errorf(resourceParsingErrorTmpl, rawName, fmt.Errorf("unsupported resource type: %s", rawName)) + return nil, fmt.Errorf(resourceParsingErrorTmpl, rawName, fmt.Errorf("relative name does not exist: %s", rawName)) } serviceMap, ok := apis[relName.TypeKey] @@ -80,7 +80,7 @@ func (apis GeneratedResources) GetRestConfig(rawName string, fullName *gcputil.F return nil, fmt.Errorf(resourceParsingErrorTmpl, rawName, errorMultipleServices) } -func (apis GeneratedResources) Parse(rawName string) (IamResource, error) { +func (apis GeneratedResources) Parse(rawName string) (Resource, error) { rUrl, err := url.Parse(rawName) if err != nil { return nil, fmt.Errorf(`resource "%s" is invalid URI`, rawName) @@ -123,8 +123,10 @@ func (apis GeneratedResources) Parse(rawName string) (IamResource, error) { if err != nil { return nil, err } - return &parsedIamResource{ - relativeId: relName, - config: cfg, - }, nil + switch cfg.TypeKey { + case "projects/dataset": + return &DatasetResource{relativeId: relName, config: cfg}, nil + default: + return &IamResource{relativeId: relName, config: cfg}, nil + } } diff --git a/plugin/iamutil/iam_resource_parser_test.go b/plugin/iamutil/resource_parser_test.go similarity index 83% rename from plugin/iamutil/iam_resource_parser_test.go rename to plugin/iamutil/resource_parser_test.go index fca0db9f..5a1e080d 100644 --- a/plugin/iamutil/iam_resource_parser_test.go +++ b/plugin/iamutil/resource_parser_test.go @@ -11,10 +11,12 @@ import ( "github.com/hashicorp/errwrap" ) +var letters = "ABCDEFGHIJKLMNOP" + func TestEnabledIamResources_RelativeName(t *testing.T) { - enabledApis := GetEnabledIamResources() + enabledApis := GetEnabledResources() - for resourceType, services := range generatedIamResources { + for resourceType, services := range generatedResources { if resourceType == "" { continue } @@ -37,7 +39,7 @@ func TestEnabledIamResources_RelativeName(t *testing.T) { } if resource != nil { - if err = verifyResource(resourceType, resource.(*parsedIamResource)); err != nil { + if err = verifyResource(resourceType, resource.(*IamResource)); err != nil { t.Errorf("could not verify resource for relative resource name %q: %sv", testRelName, err) } } @@ -49,9 +51,9 @@ func TestEnabledIamResources_RelativeName(t *testing.T) { } func TestEnabledIamResources_FullName(t *testing.T) { - enabledApis := GetEnabledIamResources() + enabledApis := GetEnabledResources() - for resourceType, services := range generatedIamResources { + for resourceType, services := range generatedResources { if resourceType == "" { continue } @@ -65,7 +67,7 @@ func TestEnabledIamResources_FullName(t *testing.T) { t.Errorf("failed to get resource for full resource name %s (type: %s): %v", testFullName, resourceType, err) continue } - if err = verifyResource(resourceType, resource.(*parsedIamResource)); err != nil { + if err = verifyResource(resourceType, resource.(*IamResource)); err != nil { t.Errorf("could not verify resource for relative resource name %s: %v", testFullName, err) continue } @@ -77,7 +79,7 @@ func TestEnabledIamResources_FullName(t *testing.T) { } } -func constructSelfLink(relName string, cfg IamRestResource) (string, error) { +func constructSelfLink(relName string, cfg RestResource) (string, error) { reqUrl := cfg.GetMethod.BaseURL + cfg.GetMethod.Path _, err := url.Parse(reqUrl) @@ -102,9 +104,9 @@ func constructSelfLink(relName string, cfg IamRestResource) (string, error) { } func TestEnabledIamResources_SelfLink(t *testing.T) { - enabledApis := GetEnabledIamResources() + enabledApis := GetEnabledResources() - for resourceType, services := range generatedIamResources { + for resourceType, services := range generatedResources { for _, versions := range services { for _, cfg := range versions { relName := getFakeId(resourceType) @@ -123,8 +125,10 @@ func TestEnabledIamResources_SelfLink(t *testing.T) { if err != nil { t.Errorf("failed to get resource for self link %s (type: %s): %v", testSelfLink, resourceType, err) } - if err = verifyResource(resourceType, resource.(*parsedIamResource)); err != nil { - t.Errorf("could not verify resource for self link %s: %v", testSelfLink, err) + if r, ok := resource.(*IamResource); ok { + if err = verifyResource(resourceType, r); err != nil { + t.Errorf("could not verify resource for self link %s: %v", testSelfLink, err) + } } } else if resource != nil || err == nil { t.Errorf("expected error for using self link %s (type: %s), got resource:\n %v\n", testSelfLink, resourceType, resource) @@ -135,7 +139,7 @@ func TestEnabledIamResources_SelfLink(t *testing.T) { } } -func expectVersionError(versions map[string]IamRestResource) bool { +func expectVersionError(versions map[string]RestResource) bool { if len(versions) == 1 { return false } @@ -193,7 +197,7 @@ func verifyHttpMethod(typeKey string, m *RestMethod) error { } func TestIamEnabledResources_ValidateGeneratedConfig(t *testing.T) { - for typeKey, services := range generatedIamResources { + for typeKey, services := range generatedResources { for service, versions := range services { for ver, cfg := range versions { if cfg.Service != service { @@ -216,17 +220,19 @@ func getFakeId(resourceType string) string { fakeId := "" for idx, cid := range collectionIds { - fakeId += fmt.Sprintf("%s/aFakeId%d/", cid, idx) + suffix := letters[idx] + fakeId += fmt.Sprintf("%s/aFakeId%s/", cid, string(suffix)) } return strings.Trim(fakeId, "/") } -func verifyResource(rType string, resource *parsedIamResource) error { +func verifyResource(rType string, resource *IamResource) (err error) { + var req *http.Request if resource.relativeId.TypeKey != rType { return fmt.Errorf("expected resource type %s, actual resource has different type %s", rType, resource.relativeId.TypeKey) } - req, err := resource.GetIamPolicyRequest() + req, err = constructRequest(resource, &resource.config.GetMethod, nil) if err != nil { return errwrap.Wrapf("unable to construct GetIamPolicyRequest: {{err}}", err) } @@ -234,7 +240,7 @@ func verifyResource(rType string, resource *parsedIamResource) error { return err } - req, err = resource.SetIamPolicyRequest(nil) + req, err = constructRequest(resource, &resource.config.SetMethod, strings.NewReader("{}")) if err != nil { return errwrap.Wrapf("unable to construct SetIamPolicyRequest: {{err}}", err) } @@ -247,7 +253,8 @@ func verifyResource(rType string, resource *parsedIamResource) error { func verifyConstructRequest(req *http.Request, resourceType string) error { collectionIds := strings.Split(resourceType, "/") for idx := range collectionIds { - rid := fmt.Sprintf("/aFakeId%d", idx) + suffix := letters[idx] + rid := fmt.Sprintf("/aFakeId%s", string(suffix)) if !strings.Contains(req.URL.Path, rid) { return fmt.Errorf("expected expanded request URL %s to contain %s", req.URL.String(), rid) } diff --git a/plugin/iamutil/iam_resources_generated.go b/plugin/iamutil/resources_generated.go similarity index 90% rename from plugin/iamutil/iam_resources_generated.go rename to plugin/iamutil/resources_generated.go index 059accaa..e33030c6 100644 --- a/plugin/iamutil/iam_resources_generated.go +++ b/plugin/iamutil/resources_generated.go @@ -1,14 +1,14 @@ // THIS FILE IS AUTOGENERATED USING go generate. DO NOT EDIT. package iamutil -func GetEnabledIamResources() GeneratedResources { - return generatedIamResources +func GetEnabledResources() GeneratedResources { + return generatedResources } -var generatedIamResources = map[string]map[string]map[string]IamRestResource{ +var generatedResources = map[string]map[string]map[string]RestResource{ "": { "iap": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "v1", TypeKey: "", Service: "iap", @@ -27,7 +27,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "v1beta1", TypeKey: "", Service: "iap", @@ -50,7 +50,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "b": { "storage": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "buckets", TypeKey: "b", Service: "storage", @@ -76,7 +76,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "b/o": { "storage": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "objects", TypeKey: "b/o", Service: "storage", @@ -104,7 +104,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "billingAccounts": { "cloudbilling": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "billingAccounts", TypeKey: "billingAccounts", Service: "cloudbilling", @@ -127,7 +127,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "buckets": { "storage": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "buckets", TypeKey: "buckets", Service: "storage", @@ -153,7 +153,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "buckets/objects": { "storage": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "objects", TypeKey: "buckets/objects", Service: "storage", @@ -181,7 +181,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "folders": { "cloudresourcemanager": { - "v2": IamRestResource{ + "v2": RestResource{ Name: "folders", TypeKey: "folders", Service: "cloudresourcemanager", @@ -200,7 +200,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v2beta1": IamRestResource{ + "v2beta1": RestResource{ Name: "folders", TypeKey: "folders", Service: "cloudresourcemanager", @@ -223,7 +223,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "organizations": { "cloudresourcemanager": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "organizations", TypeKey: "organizations", Service: "cloudresourcemanager", @@ -242,7 +242,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "organizations", TypeKey: "organizations", Service: "cloudresourcemanager", @@ -265,7 +265,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "organizations/sources": { "securitycenter": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "sources", TypeKey: "organizations/sources", Service: "securitycenter", @@ -284,7 +284,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "sources", TypeKey: "organizations/sources", Service: "securitycenter", @@ -303,7 +303,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1p1beta1": IamRestResource{ + "v1p1beta1": RestResource{ Name: "sources", TypeKey: "organizations/sources", Service: "securitycenter", @@ -326,7 +326,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects": { "cloudresourcemanager": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "projects", TypeKey: "projects", Service: "cloudresourcemanager", @@ -347,7 +347,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "projects", TypeKey: "projects", Service: "cloudresourcemanager", @@ -372,7 +372,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/backendBuckets": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "backendBuckets", TypeKey: "projects/backendBuckets", Service: "compute", @@ -398,7 +398,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/configs": { "runtimeconfig": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "configs", TypeKey: "projects/configs", Service: "runtimeconfig", @@ -421,7 +421,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/datasets": { "bigquery": { - "v2": IamRestResource{ + "v2": RestResource{ Name: "datasets", TypeKey: "projects/datasets", Service: "bigquery", @@ -444,7 +444,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/deployments": { "deploymentmanager": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "deployments", TypeKey: "projects/deployments", Service: "deploymentmanager", @@ -466,7 +466,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v2": IamRestResource{ + "v2": RestResource{ Name: "deployments", TypeKey: "projects/deployments", Service: "deploymentmanager", @@ -488,7 +488,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v2beta": IamRestResource{ + "v2beta": RestResource{ Name: "deployments", TypeKey: "projects/deployments", Service: "deploymentmanager", @@ -514,7 +514,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/images": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "images", TypeKey: "projects/images", Service: "compute", @@ -536,7 +536,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "images", TypeKey: "projects/images", Service: "compute", @@ -558,7 +558,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "images", TypeKey: "projects/images", Service: "compute", @@ -584,7 +584,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/instanceTemplates": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "instanceTemplates", TypeKey: "projects/instanceTemplates", Service: "compute", @@ -606,7 +606,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "instanceTemplates", TypeKey: "projects/instanceTemplates", Service: "compute", @@ -628,7 +628,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "instanceTemplates", TypeKey: "projects/instanceTemplates", Service: "compute", @@ -654,7 +654,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/instances": { "bigtableadmin": { - "v2": IamRestResource{ + "v2": RestResource{ Name: "instances", TypeKey: "projects/instances", Service: "bigtableadmin", @@ -675,7 +675,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, }, "spanner": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "instances", TypeKey: "projects/instances", Service: "spanner", @@ -698,7 +698,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/instances/backups": { "spanner": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "backups", TypeKey: "projects/instances/backups", Service: "spanner", @@ -719,9 +719,32 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, }, }, + "projects/instances/clusters/backups": { + "bigtableadmin": { + "v2": RestResource{ + Name: "backups", + TypeKey: "projects/instances/clusters/backups", + Service: "bigtableadmin", + IsPreferredVersion: true, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://bigtableadmin.googleapis.com/", + Path: "v2/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://bigtableadmin.googleapis.com/", + Path: "v2/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + }, + }, "projects/instances/databases": { "spanner": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "databases", TypeKey: "projects/instances/databases", Service: "spanner", @@ -744,7 +767,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/instances/tables": { "bigtableadmin": { - "v2": IamRestResource{ + "v2": RestResource{ Name: "tables", TypeKey: "projects/instances/tables", Service: "bigtableadmin", @@ -767,7 +790,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/interconnects": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "interconnects", TypeKey: "projects/interconnects", Service: "compute", @@ -793,7 +816,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/licenseCodes": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "licenseCodes", TypeKey: "projects/licenseCodes", Service: "compute", @@ -819,7 +842,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/licenses": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "licenses", TypeKey: "projects/licenses", Service: "compute", @@ -841,7 +864,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "licenses", TypeKey: "projects/licenses", Service: "compute", @@ -863,7 +886,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "licenses", TypeKey: "projects/licenses", Service: "compute", @@ -889,7 +912,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/autoscalingPolicies": { "dataproc": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "autoscalingPolicies", TypeKey: "projects/locations/autoscalingPolicies", Service: "dataproc", @@ -908,7 +931,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "autoscalingPolicies", TypeKey: "projects/locations/autoscalingPolicies", Service: "dataproc", @@ -931,7 +954,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/connections": { "bigqueryconnection": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "connections", TypeKey: "projects/locations/connections", Service: "bigqueryconnection", @@ -952,9 +975,32 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, }, }, + "projects/locations/connectivityTests": { + "networkmanagement": { + "v1beta1": RestResource{ + Name: "connectivityTests", + TypeKey: "projects/locations/connectivityTests", + Service: "networkmanagement", + IsPreferredVersion: true, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "GET", + BaseURL: "https://networkmanagement.googleapis.com/", + Path: "v1beta1/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://networkmanagement.googleapis.com/", + Path: "v1beta1/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + }, + }, "projects/locations/datasets": { "healthcare": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "datasets", TypeKey: "projects/locations/datasets", Service: "healthcare", @@ -977,7 +1023,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/datasets/annotationStores": { "healthcare": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "annotationStores", TypeKey: "projects/locations/datasets/annotationStores", Service: "healthcare", @@ -1000,7 +1046,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/datasets/dicomStores": { "healthcare": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "dicomStores", TypeKey: "projects/locations/datasets/dicomStores", Service: "healthcare", @@ -1023,7 +1069,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/datasets/fhirStores": { "healthcare": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "fhirStores", TypeKey: "projects/locations/datasets/fhirStores", Service: "healthcare", @@ -1044,9 +1090,70 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, }, }, + "projects/locations/domains": { + "managedidentities": { + "v1": RestResource{ + Name: "domains", + TypeKey: "projects/locations/domains", + Service: "managedidentities", + IsPreferredVersion: true, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "GET", + BaseURL: "https://managedidentities.googleapis.com/", + Path: "v1/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://managedidentities.googleapis.com/", + Path: "v1/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + "v1alpha1": RestResource{ + Name: "domains", + TypeKey: "projects/locations/domains", + Service: "managedidentities", + IsPreferredVersion: false, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "GET", + BaseURL: "https://managedidentities.googleapis.com/", + Path: "v1alpha1/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://managedidentities.googleapis.com/", + Path: "v1alpha1/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + "v1beta1": RestResource{ + Name: "domains", + TypeKey: "projects/locations/domains", + Service: "managedidentities", + IsPreferredVersion: false, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "GET", + BaseURL: "https://managedidentities.googleapis.com/", + Path: "v1beta1/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://managedidentities.googleapis.com/", + Path: "v1beta1/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + }, + }, "projects/locations/entryGroups": { "datacatalog": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "entryGroups", TypeKey: "projects/locations/entryGroups", Service: "datacatalog", @@ -1069,7 +1176,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/functions": { "cloudfunctions": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "functions", TypeKey: "projects/locations/functions", Service: "cloudfunctions", @@ -1092,7 +1199,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/instances": { "datafusion": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "instances", TypeKey: "projects/locations/instances", Service: "datafusion", @@ -1112,10 +1219,31 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, }, }, + "memcache": { + "v1beta2": RestResource{ + Name: "instances", + TypeKey: "projects/locations/instances", + Service: "memcache", + IsPreferredVersion: true, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "GET", + BaseURL: "https://memcache.googleapis.com/", + Path: "v1beta2/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://memcache.googleapis.com/", + Path: "v1beta2/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + }, }, "projects/locations/keyRings": { "cloudkms": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "keyRings", TypeKey: "projects/locations/keyRings", Service: "cloudkms", @@ -1138,7 +1266,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/keyRings/cryptoKeys": { "cloudkms": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "cryptoKeys", TypeKey: "projects/locations/keyRings/cryptoKeys", Service: "cloudkms", @@ -1161,7 +1289,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/keyRings/importJobs": { "cloudkms": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "importJobs", TypeKey: "projects/locations/keyRings/importJobs", Service: "cloudkms", @@ -1182,9 +1310,55 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, }, }, + "projects/locations/namespaces": { + "servicedirectory": { + "v1beta1": RestResource{ + Name: "namespaces", + TypeKey: "projects/locations/namespaces", + Service: "servicedirectory", + IsPreferredVersion: true, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://servicedirectory.googleapis.com/", + Path: "v1beta1/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://servicedirectory.googleapis.com/", + Path: "v1beta1/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + }, + }, + "projects/locations/namespaces/services": { + "servicedirectory": { + "v1beta1": RestResource{ + Name: "services", + TypeKey: "projects/locations/namespaces/services", + Service: "servicedirectory", + IsPreferredVersion: true, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://servicedirectory.googleapis.com/", + Path: "v1beta1/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://servicedirectory.googleapis.com/", + Path: "v1beta1/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + }, + }, "projects/locations/queues": { "cloudtasks": { - "v2": IamRestResource{ + "v2": RestResource{ Name: "queues", TypeKey: "projects/locations/queues", Service: "cloudtasks", @@ -1203,7 +1377,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v2beta2": IamRestResource{ + "v2beta2": RestResource{ Name: "queues", TypeKey: "projects/locations/queues", Service: "cloudtasks", @@ -1222,7 +1396,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v2beta3": IamRestResource{ + "v2beta3": RestResource{ Name: "queues", TypeKey: "projects/locations/queues", Service: "cloudtasks", @@ -1245,7 +1419,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/registries": { "cloudiot": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "registries", TypeKey: "projects/locations/registries", Service: "cloudiot", @@ -1268,7 +1442,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/registries/groups": { "cloudiot": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "groups", TypeKey: "projects/locations/registries/groups", Service: "cloudiot", @@ -1291,7 +1465,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/services": { "run": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "services", TypeKey: "projects/locations/services", Service: "run", @@ -1310,7 +1484,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1alpha1": IamRestResource{ + "v1alpha1": RestResource{ Name: "services", TypeKey: "projects/locations/services", Service: "run", @@ -1333,7 +1507,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/tagTemplates": { "datacatalog": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "tagTemplates", TypeKey: "projects/locations/tagTemplates", Service: "datacatalog", @@ -1356,7 +1530,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/taxonomies": { "datacatalog": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "taxonomies", TypeKey: "projects/locations/taxonomies", Service: "datacatalog", @@ -1379,7 +1553,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/taxonomies/policyTags": { "datacatalog": { - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "policyTags", TypeKey: "projects/locations/taxonomies/policyTags", Service: "datacatalog", @@ -1402,7 +1576,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/locations/workflowTemplates": { "dataproc": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "workflowTemplates", TypeKey: "projects/locations/workflowTemplates", Service: "dataproc", @@ -1421,7 +1595,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "workflowTemplates", TypeKey: "projects/locations/workflowTemplates", Service: "dataproc", @@ -1444,7 +1618,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/machineImages": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "machineImages", TypeKey: "projects/machineImages", Service: "compute", @@ -1466,7 +1640,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "machineImages", TypeKey: "projects/machineImages", Service: "compute", @@ -1492,7 +1666,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/notes": { "containeranalysis": { - "v1alpha1": IamRestResource{ + "v1alpha1": RestResource{ Name: "notes", TypeKey: "projects/notes", Service: "containeranalysis", @@ -1511,7 +1685,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "notes", TypeKey: "projects/notes", Service: "containeranalysis", @@ -1534,7 +1708,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/occurrences": { "containeranalysis": { - "v1alpha1": IamRestResource{ + "v1alpha1": RestResource{ Name: "occurrences", TypeKey: "projects/occurrences", Service: "containeranalysis", @@ -1553,7 +1727,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta1": IamRestResource{ + "v1beta1": RestResource{ Name: "occurrences", TypeKey: "projects/occurrences", Service: "containeranalysis", @@ -1576,7 +1750,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/autoscalingPolicies": { "dataproc": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "autoscalingPolicies", TypeKey: "projects/regions/autoscalingPolicies", Service: "dataproc", @@ -1595,7 +1769,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "autoscalingPolicies", TypeKey: "projects/regions/autoscalingPolicies", Service: "dataproc", @@ -1618,7 +1792,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/clusters": { "dataproc": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "clusters", TypeKey: "projects/regions/clusters", Service: "dataproc", @@ -1637,7 +1811,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "clusters", TypeKey: "projects/regions/clusters", Service: "dataproc", @@ -1660,7 +1834,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/disks": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "regionDisks", TypeKey: "projects/regions/disks", Service: "compute", @@ -1683,7 +1857,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "regionDisks", TypeKey: "projects/regions/disks", Service: "compute", @@ -1710,7 +1884,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/interconnectAttachments": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "interconnectAttachments", TypeKey: "projects/regions/interconnectAttachments", Service: "compute", @@ -1737,7 +1911,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/jobs": { "dataproc": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "jobs", TypeKey: "projects/regions/jobs", Service: "dataproc", @@ -1756,7 +1930,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "jobs", TypeKey: "projects/regions/jobs", Service: "dataproc", @@ -1779,7 +1953,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/nodeTemplates": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "nodeTemplates", TypeKey: "projects/regions/nodeTemplates", Service: "compute", @@ -1802,7 +1976,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "nodeTemplates", TypeKey: "projects/regions/nodeTemplates", Service: "compute", @@ -1825,7 +1999,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "nodeTemplates", TypeKey: "projects/regions/nodeTemplates", Service: "compute", @@ -1852,7 +2026,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/operations": { "dataproc": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "operations", TypeKey: "projects/regions/operations", Service: "dataproc", @@ -1871,7 +2045,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "operations", TypeKey: "projects/regions/operations", Service: "dataproc", @@ -1894,7 +2068,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/resourcePolicies": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "resourcePolicies", TypeKey: "projects/regions/resourcePolicies", Service: "compute", @@ -1917,7 +2091,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "resourcePolicies", TypeKey: "projects/regions/resourcePolicies", Service: "compute", @@ -1940,7 +2114,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "resourcePolicies", TypeKey: "projects/regions/resourcePolicies", Service: "compute", @@ -1967,7 +2141,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/subnetworks": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "subnetworks", TypeKey: "projects/regions/subnetworks", Service: "compute", @@ -1990,7 +2164,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "subnetworks", TypeKey: "projects/regions/subnetworks", Service: "compute", @@ -2013,7 +2187,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "subnetworks", TypeKey: "projects/regions/subnetworks", Service: "compute", @@ -2040,7 +2214,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/regions/workflowTemplates": { "dataproc": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "workflowTemplates", TypeKey: "projects/regions/workflowTemplates", Service: "dataproc", @@ -2059,7 +2233,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "workflowTemplates", TypeKey: "projects/regions/workflowTemplates", Service: "dataproc", @@ -2082,7 +2256,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/repos": { "sourcerepo": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "repos", TypeKey: "projects/repos", Service: "sourcerepo", @@ -2105,13 +2279,32 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/secrets": { "secretmanager": { - "v1beta1": IamRestResource{ + "v1": RestResource{ Name: "secrets", TypeKey: "projects/secrets", Service: "secretmanager", IsPreferredVersion: true, Parameters: []string{"resource"}, CollectionReplacementKeys: map[string]string{}, + GetMethod: RestMethod{ + HttpMethod: "GET", + BaseURL: "https://secretmanager.googleapis.com/", + Path: "v1/{+resource}:getIamPolicy", + }, + SetMethod: RestMethod{ + HttpMethod: "POST", + BaseURL: "https://secretmanager.googleapis.com/", + Path: "v1/{+resource}:setIamPolicy", + RequestFormat: `{"policy": %s}`, + }, + }, + "v1beta1": RestResource{ + Name: "secrets", + TypeKey: "projects/secrets", + Service: "secretmanager", + IsPreferredVersion: false, + Parameters: []string{"resource"}, + CollectionReplacementKeys: map[string]string{}, GetMethod: RestMethod{ HttpMethod: "GET", BaseURL: "https://secretmanager.googleapis.com/", @@ -2128,7 +2321,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/serviceAccounts": { "iam": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "serviceAccounts", TypeKey: "projects/serviceAccounts", Service: "iam", @@ -2151,7 +2344,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/snapshots": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "snapshots", TypeKey: "projects/snapshots", Service: "compute", @@ -2173,7 +2366,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "snapshots", TypeKey: "projects/snapshots", Service: "compute", @@ -2195,7 +2388,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "snapshots", TypeKey: "projects/snapshots", Service: "compute", @@ -2219,7 +2412,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, }, "pubsub": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "snapshots", TypeKey: "projects/snapshots", Service: "pubsub", @@ -2242,7 +2435,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/subscriptions": { "pubsub": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "subscriptions", TypeKey: "projects/subscriptions", Service: "pubsub", @@ -2261,7 +2454,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "subscriptions", TypeKey: "projects/subscriptions", Service: "pubsub", @@ -2284,7 +2477,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/topics": { "pubsub": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "topics", TypeKey: "projects/topics", Service: "pubsub", @@ -2303,7 +2496,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1beta2": IamRestResource{ + "v1beta2": RestResource{ Name: "topics", TypeKey: "projects/topics", Service: "pubsub", @@ -2326,7 +2519,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/zones/disks": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "disks", TypeKey: "projects/zones/disks", Service: "compute", @@ -2349,7 +2542,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "disks", TypeKey: "projects/zones/disks", Service: "compute", @@ -2372,7 +2565,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "disks", TypeKey: "projects/zones/disks", Service: "compute", @@ -2399,7 +2592,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/zones/instances": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "instances", TypeKey: "projects/zones/instances", Service: "compute", @@ -2422,7 +2615,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "instances", TypeKey: "projects/zones/instances", Service: "compute", @@ -2445,7 +2638,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "instances", TypeKey: "projects/zones/instances", Service: "compute", @@ -2472,7 +2665,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/zones/nodeGroups": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "nodeGroups", TypeKey: "projects/zones/nodeGroups", Service: "compute", @@ -2495,7 +2688,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "nodeGroups", TypeKey: "projects/zones/nodeGroups", Service: "compute", @@ -2518,7 +2711,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "nodeGroups", TypeKey: "projects/zones/nodeGroups", Service: "compute", @@ -2545,7 +2738,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "projects/zones/reservations": { "compute": { - "alpha": IamRestResource{ + "alpha": RestResource{ Name: "reservations", TypeKey: "projects/zones/reservations", Service: "compute", @@ -2568,7 +2761,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "beta": IamRestResource{ + "beta": RestResource{ Name: "reservations", TypeKey: "projects/zones/reservations", Service: "compute", @@ -2591,7 +2784,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ RequestFormat: `{"policy": %s}`, }, }, - "v1": IamRestResource{ + "v1": RestResource{ Name: "reservations", TypeKey: "projects/zones/reservations", Service: "compute", @@ -2618,7 +2811,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "providers/notes": { "containeranalysis": { - "v1alpha1": IamRestResource{ + "v1alpha1": RestResource{ Name: "notes", TypeKey: "providers/notes", Service: "containeranalysis", @@ -2641,7 +2834,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "services": { "servicemanagement": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "services", TypeKey: "services", Service: "servicemanagement", @@ -2664,7 +2857,7 @@ var generatedIamResources = map[string]map[string]map[string]IamRestResource{ }, "services/consumers": { "servicemanagement": { - "v1": IamRestResource{ + "v1": RestResource{ Name: "consumers", TypeKey: "services/consumers", Service: "servicemanagement", diff --git a/plugin/path_role_set.go b/plugin/path_role_set.go index f32ed7d7..962a8d1c 100644 --- a/plugin/path_role_set.go +++ b/plugin/path_role_set.go @@ -236,7 +236,7 @@ func (b *backend) pathRoleSetDelete(ctx context.Context, req *logical.Request, d return nil, err } - iamHandle := iamutil.GetIamHandle(httpC, useragent.String()) + apiHandle := iamutil.GetApiHandle(httpC, useragent.String()) warnings := make([]string, 0) if rs.AccountId != nil { @@ -250,7 +250,7 @@ func (b *backend) pathRoleSetDelete(ctx context.Context, req *logical.Request, d warnings = append(warnings, w) } - if merr := b.removeBindings(ctx, iamHandle, rs.AccountId.EmailOrId, rs.Bindings); merr != nil { + if merr := b.removeBindings(ctx, apiHandle, rs.AccountId.EmailOrId, rs.Bindings); merr != nil { for _, err := range merr.Errors { w := fmt.Sprintf("unable to delete IAM policy bindings for service account %q (WAL entry to clean-up later has been added): %v", rs.AccountId.EmailOrId, err) warnings = append(warnings, w) diff --git a/plugin/role_set.go b/plugin/role_set.go index e8cfbead..632faeb0 100644 --- a/plugin/role_set.go +++ b/plugin/role_set.go @@ -137,7 +137,7 @@ func (b *backend) saveRoleSetWithNewAccount(ctx context.Context, s logical.Stora return nil, err } - iamHandle := iamutil.GetIamHandle(httpC, useragent.String()) + apiHandle := iamutil.GetApiHandle(httpC, useragent.String()) oldAccount := rs.AccountId oldBindings := rs.Bindings @@ -162,7 +162,7 @@ func (b *backend) saveRoleSetWithNewAccount(ctx context.Context, s logical.Stora binds = newBinds rs.Bindings = newBinds } - walIds, err := rs.updateIamPolicies(ctx, s, b.iamResources, iamHandle, binds) + walIds, err := rs.updateIamPolicies(ctx, s, b.resources, apiHandle, binds) if err != nil { tryDeleteWALs(ctx, s, oldWals...) return nil, err @@ -194,7 +194,7 @@ func (b *backend) saveRoleSetWithNewAccount(ctx context.Context, s logical.Stora // Return any errors as warnings so user knows immediate cleanup failed warnings := make([]string, 0) - if errs := b.removeBindings(ctx, iamHandle, oldAccount.EmailOrId, oldBindings); errs != nil { + if errs := b.removeBindings(ctx, apiHandle, oldAccount.EmailOrId, oldBindings); errs != nil { warnings = make([]string, len(errs.Errors), len(errs.Errors)+2) for idx, err := range errs.Errors { warnings[idx] = fmt.Sprintf("unable to immediately delete old binding (WAL cleanup entry has been added): %v", err) @@ -358,7 +358,7 @@ func (rs *RoleSet) newKeyForTokenGen(ctx context.Context, s logical.Storage, iam return walId, nil } -func (rs *RoleSet) updateIamPolicies(ctx context.Context, s logical.Storage, enabledIamResources iamutil.IamResourceParser, iamHandle *iamutil.IamHandle, rb ResourceBindings) ([]string, error) { +func (rs *RoleSet) updateIamPolicies(ctx context.Context, s logical.Storage, enabledResources iamutil.ResourceParser, apiHandle *iamutil.ApiHandle, rb ResourceBindings) ([]string, error) { wals := make([]string, 0, len(rb)) for rName, roles := range rb { walId, err := framework.PutWAL(ctx, s, walTypeIamPolicy, &walIamPolicy{ @@ -374,12 +374,12 @@ func (rs *RoleSet) updateIamPolicies(ctx context.Context, s logical.Storage, ena return wals, err } - resource, err := enabledIamResources.Parse(rName) + resource, err := enabledResources.Parse(rName) if err != nil { return wals, err } - p, err := iamHandle.GetIamPolicy(ctx, resource) + p, err := resource.GetIamPolicy(ctx, apiHandle) if err != nil { return wals, err } @@ -392,7 +392,7 @@ func (rs *RoleSet) updateIamPolicies(ctx context.Context, s logical.Storage, ena continue } - if _, err := iamHandle.SetIamPolicy(ctx, resource, newP); err != nil { + if _, err := resource.SetIamPolicy(ctx, apiHandle, newP); err != nil { return wals, err } wals = append(wals, walId) diff --git a/plugin/rollback.go b/plugin/rollback.go index 6d85f542..fca1d731 100644 --- a/plugin/rollback.go +++ b/plugin/rollback.go @@ -182,7 +182,7 @@ func (b *backend) serviceAccountPolicyRollback(ctx context.Context, req *logical } } - r, err := b.iamResources.Parse(entry.Resource) + r, err := b.resources.Parse(entry.Resource) if err != nil { return err } @@ -192,12 +192,12 @@ func (b *backend) serviceAccountPolicyRollback(ctx context.Context, req *logical return err } - iamHandle := iamutil.GetIamHandle(httpC, useragent.String()) + apiHandle := iamutil.GetApiHandle(httpC, useragent.String()) if err != nil { return err } - p, err := iamHandle.GetIamPolicy(ctx, r) + p, err := r.GetIamPolicy(ctx, apiHandle) if err != nil { return err } @@ -212,7 +212,7 @@ func (b *backend) serviceAccountPolicyRollback(ctx context.Context, req *logical return nil } - _, err = iamHandle.SetIamPolicy(ctx, r, newP) + _, err = r.SetIamPolicy(ctx, apiHandle, newP) return err } @@ -240,15 +240,15 @@ func (b *backend) deleteTokenGenKey(ctx context.Context, iamAdmin *iam.Service, return nil } -func (b *backend) removeBindings(ctx context.Context, iamHandle *iamutil.IamHandle, email string, bindings ResourceBindings) (allErr *multierror.Error) { +func (b *backend) removeBindings(ctx context.Context, apiHandle *iamutil.ApiHandle, email string, bindings ResourceBindings) (allErr *multierror.Error) { for resName, roles := range bindings { - resource, err := b.iamResources.Parse(resName) + resource, err := b.resources.Parse(resName) if err != nil { allErr = multierror.Append(allErr, errwrap.Wrapf(fmt.Sprintf("unable to delete role binding for resource '%s': {{err}}", resName), err)) continue } - p, err := iamHandle.GetIamPolicy(ctx, resource) + p, err := resource.GetIamPolicy(ctx, apiHandle) if err != nil { allErr = multierror.Append(allErr, errwrap.Wrapf(fmt.Sprintf("unable to delete role binding for resource '%s': {{err}}", resName), err)) continue @@ -261,7 +261,7 @@ func (b *backend) removeBindings(ctx context.Context, iamHandle *iamutil.IamHand if !changed { continue } - if _, err = iamHandle.SetIamPolicy(ctx, resource, newP); err != nil { + if _, err = resource.SetIamPolicy(ctx, apiHandle, newP); err != nil { allErr = multierror.Append(allErr, errwrap.Wrapf(fmt.Sprintf("unable to delete role binding for resource '%s': {{err}}", resName), err)) continue }