From 74dd99c03f1f879c9c08d5c0b833fa37992ffc2d Mon Sep 17 00:00:00 2001 From: Google APIs Date: Fri, 20 Apr 2018 11:11:59 -0700 Subject: [PATCH] Synchronize new proto/yaml changes. PiperOrigin-RevId: 193694774 --- .../v1alpha1/bill_of_materials.proto | 4 +- .../v1alpha1/containeranalysis.proto | 375 +++++++++++++++++- .../v1alpha1/image_basis.proto | 23 +- .../v1alpha1/package_vulnerability.proto | 6 +- .../v1alpha1/provenance.proto | 2 +- .../v1alpha1/source_context.proto | 2 +- 6 files changed, 377 insertions(+), 35 deletions(-) diff --git a/google/devtools/containeranalysis/v1alpha1/bill_of_materials.proto b/google/devtools/containeranalysis/v1alpha1/bill_of_materials.proto index dc0e422d..cd4ca50c 100644 --- a/google/devtools/containeranalysis/v1alpha1/bill_of_materials.proto +++ b/google/devtools/containeranalysis/v1alpha1/bill_of_materials.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -100,4 +100,6 @@ message PackageManager { // X64 architecture X64 = 2; } + + } diff --git a/google/devtools/containeranalysis/v1alpha1/containeranalysis.proto b/google/devtools/containeranalysis/v1alpha1/containeranalysis.proto index dd7a6c78..def49b4d 100644 --- a/google/devtools/containeranalysis/v1alpha1/containeranalysis.proto +++ b/google/devtools/containeranalysis/v1alpha1/containeranalysis.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import "google/protobuf/any.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; import "google/protobuf/timestamp.proto"; +import "google/rpc/status.proto"; option go_package = "google.golang.org/genproto/googleapis/devtools/containeranalysis/v1alpha1;containeranalysis"; option java_multiple_files = true; @@ -50,71 +51,101 @@ option objc_class_prefix = "GCA"; service ContainerAnalysis { // Returns the requested `Occurrence`. rpc GetOccurrence(GetOccurrenceRequest) returns (Occurrence) { - option (google.api.http) = { get: "/v1alpha1/{name=projects/*/occurrences/*}" }; + option (google.api.http) = { + get: "/v1alpha1/{name=projects/*/occurrences/*}" + }; } // Lists active `Occurrences` for a given project matching the filters. rpc ListOccurrences(ListOccurrencesRequest) returns (ListOccurrencesResponse) { - option (google.api.http) = { get: "/v1alpha1/{parent=projects/*}/occurrences" }; + option (google.api.http) = { + get: "/v1alpha1/{parent=projects/*}/occurrences" + }; } // Deletes the given `Occurrence` from the system. Use this when // an `Occurrence` is no longer applicable for the given resource. rpc DeleteOccurrence(DeleteOccurrenceRequest) returns (google.protobuf.Empty) { - option (google.api.http) = { delete: "/v1alpha1/{name=projects/*/occurrences/*}" }; + option (google.api.http) = { + delete: "/v1alpha1/{name=projects/*/occurrences/*}" + }; } // Creates a new `Occurrence`. Use this method to create `Occurrences` // for a resource. rpc CreateOccurrence(CreateOccurrenceRequest) returns (Occurrence) { - option (google.api.http) = { post: "/v1alpha1/{parent=projects/*}/occurrences" body: "occurrence" }; + option (google.api.http) = { + post: "/v1alpha1/{parent=projects/*}/occurrences" + body: "occurrence" + }; } // Updates an existing occurrence. rpc UpdateOccurrence(UpdateOccurrenceRequest) returns (Occurrence) { - option (google.api.http) = { patch: "/v1alpha1/{name=projects/*/occurrences/*}" body: "occurrence" }; + option (google.api.http) = { + patch: "/v1alpha1/{name=projects/*/occurrences/*}" + body: "occurrence" + }; } // Gets the `Note` attached to the given `Occurrence`. rpc GetOccurrenceNote(GetOccurrenceNoteRequest) returns (Note) { - option (google.api.http) = { get: "/v1alpha1/{name=projects/*/occurrences/*}/notes" }; + option (google.api.http) = { + get: "/v1alpha1/{name=projects/*/occurrences/*}/notes" + }; } // Returns the requested `Note`. rpc GetNote(GetNoteRequest) returns (Note) { - option (google.api.http) = { get: "/v1alpha1/{name=projects/*/notes/*}" }; + option (google.api.http) = { + get: "/v1alpha1/{name=projects/*/notes/*}" + }; } // Lists all `Notes` for a given project. rpc ListNotes(ListNotesRequest) returns (ListNotesResponse) { - option (google.api.http) = { get: "/v1alpha1/{parent=projects/*}/notes" }; + option (google.api.http) = { + get: "/v1alpha1/{parent=projects/*}/notes" + }; } // Deletes the given `Note` from the system. rpc DeleteNote(DeleteNoteRequest) returns (google.protobuf.Empty) { - option (google.api.http) = { delete: "/v1alpha1/{name=projects/*/notes/*}" }; + option (google.api.http) = { + delete: "/v1alpha1/{name=projects/*/notes/*}" + }; } // Creates a new `Note`. rpc CreateNote(CreateNoteRequest) returns (Note) { - option (google.api.http) = { post: "/v1alpha1/{parent=projects/*}/notes" body: "note" }; + option (google.api.http) = { + post: "/v1alpha1/{parent=projects/*}/notes" + body: "note" + }; } // Updates an existing `Note`. rpc UpdateNote(UpdateNoteRequest) returns (Note) { - option (google.api.http) = { patch: "/v1alpha1/{name=projects/*/notes/*}" body: "note" }; + option (google.api.http) = { + patch: "/v1alpha1/{name=projects/*/notes/*}" + body: "note" + }; } // Lists `Occurrences` referencing the specified `Note`. Use this method to // get all occurrences referencing your `Note` across all your customer // projects. rpc ListNoteOccurrences(ListNoteOccurrencesRequest) returns (ListNoteOccurrencesResponse) { - option (google.api.http) = { get: "/v1alpha1/{name=projects/*/notes/*}/occurrences" }; + option (google.api.http) = { + get: "/v1alpha1/{name=projects/*/notes/*}/occurrences" + }; } // Gets a summary of the number and severity of occurrences. rpc GetVulnzOccurrencesSummary(GetVulnzOccurrencesSummaryRequest) returns (GetVulnzOccurrencesSummaryResponse) { - option (google.api.http) = { get: "/v1alpha1/{parent=projects/*}/vulnzsummary" }; + option (google.api.http) = { + get: "/v1alpha1/{parent=projects/*}/occurrences:vulnerabilitySummary" + }; } // Sets the access control policy on the specified `Note` or `Occurrence`. @@ -130,7 +161,14 @@ service ContainerAnalysis { // formats: `projects/{projectid}/occurrences/{occurrenceid}` for occurrences // and projects/{projectid}/notes/{noteid} for notes rpc SetIamPolicy(google.iam.v1.SetIamPolicyRequest) returns (google.iam.v1.Policy) { - option (google.api.http) = { post: "/v1alpha1/{resource=projects/*/notes/*}:setIamPolicy" body: "*" }; + option (google.api.http) = { + post: "/v1alpha1/{resource=projects/*/notes/*}:setIamPolicy" + body: "*" + additional_bindings { + post: "/v1alpha1/{resource=projects/*/occurrences/*}:setIamPolicy" + body: "*" + } + }; } // Gets the access control policy for a note or an `Occurrence` resource. @@ -145,7 +183,14 @@ service ContainerAnalysis { // `projects/{PROJECT_ID}/occurrences/{OCCURRENCE_ID}` for occurrences and // projects/{PROJECT_ID}/notes/{NOTE_ID} for notes rpc GetIamPolicy(google.iam.v1.GetIamPolicyRequest) returns (google.iam.v1.Policy) { - option (google.api.http) = { post: "/v1alpha1/{resource=projects/*/notes/*}:getIamPolicy" body: "*" }; + option (google.api.http) = { + post: "/v1alpha1/{resource=projects/*/notes/*}:getIamPolicy" + body: "*" + additional_bindings { + post: "/v1alpha1/{resource=projects/*/occurrences/*}:getIamPolicy" + body: "*" + } + }; } // Returns the permissions that a caller has on the specified note or @@ -157,7 +202,54 @@ service ContainerAnalysis { // following formats: `projects/{PROJECT_ID}/occurrences/{OCCURRENCE_ID}` for // `Occurrences` and `projects/{PROJECT_ID}/notes/{NOTE_ID}` for `Notes` rpc TestIamPermissions(google.iam.v1.TestIamPermissionsRequest) returns (google.iam.v1.TestIamPermissionsResponse) { - option (google.api.http) = { post: "/v1alpha1/{resource=projects/*/notes/*}:testIamPermissions" body: "*" }; + option (google.api.http) = { + post: "/v1alpha1/{resource=projects/*/notes/*}:testIamPermissions" + body: "*" + additional_bindings { + post: "/v1alpha1/{resource=projects/*/occurrences/*}:testIamPermissions" + body: "*" + } + }; + } + + // Creates a new `Operation`. + rpc CreateOperation(CreateOperationRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + post: "/v1alpha1/{parent=projects/*}/operations" + body: "*" + }; + } + + // Updates an existing operation returns an error if operation + // does not exist. The only valid operations are to update mark the done bit + // change the result. + rpc UpdateOperation(UpdateOperationRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + patch: "/v1alpha1/{name=projects/*/operations/*}" + body: "*" + }; + } + + // Gets a specific scan configuration for a project. + rpc GetScanConfig(GetScanConfigRequest) returns (ScanConfig) { + option (google.api.http) = { + get: "/v1alpha1/{name=projects/*/scan_configs/*}" + }; + } + + // Lists scan configurations for a project. + rpc ListScanConfigs(ListScanConfigsRequest) returns (ListScanConfigsResponse) { + option (google.api.http) = { + get: "/v1alpha1/{parent=projects/*}/scan_configs" + }; + } + + // Updates the scan configuration to a new value. + rpc UpdateScanConfig(UpdateScanConfigRequest) returns (ScanConfig) { + option (google.api.http) = { + patch: "/v1alpha1/{name=projects/*/scan_configs/*}" + body: "scan_config" + }; } } @@ -172,6 +264,9 @@ message Occurrence { // can be used as a filter in list requests. string resource_url = 2; + // The resource for which the `Occurrence` applies. + Resource resource = 17; + // An analysis note associated with this image, in the form // "providers/{provider_id}/notes/{NOTE_ID}" // This field can be used as a filter in list requests. @@ -201,6 +296,9 @@ message Occurrence { // Describes the initial scan status for this resource. Discovery.Discovered discovered = 15; + + // Describes an attestation of an artifact. + AttestationAuthority.Attestation attestation = 16; } // A description of actions that can be taken to remedy the `Note` @@ -213,6 +311,19 @@ message Occurrence { google.protobuf.Timestamp update_time = 10; } +// Resource is an entity that can have metadata. E.g., a Docker image. +message Resource { + // The name of the resource. E.g., the name of a Docker image - "Debian". + string name = 1; + + // The unique URI of the resource. E.g., + // "https://gcr.io/project/image@sha256:foo" for a Docker image. + string uri = 2; + + // The hash of the resource content. E.g., the Docker digest. + Hash content_hash = 3; +} + // Provides a detailed description of a `Note`. message Note { // Metadata for any related URL information @@ -247,6 +358,9 @@ message Note { // The note and occurrence track the initial discovery status of a resource. DISCOVERY = 7; + + // This represents a logical "role" that can attest to artifacts. + ATTESTATION_AUTHORITY = 8; } // The name of the note in the form @@ -282,6 +396,9 @@ message Note { // A note describing a provider/analysis type. Discovery discovery = 18; + + // A note describing an attestation role. + AttestationAuthority attestation_authority = 19; } // URLs associated with this note @@ -353,8 +470,38 @@ message Deployable { message Discovery { // Provides information about the scan status of a discovered resource. message Discovered { + // Analysis status for a resource. + enum AnalysisStatus { + // Unknown + ANALYSIS_STATUS_UNSPECIFIED = 0; + + // Resource is known but no action has been taken yet. + PENDING = 1; + + // Resource is being analyzed. + SCANNING = 2; + + // Analysis has finished successfully. + FINISHED_SUCCESS = 3; + + // Analysis has finished unsuccessfully, the analysis itself is in a bad + // state. + FINISHED_FAILED = 4; + + // Analysis will not happen, the resource is not supported. + UNSUPPORTED_RESOURCE = 5; + } + // Output only. An operation that indicates the status of the current scan. google.longrunning.Operation operation = 1; + + // The status of discovery for the resource. + AnalysisStatus analysis_status = 5; + + // When an error is encountered this will contain a LocalizedMessage under + // details to show to the user. The LocalizedMessage output only and + // populated by the API. + google.rpc.Status analysis_status_error = 6; } // The kind of analysis that is handled by this discovery. @@ -415,6 +562,109 @@ message BuildSignature { KeyType key_type = 4; } +// An attestation wrapper with a PGP-compatible signature. +// This message only supports `ATTACHED` signatures, where the payload that is +// signed is included alongside the signature itself in the same file. +message PgpSignedAttestation { + // Type (for example schema) of the attestation payload that was signed. + enum ContentType { + // `ContentType` is not set. + CONTENT_TYPE_UNSPECIFIED = 0; + + // Atomic format attestation signature. See + // https://github.com/containers/image/blob/8a5d2f82a6e3263290c8e0276c3e0f64e77723e7/docs/atomic-signature.md + // The payload extracted from `signature` is a JSON blob conforming to the + // linked schema. + SIMPLE_SIGNING_JSON = 1; + } + + // The raw content of the signature, as output by GNU Privacy Guard (GPG) or + // equivalent. Since this message only supports attached signatures, the + // payload that was signed must be attached. While the signature format + // supported is dependent on the verification implementation, currently only + // ASCII-armored (`--armor` to gpg), non-clearsigned (`--sign` rather than + // `--clearsign` to gpg) are supported. Concretely, `gpg --sign --armor + // --output=signature.gpg payload.json` will create the signature content + // expected in this field in `signature.gpg` for the `payload.json` + // attestation payload. + string signature = 1; + + // Type (for example schema) of the attestation payload that was signed. + // The verifier must ensure that the provided type is one that the verifier + // supports, and that the attestation payload is a valid instantiation of that + // type (for example by validating a JSON schema). + ContentType content_type = 3; + + // This field is used by verifiers to select the public key used to validate + // the signature. Note that the policy of the verifier ultimately determines + // which public keys verify a signature based on the context of the + // verification. There is no guarantee validation will succeed if the + // verifier has no key matching this ID, even if it has a key under a + // different ID that would verify the signature. Note that this ID should also + // be present in the signature content above, but that is not expected to be + // used by the verifier. + oneof key_id { + // The cryptographic fingerprint of the key used to generate the signature, + // as output by, e.g. `gpg --list-keys`. This should be the version 4, full + // 160-bit fingerprint, expressed as a 40 character hexadecimal string. See + // https://tools.ietf.org/html/rfc4880#section-12.2 for details. + // Implementations may choose to acknowledge "LONG", "SHORT", or other + // abbreviated key IDs, but only the full fingerprint is guaranteed to work. + // In gpg, the full fingerprint can be retrieved from the `fpr` field + // returned when calling --list-keys with --with-colons. For example: + // ``` + // gpg --with-colons --with-fingerprint --force-v4-certs \ + // --list-keys attester@example.com + // tru::1:1513631572:0:3:1:5 + // pub:...... + // fpr:::::::::24FF6481B76AC91E66A00AC657A93A81EF3AE6FB: + // ``` + // Above, the fingerprint is `24FF6481B76AC91E66A00AC657A93A81EF3AE6FB`. + string pgp_key_id = 2; + } +} + +// Note kind that represents a logical attestation "role" or "authority". For +// example, an organization might have one `AttestationAuthority` for "QA" and +// one for "build". This Note is intended to act strictly as a grouping +// mechanism for the attached Occurrences (Attestations). This grouping +// mechanism also provides a security boundary, since IAM ACLs gate the ability +// for a principle to attach an Occurrence to a given Note. It also provides a +// single point of lookup to find all attached Attestation Occurrences, even if +// they don't all live in the same project. +message AttestationAuthority { + // This submessage provides human-readable hints about the purpose of the + // AttestationAuthority. Because the name of a Note acts as its resource + // reference, it is important to disambiguate the canonical name of the Note + // (which might be a UUID for security purposes) from "readable" names more + // suitable for debug output. Note that these hints should NOT be used to + // look up AttestationAuthorities in security sensitive contexts, such as when + // looking up Attestations to verify. + message AttestationAuthorityHint { + // The human readable name of this Attestation Authority, for example "qa". + string human_readable_name = 1; + } + + // Occurrence that represents a single "attestation". The authenticity of an + // Attestation can be verified using the attached signature. If the verifier + // trusts the public key of the signer, then verifying the signature is + // sufficient to establish trust. In this circumstance, the + // AttestationAuthority to which this Attestation is attached is primarily + // useful for look-up (how to find this Attestation if you already know the + // Authority and artifact to be verified) and intent (which authority was this + // attestation intended to sign for). + message Attestation { + // The signature, generally over the `resource_url`, that verifies this + // attestation. The semantics of the signature veracity are ultimately + // determined by the verification engine. + oneof signature { + PgpSignedAttestation pgp_signed_attestation = 1; + } + } + + AttestationAuthorityHint hint = 1; +} + // Message encapsulating build provenance details. message BuildDetails { // The actual provenance @@ -434,6 +684,19 @@ message BuildDetails { string provenance_bytes = 2; } +// Indicates various scans and whether they are turned on or off. +message ScanConfig { + // Output only. The name of the ScanConfig in the form + // “projects/{project_id}/ScanConfigs/{ScanConfig_id}". + string name = 1; + + // Output only. A human-readable description of what the `ScanConfig` does. + string description = 2; + + // Indicates whether the Scan is enabled. + bool enabled = 3; +} + // Request to get a Occurrence. message GetOccurrenceRequest { // The name of the occurrence of the form @@ -459,6 +722,9 @@ message ListOccurrencesRequest { // Token to provide to skip to a particular spot in the list. string page_token = 4; + + // The kind of occurrences to filter on. + Note.Kind kind = 6; } // Response including listed active occurrences. @@ -526,8 +792,7 @@ message ListNotesRequest { // @Deprecated string name = 1; - // This field contains the project Id for example: - // "project/{project_id} + // This field contains the project Id for example: "projects/{PROJECT_ID}". string parent = 5; // The filter expression. @@ -565,7 +830,7 @@ message CreateNoteRequest { string name = 1; // This field contains the project Id for example: - // "project/{project_id} + // "projects/{project_id} string parent = 4; // The ID to use for this note. @@ -613,6 +878,30 @@ message ListNoteOccurrencesResponse { string next_page_token = 2; } +// Request for creating an operation +message CreateOperationRequest { + // The project Id that this operation should be created under. + string parent = 1; + + // The ID to use for this operation. + string operation_id = 2; + + // The operation to create. + google.longrunning.Operation operation = 3; +} + +// Request for updating an existing operation +message UpdateOperationRequest { + // The name of the Operation. + // Should be of the form "projects/{provider_id}/operations/{operation_id}". + string name = 1; + + // The operation to create. + google.longrunning.Operation operation = 3; + + google.protobuf.FieldMask update_mask = 4; +} + // Metadata for all operations used and required for all operations // that created by Container Analysis Providers message OperationMetadata { @@ -648,3 +937,49 @@ message GetVulnzOccurrencesSummaryResponse { // A map of how many occurrences were found for each severity. repeated SeverityCount counts = 1; } + +// Request to get a ScanConfig. +message GetScanConfigRequest { + // The name of the ScanConfig in the form + // projects/{project_id}/scan_configs/{ScanConfig_id} + // instead. + string name = 1; +} + +// Request to list the available scan configurations. +message ListScanConfigsRequest { + // This containers the project Id i.e.: projects/{project_id} + // instead. + string parent = 1; + + // The filter expression. + string filter = 2; + + // The number of items to return. + int32 page_size = 3; + + // The page token to use for the next request. + string page_token = 4; +} + +// A list of ScanConfigs for the project. +message ListScanConfigsResponse { + // The set of scan configs + repeated ScanConfig scan_configs = 1; + + // A page token to pass in order to get more scans. + string next_page_token = 2; +} + +// A request to update a ScanConfig. +message UpdateScanConfigRequest { + // The scan config to update of the form + // projects/{project_id}/scan_configs/{ScanConfig_id} + // instead. + string name = 1; + + // The new scan configuration + ScanConfig scan_config = 2; + + google.protobuf.FieldMask update_mask = 3; +} diff --git a/google/devtools/containeranalysis/v1alpha1/image_basis.proto b/google/devtools/containeranalysis/v1alpha1/image_basis.proto index d3a50c4c..e8cfe1f8 100644 --- a/google/devtools/containeranalysis/v1alpha1/image_basis.proto +++ b/google/devtools/containeranalysis/v1alpha1/image_basis.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -121,7 +121,7 @@ message DockerImage { // associated occurrence images. string resource_url = 1; - // The fingerprint of the base image + // The fingerprint of the base image. Fingerprint fingerprint = 2; } @@ -129,21 +129,22 @@ message DockerImage { // DockerImage relationship. This image would be produced from a Dockerfile // with FROM . message Derived { - // The fingerprint of the derived image + // The fingerprint of the derived image. Fingerprint fingerprint = 1; - // Output only. The number of layers by which this image differs from - // the associated image basis. + // Output only. The number of layers by which this image differs from the + // associated image basis. uint32 distance = 2; - // This contains layer-specific metadata, if populated it - // has length "distance" and is ordered with [distance] being the - // layer immediately following the base image and [1] - // being the final layer. + // This contains layer-specific metadata, if populated it has length + // "distance" and is ordered with [distance] being the layer immediately + // following the base image and [1] being the final layer. repeated Layer layer_info = 3; - // Output only.This contains the base image url for the derived image - // Occurrence + // Output only. This contains the base image URL for the derived image + // occurrence. string base_resource_url = 4; } + + } diff --git a/google/devtools/containeranalysis/v1alpha1/package_vulnerability.proto b/google/devtools/containeranalysis/v1alpha1/package_vulnerability.proto index a19f69aa..46000359 100644 --- a/google/devtools/containeranalysis/v1alpha1/package_vulnerability.proto +++ b/google/devtools/containeranalysis/v1alpha1/package_vulnerability.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -94,6 +94,10 @@ message VulnerabilityType { // The type of package; whether native or non native(ruby gems, // node.js packages etc) string package_type = 10; + + // Whether this Detail is obsolete. Occurrences are expected not to point to + // obsolete details. + bool is_obsolete = 11; } // Used by Occurrence to point to where the vulnerability exists and how diff --git a/google/devtools/containeranalysis/v1alpha1/provenance.proto b/google/devtools/containeranalysis/v1alpha1/provenance.proto index b1f8193d..969d70f7 100644 --- a/google/devtools/containeranalysis/v1alpha1/provenance.proto +++ b/google/devtools/containeranalysis/v1alpha1/provenance.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/google/devtools/containeranalysis/v1alpha1/source_context.proto b/google/devtools/containeranalysis/v1alpha1/source_context.proto index 918f4b0e..949ac814 100644 --- a/google/devtools/containeranalysis/v1alpha1/source_context.proto +++ b/google/devtools/containeranalysis/v1alpha1/source_context.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.