From ed08adf3c302c9aef4d087b5d0881efdbe5bd6e7 Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 19 Jul 2018 11:14:53 +0200 Subject: [PATCH 001/103] Add PyYaml library --- requirements.txt | 1 + setup.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e55fe270..5f9952f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ tornado==5.0.2 tornado-xstatic XStatic==1.0.1 XStatic-term.js==0.0.7.0 +pyyaml==3.11 diff --git a/setup.py b/setup.py index 96f28a08..74c611c1 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,8 @@ 'tornado==5.0.2', 'tornado-xstatic', 'XStatic==1.0.1', - 'XStatic-term.js==0.0.7.0' + 'XStatic-term.js==0.0.7.0', + 'pyyaml==3.11' ], entry_points={ 'console_scripts': [ From b4bace014b72de0284f947e1e9d7389164c46af6 Mon Sep 17 00:00:00 2001 From: nuhi Date: Mon, 23 Jul 2018 14:24:10 +0200 Subject: [PATCH 002/103] Prepare image for upload --- .gitignore | 1 + app/api/controllers/imageRegistry.py | 29 ++++++++++++++ app/api/core.py | 3 ++ app/api/models/LXCImage.py | 56 ++++++++++++++++++++++++++- app/api/schemas/publishImageSchema.py | 26 +++++++++++++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 app/api/controllers/imageRegistry.py create mode 100644 app/api/schemas/publishImageSchema.py diff --git a/.gitignore b/.gitignore index 5bfa7f30..313eb8e8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ LXDUI.egg-info logs/ conf/lxdui.conf snap/ +tmp/ diff --git a/app/api/controllers/imageRegistry.py b/app/api/controllers/imageRegistry.py new file mode 100644 index 00000000..c6ba9444 --- /dev/null +++ b/app/api/controllers/imageRegistry.py @@ -0,0 +1,29 @@ +from flask import Blueprint, request, send_file +from flask_jwt import jwt_required + +from app.api.models.LXCImage import LXCImage +from app.api.utils import response + +from app.api.schemas.publishImageSchema import doValidate + +image_registry_api = Blueprint('image_registry_api', __name__) + + +@image_registry_api.route('/', methods=['POST']) +@jwt_required() +def publishImage(fingerprint): + input = request.get_json(silent=True) + validation = doValidate(input) + if validation: + return response.replyFailed(message=validation.message) + + input['fingerprint'] = fingerprint + try: + image = LXCImage({'fingerprint': fingerprint}) + + #Export Image - Image registry + image.exportImage(input) + + return response.replySuccess(image.getImage()) + except ValueError as e: + return response.replyFailed(message=e.__str__()) \ No newline at end of file diff --git a/app/api/core.py b/app/api/core.py index b0cf5401..4e9d083e 100644 --- a/app/api/core.py +++ b/app/api/core.py @@ -43,6 +43,9 @@ from app.api.controllers.storagePool import storage_pool_api app.register_blueprint(storage_pool_api, url_prefix='/api/storage_pool') +from app.api.controllers.imageRegistry import image_registry_api +app.register_blueprint(image_registry_api, url_prefix='/api/image_registry') + from app.api.controllers.terminal import terminal @app.route('/') diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 85f8ee49..8e1ed5c1 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -1,5 +1,9 @@ from app.api.models.LXDModule import LXDModule import logging +import subprocess +import shutil +import os +import yaml logging = logging.getLogger(__name__) @@ -53,4 +57,54 @@ def deleteImage(self): except Exception as e: logging.error('Failed to delete the image {}'.format(self.data.get('fingerprint'))) logging.exception(e) - raise ValueError(e) \ No newline at end of file + raise ValueError(e) + + def exportImage(self, input): + try: + logging.info('Exporting image {}'.format(self.data.get('fingerprint'))) + p2 = subprocess.Popen(["lxc", "image", "export", self.data.get('fingerprint')], stdout=subprocess.PIPE) + output_rez = p2.stdout.read() + + #Prepare YAML + self.prepareImageYAML(input) + + #Make dir for the export + shutil.rmtree('tmp/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) + os.makedirs('tmp/{}'.format(self.data.get('fingerprint')), exist_ok=True) + + #Move the export - Check for both extenstion .tar.gz & .tar.xz + if os.path.exists('{}.tar.gz'.format(self.data.get('fingerprint'))): + shutil.move('{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + if os.path.exists('{}.tar.xz'.format(self.data.get('fingerprint'))): + shutil.move('{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + if os.path.exists('meta-{}.tar.gz'.format(self.data.get('fingerprint'))): + shutil.move('meta-{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + if os.path.exists('meta-{}.tar.xz'.format(self.data.get('fingerprint'))): + shutil.move('meta-{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + + #Move the yaml file + shutil.move('{}.yaml'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + + return output_rez + except Exception as e: + logging.error('Failed to export the image {}'.format(self.data.get('fingerprint'))) + logging.exception(e) + raise ValueError(e) + + def prepareImageYAML(self, input): + data = { + 'title': 'Wordpress 15.1', + 'description': 'Some description', + 'author': { + 'name': 'AdaptiveScale', + 'alias': 'as', + 'email': 'info@adaptivescale.com' + }, + 'license': 'MIT', + 'readme': 'Readme.md', + 'image': '', + 'metadata': '', + 'tags': ['nginx', 'redis', 'python3', 'flask'] + } + with open('/home/nuhi/lxdui/{}.yaml'.format(input.get('fingerprint')), 'w') as yamlFile: + yaml.dump(data, yamlFile, default_flow_style=False) \ No newline at end of file diff --git a/app/api/schemas/publishImageSchema.py b/app/api/schemas/publishImageSchema.py new file mode 100644 index 00000000..8d19cf40 --- /dev/null +++ b/app/api/schemas/publishImageSchema.py @@ -0,0 +1,26 @@ +from jsonschema import validate, ValidationError + +schema = { + "oneOf": [ + {"$ref": "#/definitions/singleObject"}, # plain object + ], + "definitions": { + "singleObject": { + 'type':'object', + #'required': [], + 'properties':{ + 'name':{ + 'type':'string', + 'description':'image (name/distribution/architecture)' + } + } + } + } +} + +def doValidate(input): + try: + validate(input, schema) + return None + except ValidationError as e: + return e \ No newline at end of file From f721f5efd23f2a8195b1e20fb59e905d3d208a03 Mon Sep 17 00:00:00 2001 From: nuhi Date: Mon, 23 Jul 2018 14:49:55 +0200 Subject: [PATCH 003/103] Finalize yaml creation --- app/api/models/LXCImage.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 8e1ed5c1..2ab1bc12 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -65,9 +65,6 @@ def exportImage(self, input): p2 = subprocess.Popen(["lxc", "image", "export", self.data.get('fingerprint')], stdout=subprocess.PIPE) output_rez = p2.stdout.read() - #Prepare YAML - self.prepareImageYAML(input) - #Make dir for the export shutil.rmtree('tmp/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) os.makedirs('tmp/{}'.format(self.data.get('fingerprint')), exist_ok=True) @@ -75,16 +72,25 @@ def exportImage(self, input): #Move the export - Check for both extenstion .tar.gz & .tar.xz if os.path.exists('{}.tar.gz'.format(self.data.get('fingerprint'))): shutil.move('{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + input['image'] = 'tmp/{0}/{0}.tar.gz'.format(self.data.get('fingerprint')) if os.path.exists('{}.tar.xz'.format(self.data.get('fingerprint'))): shutil.move('{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + input['image'] = 'tmp/{0}/{0}.tar.xz'.format(self.data.get('fingerprint')) if os.path.exists('meta-{}.tar.gz'.format(self.data.get('fingerprint'))): shutil.move('meta-{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + input['metadata'] = 'tmp/{0}/meta-{0}.tar.gz'.format(self.data.get('fingerprint')) if os.path.exists('meta-{}.tar.xz'.format(self.data.get('fingerprint'))): shutil.move('meta-{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + input['metadata'] = 'tmp/{0}/meta-{0}.tar.xz'.format(self.data.get('fingerprint')) - #Move the yaml file + #Prepare & Move the yaml file + self.prepareImageYAML(input) shutil.move('{}.yaml'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + #TODO Prepare README.md + + #TODO Prepare Logo + return output_rez except Exception as e: logging.error('Failed to export the image {}'.format(self.data.get('fingerprint'))) @@ -92,6 +98,7 @@ def exportImage(self, input): raise ValueError(e) def prepareImageYAML(self, input): + if input.get('metadata') == None: input['metadata'] = '' data = { 'title': 'Wordpress 15.1', 'description': 'Some description', @@ -101,10 +108,13 @@ def prepareImageYAML(self, input): 'email': 'info@adaptivescale.com' }, 'license': 'MIT', - 'readme': 'Readme.md', - 'image': '', - 'metadata': '', - 'tags': ['nginx', 'redis', 'python3', 'flask'] + 'readme': 'README.md', + 'tags': ['nginx', 'redis', 'python3', 'flask'], + 'image': input.get('image'), + 'metadata': input.get('metadata') } - with open('/home/nuhi/lxdui/{}.yaml'.format(input.get('fingerprint')), 'w') as yamlFile: + + data.update(self.client.api.images[self.data.get('fingerprint')].get().json()['metadata']) + + with open('{}.yaml'.format(self.data.get('fingerprint')), 'w') as yamlFile: yaml.dump(data, yamlFile, default_flow_style=False) \ No newline at end of file From 2cda815d7d7376d20e605a616428f1906f5958f9 Mon Sep 17 00:00:00 2001 From: nuhi Date: Mon, 23 Jul 2018 16:09:45 +0200 Subject: [PATCH 004/103] Get Image properties on container export --- app/api/models/LXCContainer.py | 5 +++++ app/api/models/LXCImage.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/api/models/LXCContainer.py b/app/api/models/LXCContainer.py index 53ebd722..cc4f6cae 100644 --- a/app/api/models/LXCContainer.py +++ b/app/api/models/LXCContainer.py @@ -299,6 +299,11 @@ def export(self, force=False): image = container.publish(wait=True) image.add_alias(self.data.get('imageAlias'), self.data.get('name')) + try: + fingerprint = container.config.get('volatile.base_image') + self.client.api.images[image.fingerprint].put(json={'properties': self.client.api.images[fingerprint].get().json()['metadata']['properties']}) + except: + logging.error('Image does not exist.') container.start(wait=True) return self.client.api.images[image.fingerprint].get().json()['metadata'] except Exception as e: diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 2ab1bc12..427f7a0b 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -59,6 +59,7 @@ def deleteImage(self): logging.exception(e) raise ValueError(e) + #TODO Refactor this part def exportImage(self, input): try: logging.info('Exporting image {}'.format(self.data.get('fingerprint'))) @@ -115,6 +116,6 @@ def prepareImageYAML(self, input): } data.update(self.client.api.images[self.data.get('fingerprint')].get().json()['metadata']) - + with open('{}.yaml'.format(self.data.get('fingerprint')), 'w') as yamlFile: yaml.dump(data, yamlFile, default_flow_style=False) \ No newline at end of file From d072ea746b4674a6c3d6c80bdaf54a7b368c330c Mon Sep 17 00:00:00 2001 From: nuhi Date: Wed, 25 Jul 2018 11:05:44 +0200 Subject: [PATCH 005/103] Export Image CLI --- app/api/models/LXCImage.py | 44 ++++++++++++++++++++++---------------- app/cli/cli.py | 26 ++++++++++++++++++++++ 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 427f7a0b..4de5c639 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -1,4 +1,5 @@ from app.api.models.LXDModule import LXDModule +from app.lib.conf import MetaConf import logging import subprocess import shutil @@ -62,37 +63,41 @@ def deleteImage(self): #TODO Refactor this part def exportImage(self, input): try: + #Check if image exists & Update the fingerprint with the full fingerprint + self.data['fingerprint'] = self.client.images.get(self.data.get('fingerprint')).fingerprint + logging.info('Exporting image {}'.format(self.data.get('fingerprint'))) p2 = subprocess.Popen(["lxc", "image", "export", self.data.get('fingerprint')], stdout=subprocess.PIPE) output_rez = p2.stdout.read() #Make dir for the export - shutil.rmtree('tmp/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) - os.makedirs('tmp/{}'.format(self.data.get('fingerprint')), exist_ok=True) + shutil.rmtree('tmp/images/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) + os.makedirs('tmp/images/{}'.format(self.data.get('fingerprint')), exist_ok=True) #Move the export - Check for both extenstion .tar.gz & .tar.xz if os.path.exists('{}.tar.gz'.format(self.data.get('fingerprint'))): - shutil.move('{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) - input['image'] = 'tmp/{0}/{0}.tar.gz'.format(self.data.get('fingerprint')) + shutil.move('{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) + input['image'] = 'tmp/images/{0}/{0}.tar.gz'.format(self.data.get('fingerprint')) if os.path.exists('{}.tar.xz'.format(self.data.get('fingerprint'))): - shutil.move('{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) - input['image'] = 'tmp/{0}/{0}.tar.xz'.format(self.data.get('fingerprint')) + shutil.move('{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) + input['image'] = 'tmp/images/{0}/{0}.tar.xz'.format(self.data.get('fingerprint')) if os.path.exists('meta-{}.tar.gz'.format(self.data.get('fingerprint'))): - shutil.move('meta-{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) - input['metadata'] = 'tmp/{0}/meta-{0}.tar.gz'.format(self.data.get('fingerprint')) + shutil.move('meta-{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) + input['metadata'] = 'tmp/images/{0}/meta-{0}.tar.gz'.format(self.data.get('fingerprint')) if os.path.exists('meta-{}.tar.xz'.format(self.data.get('fingerprint'))): - shutil.move('meta-{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) - input['metadata'] = 'tmp/{0}/meta-{0}.tar.xz'.format(self.data.get('fingerprint')) + shutil.move('meta-{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) + input['metadata'] = 'tmp/images/{0}/meta-{0}.tar.xz'.format(self.data.get('fingerprint')) #Prepare & Move the yaml file self.prepareImageYAML(input) - shutil.move('{}.yaml'.format(self.data.get('fingerprint')), 'tmp/{}/'.format(self.data.get('fingerprint'))) + shutil.move('{}.yaml'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) #TODO Prepare README.md + open('tmp/images/{}/README.md'.format(self.data.get('fingerprint')), 'a').close() #TODO Prepare Logo - return output_rez + return MetaConf().getConfRoot() + '/tmp/images/{}'.format(self.data.get('fingerprint')) except Exception as e: logging.error('Failed to export the image {}'.format(self.data.get('fingerprint'))) logging.exception(e) @@ -101,16 +106,17 @@ def exportImage(self, input): def prepareImageYAML(self, input): if input.get('metadata') == None: input['metadata'] = '' data = { - 'title': 'Wordpress 15.1', - 'description': 'Some description', + 'title': '', + 'description': '', 'author': { - 'name': 'AdaptiveScale', - 'alias': 'as', - 'email': 'info@adaptivescale.com' + 'name': '', + 'alias': '', + 'email': '' }, - 'license': 'MIT', + 'license': '', 'readme': 'README.md', - 'tags': ['nginx', 'redis', 'python3', 'flask'], + 'tags': [], + 'logo': 'logo.png', 'image': input.get('image'), 'metadata': input.get('metadata') } diff --git a/app/cli/cli.py b/app/cli/cli.py index eedc6977..92abe6a7 100644 --- a/app/cli/cli.py +++ b/app/cli/cli.py @@ -5,6 +5,7 @@ from app.lib.cert import Certificate from app.api import core from app.ui.blueprint import uiPages +from app.api.models.LXCImage import LXCImage import click import os import signal @@ -40,6 +41,10 @@ ''' Command Groups ''' + + + + @click.group() @click.version_option(version=meta.VERSION, message='{} v{} \n{}\n{}'.format(meta.APP_NAME, meta.VERSION, meta.AUTHOR, meta.AUTHOR_URL)) # @click.version_option(message=meta.APP_NAME + ' version ' + meta.VERSION + '\n' + meta.AUTHOR + '\n' + meta.AUTHOR_URL ) @@ -115,6 +120,27 @@ def status(): click.echo(' {} : {}'.format(k, v)) +#TODO Create Image Registry Group +@lxdui.command() +@click.argument('fingerprint', nargs=1) +def prep(fingerprint): + try: + input = {} + image = LXCImage({'fingerprint': fingerprint}) + + # Export Image - Image registry + path = image.exportImage(input) + + click.echo("Image prepared successfully.") + click.echo("The image path is: {}".format(path)) + click.echo("Modify the image.yaml, upload the logo and update README.md") + click.echo("To publish the image use the command:") + click.echo("lxdui image push -u -p ") + except Exception as e: + click.echo("LXDUI failed to prepare the image.") + click.echo(e.__str__()) + + ''' User level group of commands From 968c77d49afed1506566bdb22b06686586bb0b09 Mon Sep 17 00:00:00 2001 From: nuhi Date: Wed, 25 Jul 2018 13:31:04 +0200 Subject: [PATCH 006/103] Send YAML first. Add Username and Password parameters for push. --- app/api/models/LXCImage.py | 51 +++++++++++++++++++++++++++++++++++--- app/cli/cli.py | 17 +++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 4de5c639..a30446ae 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -1,6 +1,7 @@ from app.api.models.LXDModule import LXDModule from app.lib.conf import MetaConf import logging +import requests import subprocess import shutil import os @@ -114,14 +115,56 @@ def prepareImageYAML(self, input): 'email': '' }, 'license': '', - 'readme': 'README.md', + 'readme': 'tmp/images/{}/README.md'.format(self.data.get('fingerprint')), 'tags': [], - 'logo': 'logo.png', + 'logo': 'tmp/images/{}/logo.png'.format(self.data.get('fingerprint')), 'image': input.get('image'), - 'metadata': input.get('metadata') + 'metadata': input.get('metadata'), + 'fingerprint': self.data.get('fingerprint') } data.update(self.client.api.images[self.data.get('fingerprint')].get().json()['metadata']) with open('{}.yaml'.format(self.data.get('fingerprint')), 'w') as yamlFile: - yaml.dump(data, yamlFile, default_flow_style=False) \ No newline at end of file + yaml.dump(data, yamlFile, default_flow_style=False) + + + def pushImage(self, input): + try: + self.data['fingerprint'] = self.client.images.get(self.data.get('fingerprint')).fingerprint + + if os.path.exists('tmp/images/{}'.format(self.data.get('fingerprint'))): + logging.info('Image exists. Ready for push.') + print ("Image exists. Ready for push.") + + #Prepare the files for upload. + with open('tmp/images/{0}/{0}.yaml'.format(self.data.get('fingerprint'))) as stream: + yamlData = yaml.load(stream) + + files = { + 'yaml': open('tmp/images/{0}/{0}.yaml'.format(self.data.get('fingerprint'), 'rb')) + } + + response = requests.post('http://postma-echo.com/post', files=files).json() + + print (response) + + return True + + files = { + 'image': open(yamlData['image'], 'rb'), + 'meta-image': open(yamlData['metadata'], 'rb'), + 'readme': open(yamlData['readme'], 'rb'), + # 'logo': open(yamlData['logo'], 'rb') + } + + r = requests.post('http://postma-echo.com/post', files=files) + else: + logging.error('Failed to push the image {}'.format(self.data.get('fingerprint'))) + logging.exception('Image is not prepared. Please prepare the image using the command lxdui image prep ') + raise ValueError('Image is not prepared. Please prepare the image using the command: lxdui image prep ') + + except Exception as e: + logging.error('Failed to push the image {}'.format(self.data.get('fingerprint'))) + logging.exception(e) + raise ValueError(e) diff --git a/app/cli/cli.py b/app/cli/cli.py index 92abe6a7..34531c45 100644 --- a/app/cli/cli.py +++ b/app/cli/cli.py @@ -140,6 +140,23 @@ def prep(fingerprint): click.echo("LXDUI failed to prepare the image.") click.echo(e.__str__()) +@lxdui.command() +@click.argument('fingerprint', nargs=1) +@click.option('-u', '--username', nargs=1, help='User Name') +@click.option('-p', '--password', nargs=1, help='Password') +def push(fingerprint, username, password): + try: + input = {} + + image = LXCImage({'fingerprint': fingerprint}) + + # Export Image - Image registry + image.pushImage(input) + + click.echo("Image pushed successfully.") + except Exception as e: + click.echo("LXDUI failed to push the image.") + click.echo(e.__str__()) ''' User level group of commands From 7282a7201e78cd17efa7fd336d187b254b5150bf Mon Sep 17 00:00:00 2001 From: nuhi Date: Fri, 27 Jul 2018 12:24:26 +0200 Subject: [PATCH 007/103] Repalce .yaml with image.yaml --- app/api/models/LXCImage.py | 48 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index a30446ae..ac3890f0 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -78,23 +78,25 @@ def exportImage(self, input): #Move the export - Check for both extenstion .tar.gz & .tar.xz if os.path.exists('{}.tar.gz'.format(self.data.get('fingerprint'))): shutil.move('{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) - input['image'] = 'tmp/images/{0}/{0}.tar.gz'.format(self.data.get('fingerprint')) + input['image'] = '{}.tar.gz'.format(self.data.get('fingerprint')) if os.path.exists('{}.tar.xz'.format(self.data.get('fingerprint'))): shutil.move('{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) - input['image'] = 'tmp/images/{0}/{0}.tar.xz'.format(self.data.get('fingerprint')) + input['image'] = '{}.tar.xz'.format(self.data.get('fingerprint')) if os.path.exists('meta-{}.tar.gz'.format(self.data.get('fingerprint'))): shutil.move('meta-{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) - input['metadata'] = 'tmp/images/{0}/meta-{0}.tar.gz'.format(self.data.get('fingerprint')) + input['metadata'] = 'meta-{}.tar.gz'.format(self.data.get('fingerprint')) if os.path.exists('meta-{}.tar.xz'.format(self.data.get('fingerprint'))): shutil.move('meta-{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) - input['metadata'] = 'tmp/images/{0}/meta-{0}.tar.xz'.format(self.data.get('fingerprint')) + input['metadata'] = 'meta-{}.tar.xz'.format(self.data.get('fingerprint')) #Prepare & Move the yaml file self.prepareImageYAML(input) - shutil.move('{}.yaml'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) + shutil.move('image.yaml', 'tmp/images/{}/'.format(self.data.get('fingerprint'))) #TODO Prepare README.md - open('tmp/images/{}/README.md'.format(self.data.get('fingerprint')), 'a').close() + file = open('tmp/images/{}/README.md'.format(self.data.get('fingerprint')), 'a') + file.write('#README') + file.close() #TODO Prepare Logo @@ -115,9 +117,9 @@ def prepareImageYAML(self, input): 'email': '' }, 'license': '', - 'readme': 'tmp/images/{}/README.md'.format(self.data.get('fingerprint')), + 'readme': 'README.md'.format(self.data.get('fingerprint')), 'tags': [], - 'logo': 'tmp/images/{}/logo.png'.format(self.data.get('fingerprint')), + 'logo': 'logo.png'.format(self.data.get('fingerprint')), 'image': input.get('image'), 'metadata': input.get('metadata'), 'fingerprint': self.data.get('fingerprint') @@ -125,7 +127,7 @@ def prepareImageYAML(self, input): data.update(self.client.api.images[self.data.get('fingerprint')].get().json()['metadata']) - with open('{}.yaml'.format(self.data.get('fingerprint')), 'w') as yamlFile: + with open('image.yaml', 'w') as yamlFile: yaml.dump(data, yamlFile, default_flow_style=False) @@ -138,27 +140,29 @@ def pushImage(self, input): print ("Image exists. Ready for push.") #Prepare the files for upload. - with open('tmp/images/{0}/{0}.yaml'.format(self.data.get('fingerprint'))) as stream: + with open('tmp/images/{}/image.yaml'.format(self.data.get('fingerprint'))) as stream: yamlData = yaml.load(stream) files = { - 'yaml': open('tmp/images/{0}/{0}.yaml'.format(self.data.get('fingerprint'), 'rb')) + 'yaml': open('tmp/images/{}/image.yaml'.format(self.data.get('fingerprint'), 'rb')) } - response = requests.post('http://postma-echo.com/post', files=files).json() + response = requests.post('http://192.168.100.156:3000/cliAddPackage', files=files, data={'id': self.data.get('fingerprint')}).json() - print (response) + print("yaml uploaded successfully.") - return True + print("Uploading:") + for file in response['filesRequired']: + for key in file: + files = {} + if file[key] != '': + try: + files['file'] = open('tmp/images/{}/{}'.format(self.data.get('fingerprint'), file[key]), 'rb') + requests.post('http://192.168.100.156:3000/cliAddFile', files=files, data={'id': self.data.get('fingerprint')}).json() + print('File {} uploaded successfully'.format(file[key])) + except: + print('File {} does not exist'.format(file[key])) - files = { - 'image': open(yamlData['image'], 'rb'), - 'meta-image': open(yamlData['metadata'], 'rb'), - 'readme': open(yamlData['readme'], 'rb'), - # 'logo': open(yamlData['logo'], 'rb') - } - - r = requests.post('http://postma-echo.com/post', files=files) else: logging.error('Failed to push the image {}'.format(self.data.get('fingerprint'))) logging.exception('Image is not prepared. Please prepare the image using the command lxdui image prep ') From eba4dded2e43a6c50ea69e058c798c48a80d18e9 Mon Sep 17 00:00:00 2001 From: nuhi Date: Fri, 27 Jul 2018 17:03:11 +0200 Subject: [PATCH 008/103] Pull Image Implementation --- app/api/models/LXCImage.py | 32 ++++++++++++++++++++++++++++++++ app/cli/cli.py | 18 ++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index ac3890f0..6040736f 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -6,6 +6,7 @@ import shutil import os import yaml +import tarfile logging = logging.getLogger(__name__) @@ -172,3 +173,34 @@ def pushImage(self, input): logging.error('Failed to push the image {}'.format(self.data.get('fingerprint'))) logging.exception(e) raise ValueError(e) + + + def importImage(self, input): + + logging.info('Importing image {}'.format(self.data.get('fingerprint'))) + + shutil.rmtree('tmp/downloaded/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) + os.makedirs('tmp/downloaded/{}'.format(self.data.get('fingerprint')), exist_ok=True) + + # Download and extract the file + r = requests.get('http://192.168.100.156:3000/cliDownloadRepo/{}'.format(self.data.get('fingerprint')), stream=True) + with open('tmp/downloaded/{}/package.tar.gz'.format(self.data.get('fingerprint')), 'wb') as f: + for chunk in r.iter_content(chunk_size=1024): + if chunk: + f.write(chunk) + + tfile = tarfile.open('tmp/downloaded/{}/package.tar.gz'.format(self.data.get('fingerprint')), 'r:gz') + tfile.extractall('tmp/downloaded/{}/'.format(self.data.get('fingerprint'))) + + p2 = subprocess.Popen(["lxc", "image", "import", + "tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint')), + "tmp/downloaded/{0}/{0}.tar.xz".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) + output_rez = p2.stdout.read() + + #os.remove('tmp/downloaded/{}/package.tar.gz'.format(self.data.get('fingerprint'))) + #os.remove("tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint'))) + #os.remove("tmp/downloaded/{0}/{0}.tar.xz".format(self.data.get('fingerprint'))) + + # self.client.images.create(image_data='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz', + # metadata='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/meta-394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz') + diff --git a/app/cli/cli.py b/app/cli/cli.py index 34531c45..c5ef51a1 100644 --- a/app/cli/cli.py +++ b/app/cli/cli.py @@ -158,6 +158,24 @@ def push(fingerprint, username, password): click.echo("LXDUI failed to push the image.") click.echo(e.__str__()) +import tarfile +@lxdui.command() +@click.argument('fingerprint', nargs=1) +def pull(fingerprint): + try: + input = {} + + image = LXCImage({'fingerprint': fingerprint}) + + print("Downlaoding image with fingerprint {}".format(fingerprint)) + # Import Image - Image registry + image.importImage(input) + + click.echo("Image imported successfully.") + except Exception as e: + click.echo("LXDUI failed to download/import the image.") + click.echo(e.__str__()) + ''' User level group of commands From 29d4478f8d82744894e3f00e553ce9b1ca6148d9 Mon Sep 17 00:00:00 2001 From: nuhi Date: Wed, 1 Aug 2018 16:02:37 +0200 Subject: [PATCH 009/103] Firebase Authentication. Image Import --- app/api/models/LXCImage.py | 33 ++++++++++++++++++++++--- app/api/utils/authentication.py | 1 + app/api/utils/firebaseAuthentication.py | 15 +++++++++++ app/cli/cli.py | 18 ++++++++++++-- 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 app/api/utils/firebaseAuthentication.py diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 6040736f..18c0fab7 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -1,5 +1,6 @@ from app.api.models.LXDModule import LXDModule from app.lib.conf import MetaConf +from app.api.utils.firebaseAuthentication import firebaseLogin import logging import requests import subprocess @@ -134,6 +135,13 @@ def prepareImageYAML(self, input): def pushImage(self, input): try: + #Login + result = firebaseLogin(input.get('username'), input.get('password')) + if result.ok: + token = result.json()['idToken'] + else: + raise ValueError('Login failed: {}'.format(result.json()['error']['message'])) + self.data['fingerprint'] = self.client.images.get(self.data.get('fingerprint')).fingerprint if os.path.exists('tmp/images/{}'.format(self.data.get('fingerprint'))): @@ -148,7 +156,9 @@ def pushImage(self, input): 'yaml': open('tmp/images/{}/image.yaml'.format(self.data.get('fingerprint'), 'rb')) } - response = requests.post('http://192.168.100.156:3000/cliAddPackage', files=files, data={'id': self.data.get('fingerprint')}).json() + headers = {'Authorization': token} + + response = requests.post('http://192.168.100.160:3000/cliAddPackage', headers=headers, files=files, data={'id': self.data.get('fingerprint')}).json() print("yaml uploaded successfully.") @@ -159,7 +169,7 @@ def pushImage(self, input): if file[key] != '': try: files['file'] = open('tmp/images/{}/{}'.format(self.data.get('fingerprint'), file[key]), 'rb') - requests.post('http://192.168.100.156:3000/cliAddFile', files=files, data={'id': self.data.get('fingerprint')}).json() + requests.post('http://192.168.100.160:3000/cliAddFile', headers=headers, files=files, data={'id': self.data.get('fingerprint')}).json() print('File {} uploaded successfully'.format(file[key])) except: print('File {} does not exist'.format(file[key])) @@ -183,7 +193,7 @@ def importImage(self, input): os.makedirs('tmp/downloaded/{}'.format(self.data.get('fingerprint')), exist_ok=True) # Download and extract the file - r = requests.get('http://192.168.100.156:3000/cliDownloadRepo/{}'.format(self.data.get('fingerprint')), stream=True) + r = requests.get('http://192.168.100.160:3000/cliDownloadRepo/{}'.format(self.data.get('fingerprint')), stream=True) with open('tmp/downloaded/{}/package.tar.gz'.format(self.data.get('fingerprint')), 'wb') as f: for chunk in r.iter_content(chunk_size=1024): if chunk: @@ -204,3 +214,20 @@ def importImage(self, input): # self.client.images.create(image_data='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz', # metadata='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/meta-394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz') + + def listHub(self, input): + try: + logging.info('Listing images') + output = "# | Title | Fingerprint | OS | Author\n" + + result = requests.get('http://192.168.100.160:3000/cliListRepos') + i = 1 + for r in result.json(): + output += '{} | {} | {} | {} | {}\n'.format(i, r['title'], r['fingerprint'], r['properties'].get('name'), r['author']['name']) + i+=1 + + return output + except Exception as e: + logging.error('Failed to list images from kuti.io') + logging.exception(e) + raise ValueError(e) \ No newline at end of file diff --git a/app/api/utils/authentication.py b/app/api/utils/authentication.py index 8ec2f6b6..a98a2abb 100644 --- a/app/api/utils/authentication.py +++ b/app/api/utils/authentication.py @@ -7,6 +7,7 @@ from app.api.utils import converters import app.__metadata__ as meta import logging +import requests logging = logging.getLogger(__name__) diff --git a/app/api/utils/firebaseAuthentication.py b/app/api/utils/firebaseAuthentication.py new file mode 100644 index 00000000..8b418f22 --- /dev/null +++ b/app/api/utils/firebaseAuthentication.py @@ -0,0 +1,15 @@ +import requests + +__FIREBASE_USER_VERIFY_SERVICE = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword" +__FIREBASE_API_KEY = "AIzaSyAVIWywqLtxfRLxZfjAauCK6tH-udR_jeo" + +def firebaseLogin(email, passwd): + url = "%s?key=%s" % (__FIREBASE_USER_VERIFY_SERVICE, __FIREBASE_API_KEY) + data = {"email": email, + "password": passwd, + "returnSecureToken": True} + + result = requests.post(url, json=data) + json_result = result + + return json_result \ No newline at end of file diff --git a/app/cli/cli.py b/app/cli/cli.py index c5ef51a1..9d73ed0a 100644 --- a/app/cli/cli.py +++ b/app/cli/cli.py @@ -142,11 +142,13 @@ def prep(fingerprint): @lxdui.command() @click.argument('fingerprint', nargs=1) -@click.option('-u', '--username', nargs=1, help='User Name') +@click.option('-u', '--username', nargs=1, help='Username') @click.option('-p', '--password', nargs=1, help='Password') def push(fingerprint, username, password): try: input = {} + input['username'] = username + input['password'] = password image = LXCImage({'fingerprint': fingerprint}) @@ -158,7 +160,6 @@ def push(fingerprint, username, password): click.echo("LXDUI failed to push the image.") click.echo(e.__str__()) -import tarfile @lxdui.command() @click.argument('fingerprint', nargs=1) def pull(fingerprint): @@ -176,6 +177,19 @@ def pull(fingerprint): click.echo("LXDUI failed to download/import the image.") click.echo(e.__str__()) +@lxdui.command() +def list(): + try: + input = {} + image = LXCImage({'fingerprint': 'a'}) + + # List Images from kuti.io + print (image.listHub(input)) + + except Exception as e: + click.echo("LXDUI failed to list the images from kuti.io.") + click.echo(e.__str__()) + ''' User level group of commands From 0c015b15eaae52cf02502617360a490574481840 Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 2 Aug 2018 11:10:46 +0200 Subject: [PATCH 010/103] List HubImages --- app/api/models/LXCImage.py | 12 ++++---- app/api/models/LXDModule.py | 11 ++++++++ app/ui/blueprint.py | 14 ++++++++- app/ui/templates/images.html | 55 ++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 18c0fab7..4ddb6bd1 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -221,12 +221,14 @@ def listHub(self, input): output = "# | Title | Fingerprint | OS | Author\n" result = requests.get('http://192.168.100.160:3000/cliListRepos') - i = 1 - for r in result.json(): - output += '{} | {} | {} | {} | {}\n'.format(i, r['title'], r['fingerprint'], r['properties'].get('name'), r['author']['name']) - i+=1 - return output + return result.json() + # i = 1 + # for r in result.json(): + # output += '{} | {} | {} | {} | {}\n'.format(i, r['title'], r['fingerprint'], r['properties'].get('name'), r['author']['name']) + # i+=1 + # + # return output except Exception as e: logging.error('Failed to list images from kuti.io') logging.exception(e) diff --git a/app/api/models/LXDModule.py b/app/api/models/LXDModule.py index 5279946a..ff00f6e6 100644 --- a/app/api/models/LXDModule.py +++ b/app/api/models/LXDModule.py @@ -58,6 +58,17 @@ def listNightlyImages(self): logging.exception(e) raise ValueError(e) + def listHubImages(self, input): + try: + logging.info('Listing images') + result = requests.get('http://192.168.100.160:3000/cliListRepos') + + return result.json() + except Exception as e: + logging.error('Failed to list images from kuti.io') + logging.exception(e) + raise ValueError(e) + def detailsRemoteImage(self, alias): try: remoteImagesLink = Config().get(meta.APP_NAME, '{}.images.remote'.format(meta.APP_NAME.lower())) diff --git a/app/ui/blueprint.py b/app/ui/blueprint.py index ec4761db..40870690 100644 --- a/app/ui/blueprint.py +++ b/app/ui/blueprint.py @@ -97,16 +97,19 @@ def images(): profiles = getProfiles() remoteImages = getRemoteImages() nightlyImages = getNightlyImages() + hubImages = getHubImages() remoteImagesLink = Config().get(meta.APP_NAME, '{}.images.remote'.format(meta.APP_NAME.lower())) return render_template('images.html', currentpage='Images', localImages=localImages, remoteImages=remoteImages, nightlyImages=nightlyImages, + hubImages=hubImages, profiles=profiles, jsData={ 'local': json.dumps(localImages), 'remote': json.dumps(remoteImages), - 'nightly': json.dumps(nightlyImages) + 'nightly': json.dumps(nightlyImages), + 'hub': json.dumps(hubImages) }, memory=memory(), lxdui_current_version=VERSION, @@ -141,6 +144,15 @@ def getNightlyImages(): return nightlyImages + +def getHubImages(): + try: + hubImages = LXDModule.listHubImages() + except: + hubImages = [] + + return hubImages + def getProfiles(): try: profiles = LXDModule().listProfiles() diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index 8b43ab84..eeb1f533 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -47,6 +47,10 @@ +
  • + +
  • @@ -287,6 +291,57 @@
    + +
    +
    + +
    +
    +
    + +
    +
    +
    + + +
    +
    + + + + + + + + + + + {% for image in remoteImages %} + + + + + + + + + {% endfor %} + +
    OSDescriptionAliasVerArch
    {{ image.name}}{{ image.name|title }} {{ image.distribution }} ({{image.architecture}}) + {{ image.image }}{{ image.distribution }}{{ image.architecture }}
    +
    +
    +
    +
    From 11c6bac4daa17fedd9520273b76634f9a8b01e8c Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 2 Aug 2018 12:18:39 +0200 Subject: [PATCH 011/103] List Hub Images --- app/__metadata__.py | 1 + app/api/models/LXCImage.py | 22 +++++++++++----------- app/api/models/LXDModule.py | 4 ++-- app/ui/blueprint.py | 5 +++-- app/ui/static/js/app.js | 10 +++++----- app/ui/templates/images.html | 25 +++++++++++++++---------- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/app/__metadata__.py b/app/__metadata__.py index 049d99dc..b92f56c1 100644 --- a/app/__metadata__.py +++ b/app/__metadata__.py @@ -8,6 +8,7 @@ AUTHOR_URL = 'http://www.adaptivescale.com' AUTHOR_EMAIL = 'info@adaptivescale.com' KEYWORDS = 'lxc lxc-containers lxd' +IMAGE_HUB = 'http://192.168.100.166:3000' ''' diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 4ddb6bd1..99267870 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -1,6 +1,7 @@ from app.api.models.LXDModule import LXDModule from app.lib.conf import MetaConf from app.api.utils.firebaseAuthentication import firebaseLogin +from app import __metadata__ as meta import logging import requests import subprocess @@ -158,7 +159,7 @@ def pushImage(self, input): headers = {'Authorization': token} - response = requests.post('http://192.168.100.160:3000/cliAddPackage', headers=headers, files=files, data={'id': self.data.get('fingerprint')}).json() + response = requests.post('{}/cliAddPackage'.format(meta.IMAGE_HUB), headers=headers, files=files, data={'id': self.data.get('fingerprint')}).json() print("yaml uploaded successfully.") @@ -169,7 +170,7 @@ def pushImage(self, input): if file[key] != '': try: files['file'] = open('tmp/images/{}/{}'.format(self.data.get('fingerprint'), file[key]), 'rb') - requests.post('http://192.168.100.160:3000/cliAddFile', headers=headers, files=files, data={'id': self.data.get('fingerprint')}).json() + requests.post('{}/cliAddFile'.format(meta.IMAGE_HUB), headers=headers, files=files, data={'id': self.data.get('fingerprint')}).json() print('File {} uploaded successfully'.format(file[key])) except: print('File {} does not exist'.format(file[key])) @@ -193,7 +194,7 @@ def importImage(self, input): os.makedirs('tmp/downloaded/{}'.format(self.data.get('fingerprint')), exist_ok=True) # Download and extract the file - r = requests.get('http://192.168.100.160:3000/cliDownloadRepo/{}'.format(self.data.get('fingerprint')), stream=True) + r = requests.get('{}/cliDownloadRepo/{}'.format(meta.IMAGE_HUB, self.data.get('fingerprint')), stream=True) with open('tmp/downloaded/{}/package.tar.gz'.format(self.data.get('fingerprint')), 'wb') as f: for chunk in r.iter_content(chunk_size=1024): if chunk: @@ -220,15 +221,14 @@ def listHub(self, input): logging.info('Listing images') output = "# | Title | Fingerprint | OS | Author\n" - result = requests.get('http://192.168.100.160:3000/cliListRepos') + result = requests.get('{}/cliListRepos'.format(meta.IMAGE_HUB)) - return result.json() - # i = 1 - # for r in result.json(): - # output += '{} | {} | {} | {} | {}\n'.format(i, r['title'], r['fingerprint'], r['properties'].get('name'), r['author']['name']) - # i+=1 - # - # return output + i = 1 + for r in result.json(): + output += '{} | {} | {} | {} | {}\n'.format(i, r['title'], r['fingerprint'], r['properties'].get('name'), r['author']['name']) + i+=1 + + return output except Exception as e: logging.error('Failed to list images from kuti.io') logging.exception(e) diff --git a/app/api/models/LXDModule.py b/app/api/models/LXDModule.py index ff00f6e6..a74047b9 100644 --- a/app/api/models/LXDModule.py +++ b/app/api/models/LXDModule.py @@ -58,10 +58,10 @@ def listNightlyImages(self): logging.exception(e) raise ValueError(e) - def listHubImages(self, input): + def listHubImages(self): try: logging.info('Listing images') - result = requests.get('http://192.168.100.160:3000/cliListRepos') + result = requests.get('{}/cliListRepos'.format(meta.IMAGE_HUB)) return result.json() except Exception as e: diff --git a/app/ui/blueprint.py b/app/ui/blueprint.py index 40870690..0a00c29e 100644 --- a/app/ui/blueprint.py +++ b/app/ui/blueprint.py @@ -113,7 +113,8 @@ def images(): }, memory=memory(), lxdui_current_version=VERSION, - remoteImagesLink=remoteImagesLink) + remoteImagesLink=remoteImagesLink, + imageHubLink=meta.IMAGE_HUB) def getLocalImages(): @@ -147,7 +148,7 @@ def getNightlyImages(): def getHubImages(): try: - hubImages = LXDModule.listHubImages() + hubImages = LXDModule().listHubImages() except: hubImages = [] diff --git a/app/ui/static/js/app.js b/app/ui/static/js/app.js index 9096b2a0..d0e724b8 100644 --- a/app/ui/static/js/app.js +++ b/app/ui/static/js/app.js @@ -75,11 +75,11 @@ var App = App || { if(response.status == 401 && window.location!== WEB){ window.location = WEB; } - if((App.helpers.parseJwt(sessionStorage.getItem('authToken')).exp-App.helpers.currentAppTime())<120){ - if(!App.tokenRefreshing){ - App.updateTokenExpiration.call(App); - } - } +// if((App.helpers.parseJwt(sessionStorage.getItem('authToken')).exp-App.helpers.currentAppTime())<120){ +// if(!App.tokenRefreshing){ +// App.updateTokenExpiration.call(App); +// } +// } if(App.ongoingOperation ==0) $('.loader').hide(); if(App.tokenRefreshing){ diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index eeb1f533..2201d44e 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -305,6 +305,11 @@
    +
    + +
    @@ -316,24 +321,24 @@
    class="table table-bordered table-responsive table-fixed width100"> + Title OS Description - Alias Ver Arch + Size - {% for image in remoteImages %} - + {% for image in hubImages %} + - {{ image.name}} - {{ image.name|title }} {{ image.distribution }} ({{image.architecture}}) + {{ image.title or 'NA' }} + {{ image.properties['os'] or 'NA' }} + {{ image.description or 'NA' }} - {{ image.image }} - {{ image.distribution }} - {{ image.architecture }} + {{ image.properties.release or 'NA' }} + {{ image.architecture or 'NA' }} + {{image.size or 'NA' }} {% endfor %} From b9cfe262f9b99fba50646aef0552c087e833cbc1 Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 2 Aug 2018 14:06:35 +0200 Subject: [PATCH 012/103] Import Image --- app/api/controllers/image.py | 14 +++++++++++ app/api/models/LXCImage.py | 39 ++++++++++++++++++++++++------ app/ui/static/js/images.js | 46 +++++++++++++++++++++++++++++++++++- app/ui/templates/images.html | 2 +- 4 files changed, 92 insertions(+), 9 deletions(-) diff --git a/app/api/controllers/image.py b/app/api/controllers/image.py index 1609235e..20ebccbb 100644 --- a/app/api/controllers/image.py +++ b/app/api/controllers/image.py @@ -83,5 +83,19 @@ def downloadImage(): try: client = LXDModule() return response.replySuccess(client.downloadImage(input.get('image')), message='Image {} downloaded successfully.'.format(input.get('image'))) + except ValueError as e: + return response.replyFailed(message=e.__str__()) + +@image_api.route('/hub', methods=['POST']) +@jwt_required() +def downloadHubImage(): + input = request.get_json(silent=True) + validation = doValidate(input) + if validation: + return response.replyFailed(message=validation.message) + input['fingerprint'] = input.get('image') + try: + client = LXCImage(input) + return response.replySuccess(client.importImage(input), message='Image {} downloaded successfully.'.format(input.get('fingerprint'))) except ValueError as e: return response.replyFailed(message=e.__str__()) \ No newline at end of file diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 99267870..18f9705c 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -159,12 +159,18 @@ def pushImage(self, input): headers = {'Authorization': token} - response = requests.post('{}/cliAddPackage'.format(meta.IMAGE_HUB), headers=headers, files=files, data={'id': self.data.get('fingerprint')}).json() + response = requests.post('{}/cliAddPackage'.format(meta.IMAGE_HUB), headers=headers, files=files, data={'id': self.data.get('fingerprint')}) + + if response.ok == False: + logging.error('Failed to push the image {}'.format(self.data.get('fingerprint'))) + raise ValueError( + response.json()['message']) + return print("yaml uploaded successfully.") print("Uploading:") - for file in response['filesRequired']: + for file in response.json()['filesRequired']: for key in file: files = {} if file[key] != '': @@ -203,14 +209,33 @@ def importImage(self, input): tfile = tarfile.open('tmp/downloaded/{}/package.tar.gz'.format(self.data.get('fingerprint')), 'r:gz') tfile.extractall('tmp/downloaded/{}/'.format(self.data.get('fingerprint'))) - p2 = subprocess.Popen(["lxc", "image", "import", + with open('tmp/downloaded/{}/image.yaml'.format(self.data.get('fingerprint'))) as stream: + yamlData = yaml.load(stream) + + + if os.path.exists("tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint'))) and os.path.exists("tmp/downloaded/{0}/{0}.tar.xz".format(self.data.get('fingerprint'))): + p2 = subprocess.Popen(["lxc", "image", "import", "tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint')), "tmp/downloaded/{0}/{0}.tar.xz".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) - output_rez = p2.stdout.read() + output_rez = p2.stdout.read() + + elif os.path.exists("tmp/downloaded/{0}/meta-{0}.tar.gz".format(self.data.get('fingerprint'))) and os.path.exists("tmp/downloaded/{0}/{0}.tar.gz".format(self.data.get('fingerprint'))): + p2 = subprocess.Popen(["lxc", "image", "import", + "tmp/downloaded/{0}/meta-{0}.tar.gz".format(self.data.get('fingerprint')), + "tmp/downloaded/{0}/{0}.tar.gz".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) + output_rez = p2.stdout.read() + + elif os.path.exists("tmp/downloaded/{0}/meta-{0}.tar.gz".format(self.data.get('fingerprint'))) == False and os.path.exists("tmp/downloaded/{0}/{0}.tar.gz".format(self.data.get('fingerprint'))): + p2 = subprocess.Popen(["lxc", "image", "import", + "tmp/downloaded/{0}/{0}.tar.gz".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) + output_rez = p2.stdout.read() + + elif os.path.exists("tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint'))) == False and os.path.exists("tmp/downloaded/{0}/{0}.tar.gz".format(self.data.get('fingerprint'))): + p2 = subprocess.Popen(["lxc", "image", "import", + "tmp/downloaded/{0}/{0}.tar.xz".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) + output_rez = p2.stdout.read() - #os.remove('tmp/downloaded/{}/package.tar.gz'.format(self.data.get('fingerprint'))) - #os.remove("tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint'))) - #os.remove("tmp/downloaded/{0}/{0}.tar.xz".format(self.data.get('fingerprint'))) + #shutil.rmtree('tmp/downloaded/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) # self.client.images.create(image_data='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz', # metadata='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/meta-394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz') diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index 87eb37c3..40af385e 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -7,6 +7,7 @@ App.images = App.images || { tableLocal: null, tableRemote: null, tableNightly: null, + tableHub: null, tableSettings: { searching:true, responsive: { @@ -54,11 +55,13 @@ App.images = App.images || { $('#btnLocalImages').on('click', $.proxy(this.switchView, this, 'localList')); $('#btnRemoteImages').on('click', $.proxy(this.switchView, this, 'remoteList')); $('#btnNightlyImages').on('click', $.proxy(this.switchView, this, 'nightlyList')); + $('#btnHubImages').on('click', $.proxy(this.switchView, this, 'hubList')); $('#buttonUpdate').on('click', $.proxy(this.getData, this)); $('#buttonDelete').on('click', $.proxy(this.doDeleteLocalImages, this)); $('#buttonDownload').on('click', $.proxy(this.doDownload, this)); $('#buttonDownloadNightly').on('click', $.proxy(this.doDownload, this)); + $('#buttonDownloadHub').on('click', $.proxy(this.doDownload, this)); $('#buttonLaunchContainers').on('click', $.proxy(this.launchContainers, this)); $('#buttonBack').on('click', $.proxy(this.switchView, this, 'localList')); $('.image').on('click', $.proxy(this.setActive, this)); @@ -69,6 +72,7 @@ App.images = App.images || { this.initLocalTable(); this.initRemoteTable(); this.initNightlyTable(); + this.initHubTable(); $('#selectAllLocal').on('change', $.proxy(this.toggleSelectAll, this, 'Local')); $('#selectAllRemote').on('change', $.proxy(this.toggleSelectAll, this, 'Remote')); this.itemTemplate = $('.itemTemplate').clone(); @@ -143,6 +147,14 @@ App.images = App.images || { this.tableNightly.on('select', $.proxy(this.onItemSelectChange, this)); this.tableNightly.on('deselect', $.proxy(this.onItemSelectChange, this)); }, + initHubTable: function() { + this.tableHub =$('#tableImagesHub').DataTable(App.mergeProps(this.tableSettings, {rowId: 'fingerprint'})); + this.setHubTableEvents(); + }, + setHubTableEvents: function() { + this.tableHub.on('select', $.proxy(this.onItemSelectChange, this)); + this.tableHub.on('deselect', $.proxy(this.onItemSelectChange, this)); + }, filterRemoteTable: function(e) { this.tableRemote.search(e.target.value).draw(); }, @@ -172,6 +184,13 @@ App.images = App.images || { $('#selectAllNightly').prop('checked',this.tableNightly.rows({selected:true}).count()==this.tableNightly.rows().count()); return; } + if(this.activeTab=='hub'){ + var state = this.tableHub.rows({selected:true}).count()>0 + var visibility= !state?'attr':'removeAttr'; + $('#buttonDownloadHub')[visibility]('disabled', 'disabled'); + $('#selectAllHub').prop('checked',this.tableHub.rows({selected:true}).count()==this.tableHub.rows().count()); + return; + } }, doDeleteLocalImages: function(){ this.tableLocal.rows( { selected: true } ).data().map(function(row){ @@ -220,6 +239,20 @@ App.images = App.images || { }); this.tableRemote.row('#'+row['image']).remove().draw(false); }.bind(this)); + } else if(activeTab=='hub') { + this.tableHub.rows({selected: true}).data().map(function(row){ + $.ajax({ + url:App.baseAPI+'image/hub', + type: 'POST', + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify({ + image:row['fingerprint'] + }), + success: $.proxy(this.onDownloadSuccess, this, row['fingerprint']) + }); + //this.tableHub.row('#'+row['image']).remove().draw(false); + }.bind(this)); } }, onDownloadSuccess: function(imageName, response){ @@ -241,10 +274,14 @@ App.images = App.images || { return $.get(App.baseAPI+'image/remote', $.proxy(this.getDataSuccess, this)); if(this.activeTab=='nightly') return $.get(App.baseAPI+'image/remote/nightly/list', $.proxy(this.getDataSuccess, this)); + if(this.activeTab=='hub') + return $.get(App.baseAPI+'image/remote/hub/list', $.proxy(this.getDataSuccess, this)); }, activateScreen: function(screen){ this.tableLocal.rows({selected:true}).deselect(); this.tableRemote.rows({selected:true}).deselect(); + this.tableNightly.rows({selected:true}).deselect(); + this.tableHub.rows({selected:true}).deselect(); $('.mg-bottom15').show(); if(screen==='local'){ $('#tableImagesLocalWrapper').show(); @@ -267,10 +304,14 @@ App.images = App.images || { this.activeTab = 'remote'; return; } - if(screen=='nightly') { + if(screen==='nightly') { this.activeTab = 'nightly'; return; } + if(screen==='hub') { + this.activeTab = 'hub'; + return; + } }, updateLocalTable: function(jsonData){ this.data = jsonData; @@ -424,6 +465,9 @@ App.images = App.images || { if(view=='nightlyList'){ return this.activateScreen('nightly'); } + if (view=='hubList') { + return this.activateScreen('hub'); + } $('#buttonLaunchContainers').hide(); $('#buttonDelete').hide(); $('#rawJSONImages').hide(); diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index 2201d44e..0e9df3c9 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -302,7 +302,7 @@
    -
    From f36546fb7f42e45b57351b239e319c5ad48c3d5a Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 2 Aug 2018 14:07:08 +0200 Subject: [PATCH 013/103] Import Image - Remove file at the end --- app/api/models/LXCImage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 18f9705c..558583d8 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -235,7 +235,7 @@ def importImage(self, input): "tmp/downloaded/{0}/{0}.tar.xz".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) output_rez = p2.stdout.read() - #shutil.rmtree('tmp/downloaded/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) + shutil.rmtree('tmp/downloaded/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) # self.client.images.create(image_data='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz', # metadata='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/meta-394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz') From 926132dc85a208d476970a152d3065cea0b3a49f Mon Sep 17 00:00:00 2001 From: nuhi Date: Mon, 6 Aug 2018 11:52:23 +0200 Subject: [PATCH 014/103] Change IMAGE_HUB to hub.kuti.io --- app/__metadata__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__metadata__.py b/app/__metadata__.py index b92f56c1..916e7de9 100644 --- a/app/__metadata__.py +++ b/app/__metadata__.py @@ -8,7 +8,7 @@ AUTHOR_URL = 'http://www.adaptivescale.com' AUTHOR_EMAIL = 'info@adaptivescale.com' KEYWORDS = 'lxc lxc-containers lxd' -IMAGE_HUB = 'http://192.168.100.166:3000' +IMAGE_HUB = 'http://hub.kuti.io' ''' From 1675f70ce7b95194006f5642b8bfdecacafb1b6d Mon Sep 17 00:00:00 2001 From: nuhi Date: Mon, 6 Aug 2018 13:24:11 +0200 Subject: [PATCH 015/103] Fix Modal Bug for Move,Clone,Snapshot & Export Container --- app/ui/static/js/container.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/ui/static/js/container.js b/app/ui/static/js/container.js index d6f63fae..f6204ef7 100644 --- a/app/ui/static/js/container.js +++ b/app/ui/static/js/container.js @@ -408,7 +408,7 @@ App.containers = App.containers || { onCloneSuccess: function(response){ console.log(response); console.log('clonedSuccess:', 'TODO - add alert and refresh local data'); - $("#myModal").modal("hide"); + $("#cloneContainerModal").modal("hide"); location.reload(); }, moveContainer: function() { @@ -426,7 +426,7 @@ App.containers = App.containers || { onMoveSuccess: function(response){ console.log(response); console.log('Moved Success:', 'TODO - add alert and refresh local data'); - $("#myModal").modal("hide"); + $("#moveContainerModal").modal("hide"); location.reload(); }, exportContainer: function() { @@ -445,7 +445,7 @@ App.containers = App.containers || { onExportSuccess: function(response){ console.log(response); console.log('Export Success:', 'TODO - add alert and refresh local data'); - $("#myModal").modal("hide"); + $("#exportContainerModal").modal("hide"); }, snapshotContainer: function() { $.ajax({ @@ -462,7 +462,7 @@ App.containers = App.containers || { onSnapshotSuccess: function(response){ console.log(response); console.log('Snapshot Success:', 'TODO - add alert and refresh local data'); - $("#myModal").modal("hide"); + $("#snapshotContainerModal").modal("hide"); }, toggleSelectAll(event){ if(event.target.checked){ From 05d7b2f272c6747d58c4b81f32ad21586d0c196c Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 9 Aug 2018 23:38:23 +0200 Subject: [PATCH 016/103] Fix - Container Actions inside container-details --- app/ui/static/js/container-details.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/ui/static/js/container-details.js b/app/ui/static/js/container-details.js index c5a18ac6..1e1a07fd 100644 --- a/app/ui/static/js/container-details.js +++ b/app/ui/static/js/container-details.js @@ -66,12 +66,18 @@ App.containerDetails = App.containerDetails || { $('#buttonExportContainerDetail').on('click', $.proxy(this.showExportContainer, this)); $('#buttonSnapshotContainerDetail').on('click', $.proxy(this.showSnapshotContainer, this)); - $('#cloneContainerForm').on('submit', $.proxy(this.cloneContainerDetail, this)); - $('#moveContainerForm').on('submit', $.proxy(this.moveContainerDetail, this)); - $('#exportContainerForm').on('submit', $.proxy(this.exportContainerDetail, this)); - $('#snapshotContainerForm').on('submit', $.proxy(this.snapshotContainerDetail, this)); + $('#cloneContainerSubmit').on('submit', $.proxy(this.cloneContainerDetail, this)); + $('#moveContainerSubmit').on('submit', $.proxy(this.moveContainerDetail, this)); + $('#exportContainerSubmit').on('submit', $.proxy(this.exportContainerDetail, this)); + $('#snapshotContainerSubmit').on('submit', $.proxy(this.snapshotContainerDetail, this)); $('#containerFromSnapshotForm').on('submit', $.proxy(this.newContainerFromSnapshotDetail, this)); + $('#cloneContainerSubmit').on('click', $.proxy(this.cloneContainerDetail, this)); + $('#moveContainerSubmit').on('click', $.proxy(this.moveContainerDetail, this)); + $('#exportContainerSubmit').on('click', $.proxy(this.exportContainerDetail, this)); + $('#snapshotContainerSubmit').on('click', $.proxy(this.snapshotContainerDetail, this)); + $('#containerFromSnapshotForm').on('click', $.proxy(this.newContainerFromSnapshotDetail, this)); + $('.profileTag').on('click', $.proxy(this.deleteProfile, this)); $('#buttonAdd').on('click', $.proxy(this.onAddProfileClick, this)); $('.formModifier').on('click', $.proxy(this.formChanged, this)); From 45d55cfd8a8a42b69a9c8173adc74e7698c19462 Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 9 Aug 2018 23:42:18 +0200 Subject: [PATCH 017/103] Update text all container will be updated on network update --- app/ui/templates/container-details.html | 2 +- app/ui/templates/network.html | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/ui/templates/container-details.html b/app/ui/templates/container-details.html index f6eef292..01f0be1c 100644 --- a/app/ui/templates/container-details.html +++ b/app/ui/templates/container-details.html @@ -607,7 +607,7 @@
    diff --git a/app/ui/templates/network.html b/app/ui/templates/network.html index ae6bafac..7c619aa6 100644 --- a/app/ui/templates/network.html +++ b/app/ui/templates/network.html @@ -127,9 +127,7 @@
    Config:
    />
    - + @@ -139,6 +137,9 @@
    Config:
    + From 28872c30d53ed4b978ae1bf8b3378c0b7f7aaf93 Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 9 Aug 2018 23:53:33 +0200 Subject: [PATCH 018/103] Set the default CPU architecture for images based on LXD Host --- app/ui/blueprint.py | 4 +++- app/ui/static/js/images.js | 6 ++++++ app/ui/templates/images.html | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/ui/blueprint.py b/app/ui/blueprint.py index 0a00c29e..8e39d250 100644 --- a/app/ui/blueprint.py +++ b/app/ui/blueprint.py @@ -6,6 +6,7 @@ from app.__metadata__ import VERSION import json import os +import platform uiPages = Blueprint('uiPages', __name__, template_folder='./templates', static_folder='./static') @@ -114,7 +115,8 @@ def images(): memory=memory(), lxdui_current_version=VERSION, remoteImagesLink=remoteImagesLink, - imageHubLink=meta.IMAGE_HUB) + imageHubLink=meta.IMAGE_HUB, + architecture=platform.machine()) def getLocalImages(): diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index 40af385e..40c1c449 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -83,6 +83,12 @@ App.images = App.images || { $('#architectureNightly').on('change', $.proxy(this.filterNightlyTable, this)); this.initKeyValuePairs(); + + if (architecture == 'x86_64') { + architecture = 'amd64'; + } + this.tableRemote.search(architecture).draw(); + this.tableNightly.search(architecture).draw(); }, convertImageSize:function(index, item){ $(item).text(App.formatBytes($(item).text())); diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index 0e9df3c9..70604d65 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -3,6 +3,7 @@ From 3dfd4cefb6d2fb69c64785661e136cba394e2662 Mon Sep 17 00:00:00 2001 From: nuhi Date: Fri, 10 Aug 2018 00:09:36 +0200 Subject: [PATCH 019/103] When no local images exists, the nightly tab should be displayed by default --- app/ui/static/js/images.js | 8 ++++++++ app/ui/templates/images.html | 1 + 2 files changed, 9 insertions(+) diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index 40c1c449..8e53d25a 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -89,6 +89,14 @@ App.images = App.images || { } this.tableRemote.search(architecture).draw(); this.tableNightly.search(architecture).draw(); + + + console.log(localImagesLength); + if (localImagesLength == 0){ + this.switchView('nightlyList'); + $('.nav-tabs li:eq(1) a').tab('show'); + //$('#btnNightlyImages').on('click', $.proxy(this.switchView, this, 'nightlyList')); + } }, convertImageSize:function(index, item){ $(item).text(App.formatBytes($(item).text())); diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index 70604d65..4b146c27 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -4,6 +4,7 @@ var constLocalImages = {{jsData.local|safe}} || {}; var constRemoteImages = {{jsData.remote|safe}} || {}; var architecture = '{{architecture}}'; + var localImagesLength = {{localImages|length}} From bb2255e8819b7af277e337ed710c8291e62347bc Mon Sep 17 00:00:00 2001 From: nuhi Date: Fri, 10 Aug 2018 00:32:35 +0200 Subject: [PATCH 020/103] When downloading an image from Hub add the tile as alias --- app/api/models/LXCImage.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 558583d8..c5f29e57 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -237,6 +237,9 @@ def importImage(self, input): shutil.rmtree('tmp/downloaded/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) + image = self.client.images.get(self.data.get('fingerprint')) + image.add_alias(yamlData['title'], yamlData['title']) + # self.client.images.create(image_data='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz', # metadata='tmp/images/394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd/meta-394986c986a778f64903fa043a3e280bda41e4793580b22c5d991ec948ced6dd.tar.xz') From d9adb06d1225037d5e608d03da84bda45e6eced1 Mon Sep 17 00:00:00 2001 From: nuhi Date: Fri, 10 Aug 2018 00:40:57 +0200 Subject: [PATCH 021/103] Update cli - Add image group --- app/cli/cli.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/app/cli/cli.py b/app/cli/cli.py index 9d73ed0a..734c391c 100644 --- a/app/cli/cli.py +++ b/app/cli/cli.py @@ -120,10 +120,15 @@ def status(): click.echo(' {} : {}'.format(k, v)) -#TODO Create Image Registry Group -@lxdui.command() +@click.group() +def image(): + """Work with image registry""" + pass + +@image.command() @click.argument('fingerprint', nargs=1) def prep(fingerprint): + """Prepare an image for upload""" try: input = {} image = LXCImage({'fingerprint': fingerprint}) @@ -140,11 +145,12 @@ def prep(fingerprint): click.echo("LXDUI failed to prepare the image.") click.echo(e.__str__()) -@lxdui.command() +@image.command() @click.argument('fingerprint', nargs=1) @click.option('-u', '--username', nargs=1, help='Username') @click.option('-p', '--password', nargs=1, help='Password') def push(fingerprint, username, password): + """Push an image to hub.kuti.io""" try: input = {} input['username'] = username @@ -160,9 +166,10 @@ def push(fingerprint, username, password): click.echo("LXDUI failed to push the image.") click.echo(e.__str__()) -@lxdui.command() +@image.command() @click.argument('fingerprint', nargs=1) def pull(fingerprint): + """Pull an image from hub.kuti.io""" try: input = {} @@ -177,8 +184,9 @@ def pull(fingerprint): click.echo("LXDUI failed to download/import the image.") click.echo(e.__str__()) -@lxdui.command() +@image.command() def list(): + """List images from hub.kuti.io""" try: input = {} image = LXCImage({'fingerprint': 'a'}) @@ -363,6 +371,7 @@ def delete(): lxdui.add_command(user) lxdui.add_command(config) lxdui.add_command(cert) +lxdui.add_command(image) if __name__ == '__main__': From 6c52e655c123998125394d6c223f2adb69442c15 Mon Sep 17 00:00:00 2001 From: Ajdin Date: Fri, 10 Aug 2018 16:19:54 +0200 Subject: [PATCH 022/103] added: Containers - show New instance instead of empty list --- app/ui/templates/containers.html | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/app/ui/templates/containers.html b/app/ui/templates/containers.html index b9b61e5f..8844873f 100644 --- a/app/ui/templates/containers.html +++ b/app/ui/templates/containers.html @@ -33,14 +33,17 @@ Unfreeze - - - New Instance - + {% if containers|length>0 %} + + + New Instance + + {% endif %}
    + {% if containers|length > 0 %} @@ -123,6 +126,19 @@ {% endfor %}
    + {% else %} +
    +
    +
    +
    No instances available.
    + + + Create New Instance + +
    +
    +
    + {% endif %}
    From b3931eb31c83d76318754bfa10c3e584ac55d2be Mon Sep 17 00:00:00 2001 From: Ajdin Date: Fri, 10 Aug 2018 16:47:26 +0200 Subject: [PATCH 023/103] added: Image-details toastr and loading when downloading --- app/ui/static/js/images.js | 4 +++- app/ui/templates/images.html | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index 40af385e..620723a2 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -210,7 +210,9 @@ App.images = App.images || { }, doDownload: function(){ activeTab = this.activeTab; - $('#modalDownloadButton').attr('disabled', 'disabled'); + $('#modalDownloadButton').hide(); + toastr.success('Image is being downloaded','Downloading'); + $('.imageDownloadLoader').show(); if(activeTab=='nightly') { this.tableNightly.rows({selected: true}).data().map(function(row){ $.ajax({ diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index 0e9df3c9..be7e1c6c 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -525,7 +525,7 @@
    Key :
    + + From 7f3a7418a59803db035007c199809250c21ffcec Mon Sep 17 00:00:00 2001 From: shasivar Date: Fri, 17 Aug 2018 16:14:57 +0200 Subject: [PATCH 026/103] Added markdown editor --- app/ui/static/js/images.js | 5 +++++ app/ui/templates/images.html | 14 +++++++------- app/ui/templates/index.html | 5 ++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index 7466d3eb..8ad10d2a 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -83,6 +83,11 @@ App.images = App.images || { $('#architectureRemote').on('change', $.proxy(this.filterRemoteTable, this)); $('#architectureNightly').on('change', $.proxy(this.filterNightlyTable, this)); + new SimpleMDE({ + element: document.getElementById("documentation"), + spellChecker: false, + hideIcons: ["side-by-side", "fullscreen"], + }); this.initKeyValuePairs(); if (architecture == 'x86_64') { diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index b860ee6a..a65e3bbc 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -587,7 +587,7 @@ + From b69965719cf17857220f9b075193bcfb08648809 Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 23 Aug 2018 18:42:28 +0200 Subject: [PATCH 028/103] UI - Publish Image --- app/api/controllers/image.py | 20 ++++++++++++++++++++ app/ui/static/js/images.js | 27 ++++++++++++++++----------- app/ui/templates/images.html | 9 +++------ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/app/api/controllers/image.py b/app/api/controllers/image.py index 20ebccbb..9f64f9ee 100644 --- a/app/api/controllers/image.py +++ b/app/api/controllers/image.py @@ -86,6 +86,26 @@ def downloadImage(): except ValueError as e: return response.replyFailed(message=e.__str__()) + +@image_api.route('/hub/publish', methods=['POST']) +@jwt_required() +def publishHubImage(): + input = request.get_json(silent=True) + # validation = doValidate(input) + # if validation: + # return response.replyFailed(message=validation.message) + #input['fingerprint'] = input.get('fingerprint') + input['username'] = 'nb18411@seeu.edu.mk' + input['password'] = 'nushi123' + try: + client = LXCImage(input) + client.exportImage(input) + client.pushImage(input) + return response.replySuccess(message='Image {} pushed successfully.'.format(input.get('fingerprint'))) + except ValueError as e: + return response.replyFailed(message=e.__str__()) + + @image_api.route('/hub', methods=['POST']) @jwt_required() def downloadHubImage(): diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index ae000f8c..286b0caf 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -85,7 +85,7 @@ App.images = App.images || { $('#architectureNightly').on('change', $.proxy(this.filterNightlyTable, this)); this.publishImageForm = $('#publishImageToHubForm'); - this.publishImageForm.on('click', $.proxy(this.doPublishImage, this)); + this.publishImageForm.on('submit', $.proxy(this.doPublishImage, this)); new SimpleMDE({ element: document.getElementById("documentation"), @@ -729,22 +729,27 @@ App.images = App.images || { }, doPublishImage: function(e){ e.preventDefault(); - console.log("i am here"); + var image = this.getImageByFingerPrint(this.data, this.tableLocal.rows({selected:true}).data()[0]['fingerprint']); + var tempJSON = this.publishImageForm.serializeJSON(); + tempJSON['fingerprint'] = image.fingerprint; console.log(tempJSON); -// $.ajax({ -// url: App.baseAPI +'container/', -// type:'POST', -// dataType:'json', -// contentType: 'application/json', -// data: JSON.stringify(tempJSON), -// success: $.proxy(this.onCreateSuccess, this), -// error: $.proxy(this.onCreateFailed, this) -// }); + $.ajax({ + url: App.baseAPI +'image/hub/publish', + type:'POST', + dataType:'json', + contentType: 'application/json', + data: JSON.stringify(tempJSON), + success: $.proxy(this.onPublishSuccess, this), + error: $.proxy(this.onPublishFailed, this) + }); }, onPublishSuccess: function(response){ console.log('success'); }, + onPublishFailed: function(response) { + console.log('failed'); + } } \ No newline at end of file diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index 03231aee..7b9ad598 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -655,16 +655,13 @@
    Image
    - +
    - +
    - +
    From e94a5787d107633cf9b49a80e412fbcc26e6d365 Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 23 Aug 2018 21:44:08 +0200 Subject: [PATCH 029/103] Integrate Hub Publish from UI --- app/api/controllers/image.py | 12 ++++++------ app/api/models/LXCImage.py | 22 +++++++++++++--------- app/ui/static/js/images.js | 21 +++++++++++++++------ 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/app/api/controllers/image.py b/app/api/controllers/image.py index 9f64f9ee..1602b4e5 100644 --- a/app/api/controllers/image.py +++ b/app/api/controllers/image.py @@ -87,19 +87,19 @@ def downloadImage(): return response.replyFailed(message=e.__str__()) +import json @image_api.route('/hub/publish', methods=['POST']) @jwt_required() def publishHubImage(): - input = request.get_json(silent=True) - # validation = doValidate(input) - # if validation: - # return response.replyFailed(message=validation.message) - #input['fingerprint'] = input.get('fingerprint') + #input = request.get_json(silent=True) + input = json.loads(request.form.get('input')) + logo = request.files['logo'] + input['logo'] = logo.filename input['username'] = 'nb18411@seeu.edu.mk' input['password'] = 'nushi123' try: client = LXCImage(input) - client.exportImage(input) + client.exportImage(input, logo) client.pushImage(input) return response.replySuccess(message='Image {} pushed successfully.'.format(input.get('fingerprint'))) except ValueError as e: diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index c5f29e57..4d924bfd 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -65,8 +65,9 @@ def deleteImage(self): raise ValueError(e) #TODO Refactor this part - def exportImage(self, input): + def exportImage(self, input, logo=None): try: + print(input) #Check if image exists & Update the fingerprint with the full fingerprint self.data['fingerprint'] = self.client.images.get(self.data.get('fingerprint')).fingerprint @@ -98,10 +99,13 @@ def exportImage(self, input): #TODO Prepare README.md file = open('tmp/images/{}/README.md'.format(self.data.get('fingerprint')), 'a') - file.write('#README') + file.write('#README\n') + file.write(input.get('documentation')) file.close() #TODO Prepare Logo + if logo: + logo.save('tmp/images/{}/{}'.format(self.data.get('fingerprint'), logo.filename)) return MetaConf().getConfRoot() + '/tmp/images/{}'.format(self.data.get('fingerprint')) except Exception as e: @@ -112,17 +116,17 @@ def exportImage(self, input): def prepareImageYAML(self, input): if input.get('metadata') == None: input['metadata'] = '' data = { - 'title': '', - 'description': '', + 'title': input.get('imageAlias', ''), + 'description': input.get('imageDescription', ''), 'author': { - 'name': '', + 'name': input.get('authorName', ''), 'alias': '', - 'email': '' + 'email': input.get('authorEmail', '') }, - 'license': '', - 'readme': 'README.md'.format(self.data.get('fingerprint')), + 'license': input.get('license', ''), + 'readme': 'README.md', 'tags': [], - 'logo': 'logo.png'.format(self.data.get('fingerprint')), + 'logo': input.get('logo', ''), 'image': input.get('image'), 'metadata': input.get('metadata'), 'fingerprint': self.data.get('fingerprint') diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index 286b0caf..41c5ef63 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -46,6 +46,7 @@ App.images = App.images || { publishImageForm: null, itemTemplate:null, rawJson:null, + simplemde:null, init: function(opts){ this.data = constLocalImages || []; this.remoteData = constRemoteImages || []; @@ -87,7 +88,7 @@ App.images = App.images || { this.publishImageForm = $('#publishImageToHubForm'); this.publishImageForm.on('submit', $.proxy(this.doPublishImage, this)); - new SimpleMDE({ + this.simplemde = new SimpleMDE({ element: document.getElementById("documentation"), spellChecker: false, hideIcons: ["side-by-side", "fullscreen"], @@ -731,23 +732,31 @@ App.images = App.images || { e.preventDefault(); var image = this.getImageByFingerPrint(this.data, this.tableLocal.rows({selected:true}).data()[0]['fingerprint']); + var logoImg = $('input[name="logo"]').get(0).files[0]; + var tempJSON = this.publishImageForm.serializeJSON(); + + var formData = new FormData(); + formData.append('logo', logoImg); + tempJSON['fingerprint'] = image.fingerprint; + tempJSON['documentation'] = this.simplemde.value(); - console.log(tempJSON); + formData.append('input', JSON.stringify(tempJSON)); $.ajax({ url: App.baseAPI +'image/hub/publish', type:'POST', - dataType:'json', - contentType: 'application/json', - data: JSON.stringify(tempJSON), + processData: false, + contentType: false, + enctype: 'multipart/form-data', + data: formData, success: $.proxy(this.onPublishSuccess, this), error: $.proxy(this.onPublishFailed, this) }); }, onPublishSuccess: function(response){ - console.log('success'); + location.reaload(); }, onPublishFailed: function(response) { console.log('failed'); From 57112908242ad2911a7d1d1c9b376a999c6a176a Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 23 Aug 2018 22:26:18 +0200 Subject: [PATCH 030/103] Fix Architecture Filter. Bug Fixes --- app/api/models/LXCImage.py | 4 ++-- app/ui/static/js/images.js | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 4d924bfd..3a8d0d5c 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -67,7 +67,6 @@ def deleteImage(self): #TODO Refactor this part def exportImage(self, input, logo=None): try: - print(input) #Check if image exists & Update the fingerprint with the full fingerprint self.data['fingerprint'] = self.client.images.get(self.data.get('fingerprint')).fingerprint @@ -129,7 +128,8 @@ def prepareImageYAML(self, input): 'logo': input.get('logo', ''), 'image': input.get('image'), 'metadata': input.get('metadata'), - 'fingerprint': self.data.get('fingerprint') + 'fingerprint': self.data.get('fingerprint'), + 'public': True } data.update(self.client.api.images[self.data.get('fingerprint')].get().json()['metadata']) diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index 41c5ef63..dce91813 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -101,6 +101,9 @@ App.images = App.images || { this.tableRemote.search(architecture).draw(); this.tableNightly.search(architecture).draw(); + $('#architectureNightly').val(architecture); + $('#architectureRemote').val(architecture); + if (localImagesLength == 0){ this.switchView('nightlyList'); $('.nav-tabs li:eq(1) a').tab('show'); @@ -756,7 +759,7 @@ App.images = App.images || { }); }, onPublishSuccess: function(response){ - location.reaload(); + location.reload(); }, onPublishFailed: function(response) { console.log('failed'); From b5a3324111ef6d89f6a6afb08ec12dd673dff44d Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 23 Aug 2018 22:45:41 +0200 Subject: [PATCH 031/103] Fix local and hub showing nan for siz --- app/ui/static/js/images.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index dce91813..e2a72be2 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -448,7 +448,7 @@ App.images = App.images || { ] })); this.tableNightly.search(architecture).draw(); - $('.imageSize').each(this.convertImageSize); + $('#tableImagesNightly .imageSize').each(this.convertImageSize); }, getDataSuccess: function(response){ this.setLoading(false); From e053a037d261ec5c29af30ae7e3c04fe0e37c9cf Mon Sep 17 00:00:00 2001 From: shasivar Date: Fri, 24 Aug 2018 14:29:30 +0200 Subject: [PATCH 032/103] Added attribute on propertiesmessages --- app/ui/static/js/properties.js | 136 +++++++++++++++++++++------------ app/ui/templates/images.html | 2 +- 2 files changed, 88 insertions(+), 50 deletions(-) diff --git a/app/ui/static/js/properties.js b/app/ui/static/js/properties.js index 03b8cc6d..d82efccf 100644 --- a/app/ui/static/js/properties.js +++ b/app/ui/static/js/properties.js @@ -3,187 +3,225 @@ App.properties = App.properties || { 'boot.autostart': { disabled: true, description: 'Always start the container when LXD starts (if not set, restore last state)', - type: 'boolean' + type: 'boolean', + valueDescription: '0 for false 1 for true' }, 'boot.autostart.delay': { disabled: true, description: 'Number of seconds to wait after the container started before starting the next one', - type: 'integer' + type: 'integer', + valueDescription: '' }, 'boot.autostart.priority': { disabled: true, - description: 'What order to start the containers in (starting with highest)', - type: 'integer' + description: 'What order to start the containers in', + type: 'integer', + valueDescription: '(starting with highest)' }, 'boot.host_shutdown_timeout': { disabled: true, description: 'Seconds to wait for container to shutdown before it is force stopped', - type: 'integer' + type: 'integer', + valueDescription: '' }, 'boot.stop.priority': { disabled: true, - description: 'What order to shutdown the containers (starting with highest)', - type: 'integer' + description: 'What order to shutdown the containers', + type: 'integer', + valueDescription: '(starting with highest)' }, 'environment.*': { disabled: true, description: 'key/value environment variables to export to the container and set on exec', - type: 'string' + type: 'string', + valueDescription: '' }, 'limits.cpu': { disabled: true, description: 'Number or range of CPUs to expose to the container', - type: 'string' + type: 'string', + valueDescription: '' }, 'limits.cpu.allowance': { disabled: true, - description: 'How much of the CPU can be used. Can be a percentage (e.g. 50%) for a soft limit or hard a chunk of time (25ms/100ms)', - type: 'string' + description: 'How much of the CPU can be used', + type: 'string', + valueDescription: 'Can be a percentage (e.g. 50%) for a soft limit or hard a chunk of time (25ms/100ms)' }, 'limits.cpu.priority': { disabled: true, - description: 'CPU scheduling priority compared to other containers sharing the same CPUs (overcommit) (integer between 0 and 10)', - type: 'integer' + description: 'CPU scheduling priority compared to other containers sharing the same CPUs (overcommit)', + type: 'integer', + valueDescription: '(integer between 0 and 10)' }, 'limits.disk.priority': { disabled: true, - description: 'When under load, how much priority to give to the container\'s I/O requests (integer between 0 and 10)', - type: 'integer' + description: 'When under load, how much priority to give to the container\'s I/O requests', + type: 'integer', + valueDescription: '(integer between 0 and 10)' }, 'limits.kernel.*': { disabled: true, - description: 'This limits kernel resources per container (e.g. number of open files)', - type: 'string' + description: 'This limits kernel resources per container', + type: 'string', + valueDescription: '(e.g. number of open files)' }, 'limits.memory': { disabled: true, - description: 'Percentage of the host\'s memory or fixed value in bytes (supports kB, MB, GB, TB, PB and EB suffixes)', - type: 'string' + description: 'Percentage of the host\'s memory or fixed value in bytes', + type: 'string', + valueDescription: '(supports kB, MB, GB, TB, PB and EB suffixes)' }, 'limits.memory.enforce': { disabled: true, description: 'If hard, container can\'t exceed its memory limit. If soft, the container can exceed its memory limit when extra host memory is available.', - type: 'string' + type: 'string', + valueDescription: '' }, 'limits.memory.swap': { disabled: true, description: 'Whether to allow some of the container\'s memory to be swapped out to disk', - type: 'boolean' + type: 'boolean', + valueDescription: '' }, 'limits.memory.swap.priority': { disabled: true, - description: 'The higher this is set, the least likely the container is to be swapped to disk (integer between 0 and 10)', - type: 'integer' + description: 'The higher this is set, the least likely the container is to be swapped to disk', + type: 'integer', + valueDescription: '(integer between 0 and 10)' }, 'limits.network.priority': { disabled: true, - description: 'When under load, how much priority to give to the container\'s network requests (integer between 0 and 10)', - type: 'integer' + description: 'When under load, how much priority to give to the container\'s network requests', + type: 'integer', + valueDescription: '(integer between 0 and 10)' }, 'limits.processes': { disabled: true, description: 'Maximum number of processes that can run in the container', - type: 'integer' + type: 'integer', + valueDescription: '' }, 'linux.kernel_modules': { disabled: true, description: 'Comma separated list of kernel modules to load before starting the container', - type: 'string' + type: 'string', + valueDescription: '' }, 'migration.incremental.memory': { disabled: true, description: 'Incremental memory transfer of the container\'s memory to reduce downtime.', - type: 'boolean' + type: 'boolean', + valueDescription: '' }, 'migration.incremental.memory.goal': { disabled: true, description: 'Percentage of memory to have in sync before stopping the container.', - type: 'integer' + type: 'integer', + valueDescription: '' }, 'migration.incremental.memory.iterations': { disabled: true, description: 'Maximum number of transfer operations to go through before stopping the container.', - type: 'integer' + type: 'integer', + valueDescription: '' }, 'nvidia.runtime': { disabled: true, description: 'Pass the host NVIDIA and CUDA runtime libraries into the container', - type: 'boolean' + type: 'boolean', + valueDescription: '' + }, 'raw.apparmor': { disabled: true, description: 'Apparmor profile entries to be appended to the generated profile', - type: 'blob' + type: 'blob', + valueDescription: '' }, 'raw.idmap': { disabled: true, - description: 'Raw idmap configuration (e.g. "both 1000 1000")', - type: 'blob' + description: 'Raw idmap configuration', + type: 'blob', + valueDescription: '(e.g. "both 1000 1000")' }, 'raw.lxc': { disabled: true, description: 'Raw LXC configuration to be appended to the generated one', - type: 'blob' + type: 'blob', + valueDescription: '' }, 'raw.seccomp': { disabled: true, description: 'Raw Seccomp configuration', - type: 'blob' + type: 'blob', + valueDescription: '' }, 'security.devlxd': { disabled: true, description: 'Controls the presence of /dev/lxd in the container', - type: 'boolean' + type: 'boolean', + valueDescription: '' }, 'security.devlxd.images': { disabled: true, description: 'Controls the availability of the /1.0/images API over devlxd', - type: 'boolean' + type: 'boolean', + valueDescription: '' }, 'security.idmap.base': { disabled: true, - description: 'The base host ID to use for the allocation (overrides auto-detection)', - type: 'integer' + description: 'The base host ID to use for the allocation', + type: 'integer', + valueDescription: '(overrides auto-detection)' }, 'security.idmap.isolated': { disabled: true, - description: 'Use an idmap for this container that is unique among containers with isolated set.', - type: 'boolean' + description: 'Use an idmap for this container that is unique among containers with isolated set', + type: 'boolean', + valueDescription: '' }, 'security.idmap.size': { disabled: true, description: 'The size of the idmap to use', - type: 'integer' + type: 'integer', + valueDescription: '' }, 'security.nesting': { disabled: true, description: 'Support running lxd (nested) inside the container', - type: 'boolean' + type: 'boolean', + valueDescription: '' }, 'security.privileged': { disabled: true, description: 'Runs the container in privileged mode', - type: 'boolean' + type: 'boolean', + valueDescription: '' }, 'security.syscalls.blacklist': { disabled: true, description: 'A \'\n\' separated list of syscalls to blacklist', - type: 'string' + type: 'string', + valueDescription: '' }, 'security.syscalls.blacklist_compat': { disabled: true, description: 'On x86_64 this enables blocking of compat_* syscalls, it is a no-op on other arches', - type: 'boolean' + type: 'boolean', + valueDescription: '' }, 'security.syscalls.blacklist_default': { disabled: true, description: 'Enables the default syscall blacklist', - type: 'boolean' + type: 'boolean', + valueDescription: '' }, 'security.syscalls.whitelist': { disabled: true, description: 'A \'\n\' separated list of syscalls to whitelist (mutually exclusive with security.syscalls.blacklist*)', - type: 'string' + type: 'string', + valueDescription: '' }, }, left: [ diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index a65e3bbc..319ed66e 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -308,7 +308,7 @@
    From 6c447422413a8a3e4483d10b4f77e70df426a3ae Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 23 Aug 2018 23:03:34 +0200 Subject: [PATCH 034/103] Bump LXDUI version --- app/__metadata__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__metadata__.py b/app/__metadata__.py index 916e7de9..4eb34166 100644 --- a/app/__metadata__.py +++ b/app/__metadata__.py @@ -1,6 +1,6 @@ APP_NAME = 'LXDUI' APP_CLI_CMD = 'lxdui' -VERSION = '2.1.2' +VERSION = '2.2' GIT_URL = 'https://github.com/AdaptiveScale/lxdui.git' LXD_URL = 'http://localhost:8443' LICENSE = 'Apache 2.0' From 9043dc75b3bd6290f1b2ccec4c6fb1b6e28c36ed Mon Sep 17 00:00:00 2001 From: nuhi Date: Thu, 23 Aug 2018 23:21:12 +0200 Subject: [PATCH 035/103] For all logos use filename: logo.png --- app/api/models/LXCImage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index 3a8d0d5c..ef6bf1c4 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -104,7 +104,7 @@ def exportImage(self, input, logo=None): #TODO Prepare Logo if logo: - logo.save('tmp/images/{}/{}'.format(self.data.get('fingerprint'), logo.filename)) + logo.save('tmp/images/{}/{}'.format(self.data.get('fingerprint'), 'logo.png')) return MetaConf().getConfRoot() + '/tmp/images/{}'.format(self.data.get('fingerprint')) except Exception as e: @@ -125,7 +125,7 @@ def prepareImageYAML(self, input): 'license': input.get('license', ''), 'readme': 'README.md', 'tags': [], - 'logo': input.get('logo', ''), + 'logo': 'logo.png', 'image': input.get('image'), 'metadata': input.get('metadata'), 'fingerprint': self.data.get('fingerprint'), From 27f16e5ee1a9863657bea722cf6ea8069b2d66ba Mon Sep 17 00:00:00 2001 From: Ajdin Date: Fri, 24 Aug 2018 15:32:13 +0200 Subject: [PATCH 036/103] fixed: Terminal session not closing by removing shared session --- app/lib/termmanager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/lib/termmanager.py b/app/lib/termmanager.py index a9e9c04e..cdc49556 100644 --- a/app/lib/termmanager.py +++ b/app/lib/termmanager.py @@ -54,6 +54,10 @@ def kill(self, name, sig=signal.SIGTERM): term = self.terminals[name] term.kill(sig) # This should lead to an EOF + def client_disconnected(self, websocket): + if (len(self.terminals[websocket.term_name].clients)==0): + del self.terminals[websocket.term_name] + @gen.coroutine def terminate(self, name, force=False): term = self.terminals[name] From 39c46dd9b54d36b65731d8a4f15e42087d5bd60a Mon Sep 17 00:00:00 2001 From: nuhi Date: Mon, 27 Aug 2018 14:20:37 +0200 Subject: [PATCH 037/103] Implement squashfs support also --- app/api/models/LXCImage.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/api/models/LXCImage.py b/app/api/models/LXCImage.py index ef6bf1c4..900ba5d1 100644 --- a/app/api/models/LXCImage.py +++ b/app/api/models/LXCImage.py @@ -85,12 +85,18 @@ def exportImage(self, input, logo=None): if os.path.exists('{}.tar.xz'.format(self.data.get('fingerprint'))): shutil.move('{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) input['image'] = '{}.tar.xz'.format(self.data.get('fingerprint')) + if os.path.exists('{}.squashfs'.format(self.data.get('fingerprint'))): + shutil.move('{}.squashfs'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) + input['image'] = '{}.squashfs'.format(self.data.get('fingerprint')) if os.path.exists('meta-{}.tar.gz'.format(self.data.get('fingerprint'))): shutil.move('meta-{}.tar.gz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) input['metadata'] = 'meta-{}.tar.gz'.format(self.data.get('fingerprint')) if os.path.exists('meta-{}.tar.xz'.format(self.data.get('fingerprint'))): shutil.move('meta-{}.tar.xz'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) input['metadata'] = 'meta-{}.tar.xz'.format(self.data.get('fingerprint')) + if os.path.exists('meta-{}.tar.xz'.format(self.data.get('fingerprint'))): + shutil.move('meta-{}.squashfs'.format(self.data.get('fingerprint')), 'tmp/images/{}/'.format(self.data.get('fingerprint'))) + input['metadata'] = 'meta-{}.squashfs'.format(self.data.get('fingerprint')) #Prepare & Move the yaml file self.prepareImageYAML(input) @@ -239,6 +245,23 @@ def importImage(self, input): "tmp/downloaded/{0}/{0}.tar.xz".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) output_rez = p2.stdout.read() + elif os.path.exists("tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint'))) == False and os.path.exists("tmp/downloaded/{0}/{0}.squashfs".format(self.data.get('fingerprint'))): + p2 = subprocess.Popen(["lxc", "image", "import", + "tmp/downloaded/{0}/{0}.squashfs".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) + output_rez = p2.stdout.read() + + elif os.path.exists("tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint'))) and os.path.exists("tmp/downloaded/{0}/{0}.squashfs".format(self.data.get('fingerprint'))): + p2 = subprocess.Popen(["lxc", "image", "import", + "tmp/downloaded/{0}/meta-{0}.tar.xz".format(self.data.get('fingerprint')), + "tmp/downloaded/{0}/{0}.squashfs".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) + output_rez = p2.stdout.read() + + elif os.path.exists("tmp/downloaded/{0}/meta-{0}.squashfs".format(self.data.get('fingerprint'))) and os.path.exists("tmp/downloaded/{0}/{0}.squashfs".format(self.data.get('fingerprint'))): + p2 = subprocess.Popen(["lxc", "image", "import", + "tmp/downloaded/{0}/meta-{0}.squashfs".format(self.data.get('fingerprint')), + "tmp/downloaded/{0}/{0}.squashfs".format(self.data.get('fingerprint'))], stdout=subprocess.PIPE) + output_rez = p2.stdout.read() + shutil.rmtree('tmp/downloaded/{}/'.format(self.data.get('fingerprint')), ignore_errors=True) image = self.client.images.get(self.data.get('fingerprint')) From 978200c59cb21fcdf2501d931f788a37fbf6f44b Mon Sep 17 00:00:00 2001 From: nuhi Date: Mon, 27 Aug 2018 15:00:47 +0200 Subject: [PATCH 038/103] Finalize the publish image UI --- app/api/controllers/image.py | 2 -- app/ui/static/js/images.js | 15 +++++++++++- app/ui/templates/images.html | 44 +++++++++++++++++++++++++++--------- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/app/api/controllers/image.py b/app/api/controllers/image.py index 1602b4e5..4fb165c2 100644 --- a/app/api/controllers/image.py +++ b/app/api/controllers/image.py @@ -95,8 +95,6 @@ def publishHubImage(): input = json.loads(request.form.get('input')) logo = request.files['logo'] input['logo'] = logo.filename - input['username'] = 'nb18411@seeu.edu.mk' - input['password'] = 'nushi123' try: client = LXCImage(input) client.exportImage(input, logo) diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index e2a72be2..c13c34ec 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -39,7 +39,7 @@ App.images = App.images || { tempButton.on('click', $.proxy(App.images.showJSON, App.images)); $('#'+$(this).closest('table').attr('id')+'_wrapper .json-place').prepend(tempButton); tempButton.show(); - }, + } }, containerTemplate:null, newContainerForm:null, @@ -76,6 +76,10 @@ App.images = App.images || { this.initRemoteTable(); this.initNightlyTable(); this.initHubTable(); + + this.tableLocal.on('select', $.proxy(this.onRowSelected, this)); + this.tableLocal.on('deselect', $.proxy(this.onRowSelected, this)); + $('#selectAllLocal').on('change', $.proxy(this.toggleSelectAll, this, 'Local')); $('#selectAllRemote').on('change', $.proxy(this.toggleSelectAll, this, 'Remote')); this.itemTemplate = $('.itemTemplate').clone(); @@ -87,6 +91,7 @@ App.images = App.images || { this.publishImageForm = $('#publishImageToHubForm'); this.publishImageForm.on('submit', $.proxy(this.doPublishImage, this)); + $('#publishToHub').on('click', $.proxy(this.doPublishImage, this)); this.simplemde = new SimpleMDE({ element: document.getElementById("documentation"), @@ -186,6 +191,14 @@ App.images = App.images || { filterNightlyTable: function(e) { this.tableNightly.search(e.target.value).draw(); }, + onRowSelected: function(e, dt, type, indexes ){ + if(this.tableLocal.rows({selected:true}).count() == 1){ + $('#buttonPublish').removeAttr('disabled', 'disabled'); + } + else { + $('#buttonPublish').attr('disabled', 'disabled'); + } + }, onItemSelectChange : function(e, dt, type, indexes ){ if(this.activeTab=='local'){ var state = this.tableLocal.rows({selected:true}).count()>0; diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index 685b3076..c6270f8e 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -71,7 +71,7 @@
    class="glyphicon glyphicon-remove-sign"> Delete     |     -
    + + +
    - - - + From 2fb797a0ae1596f391e420cc22a7b106baabc9f7 Mon Sep 17 00:00:00 2001 From: Ajdin Date: Wed, 29 Aug 2018 15:56:13 +0200 Subject: [PATCH 039/103] updated: Buttons toggle based on state updated: Wizard step control manually handled --- app/ui/static/js/images.js | 56 +++++++++++++++++++++++++++++++++++- app/ui/templates/images.html | 18 ++++++------ 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/app/ui/static/js/images.js b/app/ui/static/js/images.js index c13c34ec..b459c438 100644 --- a/app/ui/static/js/images.js +++ b/app/ui/static/js/images.js @@ -47,6 +47,7 @@ App.images = App.images || { itemTemplate:null, rawJson:null, simplemde:null, + publishPage:0, init: function(opts){ this.data = constLocalImages || []; this.remoteData = constRemoteImages || []; @@ -107,12 +108,18 @@ App.images = App.images || { this.tableNightly.search(architecture).draw(); $('#architectureNightly').val(architecture); - $('#architectureRemote').val(architecture); + $('#architectureRemote').val(architecture); if (localImagesLength == 0){ this.switchView('nightlyList'); $('.nav-tabs li:eq(1) a').tab('show'); } + $('#buttonPublishNext').on('click', $.proxy(this.onPublishNext, this)); + $('#buttonPublishBack').on('click', $.proxy(this.onPublishBack, this)); + + $('#btnImageDetails').on('click', $.proxy(this.onPublishSwitchToPage, this, 0)); + $('#btnReadme').on('click', $.proxy(this.onPublishSwitchToPage, this, 1)); + $('#btnAuthorization').on('click', $.proxy(this.onPublishSwitchToPage, this, 2)); }, convertImageSize:function(index, item){ $(item).text(App.formatBytes($(item).text())); @@ -743,6 +750,8 @@ App.images = App.images || { $('#architecture').text(image.architecture); $('#os').text(image.properties['os']); $('#release').text(image.properties['release']); + this.publishPage=0; + this.updatePublishButtons(); }, doPublishImage: function(e){ e.preventDefault(); @@ -776,5 +785,50 @@ App.images = App.images || { }, onPublishFailed: function(response) { console.log('failed'); + }, + updatePublishButtons: function(){ + $('.tabImageDetails, .tabImageReadme, .tabImageAuthorization').removeClass('active'); + switch(this.publishPage){ + case 0: + $('#buttonPublishCancel').show(); + $('#buttonPublishNext').show(); + $('#buttonPublishBack').hide(); + $('#buttonPublishToHUB').hide(); + $('.tabImageDetails').addClass('active'); + $('#5').show(); + $('#6, #7').hide(); + return; + case 1: + $('#buttonPublishCancel').hide(); + $('#buttonPublishToHUB').hide(); + $('#buttonPublishBack').show(); + $('#buttonPublishNext').show(); + $('.tabImageReadme').addClass('active'); + $('#6').show(); + $('#5, #7').hide(); + return; + case 2: + $('#buttonPublishCancel').hide(); + $('#buttonPublishNext').hide(); + $('#buttonPublishBack').show(); + $('#buttonPublishToHUB').show(); + $('.tabImageAuthorization').addClass('active'); + $('#7').show(); + $('#6, #5').hide(); + return; + } + }, + onPublishBack: function(){ + this.publishPage--; + this.updatePublishButtons(); + }, + onPublishNext: function(){ + console.log('here'); + this.publishPage++; + this.updatePublishButtons(); + }, + onPublishSwitchToPage:function(pageNumber){ + this.publishPage=pageNumber; + this.updatePublishButtons(); } } \ No newline at end of file diff --git a/app/ui/templates/images.html b/app/ui/templates/images.html index c6270f8e..180f4996 100644 --- a/app/ui/templates/images.html +++ b/app/ui/templates/images.html @@ -602,14 +602,14 @@