chore: Generator creates separate libraries (#2136)
This commit is contained in:
parent
a6a5b6071e
commit
6b972afb60
8
Rakefile
8
Rakefile
|
@ -31,10 +31,12 @@ namespace :kokoro do
|
||||||
end
|
end
|
||||||
|
|
||||||
task :run_tests do
|
task :run_tests do
|
||||||
["google-apis-core", "google-api-client"].each do |gem_name|
|
gem_directories = ["google-apis-core", "google-api-client"] # + Dir.glob("generated/*")
|
||||||
cd gem_name do
|
gem_directories.each do |dir|
|
||||||
|
next unless File.file?(File.join(dir, "Gemfile"))
|
||||||
|
cd dir do
|
||||||
Bundler.with_clean_env do
|
Bundler.with_clean_env do
|
||||||
sh "bundle update"
|
sh "bundle install"
|
||||||
sh "bundle exec rake spec"
|
sh "bundle exec rake spec"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,400 @@
|
||||||
|
# Authentication for Legacy REST Clients
|
||||||
|
|
||||||
|
This document describes how authentication, authorization, and accounting are accomplished. For all API calls, your application needs to be authenticated. When an API accesses a user's private data, your application must also be authorized by the user to access the data. For example, accessing a public Google+ post would not require user authorization, but accessing a user's private calendar would. Also, for quota and billing purposes, all API calls involve accounting. This document summarizes the protocols used by Google APIs and provides links to more information.
|
||||||
|
|
||||||
|
> **Note:** this is a "general" document, and not specific to this particular service client. In particular, you may find that the examples in this document are for a different client. All legacy REST clients follow the same usage patterns, and you should be able to adapt the provided examples to the client you are using.
|
||||||
|
|
||||||
|
## Access types
|
||||||
|
|
||||||
|
It is important to understand the basics of how API authentication and authorization are handled. All API calls must use either simple or authorized access (defined below). Many API methods require authorized access, but some can use either. For API methods that can use either, some will behave differently, depending on whether you use simple or authorized access. See the API's method documentation to determine the appropriate access type.
|
||||||
|
|
||||||
|
### Simple API access using API keys
|
||||||
|
|
||||||
|
For API calls that do not access any private user data, your application may use an **API key**, authenticating as an application belonging to your Google API Console project. Every simple access call your application makes must include this key. This is needed to measure project usage for accounting purposes.
|
||||||
|
|
||||||
|
### Authorized API access using OAuth 2.0
|
||||||
|
|
||||||
|
For API calls that access private user data, the user must grant your application access. This also implies that both your application, and the user granting access, must be authenticated. All of this is accomplished with **OAuth 2.0** and libraries written for it.
|
||||||
|
|
||||||
|
## Using API keys
|
||||||
|
|
||||||
|
When calling APIs that do not access private user data, you can use simple API keys. These keys are used to authenticate your application for accounting purposes. The Google Developers Console documentation also describes [API keys](https://developers.google.com/console/help/using-keys).
|
||||||
|
|
||||||
|
> Note: If you do need to access private user data, you must use OAuth 2.0 authentication.
|
||||||
|
|
||||||
|
To use API keys, set the `key` attribute on service objects. For example, here is how to use API keys to authenticate for the translate V2 service.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/translate_v2'
|
||||||
|
|
||||||
|
translate = Google::Apis::TranslateV2::TranslateService.new
|
||||||
|
translate.key = 'YOUR_API_KEY_HERE'
|
||||||
|
result = translate.list_translations('Hello world!', 'es', source: 'en')
|
||||||
|
puts result.translations.first.translated_text
|
||||||
|
```
|
||||||
|
|
||||||
|
All calls made using that service object will include your API key.
|
||||||
|
|
||||||
|
> **Warning:** Keep your API key private. If someone obtains your key, they could use it to consume your quota or incur charges against your API Console project.
|
||||||
|
|
||||||
|
## Using OAuth 2.0
|
||||||
|
|
||||||
|
This section describes OAuth 2.0, when to use it, how to acquire client IDs and client secrets, and generally how to use OAuth 2.0 with the Google API Client Library for Ruby.
|
||||||
|
|
||||||
|
### About OAuth 2.0
|
||||||
|
|
||||||
|
OAuth 2.0 is the authorization protocol used by Google APIs. It is summarized on the Authentication page of this library's documentation, and there are other good references as well:
|
||||||
|
|
||||||
|
- [The OAuth 2.0 Authorization Protocol](https://tools.ietf.org/html/rfc6749)
|
||||||
|
- [Using OAuth 2.0 to Access Google APIs](https://developers.google.com/identity/protocols/OAuth2)
|
||||||
|
|
||||||
|
The [Signet](https://github.com/googleapis/signet) library is included with the Google API Client Library for Ruby. It handles all steps of the OAuth 2.0 protocol required for making API calls. It is available as a separate gem if you only need an OAuth 2.0 library.
|
||||||
|
|
||||||
|
### OAuth 2.0 Concepts
|
||||||
|
|
||||||
|
**Scope:** Each API defines one or more scopes that declare a set of operations permitted. For example, an API might have read-only and read-write scopes. When your application requests access to user data, the request must include one or more scopes. The user needs to approve the scope of access your application is requesting.
|
||||||
|
|
||||||
|
**Refresh and access tokens:** When a user grants your application access, the OAuth 2.0 authorization server provides your application with refresh and access tokens. These tokens are only valid for the scope requested. Your application uses access tokens to authorize API calls. Access tokens expire, but refresh tokens do not. Your application can use a refresh token to acquire a new access token.
|
||||||
|
|
||||||
|
**Client ID and client secret:** These strings uniquely identify your application and are used to acquire tokens. They are created for your project on the [API Console](https://console.developers.google.com/). There are three types of client IDs:
|
||||||
|
|
||||||
|
* [Web application](https://developers.google.com/accounts/docs/OAuth2WebServer) client IDs
|
||||||
|
* [Installed application](https://developers.google.com/accounts/docs/OAuth2InstalledApp) client IDs
|
||||||
|
* [Service Account](https://developers.google.com/accounts/docs/OAuth2ServiceAccount) client IDs
|
||||||
|
|
||||||
|
### Acquiring client IDs and secrets
|
||||||
|
|
||||||
|
To find your project's client ID and client secret, do the following:
|
||||||
|
|
||||||
|
* Select an existing OAuth 2.0 credential or open the Credentials page.
|
||||||
|
* If you haven't done so already, create your project's OAuth 2.0 credentials by clicking Create credentials > OAuth client ID, and providing the information needed to create the credentials.
|
||||||
|
* Look for the Client ID in the OAuth 2.0 client IDs section. For details, click the client ID.
|
||||||
|
|
||||||
|
There are different types of client IDs, so be sure to get the correct type for your application:
|
||||||
|
|
||||||
|
* [Web application](https://developers.google.com/accounts/docs/OAuth2WebServer) client IDs
|
||||||
|
* [Installed application](https://developers.google.com/accounts/docs/OAuth2InstalledApp) client IDs
|
||||||
|
* [Service Account](https://developers.google.com/accounts/docs/OAuth2ServiceAccount) client IDs
|
||||||
|
|
||||||
|
> **Warning**: Keep your client secret private. If someone obtains your client secret, they could use it to consume your quota, incur charges against your Google APIs Console project, and request access to user data.
|
||||||
|
|
||||||
|
#### Client Secrets files
|
||||||
|
|
||||||
|
The Google APIs Client Library for Ruby uses the `client_secrets.json` file format for storing the `client_id`, `client_secret`, and other OAuth 2.0 parameters.
|
||||||
|
|
||||||
|
See [Creating authorization credentials](https://developers.google.com/identity/protocols/OAuth2WebServer#creatingcred) for how to obtain a `client_secrets.json` file.
|
||||||
|
|
||||||
|
The `client_secrets.json` file format is a [JSON](http://www.json.org/) formatted file containing the client ID, client secret, and other OAuth 2.0 parameters. Here is an example client_secrets.json file for a web application:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"web": {
|
||||||
|
"client_id": "asdfjasdljfasdkjf",
|
||||||
|
"client_secret": "1912308409123890",
|
||||||
|
"redirect_uris": ["https://www.example.com/oauth2callback"],
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"token_uri": "https://accounts.google.com/o/oauth2/token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is an example client_secrets.json file for an installed application:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"installed": {
|
||||||
|
"client_id": "837647042410-75ifg...usercontent.com",
|
||||||
|
"client_secret":"asdlkfjaskd",
|
||||||
|
"redirect_uris": ["http://localhost", "urn:ietf:wg:oauth:2.0:oob"],
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"token_uri": "https://accounts.google.com/o/oauth2/token"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The format defines one of two client ID types:
|
||||||
|
|
||||||
|
- `web`: Web application.
|
||||||
|
- `installed`: Installed application.
|
||||||
|
|
||||||
|
The `web` and `installed` sub-objects have the following mandatory members:
|
||||||
|
|
||||||
|
- `client_id` (string): The client ID.
|
||||||
|
- `client_secret` (string): The client secret.
|
||||||
|
- `redirect_uris` (list of strings): A list of valid redirection endpoint URIs. This list should match the list entered for the client ID on the [API Access pane](https://code.google.com/apis/console#:access) of the Google APIs Console.
|
||||||
|
- `auth_uri` (string): The authorization server endpoint URI.
|
||||||
|
- `token_uri` (string): The token server endpoint URI.
|
||||||
|
|
||||||
|
All of the above members are mandatory. The following optional parameters may appear:
|
||||||
|
|
||||||
|
- `client_email` (string) The service account email associated with the client.
|
||||||
|
- `auth_provider_x509_cert_url` (string) The URL of the public x509 certificate, used to verify the signature on JWTs, such as ID tokens, signed by the authentication provider.
|
||||||
|
- `client_x509_cert_url` (string) The URL of the public x509 certificate, used to verify JWTs signed by the client.
|
||||||
|
|
||||||
|
The following shows how you can use a client_secrets.json file and the Google::APIClient::ClientSecrets class to create a new authorization object:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/api_client/client_secrets'
|
||||||
|
|
||||||
|
CLIENT_SECRETS = Google::APIClient::ClientSecrets.load
|
||||||
|
authorization = CLIENT_SECRETS.to_authorization
|
||||||
|
|
||||||
|
# You can then use this with an API client, e.g.:
|
||||||
|
client.authorization = authorization
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flows
|
||||||
|
|
||||||
|
There are three main OAuth 2.0 flows in the Google API Client Library for Ruby: web server, installed application and service account.
|
||||||
|
|
||||||
|
#### Web server
|
||||||
|
|
||||||
|
We start by retrieving the client ID and client secret from a preconfigured client_secrets.json file:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
```
|
||||||
|
|
||||||
|
For web-based applications, we then redirect the user to an authorization page:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Request authorization
|
||||||
|
redirect user_credentials.authorization_uri.to_s, 303
|
||||||
|
```
|
||||||
|
|
||||||
|
The user completes the steps on her browser, and control gets returned to the application via the callback URL:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
get '/oauth2callback' do
|
||||||
|
# Exchange token
|
||||||
|
user_credentials.code = params[:code] if params[:code]
|
||||||
|
user_credentials.fetch_access_token!
|
||||||
|
redirect to('/')
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
user_credentials now has everything needed to make authenticated requests:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
events = calendar.list_events('primary', options: { authorization: user_credentials })
|
||||||
|
```
|
||||||
|
|
||||||
|
Below is the full sample we've been looking at.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/calendar_v3'
|
||||||
|
require 'google/api_client/client_secrets'
|
||||||
|
require 'sinatra'
|
||||||
|
require 'logger'
|
||||||
|
|
||||||
|
enable :sessions
|
||||||
|
|
||||||
|
def logger; settings.logger end
|
||||||
|
|
||||||
|
def calendar; settings.calendar; end
|
||||||
|
|
||||||
|
def user_credentials
|
||||||
|
# Build a per-request oauth credential based on token stored in session
|
||||||
|
# which allows us to use a shared API client.
|
||||||
|
@authorization ||= (
|
||||||
|
auth = settings.authorization.dup
|
||||||
|
auth.redirect_uri = to('/oauth2callback')
|
||||||
|
auth.update_token!(session)
|
||||||
|
auth
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
configure do
|
||||||
|
log_file = File.open('calendar.log', 'a+')
|
||||||
|
log_file.sync = true
|
||||||
|
logger = Logger.new(log_file)
|
||||||
|
logger.level = Logger::DEBUG
|
||||||
|
|
||||||
|
Google::Apis::ClientOptions.default.application_name = 'Ruby Calendar sample'
|
||||||
|
Google::Apis::ClientOptions.default.application_version = '1.0.0'
|
||||||
|
calendar_api = Google::Apis::CalendarV3::CalendarService.new
|
||||||
|
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
authorization = client_secrets.to_authorization
|
||||||
|
authorization.scope = 'https://www.googleapis.com/auth/calendar'
|
||||||
|
|
||||||
|
set :authorization, authorization
|
||||||
|
set :logger, logger
|
||||||
|
set :calendar, calendar_api
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
# Ensure user has authorized the app
|
||||||
|
unless user_credentials.access_token || request.path_info =~ /^\/oauth2/
|
||||||
|
redirect to('/oauth2authorize')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
# Serialize the access/refresh token to the session and credential store.
|
||||||
|
session[:access_token] = user_credentials.access_token
|
||||||
|
session[:refresh_token] = user_credentials.refresh_token
|
||||||
|
session[:expires_in] = user_credentials.expires_in
|
||||||
|
session[:issued_at] = user_credentials.issued_at
|
||||||
|
end
|
||||||
|
|
||||||
|
get '/oauth2authorize' do
|
||||||
|
# Request authorization
|
||||||
|
redirect user_credentials.authorization_uri.to_s, 303
|
||||||
|
end
|
||||||
|
|
||||||
|
get '/oauth2callback' do
|
||||||
|
# Exchange token
|
||||||
|
user_credentials.code = params[:code] if params[:code]
|
||||||
|
user_credentials.fetch_access_token!
|
||||||
|
redirect to('/')
|
||||||
|
end
|
||||||
|
|
||||||
|
get '/' do
|
||||||
|
# Fetch list of events on the user's default calandar
|
||||||
|
events = calendar.list_events('primary', options: { authorization: user_credentials })
|
||||||
|
[200, {'Content-Type' => 'application/json'}, events.to_h.to_json]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
For more detailed information, see the [Web Application Flow Documentation](oauth-web.md).
|
||||||
|
|
||||||
|
#### Installed application
|
||||||
|
|
||||||
|
We start by retrieving the client ID and client secret from a preconfigured client_secrets.json file:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
```
|
||||||
|
|
||||||
|
For installed applications, we can use the Google::APIClient::InstalledAppFlow helper class to handle most of the setup:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
flow = Google::APIClient::InstalledAppFlow.new(
|
||||||
|
:client_id => client_secrets.client_id,
|
||||||
|
:client_secret => client_secrets.client_secret,
|
||||||
|
:scope => ['https://www.googleapis.com/auth/adsense.readonly']
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The user completes the steps on her browser, which is opened automatically, and the authorization code is fed into the application automatically, so all it takes is:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
adsense.authorization = flow.authorize(storage)
|
||||||
|
```
|
||||||
|
|
||||||
|
The client now has everything needed to make an authenticated request:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
report = adsense.generate_report(start_date: '2011-01-01', end_date: '2011-08-31',
|
||||||
|
metric: %w(PAGE_VIEWS AD_REQUESTS AD_REQUESTS_COVERAGE
|
||||||
|
CLICKS AD_REQUESTS_CTR COST_PER_CLICK
|
||||||
|
AD_REQUESTS_RPM EARNINGS),
|
||||||
|
dimension: %w(DATE),
|
||||||
|
sort: %w(+DATE))
|
||||||
|
```
|
||||||
|
|
||||||
|
Below is the full sample we've been looking at.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# AdSense Management API command-line sample.
|
||||||
|
require 'google/apis/adsense_v1_4'
|
||||||
|
require 'google/api_client/client_secrets'
|
||||||
|
require 'google/api_client/auth/installed_app'
|
||||||
|
require 'google/api_client/auth/storage'
|
||||||
|
require 'google/api_client/auth/storages/file_store'
|
||||||
|
require 'logger'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
CREDENTIAL_STORE_FILE = "#{$0}-oauth2.json"
|
||||||
|
|
||||||
|
# Handles authentication and loading of the API.
|
||||||
|
def setup
|
||||||
|
log_file = File.open('adsense.log', 'a+')
|
||||||
|
log_file.sync = true
|
||||||
|
logger = Logger.new(log_file)
|
||||||
|
logger.level = Logger::DEBUG
|
||||||
|
|
||||||
|
adsense = Google::Apis::AdsenseV1_4::AdSenseService.new
|
||||||
|
|
||||||
|
# Stores auth credentials in a file, so they survive multiple runs
|
||||||
|
# of the application. This avoids prompting the user for authorization every
|
||||||
|
# time the access token expires, by remembering the refresh token.
|
||||||
|
# Note: FileStorage is not suitable for multi-user applications.
|
||||||
|
storage = Google::APIClient::Storage.new(
|
||||||
|
Google::APIClient::FileStore.new(CREDENTIAL_STORE_FILE))
|
||||||
|
adsense.authorization = storage.authorize
|
||||||
|
if storage.authorization.nil?
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
# The InstalledAppFlow is a helper class to handle the OAuth 2.0 installed
|
||||||
|
# application flow, which ties in with Stroage to store credentials
|
||||||
|
# between runs.
|
||||||
|
flow = Google::APIClient::InstalledAppFlow.new(
|
||||||
|
:client_id => client_secrets.client_id,
|
||||||
|
:client_secret => client_secrets.client_secret,
|
||||||
|
:scope => ['https://www.googleapis.com/auth/adsense.readonly']
|
||||||
|
)
|
||||||
|
adsense.authorization = flow.authorize(storage)
|
||||||
|
end
|
||||||
|
return adsense
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generates a report for the default account.
|
||||||
|
def generate_report(adsense)
|
||||||
|
report = adsense.generate_report(start_date: '2011-01-01', end_date: '2011-08-31',
|
||||||
|
metric: %w(PAGE_VIEWS AD_REQUESTS AD_REQUESTS_COVERAGE
|
||||||
|
CLICKS AD_REQUESTS_CTR COST_PER_CLICK
|
||||||
|
AD_REQUESTS_RPM EARNINGS),
|
||||||
|
dimension: %w(DATE),
|
||||||
|
sort: %w(+DATE))
|
||||||
|
|
||||||
|
# Display headers.
|
||||||
|
report.headers.each do |header|
|
||||||
|
print '%25s' % header.name
|
||||||
|
end
|
||||||
|
puts
|
||||||
|
|
||||||
|
# Display results.
|
||||||
|
report.rows.each do |row|
|
||||||
|
row.each do |column|
|
||||||
|
print '%25s' % column
|
||||||
|
end
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if __FILE__ == $0
|
||||||
|
adsense = setup()
|
||||||
|
generate_report(adsense)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
For more detailed information, see the [Installed Application Flow Documentation](oauth-installed.md).
|
||||||
|
|
||||||
|
### Service accounts
|
||||||
|
|
||||||
|
For server-to-server interactions, like those between a web application and Google Cloud Storage, Prediction, or BigQuery APIs, you can use service accounts.
|
||||||
|
|
||||||
|
```rb
|
||||||
|
require 'googleauth'
|
||||||
|
require 'google/apis/compute_v1'
|
||||||
|
|
||||||
|
compute = Google::Apis::ComputeV1::ComputeService.new
|
||||||
|
|
||||||
|
# Get the environment configured authorization
|
||||||
|
scopes = ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/compute']
|
||||||
|
compute.authorization = Google::Auth.get_application_default(scopes)
|
||||||
|
```
|
||||||
|
|
||||||
|
For more detailed information, see the [Server Application Flow Documentation](oauth-server.md).
|
||||||
|
|
||||||
|
## Using environment variables
|
||||||
|
|
||||||
|
The [GoogleAuth Library for Ruby](https://github.com/google/google-auth-library-ruby) also supports authorization via environment variables if you do not want to check in developer credentials or private keys. Simply set the following variables for your application:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
GOOGLE_ACCOUNT_TYPE="YOUR ACCOUNT TYPE" # ie. 'service'
|
||||||
|
GOOGLE_CLIENT_EMAIL="YOUR GOOGLE DEVELOPER EMAIL"
|
||||||
|
GOOGLE_PRIVATE_KEY="YOUR GOOGLE DEVELOPER API KEY"
|
||||||
|
```
|
|
@ -0,0 +1,193 @@
|
||||||
|
# Using OAuth 2.0 for Installed Applications
|
||||||
|
|
||||||
|
The Google APIs Client Library for Ruby supports using OAuth 2.0 in applications that are installed on a device such as a computer, a cell phone, or a tablet. Installed apps are distributed to individual machines, and it is assumed that these apps cannot keep secrets. These apps might access a Google API while the user is present at the app, or when the app is running in the background.
|
||||||
|
|
||||||
|
This document is for you if:
|
||||||
|
|
||||||
|
- You are writing an installed app for a platform other than Android or iOS, and
|
||||||
|
- Your installed app will run on devices that have a system browser and rich input capabilities, such as devices with full keyboards.
|
||||||
|
|
||||||
|
If you are writing an app for Android or iOS, use [Google Sign-In](https://developers.google.com/identity) to authenticate your users. The Google Sign-In button manages the OAuth 2.0 flow both for authentication and for obtaining authorization to Google APIs. To add the Google Sign-In button, follow the steps for [Android](https://developers.google.com/identity/sign-in/android) or [iOS](https://developers.google.com/identity/sign-in/ios).
|
||||||
|
|
||||||
|
If your app will run on devices that do not have access to a system browser, or devices with limited input capabilities (for example, if your app will run on game consoles, video cameras, or printers), then see [Using OAuth 2.0 for Devices](https://developers.google.com/accounts/docs/OAuth2ForDevices).
|
||||||
|
|
||||||
|
> **Note:** this is a "general" document, and not specific to this particular service client. In particular, you may find that the examples in this document are for a different client. All legacy REST clients follow the same usage patterns, and you should be able to adapt the provided examples to the client you are using.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
To use OAuth 2.0 in a locally-installed application, first create application credentials for your project in the API Console.
|
||||||
|
|
||||||
|
Then, when your application needs to access a user's data with a Google API, your application sends the user to Google's OAuth 2.0 server. The OAuth 2.0 server authenticates the user and obtains consent from the user for your application to access the user's data.
|
||||||
|
|
||||||
|
Next, Google's OAuth 2.0 server sends a single-use authorization code to your application, either in the title bar of the browser or in the query string of an HTTP request to the local host. Your application exchanges this authorization code for an access token.
|
||||||
|
|
||||||
|
Finally, your application can use the access token to call Google APIs.
|
||||||
|
|
||||||
|
This flow is similar to the one shown in the [Using OAuth 2.0 for Web Server Applications](docs/oauth-server.md), but with three differences:
|
||||||
|
|
||||||
|
- When creating a client ID, you specify that your application is an Installed application. This results in a different value for the redirect_uri parameter.
|
||||||
|
- The client ID and client secret obtained from the API Console are embedded in the source code of your application. In this context, the client secret is obviously not treated as a secret.
|
||||||
|
- The authorization code can be returned to your application in the title bar of the browser or in the query string of an HTTP request to the local host.
|
||||||
|
|
||||||
|
## Creating application credentials
|
||||||
|
|
||||||
|
All applications that use OAuth 2.0 must have credentials that identify the application to the OAuth 2.0 server. Applications that have these credentials can access the APIs that you enabled for your project.
|
||||||
|
|
||||||
|
To obtain application credentials for your project, complete these steps:
|
||||||
|
|
||||||
|
1. Open the [Credentials page](https://console.developers.google.com/apis/credentials) in the API Console.
|
||||||
|
1. If you haven't done so already, create your OAuth 2.0 credentials by clicking **Create new Client ID** under the **OAuth** heading and selecting the **Installed application** type. Next, look for your application's client ID and client secret in the relevant table.
|
||||||
|
|
||||||
|
Download the client_secrets.json file and securely store it in a location that only your application can access.
|
||||||
|
|
||||||
|
> **Important:** Do not store the client_secrets.json file in a publicly-accessible location, and if you share the source code to your application—for example, on GitHub—store the client_secrets.json file outside of your source tree to avoid inadvertently sharing your client credentials.
|
||||||
|
|
||||||
|
## Configuring the client object
|
||||||
|
|
||||||
|
Use the client application credentials that you created to configure a client object in your application. When you configure a client object, you specify the scopes your application needs to access, along with a redirect URI, which will handle the response from the OAuth 2.0 server.
|
||||||
|
|
||||||
|
### Choosing a redirect URI
|
||||||
|
|
||||||
|
When you create a client ID in the [Google API Console](https://console.developers.google.com/), two redirect_uri parameters are created for you: `urn:ietf:wg:oauth:2.0:oob` and `http://localhost`. The value your application uses determines how the authorization code is returned to your application.
|
||||||
|
|
||||||
|
#### http://localhost
|
||||||
|
|
||||||
|
This value signals to the Google Authorization Server that the authorization code should be returned as a query string parameter to the web server on the client. You can specify a port number without changing the [Google API Console](https://console.developers.google.com/) configuration. To receive the authorization code using this URI, your application must be listening on the local web server. This is possible on many, but not all, platforms. If your platform supports it, this is the recommended mechanism for obtaining the authorization code.
|
||||||
|
|
||||||
|
> **Note:** In some cases, although it is possible to listen, other software (such as a Windows firewall) prevents delivery of the message without significant client configuration.
|
||||||
|
|
||||||
|
#### urn:ietf:wg:oauth:2.0:oob
|
||||||
|
|
||||||
|
This value signals to the Google Authorization Server that the authorization code should be returned in the title bar of the browser, with the page text prompting the user to copy the code and paste it in the application. This is useful when the client (such as a Windows application) cannot listen on an HTTP port without significant client configuration.
|
||||||
|
|
||||||
|
When you use this value, your application can then detect that the page has loaded, and can read the title of the HTML page to obtain the authorization code. It is then up to your application to close the browser window if you want to ensure that the user never sees the page that contains the authorization code. The mechanism for doing this varies from platform to platform.
|
||||||
|
|
||||||
|
If your platform doesn't allow you to detect that the page has loaded or read the title of the page, you can have the user paste the code back to your application, as prompted by the text in the confirmation page that the OAuth 2.0 server generates.
|
||||||
|
|
||||||
|
#### urn:ietf:wg:oauth:2.0:oob:auto
|
||||||
|
|
||||||
|
urn:ietf:wg:oauth:2.0:oob:auto
|
||||||
|
This is identical to urn:ietf:wg:oauth:2.0:oob, but the text in the confirmation page that the OAuth 2.0 server generates won't instruct the user to copy the authorization code, but instead will simply ask the user to close the window.
|
||||||
|
|
||||||
|
This is useful when your application reads the title of the HTML page (by checking window titles on the desktop, for example) to obtain the authorization code, but can't close the page on its own.
|
||||||
|
|
||||||
|
### Creating the object
|
||||||
|
|
||||||
|
To create a client object from the client_secrets.json file, use the to_authorization method of a ClientSecrets object. For example, to request read-only access to a user's Google Drive:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/api_client'
|
||||||
|
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
auth_client = client_secrets.to_authorization
|
||||||
|
auth_client.update!(
|
||||||
|
:scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
|
||||||
|
:redirect_uri => 'urn:ietf:wg:oauth:2.0:oob'
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Your application uses the client object to perform OAuth 2.0 operations, such as generating authorization request URIs and applying access tokens to HTTP requests.
|
||||||
|
|
||||||
|
## Sending users to Google's OAuth 2.0 server
|
||||||
|
|
||||||
|
When your application needs to access a user's data, redirect the user to Google's OAuth 2.0 server.
|
||||||
|
|
||||||
|
1. Generate a URL to request access from Google's OAuth 2.0 server:
|
||||||
|
|
||||||
|
`auth_uri = auth_client.authorization_uri.to_s`
|
||||||
|
|
||||||
|
2. Open auth_uri in a browser:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'launchy'
|
||||||
|
|
||||||
|
Launchy.open(auth_uri)
|
||||||
|
```
|
||||||
|
|
||||||
|
Google's OAuth 2.0 server will authenticate the user and obtain consent from the user for your application to access the requested scopes. The response will be sent back to your application using the redirect URI specified in the client object.
|
||||||
|
|
||||||
|
## Handling the OAuth 2.0 server response
|
||||||
|
|
||||||
|
The OAuth 2.0 server responds to your application's access request by using the URI specified in the request.
|
||||||
|
|
||||||
|
If the user approves the access request, then the response contains an authorization code. If the user does not approve the request, the response contains an error message. Depending on the redirect URI that you specified, the response is in the query string of an HTTP request to the local host, or in a web page, from which the user can copy and paste the authorization code.
|
||||||
|
|
||||||
|
To exchange an authorization code for an access token, use the fetch_access_token! method:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
auth_client.code = auth_code
|
||||||
|
auth_client.fetch_access_token!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Calling Google APIs
|
||||||
|
|
||||||
|
Use the auth_client object to call Google APIs by completing the following steps:
|
||||||
|
|
||||||
|
1. Build a service object for the API that you want to call. You build a a service object by calling the discovered_api function with the name and version of the API. For example, to call version 2 of the Drive API:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/drive_v2'
|
||||||
|
drive = Google::Apis::DriveV2::DriveService.new
|
||||||
|
drive.authorization = auth_client
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Make requests to the API service using the interface provided by the service object. For example, to list the files in the authenticated user's Google Drive:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
files = drive.list_files()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete example
|
||||||
|
|
||||||
|
The following example prints a JSON-formatted list of files in a user's Google Drive after the user authenticates and gives consent for the application to access the user's Drive files.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/drive_v2'
|
||||||
|
require 'google/api_client/client_secrets'
|
||||||
|
require 'launchy'
|
||||||
|
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
auth_client = client_secrets.to_authorization
|
||||||
|
auth_client.update!(
|
||||||
|
:scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
|
||||||
|
:redirect_uri => 'urn:ietf:wg:oauth:2.0:oob'
|
||||||
|
)
|
||||||
|
|
||||||
|
auth_uri = auth_client.authorization_uri.to_s
|
||||||
|
Launchy.open(auth_uri)
|
||||||
|
|
||||||
|
puts 'Paste the code from the auth response page:'
|
||||||
|
auth_client.code = gets
|
||||||
|
auth_client.fetch_access_token!
|
||||||
|
|
||||||
|
drive = Google::Apis::DriveV2::DriveService.new
|
||||||
|
drive.authorization = auth_client
|
||||||
|
files = drive.list_files
|
||||||
|
|
||||||
|
files.items.each do |file|
|
||||||
|
puts file.title
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to use a local web server to handle the OAuth 2.0 response, you can use the InstalledAppFlow helper to simplify the process. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/drive_v2'
|
||||||
|
require 'google/api_client/client_secrets'
|
||||||
|
require 'google/api_client/auth/installed_app'
|
||||||
|
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
flow = Google::APIClient::InstalledAppFlow.new(
|
||||||
|
:client_id => client_secrets.client_id,
|
||||||
|
:client_secret => client_secrets.client_secret,
|
||||||
|
:scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
|
||||||
|
:port => 5000)
|
||||||
|
|
||||||
|
drive = Google::Apis::DriveV2::DriveService.new
|
||||||
|
drive.authorization = flow.authorize
|
||||||
|
files = drive.list_files
|
||||||
|
|
||||||
|
files.items.each do |file|
|
||||||
|
puts file.title
|
||||||
|
end
|
||||||
|
```
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Using OAuth 2.0 for Server to Server Applications
|
||||||
|
|
||||||
|
The Google APIs Client Library for Ruby supports using OAuth 2.0 for server-to-server interactions such as those between a web application and a Google service. For this scenario you need a service account, which is an account that belongs to your application instead of to an individual end user. Your application calls Google APIs on behalf of the service account, so users aren't directly involved. This scenario is sometimes called "two-legged OAuth," or "2LO." (The related term "three-legged OAuth" refers to scenarios in which your application calls Google APIs on behalf of end users, and in which user consent is sometimes required.)
|
||||||
|
|
||||||
|
Typically, an application uses a service account when the application uses Google APIs to work with its own data rather than a user's data. For example, an application that uses [Google Cloud Datastore](https://cloud.google.com/datastore/) for data persistence would use a service account to authenticate its calls to the Google Cloud Datastore API.
|
||||||
|
|
||||||
|
If you have a G Suite domain—if you use [G Suite](https://gsuite.google.com/), for example—an administrator of the G Suite domain can authorize an application to access user data on behalf of users in the G Suite domain. For example, an application that uses the [Google Calendar API](https://developers.google.com/calendar/) to add events to the calendars of all users in a G Suite domain would use a service account to access the Google Calendar API on behalf of users. Authorizing a service account to access data on behalf of users in a domain is sometimes referred to as "delegating domain-wide authority" to a service account.
|
||||||
|
|
||||||
|
> **Note:** When you use [G Suite Marketplace](https://www.google.com/enterprise/marketplace/) to install an application for your domain, the required permissions are automatically granted to the application. You do not need to manually authorize the service accounts that the application uses.
|
||||||
|
|
||||||
|
> **Note:** Although you can use service accounts in applications that run from a Google Apps domain, service accounts are not members of your Google Apps account and aren't subject to domain policies set by Google Apps administrators. For example, a policy set in the Google Apps admin console to restrict the ability of Apps end users to share documents outside of the domain would not apply to service accounts.
|
||||||
|
|
||||||
|
This document describes how an application can complete the server-to-server OAuth 2.0 flow by using the Google APIs Client Library for Ruby.
|
||||||
|
|
||||||
|
> **Note:** this is a "general" document, and not specific to this particular service client. In particular, you may find that the examples in this document are for a different client. All legacy REST clients follow the same usage patterns, and you should be able to adapt the provided examples to the client you are using.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
To support server-to-server interactions, first create a service account for your project in the API Console. If you want to access user data for users in your Google Apps domain, then delegate domain-wide access to the service account.
|
||||||
|
|
||||||
|
Then, your application prepares to make authorized API calls by using the service account's credentials to request an access token from the OAuth 2.0 auth server.
|
||||||
|
|
||||||
|
Finally, your application can use the access token to call Google APIs.
|
||||||
|
|
||||||
|
## Creating a service account
|
||||||
|
|
||||||
|
A service account's credentials include a generated email address that is unique, a client ID, and at least one public/private key pair.
|
||||||
|
|
||||||
|
If your application runs on Google App Engine, a service account is set up automatically when you create your project.
|
||||||
|
|
||||||
|
If your application runs on Google Compute Engine, a service account is also set up automatically when you create your project, but you must specify the scopes that your application needs access to when you create a Google Compute Engine instance. For more information, see [Preparing an instance to use service accounts](https://cloud.google.com/compute/docs/authentication#using).
|
||||||
|
|
||||||
|
If your application doesn't run on Google App Engine or Google Compute Engine, you must obtain these credentials in the Google API Console. To generate service-account credentials, or to view the public credentials that you've already generated, do the following:
|
||||||
|
|
||||||
|
1. Open the [**Service accounts** page](https://console.developers.google.com/permissions/serviceaccounts). If prompted, select a project.
|
||||||
|
1. Click **Create service account**.
|
||||||
|
1. In the **Create service account** window, type a name for the service account, and select **Furnish a new private key**. If you want to [grant G Suite domain-wide authority](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority) to the service account, also select **Enable G Suite Domain-wide Delegation**. Then click **Create**.
|
||||||
|
|
||||||
|
Your new public/private key pair is generated and downloaded to your machine; it serves as the only copy of this key. You are responsible for storing it securely.
|
||||||
|
|
||||||
|
You can return to the [API Console](https://console.developers.google.com/) at any time to view the client ID, email address, and public key fingerprints, or to generate additional public/private key pairs. For more details about service account credentials in the API Console, see [Service accounts](https://developers.google.com/console/help/service-accounts) in the API Console help file.
|
||||||
|
|
||||||
|
Take note of the service account's email address and store the service account's private key file in a location accessible to your application. Your application needs them to make authorized API calls.
|
||||||
|
|
||||||
|
> **Note:** You must store and manage private keys securely in both development and production environments. Google does not keep a copy of your private keys, only your public keys.
|
||||||
|
|
||||||
|
## Delegating domain-wide authority to the service account
|
||||||
|
|
||||||
|
If your application runs in a Google Apps domain and accesses user data, the service account that you created needs to be granted access to the user data that you want to access.
|
||||||
|
|
||||||
|
The following steps must be performed by an administrator of the Google Apps domain:
|
||||||
|
|
||||||
|
1. Go to your Google Apps domain’s [Admin console](http://admin.google.com/).
|
||||||
|
1. Select **Security** from the list of controls. If you don't see **Security** listed, select **More controls** from the gray bar at the bottom of the page, then select **Security** from the list of controls. If you can't see the controls, make sure you're signed in as an administrator for the domain.
|
||||||
|
1. Select **Advanced settings** from the list of options.
|
||||||
|
1. Select **Manage third party OAuth Client access** in the **Authentication** section.
|
||||||
|
1. In the **Client name** field enter the service account's **Client ID**.
|
||||||
|
1. In the **One or More API Scopes** field enter the list of scopes that your application should be granted access to. For example, if your application needs domain-wide access to the Google Drive API and the Google Calendar API, enter: `https://www.googleapis.com/auth/drive`, `https://www.googleapis.com/auth/calendar`.
|
||||||
|
1. Click **Authorize**.
|
||||||
|
|
||||||
|
Your application now has the authority to make API calls as users in your domain (to "impersonate" users). When you prepare to make authorized API calls, you specify the user to impersonate.
|
||||||
|
|
||||||
|
## Preparing to make an authorized API call
|
||||||
|
|
||||||
|
After you obtain the client email address and private key from the API Console, complete the following steps:
|
||||||
|
|
||||||
|
1. Create a Client object from the service account's credentials and the scopes your application needs access to. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'googleauth'
|
||||||
|
require 'google/apis/compute_v1'
|
||||||
|
|
||||||
|
compute = Google::Apis::ComputeV1::ComputeService.new
|
||||||
|
|
||||||
|
# Get the environment configured authorization
|
||||||
|
scopes = ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/compute']
|
||||||
|
compute.authorization = Google::Auth.get_application_default(scopes)
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have delegated domain-wide access to the service account and you want to impersonate a user account, specify the email address of the user account in the :sub parameter. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'googleauth'
|
||||||
|
require 'google/apis/sqladmin_v1beta4'
|
||||||
|
|
||||||
|
# Get the environment configured authorization
|
||||||
|
scopes = ['https://www.googleapis.com/auth/sqlservice.admin']
|
||||||
|
authorization = Google::Auth.get_application_default(scopes)
|
||||||
|
|
||||||
|
# Clone and set the subject
|
||||||
|
auth_client = authorization.dup
|
||||||
|
auth_client.sub = 'user@example.org'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Use the fetch_access_token! method of the client object to acquire an access token. For example:
|
||||||
|
|
||||||
|
`auth_client.fetch_access_token!`
|
||||||
|
|
||||||
|
Use the authorized Client object to call Google APIs in your application.
|
||||||
|
|
||||||
|
## Calling Google APIs
|
||||||
|
|
||||||
|
Use the authorized Client object to call Google APIs by completing the following steps:
|
||||||
|
|
||||||
|
1. Build a service object for the API that you want to call. You build a a service object by calling the discovered_api method of an APIClient object with the name and version of the API. For example, to call version 1beta3 of the Cloud SQL Administration API:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
sqladmin = Google::Apis::SqladminV1beta4::SqladminService.new
|
||||||
|
sqladmin.authorization = auth_client
|
||||||
|
|
||||||
|
# Make requests to the API service using the interface
|
||||||
|
# provided by the service object, and providing the authorized
|
||||||
|
# Client object. For example, to list the instances of Cloud SQL
|
||||||
|
# databases in the examinable-example-123 project:
|
||||||
|
|
||||||
|
instances = sqladmin.list_instances('examinable-example-123')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete example
|
||||||
|
|
||||||
|
The following example prints a JSON-formatted list of Cloud SQL instances in a project.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'googleauth'
|
||||||
|
require 'google/apis/sqladmin_v1beta4'
|
||||||
|
|
||||||
|
sqladmin = Google::Apis::SqladminV1beta4::SqladminService.new
|
||||||
|
|
||||||
|
# Get the environment configured authorization
|
||||||
|
scopes = ['https://www.googleapis.com/auth/sqlservice.admin']
|
||||||
|
sqladmin.authorization = Google::Auth.get_application_default(scopes)
|
||||||
|
|
||||||
|
instances = sqladmin.list_instances('examinable-example-123')
|
||||||
|
puts instances.to_h
|
||||||
|
```
|
|
@ -0,0 +1,267 @@
|
||||||
|
# Using OAuth 2.0 for Web Server Applications
|
||||||
|
|
||||||
|
This document explains how web server applications use the Google API Client Library for Ruby to implement OAuth 2.0 authorization to access Google APIs. OAuth 2.0 allows users to share specific data with an application while keeping their usernames, passwords, and other information private. For example, an application can use OAuth 2.0 to obtain permission from users to store files in their Google Drives.
|
||||||
|
|
||||||
|
This OAuth 2.0 flow is specifically for user authorization. It is designed for applications that can store confidential information and maintain state. A properly authorized web server application can access an API while the user interacts with the application or after the user has left the application.
|
||||||
|
|
||||||
|
Web server applications frequently also use service accounts to authorize API requests, particularly when calling Cloud APIs to access project-based data rather than user-specific data. Web server applications can use service accounts in conjunction with user authorization.
|
||||||
|
|
||||||
|
> **Note:** this is a "general" document, and not specific to this particular service client. In particular, you may find that the examples in this document are for a different client. All legacy REST clients follow the same usage patterns, and you should be able to adapt the provided examples to the client you are using.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Enable APIs for your project
|
||||||
|
|
||||||
|
Any application that calls Google APIs needs to enable those APIs in the API Console. To enable the appropriate APIs for your project:
|
||||||
|
|
||||||
|
1. Open the [Library](https://console.developers.google.com/apis/library) page in the API Console.
|
||||||
|
1. Select the project associated with your application. Create a project if you do not have one already.
|
||||||
|
1. Use the **Library** page to find each API that your application will use. Click on each API and enable it for your project.
|
||||||
|
|
||||||
|
### Create authorization credentials
|
||||||
|
|
||||||
|
Any application that uses OAuth 2.0 to access Google APIs must have authorization credentials that identify the application to Google's OAuth 2.0 server. The following steps explain how to create credentials for your project. Your applications can then use the credentials to access APIs that you have enabled for that project.
|
||||||
|
|
||||||
|
* Open the [Credentials page](https://console.developers.google.com/apis/credentials) in the API Console.
|
||||||
|
* Click **Create credentials > OAuth client ID**.
|
||||||
|
* Complete the form. Set the application type to `Web application`. Applications that use languages and frameworks like PHP, Java, Python, Ruby, and .NET must specify authorized **redirect URIs**. The redirect URIs are the endpoints to which the OAuth 2.0 server can send responses. For testing, you can specify URIs that refer to the local machine, such as `http://localhost:8080`. With that in mind, please note that all of the examples in this document use `http://localhost:8080` as the redirect URI. We recommend that you design your app's auth endpoints so that your application does not expose authorization codes to other resources on the page.
|
||||||
|
|
||||||
|
After creating your credentials, download the **client_secret.json** file from the API Console. Securely store the file in a location that only your application can access.
|
||||||
|
|
||||||
|
> **Important:** Do not store the **client_secret.json** file in a publicly-accessible location. In addition, if you share the source code to your application—for example, on GitHub—store the **client_secret.json** file outside of your source tree to avoid inadvertently sharing your client credentials.
|
||||||
|
|
||||||
|
### Identify access scopes
|
||||||
|
|
||||||
|
Scopes enable your application to only request access to the resources that it needs while also enabling users to control the amount of access that they grant to your application. Thus, there may be an inverse relationship between the number of scopes requested and the likelihood of obtaining user consent.
|
||||||
|
|
||||||
|
Before you start implementing OAuth 2.0 authorization, we recommend that you identify the scopes that your app will need permission to access.
|
||||||
|
|
||||||
|
We also recommend that your application request access to authorization scopes via an incremental authorization process, in which your application requests access to user data in context. This best practice helps users to more easily understand why your application needs the access it is requesting.
|
||||||
|
|
||||||
|
The [OAuth 2.0 API Scopes document](https://developers.google.com/identity/protocols/googlescopes) contains a full list of scopes that you might use to access Google APIs.
|
||||||
|
|
||||||
|
## Obtaining OAuth 2.0 access tokens
|
||||||
|
|
||||||
|
The following steps show how your application interacts with Google's OAuth 2.0 server to obtain a user's consent to perform an API request on the user's behalf. Your application must have that consent before it can execute a Google API request that requires user authorization.
|
||||||
|
|
||||||
|
### Step 1: Configure the client object
|
||||||
|
|
||||||
|
Your first step is to configure the client object, which your application uses to obtain user authorization and to make authorized API requests.
|
||||||
|
|
||||||
|
The client object identifies the scopes that your application is requesting permission to access. These values inform the consent screen that Google displays to the user. The Choosing access scopes section provides information about how to determine which scopes your application should request permission to access.
|
||||||
|
|
||||||
|
Use the client_secrets.json file that you created to configure a client object in your application. When you configure a client object, you specify the scopes your application needs to access, along with the URL to your application's auth endpoint, which will handle the response from the OAuth 2.0 server.
|
||||||
|
|
||||||
|
For example, to request read-only, offline access to a user's Google Drive:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/drive_v2'
|
||||||
|
require 'google/api_client/client_secrets'
|
||||||
|
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
auth_client = client_secrets.to_authorization
|
||||||
|
auth_client.update!(
|
||||||
|
:scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
|
||||||
|
:redirect_uri => 'http://www.example.com/oauth2callback',
|
||||||
|
:additional_parameters => {
|
||||||
|
"access_type" => "offline", # offline access
|
||||||
|
"include_granted_scopes" => "true" # incremental auth
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Your application uses the client object to perform OAuth 2.0 operations, such as generating authorization request URLs and applying access tokens to HTTP requests.
|
||||||
|
|
||||||
|
### Step 2: Redirect to Google's OAuth 2.0 server
|
||||||
|
|
||||||
|
When your application needs to access a user's data, redirect the user to Google's OAuth 2.0 server.
|
||||||
|
|
||||||
|
1. Generate a URL to request access from Google's OAuth 2.0 server:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
auth_uri = auth_client.authorization_uri.to_s
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Redirect the user to auth_uri.
|
||||||
|
|
||||||
|
Google's OAuth 2.0 server authenticates the user and obtains consent from the user for your application to access the requested scopes. The response is sent back to your application using the redirect URL you specified.
|
||||||
|
|
||||||
|
### Step 3: Google prompts user for consent
|
||||||
|
|
||||||
|
In this step, the user decides whether to grant your application the requested access. At this stage, Google displays a consent window that shows the name of your application and the Google API services that it is requesting permission to access with the user's authorization credentials. The user can then consent or refuse to grant access to your application.
|
||||||
|
|
||||||
|
Your application doesn't need to do anything at this stage as it waits for the response from Google's OAuth 2.0 server indicating whether the access was granted. That response is explained in the following step.
|
||||||
|
|
||||||
|
### Step 4: Handle the OAuth 2.0 server response
|
||||||
|
|
||||||
|
The OAuth 2.0 server responds to your application's access request by using the URL specified in the request.
|
||||||
|
|
||||||
|
If the user approves the access request, then the response contains an authorization code. If the user does not approve the request, the response contains an error message. The authorization code or error message that is returned to the web server appears on the query string, as shown below:
|
||||||
|
|
||||||
|
An error response:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://oauth2.example.com/auth?error=access_denied
|
||||||
|
```
|
||||||
|
|
||||||
|
An authorization code response:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://oauth2.example.com/auth?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7
|
||||||
|
```
|
||||||
|
|
||||||
|
> Important: If your response endpoint renders an HTML page, any resources on that page will be able to see the authorization code in the URL. Scripts can read the URL directly, and the URL in the Referer HTTP header may be sent to any or all resources on the page.
|
||||||
|
>
|
||||||
|
> Carefully consider whether you want to send authorization credentials to all resources on that page (especially third-party scripts such as social plugins and analytics). To avoid this issue, we recommend that the server first handle the request, then redirect to another URL that doesn't include the response parameters.
|
||||||
|
|
||||||
|
**Sample OAuth 2.0 server response**
|
||||||
|
|
||||||
|
You can test this flow by clicking on the following sample URL, which requests read-only access to view metadata for files in your Google Drive:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://accounts.google.com/o/oauth2/v2/auth?
|
||||||
|
scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly&
|
||||||
|
access_type=offline&
|
||||||
|
include_granted_scopes=true&
|
||||||
|
state=state_parameter_passthrough_value&
|
||||||
|
redirect_uri=http%3A%2F%2Foauth2.example.com%2Fcallback&
|
||||||
|
response_type=code&
|
||||||
|
client_id=client_id
|
||||||
|
```
|
||||||
|
|
||||||
|
After completing the OAuth 2.0 flow, you should be redirected to http://localhost/oauth2callback, which will likely yield a 404 NOT FOUND error unless your local machine serves a file at that address. The next step provides more detail about the information returned in the URI when the user is redirected back to your application.
|
||||||
|
|
||||||
|
### Step 5: Exchange authorization code for refresh and access tokens
|
||||||
|
|
||||||
|
After the web server receives the authorization code, it can exchange the authorization code for an access token.
|
||||||
|
|
||||||
|
To exchange an authorization code for an access token, use the fetch_access_token! method:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
auth_client.code = auth_code
|
||||||
|
auth_client.fetch_access_token!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Calling Google APIs
|
||||||
|
|
||||||
|
Use the auth_client object to call Google APIs by completing the following steps:
|
||||||
|
|
||||||
|
1. Build a service object for the API that you want to call. For example, to call version 2 of the Drive API:
|
||||||
|
`drive = Google::Apis::DriveV2::DriveService.new`
|
||||||
|
2. Set the credentials on the service:
|
||||||
|
`drive.authorization = auth_client`
|
||||||
|
3. Make requests to the API service using the interface provided by the service object. For example, to list the files in the authenticated user's Google Drive:
|
||||||
|
`files = drive.list_files`
|
||||||
|
|
||||||
|
Alternately, authorization can be provided on a per-method basis by supplying the options parameter to a method:
|
||||||
|
|
||||||
|
`files = drive.list_files(options: { authorization: auth_client })`
|
||||||
|
|
||||||
|
## Complete example
|
||||||
|
|
||||||
|
The following example prints a JSON-formatted list of files in a user's Google Drive after the user authenticates and gives consent for the application to access the user's Drive files.
|
||||||
|
|
||||||
|
This example uses the Sinatra framework.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/drive_v2'
|
||||||
|
require 'google/api_client/client_secrets'
|
||||||
|
require 'json'
|
||||||
|
require 'sinatra'
|
||||||
|
|
||||||
|
enable :sessions
|
||||||
|
set :session_secret, 'setme'
|
||||||
|
|
||||||
|
get '/' do
|
||||||
|
unless session.has_key?(:credentials)
|
||||||
|
redirect to('/oauth2callback')
|
||||||
|
end
|
||||||
|
client_opts = JSON.parse(session[:credentials])
|
||||||
|
auth_client = Signet::OAuth2::Client.new(client_opts)
|
||||||
|
drive = Google::Apis::DriveV2::DriveService.new
|
||||||
|
files = drive.list_files(options: { authorization: auth_client })
|
||||||
|
"<pre>#{JSON.pretty_generate(files.to_h)}</pre>"
|
||||||
|
end
|
||||||
|
|
||||||
|
get '/oauth2callback' do
|
||||||
|
client_secrets = Google::APIClient::ClientSecrets.load
|
||||||
|
auth_client = client_secrets.to_authorization
|
||||||
|
auth_client.update!(
|
||||||
|
:scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
|
||||||
|
:redirect_uri => url('/oauth2callback'))
|
||||||
|
if request['code'] == nil
|
||||||
|
auth_uri = auth_client.authorization_uri.to_s
|
||||||
|
redirect to(auth_uri)
|
||||||
|
else
|
||||||
|
auth_client.code = request['code']
|
||||||
|
auth_client.fetch_access_token!
|
||||||
|
auth_client.client_secret = nil
|
||||||
|
session[:credentials] = auth_client.to_json
|
||||||
|
redirect to('/')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Incremental authorization
|
||||||
|
|
||||||
|
In the OAuth 2.0 protocol, your app requests authorization to access resources, which are identified by scopes. It is considered a best user-experience practice to request authorization for resources at the time you need them. To enable that practice, Google's authorization server supports incremental authorization. This feature lets you request scopes as they are needed and, if the user grants permission, add those scopes to your existing access token for that user.
|
||||||
|
|
||||||
|
For example, an app that lets people sample music tracks and create mixes might need very few resources at sign-in time, perhaps nothing more than the name of the person signing in. However, saving a completed mix would require access to their Google Drive. Most people would find it natural if they only were asked for access to their Google Drive at the time the app actually needed it.
|
||||||
|
|
||||||
|
In this case, at sign-in time the app might request the profile scope to perform basic sign-in, and then later request the https://www.googleapis.com/auth/drive.file scope at the time of the first request to save a mix.
|
||||||
|
|
||||||
|
To implement incremental authorization, you complete the normal flow for requesting an access token but make sure that the authorization request includes previously granted scopes. This approach allows your app to avoid having to manage multiple access tokens.
|
||||||
|
|
||||||
|
The following rules apply to an access token obtained from an incremental authorization:
|
||||||
|
|
||||||
|
The token can be used to access resources corresponding to any of the scopes rolled into the new, combined authorization.
|
||||||
|
When you use the refresh token for the combined authorization to obtain an access token, the access token represents the combined authorization and can be used for any of its scopes.
|
||||||
|
The combined authorization includes all scopes that the user granted to the API project even if the grants were requested from different clients. For example, if a user granted access to one scope using an application's desktop client and then granted another scope to the same application via a mobile client, the combined authorization would include both scopes.
|
||||||
|
If you revoke a token that represents a combined authorization, access to all of that authorization's scopes on behalf of the associated user are revoked simultaneously.
|
||||||
|
The example for configuring the client object demonstrates how to ensure authorization requests follow this best practice. The code snippet below also shows the code that you need to add to use incremental authorization.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
auth_client.update!(
|
||||||
|
:additional_parameters => {"include_granted_scopes" => "true"}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Refreshing an access token (offline access)
|
||||||
|
|
||||||
|
Access tokens periodically expire. You can refresh an access token without prompting the user for permission (including when the user is not present) if you requested offline access to the scopes associated with the token.
|
||||||
|
|
||||||
|
If you use a Google API Client Library, the client object refreshes the access token as needed as long as you configure that object for offline access.
|
||||||
|
|
||||||
|
Requesting offline access is a requirement for any application that needs to access a Google API when the user is not present. For example, an app that performs backup services or executes actions at predetermined times needs to be able to refresh its access token when the user is not present. The default style of access is called online.
|
||||||
|
|
||||||
|
Server-side web applications, installed applications, and devices all obtain refresh tokens during the authorization process. Refresh tokens are not typically used in client-side (JavaScript) web applications.
|
||||||
|
|
||||||
|
If your application needs offline access to a Google API, set the API client's access type to offline:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
auth_client.update!(
|
||||||
|
:additional_parameters => {"access_type" => "offline"}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
After a user grants offline access to the requested scopes, you can continue to use the API client to access Google APIs on the user's behalf when the user is offline. The client object will refresh the access token as needed.
|
||||||
|
|
||||||
|
## Revoking a token
|
||||||
|
|
||||||
|
In some cases a user may wish to revoke access given to an application. A user can revoke access by visiting Account Settings. It is also possible for an application to programmatically revoke the access given to it. Programmatic revocation is important in instances where a user unsubscribes or removes an application. In other words, part of the removal process can include an API request to ensure the permissions granted to the application are removed.
|
||||||
|
|
||||||
|
To programmatically revoke a token, make an HTTP request to the oauth2.revoke endpoint:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
uri = URI('https://accounts.google.com/o/oauth2/revoke')
|
||||||
|
params = { :token => auth_client.access_token }
|
||||||
|
uri.query = URI.encode_www_form(params)
|
||||||
|
response = Net::HTTP.get(uri)
|
||||||
|
```
|
||||||
|
|
||||||
|
The token can be an access token or a refresh token. If the token is an access token and it has a corresponding refresh token, the refresh token will also be revoked.
|
||||||
|
|
||||||
|
If the revocation is successfully processed, then the status code of the response is 200. For error conditions, a status code 400 is returned along with an error code.
|
||||||
|
|
||||||
|
> Note: Following a successful revocation response, it might take some time before the revocation has full effect.
|
||||||
|
|
|
@ -0,0 +1,388 @@
|
||||||
|
# Usage Guide for Legacy REST Clients
|
||||||
|
|
||||||
|
This document provides all the basic information you need to start using the legacy REST clients for Google APIs. It covers important library concepts, shows examples for various use cases, and gives links to more information.
|
||||||
|
|
||||||
|
> **Note:** this is a "general" document, and not specific to this particular service client. In particular, you may find that the examples in this document are for a different client. All legacy REST clients follow the same usage patterns, and you should be able to adapt the provided examples to the client you are using.
|
||||||
|
|
||||||
|
## Before you begin
|
||||||
|
|
||||||
|
There are a few setup steps you need to complete before you can use this library:
|
||||||
|
|
||||||
|
1. If you don't already have a Google account, [sign up](https://www.google.com/accounts).
|
||||||
|
2. If you have never created a Google APIs Console project, read about [Managing Projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects) and create a project in the [Google API Console](https://console.cloud.google.com/).
|
||||||
|
3. Most APIs need to be enabled for your project. [Enable it](https://console.cloud.google.com/apis/library/<%= api.name %>.googleapis.com) in the console.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add the client to your application's Gemfile. For example, to use the Drive V2 client:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem 'google-apis-drive_v2', '~> 0.1'
|
||||||
|
```
|
||||||
|
|
||||||
|
And then execute:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install it yourself as:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ gem install google-apis-drive_v2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
To use an API, include the corresponding generated file and instantiate the service. For example to use the Drive API:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/drive_v2'
|
||||||
|
|
||||||
|
drive = Google::Apis::DriveV2::DriveService.new
|
||||||
|
drive.authorization = ... # See Googleauth or Signet libraries
|
||||||
|
|
||||||
|
# Search for files in Drive (first page only)
|
||||||
|
files = drive.list_files(q: "title contains 'finances'")
|
||||||
|
files.items.each do |file|
|
||||||
|
puts file.title
|
||||||
|
end
|
||||||
|
|
||||||
|
# Upload a file
|
||||||
|
metadata = Google::Apis::DriveV2::File.new(title: 'My document')
|
||||||
|
metadata = drive.insert_file(metadata, upload_source: 'test.txt', content_type: 'text/plain')
|
||||||
|
|
||||||
|
# Download a file
|
||||||
|
drive.get_file(metadata.id, download_dest: '/tmp/myfile.txt')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authorization
|
||||||
|
|
||||||
|
All API calls need to be authenticated, to ensure authorized access to data, and for proper accounting for quota and billing. Google legacy REST clients support several different types of authentication, including OAuth 2.0, service accounts, API keys, and default credentials. Detailed documentation, including examples of several common authentication flows, is provided in the separate [Auth Guide](auth-guide.md). In the present document, we will discuss a few basic cases to get started.
|
||||||
|
|
||||||
|
### Auth libraries
|
||||||
|
|
||||||
|
Most auth functionality is provided in two separate Ruby gems, which legacy REST clients bring in as dependencies.
|
||||||
|
|
||||||
|
* The [signet](https://github.com/google/signet) gem is a basic implementation of [OAuth 2](https://developers.google.com/accounts/docs/OAuth2). For calls that require per-user authorization, it can be used to direct the OAuth flow needed to obtain authorization.
|
||||||
|
* The [googleauth](https://github.com/google/google-auth-library-ruby) gem builds atop signet, and offers a simple way to get credentials for use in Google APIs when auth is independent of the user. In particular, this is the recommend approach for many Cloud APIs.
|
||||||
|
* Some APIs can also take API keys for user-independent auth. API keys do not require use of a separate library.
|
||||||
|
|
||||||
|
### Getting automatic credentials
|
||||||
|
|
||||||
|
If your application is calling an API that does *not* require per-user authorization -- that is, it does not require a different access level depending on who is using the application -- you may be able to use [Application Default Credentials](https://cloud.google.com/docs/authentication/production#automatically) (ADC). Application Default Credentials is a standard way to provide your app with credentials needed to access Google services.
|
||||||
|
|
||||||
|
If your application is running in a Google hosting environment, such as Google Compute Engine, Google App Engine, Google Kubernetes Engine, Google Cloud Run, or Google Cloud Functions, that hosting environment will provide a set of default credentials to your app. The hosting environment may also provide a way to configure credentials, for example to set different scopes delineating which services can be accessed. See the hosting environment documentation for more information.
|
||||||
|
|
||||||
|
If your application is *not* running in a Google hosting environment, you should provide your own default credentials by creating a [service account](https://cloud.google.com/iam/docs/service-accounts) and downloading the service account key JSON file. Set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to the full path to the JSON file when your application runs.
|
||||||
|
|
||||||
|
Once you've obtained credentials, either by running in a Google hosting environment or by pointing the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to a JSON key file, you can load those credentials into a *credentials object* using the googleauth gem:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
authorization = Google::Auth.get_application_default
|
||||||
|
```
|
||||||
|
|
||||||
|
You will use this credential object to authorize API calls.
|
||||||
|
|
||||||
|
### Getting OAuth credentials
|
||||||
|
|
||||||
|
If your application is calling an API that requires per-user authorization, you will need to obtain an OAuth 2.0 token by requesting user authorization. See the [Auth Guide](auth-guide.md) for examples of how to implement this. The resulting authorization object will be in the form of a signet client.
|
||||||
|
|
||||||
|
### Passing authorization to requests
|
||||||
|
|
||||||
|
Once you've obtained an authorization object, either from default authorization, from OAuth 2.0, or from some other source, you can use it to authorize API requests. Authorization can be specified for the entire client, for an individual service instance, or on a per-request basis.
|
||||||
|
|
||||||
|
To set authorization for all service:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Google::Apis::RequestOptions.default.authorization = authorization
|
||||||
|
# Services instantiated after this will inherit the authorization
|
||||||
|
```
|
||||||
|
|
||||||
|
On a per-service level:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
drive = Google::Apis::DriveV2::DriveService.new
|
||||||
|
drive.authorization = authorization
|
||||||
|
|
||||||
|
# All requests made with this service will use the same authorization
|
||||||
|
```
|
||||||
|
|
||||||
|
Per-request:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
drive.get_file('123', options: { authorization: authorization })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authorization using API keys
|
||||||
|
|
||||||
|
Some APIs allow using an API key instead of OAuth2 tokens. For these APIs, set the `key` attribute of the service instance. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'google/apis/translate_v2'
|
||||||
|
|
||||||
|
translate = Google::Apis::TranslateV2::TranslateService.new
|
||||||
|
translate.key = 'YOUR_API_KEY_HERE'
|
||||||
|
result = translate.list_translations('Hello world!', 'es', source: 'en')
|
||||||
|
puts result.translations.first.translated_text
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data formats
|
||||||
|
|
||||||
|
Clients provide classes for most data types in the service's schema. Result values returned from API calls use these classes, and you can also use them to pass request parameters to a call.
|
||||||
|
|
||||||
|
### Hashes
|
||||||
|
|
||||||
|
While the API will always return instances of schema classes, plain hashes are accepted in method calls for convenience. Hash keys must be symbols matching the attribute names on the corresponding object the hash is meant to replace. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
file = {id: '123', title: 'My document', labels: { starred: true }}
|
||||||
|
file = drive.create_file(file, {}) # Returns a Drive::File instance
|
||||||
|
```
|
||||||
|
|
||||||
|
is equivalent to:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
file = Drive::File.new(id: '123', title: 'My document')
|
||||||
|
file.labels = Drive::File::Labels.new(starred: true)
|
||||||
|
file = drive.update_file(file) # Returns a Drive::File instance
|
||||||
|
```
|
||||||
|
|
||||||
|
IMPORTANT: Be careful when supplying hashes for request objects. If it is the last argument to a method, some versions of Ruby will interpret the hash as keyword arguments. To prevent this, appending an empty hash as an extra parameter will avoid misinterpretation.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
file = {id: '123', title: 'My document', labels: { starred: true }}
|
||||||
|
file = drive.create_file(file) # Raises ArgumentError: unknown keywords: id, title, labels
|
||||||
|
file = drive.create_file(file, {}) # Returns a Drive::File instance
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using raw JSON
|
||||||
|
|
||||||
|
To handle JSON serialization or deserialization in the application, set `skip_serialization` or `skip_deserializaton` options respectively. When setting `skip_serialization` in a request, the body object must be a string representing the serialized JSON.
|
||||||
|
|
||||||
|
When setting `skip_deserialization` to true, the response from the API will likewise be a string containing the raw JSON from the server.
|
||||||
|
|
||||||
|
### Naming conventions vs JSON representation
|
||||||
|
|
||||||
|
Object properties in the ruby client use the standard ruby convention for naming -- snake_case. This differs from the underlying JSON representation which typically uses camelCase for properties. There are a few notable exceptions to this rule:
|
||||||
|
|
||||||
|
* For properties that are defined as hashes with user-defined keys, no translation is performed on the key.
|
||||||
|
* For embedded field masks in requests (for example, the Sheets API), specify the camelCase form when referencing fields.
|
||||||
|
|
||||||
|
Outside those exceptions, if a property is specified using camelCase in a request, it will be ignored during serialization and omitted from the request.
|
||||||
|
|
||||||
|
## Media
|
||||||
|
|
||||||
|
Methods that allow media operations have additional parameters to specify the upload source or download destination. For uploads, the `upload_source` parameter can be specified with either a path to a file, an `IO` stream, or `StringIO` instance. For downloads, the `download_dest` parameter can also be either a path to a file, an `IO` stream, or `StringIO` instance. Both uploads & downloads are resumable. If an error occurs during transmission the request will be automatically retried from the last received byte.
|
||||||
|
|
||||||
|
### Media Upload
|
||||||
|
|
||||||
|
For APIs that support file uploads, two additional keyword parameters are available on the method. The parameter `upload_source` specifies the content to upload while `content_type` indicates the MIME type. The upload source may be either a file name, IO, or StringIO instance.
|
||||||
|
|
||||||
|
For example, to upload a file named `mymovie.m4v` to Google Drive:
|
||||||
|
|
||||||
|
```rb
|
||||||
|
require 'google/apis/drive_v2'
|
||||||
|
|
||||||
|
drive = Google::Apis::DriveV2:DriveService.new
|
||||||
|
drive.authorization = ...
|
||||||
|
drive.insert_file({title: 'My Favorite Movie'}, upload_source: 'mymovie.m4v',
|
||||||
|
content_type: 'video/mp4')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resumable media
|
||||||
|
|
||||||
|
For large media files, you can use resumable media uploads to send files, which allows files to be uploaded in smaller chunks. This is especially useful if you are transferring large files, and the likelihood of a network interruption or some other transmission failure is high. It can also reduce your bandwidth usage in the event of network failures because you don't have to restart large file uploads from the beginning.
|
||||||
|
|
||||||
|
To use resumable uploads, enable retries by setting the retry count to any value greater than 0. The client will automatically resume the upload in the event of an error, up to the configured number of retries.:
|
||||||
|
|
||||||
|
```rb
|
||||||
|
drive.insert_file(file_metadata, upload_source: 'mymovie.m4v',
|
||||||
|
content_type: 'video/mp4', options: { retries: 3 } )
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors & Retries
|
||||||
|
|
||||||
|
Retries are disabled by default, but enabling retries is strongly encouraged. The number of retries can be configured via `Google::Apis::RequestOptions`. Any number greater than 0 will enable retries.
|
||||||
|
|
||||||
|
To enable retries for all services:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Google::Apis::RequestOptions.default.retries = 5
|
||||||
|
```
|
||||||
|
|
||||||
|
With retries enabled globally, retries can be disabled for specific calls by including a retry value of 0 in the request options:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
drive.insert_file(metadata, upload_source: 'test.txt', content_type: 'text/plain', options: { retries: 0 })
|
||||||
|
```
|
||||||
|
|
||||||
|
When retries are enabled, if a server or rate limit error occurs during a request it is automatically retried with an exponentially increasing delay on subsequent retries. If a request can not be retried or if the maximum number of retries is exceeded, an exception is thrown.
|
||||||
|
|
||||||
|
### Callbacks
|
||||||
|
|
||||||
|
A block can be specified when making calls. If present, the block will be called with the result or error, rather than returning the result from the call or raising the error. Example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Search for files in Drive (first page only)
|
||||||
|
drive.list_files(q: "title contains 'finances'") do |res, err|
|
||||||
|
if err
|
||||||
|
# Handle error
|
||||||
|
else
|
||||||
|
# Handle response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This calling style is required when making batch requests as responses are not available until the entire batch is complete.
|
||||||
|
|
||||||
|
## Pagination
|
||||||
|
|
||||||
|
Some API methods may return very large lists of data. To reduce the response size, many of these API methods support pagination. With paginated results, your application can iteratively request and process large lists one page at a time. When using API methods that support pagination, responses will come back with a next_page property, which builds a request for the next page for you.
|
||||||
|
|
||||||
|
To process the first page of results, construct a request as you normally would. There's usually an API-level parameter you can provide to specify the maximum size of each page, so be sure to check the API's documentation.
|
||||||
|
|
||||||
|
`stamps = service.list_stamps(cents: 5, max_results: 20)`
|
||||||
|
|
||||||
|
For further pages, repeat the request including the next page token from the previous result.
|
||||||
|
|
||||||
|
`stamps = service.list_stamps(cents:5, max_results: 20, page_token: stamps.next_page_token)`
|
||||||
|
|
||||||
|
Here is a full example which loops through the paginated results of a user's public Google Plus activities feed:
|
||||||
|
|
||||||
|
```rb
|
||||||
|
require 'google/apis/plus_v1'
|
||||||
|
|
||||||
|
plus = Google::Apis::PlusV1::PlusService.new
|
||||||
|
plus.key = '...' # API Key from the Google Developers Console
|
||||||
|
next_page = nil
|
||||||
|
begin
|
||||||
|
puts "Fetching page of activities"
|
||||||
|
activities = plus.list_activities('103354693083460731603', 'public', page_token: next_page)
|
||||||
|
activities.items.each do |activity|
|
||||||
|
puts "#{activity.published} #{activity.title}"
|
||||||
|
end
|
||||||
|
next_page = activities.next_page_token
|
||||||
|
end while next_page
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fetching multiple pages
|
||||||
|
|
||||||
|
To fetch multiple pages of data, use the `fetch_all` method to wrap the paged query. This returns an `Enumerable` that automatically fetches additional pages as needed.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# List all calendar events
|
||||||
|
now = Time.now.iso8601
|
||||||
|
items = calendar.fetch_all do |token|
|
||||||
|
calendar.list_events('primary',
|
||||||
|
single_events: true,
|
||||||
|
order_by: 'startTime',
|
||||||
|
time_min: now,
|
||||||
|
page_token: token)
|
||||||
|
end
|
||||||
|
items.each { |event| puts event.summary }
|
||||||
|
```
|
||||||
|
|
||||||
|
For APIs that use a field other than `items` to contain the results, an alternate field name can be supplied.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# List all files in Drive
|
||||||
|
items = drive.fetch_all(items: :files) do |token|
|
||||||
|
drive.list_files(page_token: token)
|
||||||
|
end
|
||||||
|
items.each { |file| puts file.name }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batches
|
||||||
|
|
||||||
|
Multiple requests can be batched together into a single HTTP request to reduce overhead. Batched calls are executed in parallel and the responses processed once all results are available
|
||||||
|
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Fetch a bunch of files by ID
|
||||||
|
ids = ['file_id_1', 'file_id_2', 'file_id_3', 'file_id_4']
|
||||||
|
drive.batch do |drive|
|
||||||
|
ids.each do |id|
|
||||||
|
drive.get_file(id) do |res, err|
|
||||||
|
# Handle response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Media operations -- uploads & downloads -- can not be included in batch with other requests.
|
||||||
|
|
||||||
|
However, some APIs support batch uploads. To upload multiple files in a batch, use the `batch_upload` method instead. Batch uploads should only be used when uploading multiple small files. For large files, upload files individually to take advantage of the libraries built-in resumable upload support.
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
Logging is enabled by default in this library using Ruby's standard Logger class.
|
||||||
|
|
||||||
|
You can access the library logger with the logger property of Google::Apis.
|
||||||
|
|
||||||
|
You can set the logging level to one of the following:
|
||||||
|
|
||||||
|
- FATAL (least amount of logging)
|
||||||
|
- ERROR
|
||||||
|
- WARN
|
||||||
|
- INFO
|
||||||
|
- DEBUG (most amount of logging)
|
||||||
|
|
||||||
|
In the following code, the logging level is set to DEBUG and the Google Plus API is called:
|
||||||
|
|
||||||
|
```rb
|
||||||
|
require 'google/apis/plus_v1'
|
||||||
|
|
||||||
|
Google::Apis.logger.level = Logger::DEBUG
|
||||||
|
|
||||||
|
plus = Google::Apis::PlusV1::PlusService.new
|
||||||
|
activities = plus.list_activities('103354693083460731603', 'public')
|
||||||
|
```
|
||||||
|
|
||||||
|
The output of this code should include debug info:
|
||||||
|
|
||||||
|
```
|
||||||
|
D, [2015-06-26T14:33:42.583914 #12144] DEBUG -- : Sending HTTP get https://www.googleapis.com/plus/v1/people/103354693083460731603/activities/public?key=...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customizing endpoints
|
||||||
|
|
||||||
|
By default, client objects will connect to the default Google endpoints for =
|
||||||
|
their respective APIs. If you need to connect to a regional endpoint, a test
|
||||||
|
endpoint, or other custom endpoint, modify the `root_url` attribute of the
|
||||||
|
client object. For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require "google/apis/docs_v1"
|
||||||
|
docs_service = Google::Apis::DocsV1::DocsService.new
|
||||||
|
|
||||||
|
docs_service.root_url = "https://my-custom-docs-endpoint.example.com/"
|
||||||
|
|
||||||
|
document = docs_service.get_document("my-document-id")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
This section covers techniques you can use to improve the performance of your application. The documentation for the specific API you are using should have a similar page with more detail on some of these topics. For example, see the Performance Tips page for the Google Drive API.
|
||||||
|
|
||||||
|
### Partial response (fields parameter)
|
||||||
|
|
||||||
|
By default, the server sends back the full representation of a resource after processing requests. For better performance, you can ask the server to send only the fields you really need and get a partial response instead.
|
||||||
|
|
||||||
|
To request a partial response, add the standard fields parameter to any API method. The value of this parameter specifies the fields you want returned. You can use this parameter with any request that returns response data.
|
||||||
|
|
||||||
|
In the following code snippet, the list_stamps method of a fictitious Stamps API is called. The cents parameter is defined by the API to only return stamps with the given value. The value of the fields parameter is set to 'count,items/name'code>. The response will only contain stamps whose value is 5 cents, and the data returned will only include the number of stamps found along with the stamp names:
|
||||||
|
|
||||||
|
`response = service.list_stamps(cents: 5, fields: 'count,items/name')`
|
||||||
|
|
||||||
|
Note how commas are used to delimit the desired fields, and slashes are used to indicate fields that are contained in parent fields. There are other formatting options for the fields parameter, and you should see the "Performance Tips" page in the documentation for the API you are using.
|
||||||
|
|
||||||
|
### Partial update (patch)
|
||||||
|
|
||||||
|
If the API you are calling supports patch, you can avoid sending unnecessary data when modifying resources. For these APIs, you can call the patch() method and supply the arguments you wish to modify for the resource. If supported, the API's reference will have documentation for the patch() method.
|
||||||
|
|
||||||
|
For more information about patch semantics, see the "Performance Tips" page in the documentation for the API you are using.
|
||||||
|
|
||||||
|
### Batches
|
||||||
|
|
||||||
|
If you are sending many small requests you may benefit from batching, which allows those requests to be bundled into a single HTTP request.
|
|
@ -28,7 +28,8 @@ module Google
|
||||||
|
|
||||||
desc 'gen OUTDIR', 'Generate ruby API from an API description'
|
desc 'gen OUTDIR', 'Generate ruby API from an API description'
|
||||||
method_options url: :array, file: :array, from_discovery: :boolean, preferred_only: :boolean,
|
method_options url: :array, file: :array, from_discovery: :boolean, preferred_only: :boolean,
|
||||||
verbose: :boolean, names: :string, names_out: :string, api: :string, clean: :boolean
|
verbose: :boolean, names: :string, names_out: :string, api: :string,
|
||||||
|
clean: :boolean, spot_check: :boolean
|
||||||
def gen(dir)
|
def gen(dir)
|
||||||
ensure_active_support
|
ensure_active_support
|
||||||
require 'google/apis/generator'
|
require 'google/apis/generator'
|
||||||
|
@ -129,6 +130,7 @@ module Google
|
||||||
def clean_from_discovery
|
def clean_from_discovery
|
||||||
count = 0
|
count = 0
|
||||||
apis = discovery_api_list.map { |api| "#{api.name.underscore}_#{api.version.tr '.', '_'}" }
|
apis = discovery_api_list.map { |api| "#{api.name.underscore}_#{api.version.tr '.', '_'}" }
|
||||||
|
return 0 unless File.directory?("#{destination_root}/google/apis")
|
||||||
Dir.chdir("#{destination_root}/google/apis") do
|
Dir.chdir("#{destination_root}/google/apis") do
|
||||||
Dir.glob("*.rb").each do |filename|
|
Dir.glob("*.rb").each do |filename|
|
||||||
filename = File.basename(filename, ".rb")
|
filename = File.basename(filename, ".rb")
|
||||||
|
@ -143,10 +145,19 @@ module Google
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_api(json)
|
def generate_api(json)
|
||||||
files = generator.render(json)
|
result = generator.render(json)
|
||||||
|
files = updater.analyze(destination_root, result)
|
||||||
files.each do |file, content|
|
files.each do |file, content|
|
||||||
create_file(file) { |*| content }
|
create_file(file) { |*| content }
|
||||||
end
|
end
|
||||||
|
run_spot_check(result.gem_name) if !files.empty? && options[:spot_check]
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_spot_check(gem_name)
|
||||||
|
Dir.chdir(File.join(destination_root, gem_name)) do
|
||||||
|
system("bundle install") or error_and_exit("Failed to install bundle for newly generated library")
|
||||||
|
system("bundle exec rake ci") or error_and_exit("Failed to run spot check for newly generated library")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def discovery
|
def discovery
|
||||||
|
@ -171,6 +182,13 @@ module Google
|
||||||
@generator ||= Google::Apis::Generator.new(api_names: options[:names], api_names_out: options[:names_out])
|
@generator ||= Google::Apis::Generator.new(api_names: options[:names], api_names_out: options[:names_out])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def updater
|
||||||
|
@updater ||= begin
|
||||||
|
require 'google/apis/generator/updater'
|
||||||
|
Google::Apis::Generator::Updater.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def api_list_config
|
def api_list_config
|
||||||
@api_list_config ||= Psych.load_file(__dir__ + "/../../api_list_config.yaml")
|
@api_list_config ||= Psych.load_file(__dir__ + "/../../api_list_config.yaml")
|
||||||
end
|
end
|
||||||
|
@ -181,11 +199,14 @@ module Google
|
||||||
require 'active_support/inflector'
|
require 'active_support/inflector'
|
||||||
require 'active_support/core_ext'
|
require 'active_support/core_ext'
|
||||||
rescue LoadError => e
|
rescue LoadError => e
|
||||||
error 'ActiveSupport is required, please run:'
|
error_and_exit('ActiveSupport is required. Please run:', 'gem install activesupport')
|
||||||
error 'gem install activesupport'
|
|
||||||
exit 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def error_and_exit(*messages)
|
||||||
|
messages.each { |msg| error(msg) }
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ require 'google/apis/discovery_v1'
|
||||||
require 'google/apis/generator/annotator'
|
require 'google/apis/generator/annotator'
|
||||||
require 'google/apis/generator/model'
|
require 'google/apis/generator/model'
|
||||||
require 'google/apis/generator/template'
|
require 'google/apis/generator/template'
|
||||||
|
require 'google/apis/generator/version'
|
||||||
require 'active_support/inflector'
|
require 'active_support/inflector'
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
|
|
||||||
|
@ -26,14 +27,13 @@ module Google
|
||||||
class Generator
|
class Generator
|
||||||
Discovery = Google::Apis::DiscoveryV1
|
Discovery = Google::Apis::DiscoveryV1
|
||||||
|
|
||||||
|
Result = Struct.new :files, :version_path, :changelog_path, :gem_name, :revision
|
||||||
|
|
||||||
# Load templates
|
# Load templates
|
||||||
def initialize(api_names: nil, api_names_out: nil)
|
def initialize(api_names: nil, api_names_out: nil)
|
||||||
@names = Google::Apis::Generator::Names.new(api_names_out || File.join(Google::Apis::ROOT, 'api_names_out.yaml'),
|
@names = Google::Apis::Generator::Names.new(api_names_out || File.join(Google::Apis::ROOT, "api_names_out.yaml"),
|
||||||
api_names || File.join(Google::Apis::ROOT, 'api_names.yaml'))
|
api_names || File.join(Google::Apis::ROOT, "api_names.yaml"))
|
||||||
@module_template = Template.load('module.rb')
|
@templates = {}
|
||||||
@service_template = Template.load('service.rb')
|
|
||||||
@classes_template = Template.load('classes.rb')
|
|
||||||
@representations_template = Template.load('representations.rb')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generates ruby source for an API
|
# Generates ruby source for an API
|
||||||
|
@ -45,16 +45,41 @@ module Google
|
||||||
def render(json)
|
def render(json)
|
||||||
api = parse_description(json)
|
api = parse_description(json)
|
||||||
Annotator.process(api, @names)
|
Annotator.process(api, @names)
|
||||||
base_path = ActiveSupport::Inflector.underscore(api.qualified_name)
|
|
||||||
context = {
|
context = {
|
||||||
'api' => api
|
"api" => api,
|
||||||
|
"generator_version" => Google::Apis::Generator::VERSION
|
||||||
}
|
}
|
||||||
files = {}
|
|
||||||
files[base_path + '.rb'] = @module_template.render(context)
|
base_path = api.gem_name
|
||||||
files[File.join(base_path, 'service.rb')] = @service_template.render(context)
|
lib_path = File.join(base_path, "lib")
|
||||||
files[File.join(base_path, 'classes.rb')] = @classes_template.render(context)
|
spec_path = File.join(base_path, "spec")
|
||||||
files[File.join(base_path, 'representations.rb')] = @representations_template.render(context)
|
module_path = File.join(lib_path, ActiveSupport::Inflector.underscore(api.qualified_name))
|
||||||
files
|
|
||||||
|
result = Result.new({},
|
||||||
|
File.join(module_path, "gem_version.rb"),
|
||||||
|
File.join(base_path, "CHANGELOG.md"),
|
||||||
|
api.gem_name,
|
||||||
|
api.revision)
|
||||||
|
result.files[File.join(base_path, ".rspec")] = render_template("dot-rspec", context)
|
||||||
|
result.files[File.join(base_path, ".yardopts")] = render_template("dot-yardopts", context)
|
||||||
|
result.files[result.changelog_path] = render_template("initial-changelog.md", context)
|
||||||
|
result.files[File.join(base_path, "Gemfile")] = render_template("gemfile", context)
|
||||||
|
result.files[File.join(base_path, "#{api.gem_name}.gemspec")] = render_template("gemspec", context)
|
||||||
|
result.files[File.join(base_path, "LICENSE.md")] = render_template("license.md", context)
|
||||||
|
result.files[File.join(base_path, "OVERVIEW.md")] = render_template("overview.md", context)
|
||||||
|
result.files[File.join(base_path, "Rakefile")] = render_template("rakefile", context)
|
||||||
|
result.files[File.join(lib_path, "#{api.gem_name}.rb")] = render_template("entry-point.rb", context)
|
||||||
|
result.files[module_path + ".rb"] = render_template("module.rb", context)
|
||||||
|
result.files[File.join(module_path, "classes.rb")] = render_template("classes.rb", context)
|
||||||
|
result.files[File.join(module_path, "representations.rb")] = render_template("representations.rb", context)
|
||||||
|
result.files[File.join(module_path, "service.rb")] = render_template("service.rb", context)
|
||||||
|
result.files[result.version_path] = render_template("initial-gem_version.rb", context)
|
||||||
|
result.files[File.join(spec_path, "generated_spec.rb")] = render_template("generated_spec.rb", context)
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_template(name, context)
|
||||||
|
(@templates[name] ||= Template.load(name)).render(context)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Dump mapping of API names
|
# Dump mapping of API names
|
||||||
|
|
|
@ -142,6 +142,14 @@ module Google
|
||||||
sprintf('Google::Apis::%s', module_name)
|
sprintf('Google::Apis::%s', module_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def base_path
|
||||||
|
ActiveSupport::Inflector.underscore(qualified_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def gem_name
|
||||||
|
base_path.tr("/", "-")
|
||||||
|
end
|
||||||
|
|
||||||
def service_name
|
def service_name
|
||||||
class_name = (canonical_name || name).gsub(/\W/, '')
|
class_name = (canonical_name || name).gsub(/\W/, '')
|
||||||
ActiveSupport::Inflector.camelize(sprintf('%sService', class_name))
|
ActiveSupport::Inflector.camelize(sprintf('%sService', class_name))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright 2015 Google Inc.
|
# Copyright 2020 Google LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
--color
|
||||||
|
--format documentation
|
|
@ -0,0 +1,13 @@
|
||||||
|
--hide-void-return
|
||||||
|
--no-private
|
||||||
|
--verbose
|
||||||
|
--markup-provider=redcarpet
|
||||||
|
--markup=markdown
|
||||||
|
--main OVERVIEW.md
|
||||||
|
|
||||||
|
lib/<%= api.base_path %>/*.rb
|
||||||
|
lib/<%= api.base_path %>.rb
|
||||||
|
-
|
||||||
|
OVERVIEW.md
|
||||||
|
CHANGELOG.md
|
||||||
|
LICENSE.md
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# 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 "<%= api.base_path %>"
|
|
@ -0,0 +1,46 @@
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
gem "google-apis-core", path: "../../google-apis-core"
|
||||||
|
|
||||||
|
group :development do
|
||||||
|
gem 'bundler', '>= 1.7'
|
||||||
|
gem 'rake', '~> 11.2'
|
||||||
|
gem 'rspec', '~> 3.1'
|
||||||
|
gem 'json_spec', '~> 1.1'
|
||||||
|
gem 'webmock', '~> 2.1'
|
||||||
|
gem 'simplecov', '~> 0.12'
|
||||||
|
gem 'coveralls', '~> 0.8'
|
||||||
|
gem 'rubocop', '~> 0.49.0'
|
||||||
|
gem 'launchy', '~> 2.4'
|
||||||
|
gem 'dotenv', '~> 2.0'
|
||||||
|
gem 'fakefs', '~> 0.6', require: "fakefs/safe"
|
||||||
|
gem 'google-id-token', '~> 1.3'
|
||||||
|
gem 'os', '~> 0.9'
|
||||||
|
gem 'rmail', '~> 1.1'
|
||||||
|
gem 'redis', '~> 3.2'
|
||||||
|
gem 'logging', '~> 2.2'
|
||||||
|
gem 'opencensus', '~> 0.4'
|
||||||
|
gem 'httparty'
|
||||||
|
end
|
||||||
|
|
||||||
|
platforms :jruby do
|
||||||
|
group :development do
|
||||||
|
gem 'jruby-openssl'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
platforms :ruby do
|
||||||
|
group :development do
|
||||||
|
gem 'yard', '~> 0.9', '>= 0.9.11'
|
||||||
|
gem 'redcarpet', '~> 3.2'
|
||||||
|
gem 'github-markup', '~> 1.3'
|
||||||
|
gem 'pry-doc', '~> 0.8'
|
||||||
|
gem 'pry-byebug', '~> 3.2'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ENV['RAILS_VERSION']
|
||||||
|
gem 'rails', ENV['RAILS_VERSION']
|
||||||
|
end
|
|
@ -0,0 +1,32 @@
|
||||||
|
require File.expand_path("lib/<%= api.base_path %>/gem_version", __dir__)
|
||||||
|
|
||||||
|
Gem::Specification.new do |gem|
|
||||||
|
gem.name = "<%= api.gem_name %>"
|
||||||
|
gem.version = <%= api.qualified_name %>::GEM_VERSION
|
||||||
|
gem.authors = ["Google LLC"]
|
||||||
|
gem.email = "googleapis-packages@google.com"
|
||||||
|
gem.summary = "Legacy REST client for <%= api.title %> <%= api.version %>"
|
||||||
|
gem.description =
|
||||||
|
"This is the legacy REST client for <%= api.title %> <%= api.version %>." \
|
||||||
|
" Legacy REST clients are simple Ruby libraries that provide access to" \
|
||||||
|
" Google services via their HTTP REST API endpoints. These libraries are" \
|
||||||
|
" generated and updated automatically based on the discovery documents" \
|
||||||
|
" published by the service, and they handle most concerns such as" \
|
||||||
|
" authentication, pagination, retry, timeouts, and logging. You can use" \
|
||||||
|
" this client to access the <%= api.title %>, but note that some" \
|
||||||
|
" services may provide a separate modern client that is easier to use."
|
||||||
|
gem.homepage = "https://github.com/google/google-api-ruby-client"
|
||||||
|
gem.license = "Apache-2.0"
|
||||||
|
gem.metadata = {
|
||||||
|
"bug_tracker_uri" => "https://github.com/googleapis/google-api-ruby-client/issues",
|
||||||
|
"changelog_uri" => "https://github.com/googleapis/google-api-ruby-client/tree/master/generated/<%= api.gem_name %>/CHANGELOG.md",
|
||||||
|
"documentation_uri" => "https://googleapis.dev/ruby/<%= api.gem_name %>/v#{<%= api.qualified_name %>::GEM_VERSION}",
|
||||||
|
"source_code_uri" => "https://github.com/googleapis/google-api-ruby-client/tree/master/generated/<%= api.gem_name %>"
|
||||||
|
}
|
||||||
|
|
||||||
|
gem.files = Dir.glob("lib/**/*.rb") + Dir.glob("*.md") + [".yardopts"]
|
||||||
|
gem.require_paths = ["lib"]
|
||||||
|
|
||||||
|
gem.required_ruby_version = '>= 2.4'
|
||||||
|
gem.add_runtime_dependency "google-apis-core", "~> 0.0"
|
||||||
|
end
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# 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 "rspec"
|
||||||
|
|
||||||
|
RSpec.describe "<%= api.qualified_name %>" do
|
||||||
|
# Minimal test just to ensure no syntax errors in generated code
|
||||||
|
it "should load" do
|
||||||
|
expect do
|
||||||
|
require "<%= api.base_path %>"
|
||||||
|
end.not_to raise_error
|
||||||
|
expect do
|
||||||
|
<%= api.qualified_name %>::<%= api.service_name %>.new
|
||||||
|
end.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Release history for <%= api.gem_name %>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
module Google
|
||||||
|
module Apis
|
||||||
|
module <%= api.module_name %>
|
||||||
|
# Version of the <%= api.gem_name %> gem
|
||||||
|
GEM_VERSION = "0.0.0"
|
||||||
|
|
||||||
|
# Version of the code generator used to generate this client
|
||||||
|
GENERATOR_VERSION = "0.0.0"
|
||||||
|
|
||||||
|
# Revision of the discovery document this client was generated from
|
||||||
|
REVISION = "0"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright 2015 Google Inc.
|
# Copyright 2020 Google LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
require '<%= to_path(api.qualified_name) %>/service.rb'
|
require '<%= to_path(api.qualified_name) %>/service.rb'
|
||||||
require '<%= to_path(api.qualified_name) %>/classes.rb'
|
require '<%= to_path(api.qualified_name) %>/classes.rb'
|
||||||
require '<%= to_path(api.qualified_name) %>/representations.rb'
|
require '<%= to_path(api.qualified_name) %>/representations.rb'
|
||||||
|
require '<%= to_path(api.qualified_name) %>/gem_version.rb'
|
||||||
|
|
||||||
module Google
|
module Google
|
||||||
module Apis
|
module Apis
|
||||||
|
@ -26,8 +27,9 @@ module Google
|
||||||
# @see <%= api.documentation_link %>
|
# @see <%= api.documentation_link %>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
module <%= api.module_name %>
|
module <%= api.module_name %>
|
||||||
|
# Version of the <%= api.title %> this client connects to.
|
||||||
|
# This is NOT the gem version.
|
||||||
VERSION = '<%= api.version %>'
|
VERSION = '<%= api.version %>'
|
||||||
REVISION = '<%= api.revision %>'
|
|
||||||
<% if api.auth && api.auth.oauth2 -%>
|
<% if api.auth && api.auth.oauth2 -%>
|
||||||
<% for scope_string, scope in api.auth.oauth2.scopes -%>
|
<% for scope_string, scope in api.auth.oauth2.scopes -%>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Legacy REST client for version <%= api.version %> of the <%= api.title %>
|
||||||
|
|
||||||
|
This is a simple client library for version <%= api.version %> of the <%= api.title %>. It provides:
|
||||||
|
|
||||||
|
* A client object that connects to the HTTP/JSON REST endpoint for the service.
|
||||||
|
* Ruby objects for data structures related to the service.
|
||||||
|
* Integration with the googleauth gem for authentication using OAuth, API keys, and service accounts.
|
||||||
|
* Control of retry, pagination, and timeouts.
|
||||||
|
|
||||||
|
Note that although this client library is supported and will continue to be updated to track changes to the service, it is considered legacy. A more modern client may be available for many Google services, especially Cloud Platform services. See the section below titled *Which client should I use?* for more information.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
### Before you begin
|
||||||
|
|
||||||
|
There are a few setup steps you need to complete before you can use this library:
|
||||||
|
|
||||||
|
1. If you don't already have a Google account, [sign up](https://www.google.com/accounts).
|
||||||
|
2. If you have never created a Google APIs Console project, read about [Managing Projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects) and create a project in the [Google API Console](https://console.cloud.google.com/).
|
||||||
|
3. Most APIs need to be enabled for your project. [Enable it](https://console.cloud.google.com/apis/library/<%= api.name.downcase %>.googleapis.com) in the console.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Add this line to your application's Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem '<%= api.gem_name %>', '~> 0.1'
|
||||||
|
```
|
||||||
|
|
||||||
|
And then execute:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install it yourself as:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ gem install <%= api.gem_name %>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a client object
|
||||||
|
|
||||||
|
Once the gem is installed, you can load the client code and instantiate a client.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Load the client
|
||||||
|
require "<%= api.base_path %>"
|
||||||
|
|
||||||
|
# Create a client object
|
||||||
|
client = <%= api.qualified_name %>::<%= api.service_name %>.new
|
||||||
|
|
||||||
|
# Authenticate calls
|
||||||
|
client.authentication = # ... use the googleauth gem to create credentials
|
||||||
|
```
|
||||||
|
|
||||||
|
See the class reference docs for information on the methods you can call from a client.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
More detailed descriptions of the Google legacy REST clients are available in two documents.
|
||||||
|
|
||||||
|
* The [Usage Guide](https://github.com/googleapis/google-api-ruby-client/blob/master/docs/usage-guide.md) discusses how to make API calls, how to use the provided data structures, and how to work the various features of the client library, including media upload and download, error handling, retries, pagination, and logging.
|
||||||
|
* The [Auth Guide](https://github.com/googleapis/google-api-ruby-client/blob/master/docs/auth-guide.md) discusses authentication in the client libraries, including API keys, OAuth 2.0, service accounts, and environment variables.
|
||||||
|
|
||||||
|
(Note: the above documents are written for the legacy clients in general, and their examples may not reflect the <%= api.name %> service in particular.)
|
||||||
|
|
||||||
|
For reference information on specific calls in the <%= api.title %>, see the {<%= api.qualified_name %>::<%= api.service_name %> class reference docs}.
|
||||||
|
|
||||||
|
## Which client should I use?
|
||||||
|
|
||||||
|
Google provides two types of Ruby API client libraries: **legacy REST clients** and **modern clients**.
|
||||||
|
|
||||||
|
This library, `<%= api.gem_name %>`, is a legacy REST client. You can identify legacy clients by their gem names, which are always in the form `google-apis-<servicename>_<serviceversion>`. The legacy REST clients connect to HTTP/JSON REST endpoints and are automatically generated from service discovery documents. They support most API functionality, but their class interfaces are sometimes awkward.
|
||||||
|
|
||||||
|
Modern clients are produced by a modern code generator, combined with hand-crafted functionality for some services. Most modern clients connect to high-performance gRPC endpoints, although a few are backed by REST services. Modern clients are available for many Google services, especially Cloud Platform services, but do not yet support all the services covered by the legacy clients.
|
||||||
|
|
||||||
|
Gem names for modern clients are often of the form `google-cloud-<service_name>`. (For example, [google-cloud-pubsub](https://rubygems.org/gems/google-cloud-pubsub).) Note that most modern clients also have corresponding "versioned" gems with names like `google-cloud-<service_name>-<version>`. (For example, [google-cloud-pubsub-v1](https://rubygems.org/gems/google-cloud-pubsub-v1).) The "versioned" gems can be used directly, but often provide lower-level interfaces. In most cases, the main gem is recommended.
|
||||||
|
|
||||||
|
**For most users, we recommend the modern client, if one is available.** Compared with legacy clients, modern clients are generally much easier to use and more Ruby-like, support more advanced features such as streaming and long-running operations, and often provide much better performance. You may consider using a legacy client instead, if a modern client is not yet available for the service you want to use, or if you are not able to use gRPC on your infrastructure.
|
||||||
|
|
||||||
|
The [product documentation](<%= api.documentation_link %>) may provide guidance regarding the preferred client library to use.
|
||||||
|
|
||||||
|
## Supported Ruby versions
|
||||||
|
|
||||||
|
This library is supported on Ruby 2.5+.
|
||||||
|
|
||||||
|
Google provides official support for Ruby versions that are actively supported by Ruby Core -- that is, Ruby versions that are either in normal maintenance or in security maintenance, and not end of life. Currently, this means Ruby 2.5 and later. Older versions of Ruby _may_ still work, but are unsupported and not recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details about the Ruby support schedule.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This library is licensed under Apache 2.0. Full license text is available in the {file:LICENSE.md LICENSE}.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Please [report bugs at the project on Github](https://github.com/google/google-api-ruby-client/issues). Don't hesitate to [ask questions](http://stackoverflow.com/questions/tagged/google-api-ruby-client) about the client or APIs on [StackOverflow](http://stackoverflow.com).
|
|
@ -0,0 +1,25 @@
|
||||||
|
require "bundler/gem_tasks"
|
||||||
|
|
||||||
|
require 'rake/clean'
|
||||||
|
|
||||||
|
CLOBBER.include('coverage', 'doc')
|
||||||
|
CLEAN.include('.yardoc')
|
||||||
|
|
||||||
|
require 'rspec/core/rake_task'
|
||||||
|
RSpec::Core::RakeTask.new(:spec)
|
||||||
|
|
||||||
|
begin
|
||||||
|
require 'yard'
|
||||||
|
require 'yard/rake/yardoc_task'
|
||||||
|
|
||||||
|
YARD::Rake::YardocTask.new do |t|
|
||||||
|
t.files = ['lib/**/*.rb', 'generated/**/*.rb']
|
||||||
|
t.options = ['--verbose', '--markup', 'markdown']
|
||||||
|
end
|
||||||
|
rescue LoadError
|
||||||
|
puts "YARD not available"
|
||||||
|
end
|
||||||
|
|
||||||
|
task :ci => [:spec, :yard, :build]
|
||||||
|
|
||||||
|
task :default => :ci
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright 2015 Google Inc.
|
# Copyright 2020 Google LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright 2015 Google Inc.
|
# Copyright 2020 Google LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# 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 "gems"
|
||||||
|
|
||||||
|
module Google
|
||||||
|
module Apis
|
||||||
|
class Generator
|
||||||
|
# Matches generated files against current files and decides what to update
|
||||||
|
# @private
|
||||||
|
class Updater
|
||||||
|
def initialize
|
||||||
|
@gems_client = nil
|
||||||
|
@current_rubygems_versions = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def analyze(base_dir, generator_result)
|
||||||
|
modified_files = {}
|
||||||
|
version_content = changelog_content = nil
|
||||||
|
generator_result.files.each do |path, content|
|
||||||
|
full_path = File.join(base_dir, path)
|
||||||
|
old_content = File.binread(full_path) if File.readable?(full_path)
|
||||||
|
if path == generator_result.version_path
|
||||||
|
version_content = old_content || content
|
||||||
|
elsif path == generator_result.changelog_path
|
||||||
|
changelog_content = old_content || content
|
||||||
|
else
|
||||||
|
modified_files[path] = content unless content == old_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
unless modified_files.empty?
|
||||||
|
desired_gem_version = next_rubygems_version(generator_result.gem_name)
|
||||||
|
version_content, generator_version_change, revision_change =
|
||||||
|
update_version_content(version_content, desired_gem_version, generator_result.revision)
|
||||||
|
changelog_content = update_changelog_content(changelog_content, desired_gem_version, generator_version_change, revision_change)
|
||||||
|
modified_files[generator_result.version_path] = version_content
|
||||||
|
modified_files[generator_result.changelog_path] = changelog_content
|
||||||
|
end
|
||||||
|
modified_files
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def gems_client
|
||||||
|
@gems_client ||= Gems::Client.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def current_rubygems_version(gem_name)
|
||||||
|
@current_rubygems_versions[gem_name] ||= begin
|
||||||
|
gems_client.info(gem_name)["version"]
|
||||||
|
rescue Gems::NotFound
|
||||||
|
"0.0.0"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def next_rubygems_version(gem_name)
|
||||||
|
major, minor = current_rubygems_version(gem_name).split(".")
|
||||||
|
"#{major.to_i}.#{minor.to_i + 1}.0"
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def update_version_content(content, desired_gem_version, new_revision)
|
||||||
|
generator_version_change = revision_change = nil
|
||||||
|
modified_content = content.dup
|
||||||
|
modified_content.sub!(/GEM_VERSION = "([\w\.]*)"/) do |*|
|
||||||
|
"GEM_VERSION = \"#{desired_gem_version}\""
|
||||||
|
end or raise "gem_version.rb is missing GEM_VERSION"
|
||||||
|
modified_content.sub!(/GENERATOR_VERSION = "([\w\.]*)"/) do |*|
|
||||||
|
generator_version_change = Generator::VERSION unless Regexp.last_match[1] == Generator::VERSION
|
||||||
|
"GENERATOR_VERSION = \"#{Generator::VERSION}\""
|
||||||
|
end or raise "gem_version.rb is missing GENERATOR_VERSION"
|
||||||
|
modified_content.sub!(/REVISION = "([\w\.]*)"/) do |*|
|
||||||
|
revision_change = new_revision unless Regexp.last_match[1] == new_revision
|
||||||
|
"REVISION = \"#{new_revision}\""
|
||||||
|
end or raise "gem_version.rb is missing REVISION"
|
||||||
|
[modified_content, generator_version_change, revision_change]
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def update_changelog_content(content, desired_gem_version, generator_version_change, revision_change)
|
||||||
|
lines = parse_existing_changelog_entry(content, desired_gem_version)
|
||||||
|
modify_changelog_lines(lines, generator_version_change, revision_change)
|
||||||
|
entry = assemble_changelog_entry(lines, desired_gem_version)
|
||||||
|
replace_changelog_entry(content, desired_gem_version, entry)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def parse_existing_changelog_entry(content, desired_gem_version)
|
||||||
|
quoted_gem_version = Regexp.quote(desired_gem_version)
|
||||||
|
match = /\n+### v#{quoted_gem_version} \([\d-]+\)\n+((?:[^#][^\n]*\n+)*)(?=#|$)/.match content
|
||||||
|
return [] unless match
|
||||||
|
match[1].split("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def modify_changelog_lines(lines, generator_version_change, revision_change)
|
||||||
|
if generator_version_change
|
||||||
|
lines.reject! { |line| line =~ /^\* Regenerated using generator version \d[\w\.]+/ }
|
||||||
|
lines.unshift("* Regenerated using generator version #{generator_version_change}")
|
||||||
|
end
|
||||||
|
if revision_change
|
||||||
|
lines.reject! { |line| line =~ /^\* Regenerated from discovery document revision \d+/ }
|
||||||
|
lines.unshift("* Regenerated from discovery document revision #{revision_change}")
|
||||||
|
end
|
||||||
|
lines << "* Unspecified changes" if lines.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def assemble_changelog_entry(lines, desired_gem_version)
|
||||||
|
entry_lines = lines.join("\n")
|
||||||
|
date = Time.now.strftime("%Y-%m-%d")
|
||||||
|
"\n\n### v#{desired_gem_version} (#{date})\n\n#{entry_lines}\n\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
# @private
|
||||||
|
def replace_changelog_entry(content, desired_gem_version, entry)
|
||||||
|
quoted_gem_version = Regexp.quote(desired_gem_version)
|
||||||
|
modified_content = content.dup
|
||||||
|
modified_content.sub!(/\n+### v#{quoted_gem_version} \([\d-]+\)\n+(?:[^#][^\n]*\n+)*(?=#|$)/, entry) or
|
||||||
|
modified_content.sub!(/^(\n*# [^\n]+)\n+(?=#|$)/, "\\1#{entry}") or
|
||||||
|
raise "CHANGELOG doesn't seem to have the expected header"
|
||||||
|
modified_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
module Google
|
||||||
|
module Apis
|
||||||
|
class Generator
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -26,7 +26,7 @@ RSpec.describe Google::Apis::Generator do
|
||||||
before(:context) do
|
before(:context) do
|
||||||
generator = Google::Apis::Generator.new(api_names: File.join(FIXTURES_DIR, 'files', 'api_names.yaml'))
|
generator = Google::Apis::Generator.new(api_names: File.join(FIXTURES_DIR, 'files', 'api_names.yaml'))
|
||||||
discovery = File.read(File.join(FIXTURES_DIR, 'files', 'test_api.json'))
|
discovery = File.read(File.join(FIXTURES_DIR, 'files', 'test_api.json'))
|
||||||
generated_files = generator.render(discovery)
|
generated_files = generator.render(discovery).files
|
||||||
# puts generator.dump_api_names
|
# puts generator.dump_api_names
|
||||||
tempdir = Dir.mktmpdir
|
tempdir = Dir.mktmpdir
|
||||||
generated_files.each do |key, content|
|
generated_files.each do |key, content|
|
||||||
|
@ -37,7 +37,7 @@ RSpec.describe Google::Apis::Generator do
|
||||||
f.write(content)
|
f.write(content)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
$LOAD_PATH.unshift(tempdir)
|
$LOAD_PATH.unshift(File.join(tempdir, "google-apis-test_v1", "lib"))
|
||||||
require 'google/apis/test_v1'
|
require 'google/apis/test_v1'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -331,10 +331,10 @@ EOF
|
||||||
before do
|
before do
|
||||||
generated_files = Google::Apis::Generator.new.render(
|
generated_files = Google::Apis::Generator.new.render(
|
||||||
'{ "name": "minimal_api", "id": "minimal_api", "version": "v1" }'
|
'{ "name": "minimal_api", "id": "minimal_api", "version": "v1" }'
|
||||||
)
|
).files
|
||||||
|
|
||||||
namespace.send(:binding).eval(
|
namespace.send(:binding).eval(
|
||||||
generated_files.fetch('google/apis/minimal_api_v1/service.rb')
|
generated_files.fetch('google-apis-minimal_api_v1/lib/google/apis/minimal_api_v1/service.rb')
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
DIR=$(dirname $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ))
|
DIR=$(dirname $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ))
|
||||||
|
|
||||||
cd google-api-client
|
cd google-api-client
|
||||||
echo 'a' | bundle exec bin/generate-api gen generated --from-discovery --no-preferred-only --names=$DIR/api_names.yaml --names-out=$DIR/api_names_out.yaml
|
echo 'a' | bundle exec bin/generate-api gen $DIR/generated --from-discovery --no-preferred-only --names=$DIR/api_names.yaml --names-out=$DIR/api_names_out.yaml
|
||||||
|
|
|
@ -14,12 +14,12 @@ Dir.chdir "google-api-client"
|
||||||
execute "bundle install"
|
execute "bundle install"
|
||||||
|
|
||||||
if ARGV.empty?
|
if ARGV.empty?
|
||||||
execute "echo a | bundle exec bin/generate-api gen generated --from-discovery --no-preferred-only --names=#{DIR}/api_names.yaml --names-out=#{DIR}/api_names_out.yaml"
|
execute "echo a | bundle exec bin/generate-api gen #{DIR}/generated --from-discovery --no-preferred-only --names=#{DIR}/api_names.yaml --names-out=#{DIR}/api_names_out.yaml --spot-check"
|
||||||
elsif ARGV == ["--clean"] || ARGV == ["clean"]
|
elsif ARGV == ["--clean"] || ARGV == ["clean"]
|
||||||
execute "bundle exec bin/generate-api gen generated --clean"
|
execute "bundle exec bin/generate-api gen #{DIR}/generated --clean"
|
||||||
elsif ARGV.size == 2
|
elsif ARGV.size == 2
|
||||||
api, version = ARGV
|
api, version = ARGV
|
||||||
execute "echo a | bundle exec bin/generate-api gen generated --api=#{api}.#{version} --names=#{DIR}/api_names.yaml --names-out=#{DIR}/api_names_out.yaml"
|
execute "echo a | bundle exec bin/generate-api gen #{DIR}/generated --api=#{api}.#{version} --names=#{DIR}/api_names.yaml --names-out=#{DIR}/api_names_out.yaml --spot-check"
|
||||||
else
|
else
|
||||||
abort "Bad arguments: #{ARGV}"
|
abort "Bad arguments: #{ARGV}"
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue