# Copyright 2015 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 "time" require "gcloud/resource_manager/project/list" require "gcloud/resource_manager/project/updater" require "gcloud/resource_manager/policy" module Gcloud module ResourceManager ## # # Project # # Project is a high-level Google Cloud Platform entity. It is a container # for ACLs, APIs, AppEngine Apps, VMs, and other Google Cloud Platform # resources. # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.update do |p| # p.name = "My Project" # p.labels["env"] = "production" # end # class Project ## # @private The Service object. attr_accessor :service ## # @private The Google API Client object. attr_accessor :gapi ## # @private Create an empty Project object. def initialize @service = nil @gapi = Gcloud::ResourceManager::Service::API::Project.new end ## # The unique, user-assigned ID of the project. It must be 6 to 30 # lowercase letters, digits, or hyphens. It must start with a letter. # Trailing hyphens are prohibited. e.g. tokyo-rain-123 # def project_id @gapi.project_id end ## # The number uniquely identifying the project. e.g. 415104041262 # def project_number @gapi.project_number end ## # The user-assigned name of the project. # def name @gapi.name end ## # Updates the user-assigned name of the project. This field is optional # and can remain unset. # # Allowed characters are: lowercase and uppercase letters, numbers, # hyphen, single-quote, double-quote, space, and exclamation point. # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.name = "My Project" # def name= new_name ensure_service! @gapi.name = new_name @gapi = service.update_project @gapi end ## # The labels associated with this project. # # Label keys must be between 1 and 63 characters long and must conform to # the regular expression [a-z]([-a-z0-9]*[a-z0-9])?. # # Label values must be between 0 and 63 characters long and must conform # to the regular expression ([a-z]([-a-z0-9]*[a-z0-9])?)?. # # No more than 256 labels can be associated with a given resource. # (`Hash`) # # @yield [labels] a block for setting labels # @yieldparam [Hash] labels the hash accepting labels # # @example Labels are read-only and cannot be changed: # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.labels["env"] #=> "dev" # read only # project.labels["env"] = "production" # raises error # # @example Labels can be updated by passing a block, or with {#labels=}: # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.labels do |labels| # labels["env"] = "production" # end # def labels labels = @gapi.labels.to_h if block_given? yielded_labels = labels.dup yield yielded_labels self.labels = yielded_labels if yielded_labels != labels # changed else labels.freeze end end ## # Updates the labels associated with this project. # # Label keys must be between 1 and 63 characters long and must conform to # the regular expression [a-z]([-a-z0-9]*[a-z0-9])?. # # Label values must be between 0 and 63 characters long and must conform # to the regular expression ([a-z]([-a-z0-9]*[a-z0-9])?)?. # # No more than 256 labels can be associated with a given resource. # (`Hash`) # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.labels = { "env" => "production" } # def labels= new_labels ensure_service! @gapi.labels = new_labels @gapi = service.update_project @gapi end ## # The time that this project was created. # def created_at Time.parse @gapi.create_time rescue nil end ## # The project lifecycle state. # # Possible values are: # * `ACTIVE` - The normal and active state. # * `DELETE_REQUESTED` - The project has been marked for deletion by the # user (by invoking ##delete) or by the system (Google Cloud # Platform). This can generally be reversed by invoking {#undelete}. # * `DELETE_IN_PROGRESS` - The process of deleting the project has begun. # Reversing the deletion is no longer possible. # * `LIFECYCLE_STATE_UNSPECIFIED` - Unspecified state. This is only # used/useful for distinguishing unset values. # def state @gapi.lifecycle_state end ## # Checks if the state is `ACTIVE`. def active? return false if state.nil? "ACTIVE".casecmp(state).zero? end ## # Checks if the state is `LIFECYCLE_STATE_UNSPECIFIED`. def unspecified? return false if state.nil? "LIFECYCLE_STATE_UNSPECIFIED".casecmp(state).zero? end ## # Checks if the state is `DELETE_REQUESTED`. def delete_requested? return false if state.nil? "DELETE_REQUESTED".casecmp(state).zero? end ## # Checks if the state is `DELETE_IN_PROGRESS`. def delete_in_progress? return false if state.nil? "DELETE_IN_PROGRESS".casecmp(state).zero? end ## # Updates the project in a single API call. See {Project::Updater} # # @yield [project] a block yielding a project delegate # @yieldparam [Project::Updater] project the delegate object for updating # the project # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.update do |p| # p.name = "My Project" # p.labels["env"] = "production" # end # def update updater = Updater.from_project self yield updater if updater.gapi.to_h != @gapi.to_h # changed @gapi = service.update_project updater.gapi end self end ## # Reloads the project (with updated state) from the Google Cloud Resource # Manager service. # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.reload! # def reload! @gapi = service.get_project project_id end alias_method :refresh!, :reload! ## # Marks the project for deletion. This method will only affect the project # if the following criteria are met: # # * The project does not have a billing account associated with it. # * The project has a lifecycle state of `ACTIVE`. # * This method changes the project's lifecycle state from `ACTIVE` to # `DELETE_REQUESTED`. The deletion starts at an unspecified time, at # which point the lifecycle state changes to `DELETE_IN_PROGRESS`. # # Until the deletion completes, you can check the lifecycle state by # calling #reload!, or by retrieving the project with Manager#project. The # project remains visible to Manager#project and Manager#projects, but # cannot be updated. # # After the deletion completes, the project is not retrievable by the # Manager#project and Manager#projects methods. # # The caller must have modify permissions for this project. # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.active? #=> true # project.delete # project.active? #=> false # project.delete_requested? #=> true # def delete service.delete_project project_id reload! true end ## # Restores the project. You can only use this method for a project that # has a lifecycle state of `DELETE_REQUESTED`. After deletion starts, as # indicated by a lifecycle state of `DELETE_IN_PROGRESS`, the project # cannot be restored. # # The caller must have modify permissions for this project. # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # project.delete_requested? #=> true # project.undelete # project.delete_requested? #=> false # project.active? #=> true # def undelete service.undelete_project project_id reload! true end ## # Gets and updates the [Cloud IAM](https://cloud.google.com/iam/) access # control policy for this project. # # @see https://cloud.google.com/iam/docs/managing-policies Managing # Policies # @see https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/setIamPolicy # projects.setIamPolicy # # @param [Boolean] force Force load the latest policy when `true`. # Otherwise the policy will be memoized to reduce the number of API # calls made. The default is `false`. # # @yield [policy] A block for updating the policy. The latest policy will # be read from the 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 # project # # @return [Policy] the current Cloud IAM Policy for this project # # @example Policy values are memoized to reduce the number of API calls: # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # # policy = project.policy # API call # policy_2 = project.policy # No API call # # @example Use `force` to retrieve the latest policy from the service: # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # # policy = project.policy force: true # API call # policy_2 = project.policy force: true # API call # # @example Update the policy by passing a block: # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # # policy = project.policy do |p| # p.add "roles/owner", "user:owner@example.com" # end # 2 API calls # def policy force: false @policy = nil if force || block_given? @policy ||= begin ensure_service! gapi = service.get_policy project_id Policy.from_gapi gapi end return @policy unless block_given? p = @policy.deep_dup yield p self.policy = p end ## # Updates the [Cloud IAM](https://cloud.google.com/iam/) access control # policy for this project. The policy should be read from {#policy}. # See {Gcloud::ResourceManager::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. # # @see https://cloud.google.com/iam/docs/managing-policies Managing # Policies # @see https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/setIamPolicy # projects.setIamPolicy # # @param [Policy] new_policy a new or modified Cloud IAM Policy for this # project # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # # policy = project.policy # API call # # policy.add "roles/owner", "user:owner@example.com" # # project.policy = policy # API call # def policy= new_policy ensure_service! gapi = service.set_policy project_id, new_policy.to_gapi # Convert symbols to strings for backwards compatibility. # This will go away when we add a ResourceManager::Policy class. @policy = Policy.from_gapi gapi end ## # Tests the specified permissions against the [Cloud # IAM](https://cloud.google.com/iam/) access control policy. # # @see https://cloud.google.com/iam/docs/managing-policies Managing # Policies # # @param [String, Array] permissions The set of permissions to # check access for. Permissions with wildcards (such as `*` or # `storage.*`) are not allowed. # # @return [Array] The permissions that have access # # @example # require "gcloud" # # gcloud = Gcloud.new # resource_manager = gcloud.resource_manager # project = resource_manager.project "tokyo-rain-123" # perms = project.test_permissions "resourcemanager.projects.get", # "resourcemanager.projects.delete" # perms.include? "resourcemanager.projects.get" #=> true # perms.include? "resourcemanager.projects.delete" #=> false # def test_permissions *permissions permissions = Array(permissions).flatten ensure_service! gapi = service.test_permissions project_id, permissions gapi.permissions end ## # @private New Change from a Google API Client object. def self.from_gapi gapi, service new.tap do |p| p.gapi = gapi p.service = service end end protected ## # Raise an error unless an active service is available. def ensure_service! fail "Must have active connection" unless service end end end end