1# -*- coding: utf-8 -*- #
2# Copyright 2016 Google LLC. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Utilities for the API to configure cross-project networking (XPN)."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import unicode_literals
20
21from apitools.base.py import list_pager
22from googlecloudsdk.api_lib.compute import base_classes
23from googlecloudsdk.api_lib.compute import exceptions
24from googlecloudsdk.api_lib.compute import utils
25
26
27_DEFAULT_API_VERSION = 'v1'
28
29
30class XpnApiError(exceptions.Error):
31  pass
32
33
34class XpnClient(object):
35  """A client for interacting with the cross-project networking (XPN) API.
36
37  The XPN API is a subset of the Google Compute Engine API.
38  """
39
40  def __init__(self, compute_client):
41    self.compute_client = compute_client
42    self.client = compute_client.apitools_client
43    self.messages = compute_client.messages
44
45  # TODO(b/30465957): Refactor to use apitools clients directly and not the
46  # compute utilities
47  def _MakeRequest(self, request, errors):
48    return self.compute_client.MakeRequests(
49        requests=[request],
50        errors_to_collect=errors)
51
52  def _MakeRequestSync(self, request_tuple, operation_msg=None):
53    errors = []
54    results = list(self._MakeRequest(request_tuple, errors))
55
56    if errors:
57      operation_msg = operation_msg or 'complete all requests'
58      msg = 'Could not {0}:'.format(operation_msg)
59      utils.RaiseException(errors, XpnApiError, msg)
60
61    return results[0]  # if there were no errors, this will exist
62
63  def EnableHost(self, project):
64    """Enable the project with the given ID as an XPN host."""
65    request_tuple = (
66        self.client.projects,
67        'EnableXpnHost',
68        self.messages.ComputeProjectsEnableXpnHostRequest(project=project))
69    msg = 'enable [{project}] as XPN host'.format(project=project)
70    self._MakeRequestSync(request_tuple, msg)
71
72  def DisableHost(self, project):
73    """Disable the project with the given ID as an XPN host."""
74    request_tuple = (
75        self.client.projects,
76        'DisableXpnHost',
77        self.messages.ComputeProjectsDisableXpnHostRequest(project=project))
78    msg = 'disable [{project}] as XPN host'.format(project=project)
79    self._MakeRequestSync(request_tuple, msg)
80
81  def GetHostProject(self, project):
82    """Get the XPN host for the given project."""
83    request_tuple = (
84        self.client.projects,
85        'GetXpnHost',
86        self.messages.ComputeProjectsGetXpnHostRequest(project=project))
87    msg = 'get XPN host for project [{project}]'.format(project=project)
88    return self._MakeRequestSync(request_tuple, msg)
89
90  def ListEnabledResources(self, project):
91    request = self.messages.ComputeProjectsGetXpnResourcesRequest(
92        project=project)
93    return list_pager.YieldFromList(
94        self.client.projects,
95        request,
96        method='GetXpnResources',
97        batch_size_attribute='maxResults',
98        batch_size=500,
99        field='resources')
100
101  def ListOrganizationHostProjects(self, project, organization_id):
102    """List the projects in an organization that are enabled as XPN hosts.
103
104    Args:
105      project: str, project ID to make the request with.
106      organization_id: str, the ID of the organization to list XPN hosts
107          for. If None, the organization is inferred from the project.
108
109    Returns:
110      Generator for `Project`s corresponding to XPN hosts in the organization.
111    """
112    request = self.messages.ComputeProjectsListXpnHostsRequest(
113        project=project,
114        projectsListXpnHostsRequest=self.messages.ProjectsListXpnHostsRequest(
115            organization=organization_id))
116    return list_pager.YieldFromList(
117        self.client.projects,
118        request,
119        method='ListXpnHosts',
120        batch_size_attribute='maxResults',
121        batch_size=500,
122        field='items')
123
124  def _EnableXpnAssociatedResource(self, host_project, associated_resource,
125                                   xpn_resource_type):
126    """Associate the given resource with the given XPN host project.
127
128    Args:
129      host_project: str, ID of the XPN host project
130      associated_resource: ID of the resource to associate with host_project
131      xpn_resource_type: XpnResourceId.TypeValueValuesEnum, the type of the
132         resource
133    """
134    projects_enable_request = self.messages.ProjectsEnableXpnResourceRequest(
135        xpnResource=self.messages.XpnResourceId(
136            id=associated_resource,
137            type=xpn_resource_type)
138    )
139    request = self.messages.ComputeProjectsEnableXpnResourceRequest(
140        project=host_project,
141        projectsEnableXpnResourceRequest=projects_enable_request)
142    request_tuple = (self.client.projects, 'EnableXpnResource', request)
143    msg = ('enable resource [{0}] as an associated resource '
144           'for project [{1}]').format(associated_resource, host_project)
145    self._MakeRequestSync(request_tuple, msg)
146
147  def EnableXpnAssociatedProject(self, host_project, associated_project):
148    """Associate the given project with the given XPN host project.
149
150    Args:
151      host_project: str, ID of the XPN host project
152      associated_project: ID of the project to associate
153    """
154    xpn_types = self.messages.XpnResourceId.TypeValueValuesEnum
155    self._EnableXpnAssociatedResource(
156        host_project, associated_project, xpn_resource_type=xpn_types.PROJECT)
157
158  def _DisableXpnAssociatedResource(self, host_project, associated_resource,
159                                    xpn_resource_type):
160    """Disassociate the given resource from the given XPN host project.
161
162    Args:
163      host_project: str, ID of the XPN host project
164      associated_resource: ID of the resource to disassociate from host_project
165      xpn_resource_type: XpnResourceId.TypeValueValuesEnum, the type of the
166         resource
167    """
168    projects_disable_request = self.messages.ProjectsDisableXpnResourceRequest(
169        xpnResource=self.messages.XpnResourceId(
170            id=associated_resource,
171            type=xpn_resource_type)
172    )
173    request = self.messages.ComputeProjectsDisableXpnResourceRequest(
174        project=host_project,
175        projectsDisableXpnResourceRequest=projects_disable_request)
176    request_tuple = (self.client.projects, 'DisableXpnResource', request)
177    msg = ('disable resource [{0}] as an associated resource '
178           'for project [{1}]').format(associated_resource, host_project)
179    self._MakeRequestSync(request_tuple, msg)
180
181  def DisableXpnAssociatedProject(self, host_project, associated_project):
182    """Disassociate the given project from the given XPN host project.
183
184    Args:
185      host_project: str, ID of the XPN host project
186      associated_project: ID of the project to disassociate from host_project
187    """
188    xpn_types = self.messages.XpnResourceId.TypeValueValuesEnum
189    self._DisableXpnAssociatedResource(
190        host_project, associated_project, xpn_resource_type=xpn_types.PROJECT)
191
192
193def GetXpnClient(release_track):
194  holder = base_classes.ComputeApiHolder(release_track)
195  return XpnClient(holder.client)
196