Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
Implement `snapcraft update` for parts #588
Merged
sergiusens
merged 6 commits into
snapcore:master
from
sergiusens:feature/1594643/snapcraft-update
Jun 23, 2016
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
4043785
Implement `snapcraft update` for parts
sergiusens d6cda35
Fixed static tests
sergiusens 6eba59b
Use the env fixture for no_proxy
sergiusens c793182
Merge branch 'master' into feature/1594643/snapcraft-update
sergiusens d8d4f54
Merge branch 'master' into feature/1594643/snapcraft-update
sergiusens 8704893
Fix integration test
sergiusens
Jump to file or symbol
Failed to load files and symbols.
| @@ -0,0 +1,46 @@ | ||
| +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- | ||
| +# | ||
| +# Copyright (C) 2016 Canonical Ltd | ||
| +# | ||
| +# This program is free software: you can redistribute it and/or modify | ||
| +# it under the terms of the GNU General Public License version 3 as | ||
| +# published by the Free Software Foundation. | ||
| +# | ||
| +# This program is distributed in the hope that it will be useful, | ||
| +# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| +# GNU General Public License for more details. | ||
| +# | ||
| +# You should have received a copy of the GNU General Public License | ||
| +# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + | ||
| +import os | ||
| + | ||
| +import yaml | ||
| + | ||
| +import integration_tests | ||
| + | ||
| + | ||
| +class PartsTestCase(integration_tests.TestCase): | ||
| + | ||
| + def setUp(self): | ||
| + super().setUp() | ||
| + | ||
| + self.parts_dir = os.path.join('data', 'snapcraft') | ||
| + self.parts_yaml = os.path.join(self.parts_dir, 'parts.yaml') | ||
| + self.headers_yaml = os.path.join(self.parts_dir, 'headers.yaml') | ||
| + | ||
| + def test_update(self): | ||
| + self.run_snapcraft('update') | ||
| + | ||
| + self.assertTrue(os.path.exists(self.parts_yaml)) | ||
| + self.assertTrue(os.path.exists(self.headers_yaml)) | ||
| + | ||
| + def test_curl_exists(self): | ||
| + """Curl is used in most of the demos so we test for its existence.""" | ||
| + self.run_snapcraft('update') | ||
| + | ||
| + with open(self.parts_yaml) as parts_file: | ||
| + parts = yaml.load(parts_file) | ||
| + | ||
| + self.assertTrue('curl' in parts, parts) |
| @@ -0,0 +1,91 @@ | ||
| +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- | ||
| +# | ||
| +# Copyright (C) 2016 Canonical Ltd | ||
| +# | ||
| +# This program is free software: you can redistribute it and/or modify | ||
| +# it under the terms of the GNU General Public License version 3 as | ||
| +# published by the Free Software Foundation. | ||
| +# | ||
| +# This program is distributed in the hope that it will be useful, | ||
| +# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| +# GNU General Public License for more details. | ||
| +# | ||
| +# You should have received a copy of the GNU General Public License | ||
| +# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + | ||
| +import logging | ||
| +import os | ||
| + | ||
| +import requests | ||
| +import yaml | ||
| +from progressbar import (ProgressBar, Percentage, Bar) | ||
| +from xdg import BaseDirectory | ||
| + | ||
| +PARTS_URI = 'https://parts.snapcraft.io/v1/parts.yaml' | ||
| + | ||
| +logging.getLogger("urllib3").setLevel(logging.CRITICAL) | ||
| +logger = logging.getLogger(__name__) | ||
| + | ||
| + | ||
| +class _Base: | ||
| + | ||
| + def __init__(self): | ||
| + self.parts_dir = os.path.join(BaseDirectory.xdg_data_home, 'snapcraft') | ||
sergiusens
Collaborator
|
||
| + os.makedirs(self.parts_dir, exist_ok=True) | ||
| + self.parts_yaml = os.path.join(self.parts_dir, 'parts.yaml') | ||
| + | ||
| + | ||
| +class _Update(_Base): | ||
| + | ||
| + def __init__(self): | ||
| + super().__init__() | ||
| + self._headers_yaml = os.path.join(self.parts_dir, 'headers.yaml') | ||
| + self._parts_uri = os.environ.get('SNAPCRAFT_PARTS_URI', PARTS_URI) | ||
| + | ||
| + def execute(self): | ||
| + headers = self._load_headers() | ||
| + self._request = requests.get(self._parts_uri, stream=True, | ||
| + headers=headers) | ||
| + | ||
| + if self._request.status_code == 304: | ||
| + logger.info('The parts cache is already up to date.') | ||
| + return | ||
| + self._request.raise_for_status() | ||
| + | ||
| + self._download() | ||
| + self._save_headers() | ||
| + | ||
| + def _download(self): | ||
| + total_length = int(self._request.headers.get('Content-Length')) | ||
| + progress_bar = ProgressBar( | ||
| + widgets=['Updating parts list', | ||
| + Bar(marker='=', left='[', right=']'), | ||
| + ' ', Percentage()], | ||
| + maxval=total_length) | ||
| + | ||
| + total_read = 0 | ||
| + progress_bar.start() | ||
| + with open(self.parts_yaml, 'wb') as parts_file: | ||
| + for buf in self._request.iter_content(1): | ||
| + parts_file.write(buf) | ||
| + total_read += len(buf) | ||
| + progress_bar.update(total_read) | ||
| + progress_bar.finish() | ||
| + | ||
| + def _load_headers(self): | ||
| + if not os.path.exists(self._headers_yaml): | ||
| + return None | ||
| + | ||
| + with open(self._headers_yaml) as headers_file: | ||
| + return yaml.load(headers_file) | ||
| + | ||
| + def _save_headers(self): | ||
| + headers = {'If-None-Match': self._request.headers.get('ETag')} | ||
| + | ||
| + with open(self._headers_yaml, 'w') as headers_file: | ||
| + headers_file.write(yaml.dump(headers)) | ||
| + | ||
| + | ||
| +def update(): | ||
| + _Update().execute() | ||
Oops, something went wrong.
Is it possible to have this logic in a single place? Same for "parts.yaml" and "headers.yaml".