213 lines
7.5 KiB
Python
213 lines
7.5 KiB
Python
"""
|
|
Copyright (c) 2017 Platform9 Systems Inc. (http://www.platform9.com)
|
|
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.
|
|
"""
|
|
|
|
import gceutils
|
|
import hashlib
|
|
import keystoneauth1
|
|
import os
|
|
import requests
|
|
import sys
|
|
import uuid
|
|
|
|
from keystoneauth1 import loading
|
|
from keystoneauth1 import session
|
|
from keystoneclient import client
|
|
from six.moves import urllib
|
|
|
|
|
|
def get_env_param(env_name):
|
|
if env_name in os.environ:
|
|
return os.environ[env_name]
|
|
raise Exception("%s environment variable not set." % env_name)
|
|
|
|
|
|
def get_keystone_session(
|
|
auth_url=get_env_param('OS_AUTH_URL'),
|
|
project_name=os.environ.get('OS_PROJECT_NAME'),
|
|
tenant_name=os.environ.get('OS_TENANT_NAME'),
|
|
project_domain_name=os.environ.get('OS_PROJECT_DOMAIN_NAME',
|
|
'default'), # noqa
|
|
username=get_env_param('OS_USERNAME'),
|
|
user_domain_name=os.environ.get('OS_USER_DOMAIN_NAME', 'default'),
|
|
password=get_env_param('OS_PASSWORD')):
|
|
|
|
if not project_name:
|
|
if not tenant_name:
|
|
raise Exception(
|
|
"Either OS_PROJECT_NAME or OS_TENANT_NAME is required.")
|
|
project_name = tenant_name
|
|
|
|
loader = loading.get_plugin_loader('password')
|
|
auth = loader.load_from_options(
|
|
auth_url=auth_url, project_name=project_name,
|
|
project_domain_name=project_domain_name, username=username,
|
|
user_domain_name=user_domain_name, password=password)
|
|
sess = session.Session(auth=auth)
|
|
return sess
|
|
|
|
|
|
class GceImages(object):
|
|
# Identified by referring,
|
|
# 1. https://console.cloud.google.com/compute/images
|
|
# 2. https://cloud.google.com/compute/docs/images#os-compute-support
|
|
GC_PUBLIC_PROJECTS = [
|
|
'cos-cloud', 'centos-cloud', 'debian-cloud', 'rhel-cloud',
|
|
'suse-cloud', 'ubuntu-os-cloud', 'windows-cloud', 'coreos-cloud',
|
|
'windows-sql-cloud'
|
|
]
|
|
|
|
def __init__(self, service_key_path):
|
|
self.gce_svc = gceutils.get_gce_service(service_key_path)
|
|
self.img_kind = {
|
|
'RAW': 'raw',
|
|
}
|
|
self.glance_client = RestClient()
|
|
|
|
def get_all_public_images(self):
|
|
images = []
|
|
for project in self.GC_PUBLIC_PROJECTS:
|
|
images.extend(gceutils.get_images(self.gce_svc, project))
|
|
return images
|
|
|
|
def register_gce_images(self):
|
|
for image in self.get_all_public_images():
|
|
self.create_image(self._gce_to_ostack_formatter(image))
|
|
|
|
def _get_project(self, gce_link):
|
|
# Sample GCE link path:
|
|
# https://<domain>/compute/v1/projects/<project>/global/images/<image>
|
|
parsed_link = urllib.parse.urlparse(gce_link)
|
|
project = parsed_link.path.strip('/').split('/')[3]
|
|
return project
|
|
|
|
def create_image(self, img_data):
|
|
"""
|
|
Create an OpenStack image.
|
|
:param img_data: dict -- Describes GCE Image
|
|
:returns: dict -- Response from REST call
|
|
:raises: requests.HTTPError
|
|
"""
|
|
glance_id = img_data['id']
|
|
gce_id = img_data['name']
|
|
print("Creating image: {0}".format(gce_id))
|
|
gce_link = img_data['gce_link']
|
|
gce_project = self._get_project(gce_link)
|
|
img_props = {
|
|
'locations': [{
|
|
'url':
|
|
'gce://%s/%s/%s' % (gce_project, gce_id, glance_id),
|
|
'metadata': {
|
|
'gce_link': gce_link
|
|
}
|
|
}]
|
|
}
|
|
try:
|
|
resp = self.glance_client.request('POST', '/v2/images',
|
|
json=img_data)
|
|
resp.raise_for_status()
|
|
# Need to update the image in the registry
|
|
# with location information so
|
|
# the status changes from 'queued' to 'active'
|
|
self.update_properties(glance_id, img_props)
|
|
print("Created image: {0}".format(gce_id))
|
|
except keystoneauth1.exceptions.http.Conflict:
|
|
# ignore error if image already exists
|
|
pass
|
|
except requests.HTTPError as e:
|
|
raise e
|
|
|
|
def update_properties(self, imageid, props):
|
|
"""
|
|
Add or update a set of image properties on an image.
|
|
:param imageid: int -- The Ostack image UUID
|
|
:param props: dict -- Image properties to update
|
|
"""
|
|
if not props:
|
|
return
|
|
patch_body = []
|
|
for name, value in props.iteritems():
|
|
patch_body.append({
|
|
'op': 'replace',
|
|
'path': '/%s' % name,
|
|
'value': value
|
|
})
|
|
resp = self.glance_client.request('PATCH', '/v2/images/%s' % imageid,
|
|
json=patch_body)
|
|
resp.raise_for_status()
|
|
|
|
def _get_image_uuid(self, gce_id):
|
|
md = hashlib.md5()
|
|
md.update(gce_id)
|
|
return str(uuid.UUID(bytes=md.digest()))
|
|
|
|
def _gce_to_ostack_formatter(self, gce_img_data):
|
|
"""
|
|
Converts GCE img data to Openstack img data format.
|
|
:param img(dict): gce img data
|
|
:return(dict): ostack img data
|
|
"""
|
|
return {
|
|
'id': self._get_image_uuid(gce_img_data['id']),
|
|
'name': gce_img_data['name'],
|
|
'container_format': 'bare',
|
|
'disk_format': self.img_kind[gce_img_data['sourceType']],
|
|
'visibility': 'public',
|
|
'gce_image_id': gce_img_data['id'],
|
|
'gce_size': gce_img_data['diskSizeGb'],
|
|
'gce_link': gce_img_data['selfLink']
|
|
}
|
|
|
|
|
|
class RestClient(object):
|
|
def __init__(self):
|
|
auth_url = get_env_param('OS_AUTH_URL')
|
|
if auth_url.find('v2.0') > 0:
|
|
auth_url = auth_url.replace('v2.0', 'v3')
|
|
self.auth_url = auth_url
|
|
self.region_name = get_env_param('OS_REGION_NAME')
|
|
self.sess = get_keystone_session(auth_url=self.auth_url)
|
|
self.glance_endpoint = self.get_glance_endpoint()
|
|
|
|
def get_glance_endpoint(self):
|
|
self.ksclient = client.Client(auth_url=self.auth_url,
|
|
session=self.sess)
|
|
glance_service_id = self.ksclient.services.list(name='glance')[0].id
|
|
glance_url = self.ksclient.endpoints.list(
|
|
service=glance_service_id, interface='public', enabled=True,
|
|
region=self.region_name)[0].url
|
|
return glance_url
|
|
|
|
def request(self, method, path, **kwargs):
|
|
"""
|
|
Make a requests request with retry/relogin on auth failure.
|
|
"""
|
|
url = self.glance_endpoint + path
|
|
headers = self.sess.get_auth_headers()
|
|
if method == 'PUT' or method == 'PATCH':
|
|
headers['Content-Type'] = '/'.join(
|
|
['application', 'openstack-images-v2.1-json-patch'])
|
|
resp = requests.request(method, url, headers=headers, **kwargs)
|
|
else:
|
|
resp = self.sess.request(url, method, headers=headers, **kwargs)
|
|
resp.raise_for_status()
|
|
return resp
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) != 2:
|
|
print('Usage: {0} <service_key_path>'.format(sys.argv[0]))
|
|
sys.exit(1)
|
|
|
|
gce_images = GceImages(sys.argv[1])
|
|
gce_images.register_gce_images()
|