Skip to content

Commit

Permalink
Merge pull request #752 from GoogleCloudPlatform/iam
Browse files Browse the repository at this point in the history
Merge Pub/Sub Policy support from IAM topic branch
  • Loading branch information
blowmage committed Jun 29, 2016
2 parents 65ab853 + 4b656b4 commit ff9674c
Show file tree
Hide file tree
Showing 7 changed files with 607 additions and 155 deletions.
204 changes: 204 additions & 0 deletions lib/gcloud/pubsub/policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


require "gcloud/errors"

module Gcloud
module Pubsub
##
# # Policy
#
# Represents a Cloud IAM Policy for the Pub/Sub service.
#
# A common pattern for updating a resource's metadata, such as its Policy,
# is to read the current data from the service, update the data locally, and
# then send the modified data for writing. This pattern may result in a
# conflict if two or more processes attempt the sequence simultaneously. IAM
# solves this problem with the {Gcloud::Pubsub::Policy#etag} property, which
# is used to verify whether the policy has changed since the last request.
# When you make a request to with an `etag` value, Cloud IAM compares the
# `etag` value in the request with the existing `etag` value associated with
# the policy. It writes the policy only if the `etag` values match.
#
# When you update a policy, first read the policy (and its current `etag`)
# from the service, then modify the policy locally, and then write the
# modified policy to the service. See {Gcloud::Pubsub::Topic#policy} and
# {Gcloud::Pubsub::Topic#policy=}.
#
# @see https://cloud.google.com/iam/docs/managing-policies Managing policies
# @see https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#iampolicy
# google.iam.v1.IAMPolicy
#
# @attr [String] etag Used to verify whether the policy has changed since
# the last request. The policy will be written only if the `etag` values
# match.
# @attr [Hash{String => Array<String>}] roles The bindings that associate
# roles with an array of members. See [Understanding
# Roles](https://cloud.google.com/iam/docs/understanding-roles) for a
# listing of primitive and curated roles.
# See [Binding](https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#binding)
# for a listing of values and patterns for members.
#
# @example
# require "gcloud"
#
# gcloud = Gcloud.new
# pubsub = gcloud.pubsub
# topic = pubsub.topic "my-topic"
#
# policy = topic.policy # API call
#
# policy.remove "roles/owner", "user:owner@example.com" # Local call
# policy.add "roles/owner", "user:newowner@example.com" # Local call
# policy.roles["roles/viewer"] = ["allUsers"] # Local call
#
# topic.policy = policy # API call
#
class Policy
attr_reader :etag, :roles

##
# @private Creates a Policy object.
def initialize etag, roles
@etag = etag
@roles = roles
end

##
# Convenience method for adding a member to a binding on this policy.
# See [Understanding
# Roles](https://cloud.google.com/iam/docs/understanding-roles) for a
# listing of primitive and curated roles.
# See [Binding](https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#binding)
# for a listing of values and patterns for members.
#
# @param [String] role_name A Cloud IAM role, such as
# `"roles/pubsub.admin"`.
# @param [String] member A Cloud IAM identity, such as
# `"user:owner@example.com"`.
#
# @example
# require "gcloud"
#
# gcloud = Gcloud.new
# pubsub = gcloud.pubsub
# topic = pubsub.topic "my-topic"
#
# policy = topic.policy # API call
#
# policy.add "roles/owner", "user:newowner@example.com" # Local call
#
# topic.policy = policy # API call
#
def add role_name, member
role(role_name) << member
end

##
# Convenience method for removing a member from a binding on this policy.
# See [Understanding
# Roles](https://cloud.google.com/iam/docs/understanding-roles) for a
# listing of primitive and curated roles.
# See [Binding](https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#binding)
# for a listing of values and patterns for members.
#
# @param [String] role_name A Cloud IAM role, such as
# `"roles/pubsub.admin"`.
# @param [String] member A Cloud IAM identity, such as
# `"user:owner@example.com"`.
#
# @example
# require "gcloud"
#
# gcloud = Gcloud.new
# pubsub = gcloud.pubsub
# topic = pubsub.topic "my-topic"
#
# policy = topic.policy # API call
#
# policy.remove "roles/owner", "user:owner@example.com" # Local call
#
# topic.policy = policy # API call
#
def remove role_name, member
role(role_name).delete member
end

##
# Convenience method returning the array of members bound to a role in
# this policy, or an empty array if no value is present for the role in
# {#roles}. See [Understanding
# Roles](https://cloud.google.com/iam/docs/understanding-roles) for a
# listing of primitive and curated roles. See
# [Binding](https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#binding)
# for a listing of values and patterns for members.
#
# @return [Array<String>] The members strings, or an empty array.
#
# @example
# require "gcloud"
#
# gcloud = Gcloud.new
# pubsub = gcloud.pubsub
# topic = pubsub.topic "my-topic"
#
# policy = topic.policy
#
# policy.role("roles/viewer") << "user:viewer@example.com"
#
def role role_name
roles[role_name] ||= []
end

##
# Returns a deep copy of the policy.
#
# @return [Policy]
#
def deep_dup
dup.tap do |p|
roles_dup = p.roles.each_with_object({}) do |(k, v), memo|
memo[k] = v.dup rescue value
end
p.instance_variable_set "@roles", roles_dup
end
end

##
# @private Convert the Policy to a Google::Iam::V1::Policy object.
def to_grpc
Google::Iam::V1::Policy.new(
etag: etag,
bindings: roles.keys.map do |role_name|
next if roles[role_name].empty?
Google::Iam::V1::Binding.new(
role: role_name,
members: roles[role_name]
)
end
)
end

##
# @private New Policy from a Google::Iam::V1::Policy object.
def self.from_grpc grpc
roles = grpc.bindings.each_with_object({}) do |binding, memo|
memo[binding.role] = binding.members.to_a
end
new grpc.etag, roles
end
end
end
end
4 changes: 2 additions & 2 deletions lib/gcloud/pubsub/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def get_topic_policy topic_name, options = {}
def set_topic_policy topic_name, new_policy, options = {}
set_req = Google::Iam::V1::SetIamPolicyRequest.new(
resource: topic_path(topic_name, options),
policy: Google::Iam::V1::Policy.decode_json(JSON.dump(new_policy))
policy: new_policy
)

backoff { iam.set_iam_policy set_req }
Expand All @@ -279,7 +279,7 @@ def get_subscription_policy subscription_name, options = {}
def set_subscription_policy subscription_name, new_policy, options = {}
set_req = Google::Iam::V1::SetIamPolicyRequest.new(
resource: subscription_path(subscription_name, options),
policy: Google::Iam::V1::Policy.decode_json(JSON.dump(new_policy))
policy: new_policy
)

backoff { iam.set_iam_policy set_req }
Expand Down
89 changes: 52 additions & 37 deletions lib/gcloud/pubsub/subscription.rb
Original file line number Diff line number Diff line change
Expand Up @@ -398,88 +398,103 @@ def delay new_deadline, *messages
end

##
# Gets the access control policy.
# Gets the [Cloud IAM](https://cloud.google.com/iam/) access control
# policy for this subscription.
#
# By default, the policy values are memoized to reduce the number of API
# calls to the Pub/Sub service.
# @see https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#iampolicy
# google.iam.v1.IAMPolicy
#
# @param [Boolean] force Force the latest policy to be retrieved from the
# Pub/Sub service when `true`. Otherwise the policy will be memoized to
# reduce the number of API calls made to the Pub/Sub service. The
# default is `false`.
#
# @return [Hash] Returns a hash that conforms to the following structure:
# @yield [policy] A block for updating the policy. The latest policy will
# be read from the Pub/Sub service and passed to the block. After the
# block completes, the modified policy will be written to the service.
# @yieldparam [Policy] policy the current Cloud IAM Policy for this
# subscription
#
# {
# "etag"=>"CAE=",
# "bindings" => [{
# "role" => "roles/viewer",
# "members" => ["serviceAccount:your-service-account"]
# }]
# }
# @return [Policy] the current Cloud IAM Policy for this subscription
#
# @example Policy values are memoized to reduce the number of API calls:
# require "gcloud"
#
# gcloud = Gcloud.new
# pubsub = gcloud.pubsub
# sub = pubsub.subscription "my-subscription"
#
# subscription = pubsub.subscription "my-subscription"
# puts subscription.policy["bindings"]
# puts subscription.policy["rules"]
# policy = sub.policy # API call
# policy_2 = sub.policy # No API call
#
# @example Use `force` to retrieve the latest policy from the service:
# require "gcloud"
#
# gcloud = Gcloud.new
# pubsub = gcloud.pubsub
# sub = pubsub.subscription "my-subscription"
#
# subscription = pubsub.subscription "my-subscription"
# policy = subscription.policy force: true
# policy = sub.policy force: true # API call
# policy_2 = sub.policy force: true # API call
#
# @example Update the policy by passing a block:
# require "gcloud"
#
# gcloud = Gcloud.new
# pubsub = gcloud.pubsub
# sub = pubsub.subscription "my-subscription"
#
# policy = sub.policy do |p|
# p.add "roles/owner", "user:owner@example.com"
# end # 2 API calls
#
def policy force: nil
@policy = nil if force
@policy = nil if force || block_given?
@policy ||= begin
ensure_service!
grpc = service.get_subscription_policy name
JSON.parse(Google::Iam::V1::Policy.encode_json(grpc))
Policy.from_grpc grpc
rescue GRPC::BadStatus => e
raise Error.from_error(e)
end
return @policy unless block_given?
p = @policy.deep_dup
yield p
self.policy = p
end

##
# Sets the access control policy.
# Updates the [Cloud IAM](https://cloud.google.com/iam/) access control
# policy for this subscription. The policy should be read from {#policy}.
# See {Gcloud::Pubsub::Policy} for an explanation of the policy `etag`
# property and how to modify policies.
#
# You can also update the policy by passing a block to {#policy}, which
# will call this method internally after the block completes.
#
# @param [String] new_policy A hash that conforms to the following
# structure:
# @see https://cloud.google.com/pubsub/reference/rpc/google.iam.v1#iampolicy
# google.iam.v1.IAMPolicy
#
# {
# "bindings" => [{
# "role" => "roles/viewer",
# "members" => ["serviceAccount:your-service-account"]
# }]
# }
# @param [Policy] new_policy a new or modified Cloud IAM Policy for this
# subscription
#
# @example
# require "gcloud"
#
# gcloud = Gcloud.new
# pubsub = gcloud.pubsub
# sub = pubsub.subscription "my-subscription"
#
# policy = sub.policy # API call
#
# policy.add "roles/owner", "user:owner@example.com"
#
# viewer_policy = {
# "bindings" => [{
# "role" => "roles/viewer",
# "members" => ["serviceAccount:your-service-account"]
# }]
# }
# subscription = pubsub.subscription "my-subscription"
# subscription.policy = viewer_policy
# sub.policy = policy # API call
#
def policy= new_policy
ensure_service!
grpc = service.set_subscription_policy name, new_policy
@policy = JSON.parse(Google::Iam::V1::Policy.encode_json(grpc))
grpc = service.set_subscription_policy name, new_policy.to_grpc
@policy = Policy.from_grpc grpc
rescue GRPC::BadStatus => e
raise Error.from_error(e)
end
Expand Down
Loading

0 comments on commit ff9674c

Please sign in to comment.