├── .gitignore ├── LICENSE ├── Manifest.in ├── README.md ├── TODO.md ├── aptly_api_cli ├── aptly_cli ├── __init__.py ├── api │ ├── __init__.py │ └── api.py ├── cli │ ├── __init__.py │ └── cli.py └── util │ ├── __init__.py │ └── util.py ├── ci-scripts ├── aptly-check-port-running.sh ├── publish.sh └── update-3rdparty-staging.sh ├── debian ├── changelog ├── compat ├── control ├── install ├── postinst └── rules ├── dev-requirements.txt ├── docs ├── .gitignore ├── Makefile ├── api.rst ├── cli.rst ├── conf.py ├── index.rst └── util.rst ├── etc └── aptly-cli.conf ├── pylintrc ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | *.DS_Store 59 | *.sublime-project 60 | *.sublime-workspace 61 | aptly_cli.sublime-workspace 62 | aptly_cli.sublime-project 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tim Susa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Manifest.in: -------------------------------------------------------------------------------- 1 | include aptly_cli/configs/* 2 | include debian/* 3 | include aptly_cli/* 4 | include aptly_api_cli 5 | include requirements.txt 6 | include TODO.md 7 | include README.md 8 | 9 | exclude debian/aptly-cli.* 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aptly_api_cli 2 | 3 | ### Why do we need another aptly cli interface? 4 | - Because aptly-api-cli has a lot of more features build in. 5 | - aptly-api-cli is made accessible to the python community 6 | - aptly-api-cli utils are already integrated into a CI Workflow 7 | - Additional scripts are offered (see ci-scripts folder), which can be used along your CI jobs (e.g. jenkins) 8 | 9 | 10 | # Description 11 | This python command line interface, executes calls to the Aptly server remotely, without blocking the Aptly database. 12 | All functionality from here http://www.aptly.info/doc/api/ is extended by even more useful features, like showing or cleaning out snapshots, packages for couple of repos or mirrors. 13 | 14 | You can make either use of the aptly_api_request.py as a starting point for your own application or just use the cli (aptly_api_cli.py) 15 | bundled with this repository to execute your requests via command line or run scripts, calling the cli, integrated into a CI Workflow. 16 | 17 | This project is in use on production for www.lambdanow.com running on a JENKINS server. 18 | 19 | # Build 20 | 21 | ``` 22 | mkvirtualenv aptly-cli 23 | pip install -r dev-requirements.txt 24 | ./aptly-api-cli --help 25 | ``` 26 | 27 | # Installation 28 | 29 | ``` 30 | python setup.py install 31 | aptly-cli --help 32 | ``` 33 | 34 | or create a debian package (see debian folder) and install it, locally. 35 | 36 | 37 | # Get started 38 | Quickstart: Look into the ci-scripts folder to explore aptly-cli in action. 39 | 40 | ## How to configure aptly server on a CI with JENKINS? 41 | 42 | We use upstart: http://upstart.ubuntu.com/ to ensure, aptly server is running (here on Ubuntu 12.04). 43 | 44 | Therefore, please create a file: /etc/init/aptly.conf 45 | 46 | with content: 47 | 48 | ``` 49 | 50 | description "starting aptly" 51 | author "maxMuster@muster.de" 52 | 53 | start on runlevel [2345] 54 | 55 | stop on runlevel [!2345] 56 | 57 | setuid jenkins 58 | 59 | script 60 | export AWS_ACCESS_KEY_ID="PUT_YOUR_AWS_CREDENTIAL_HERE" 61 | export AWS_SECRET_ACCESS_KEY="PUT_YOUR_AWS_CREDENTIAL_HERE" 62 | 63 | /usr/bin/aptly api serve -config=/var/lib/jenkins/.aptly.conf -listen=":9003" 64 | end script 65 | 66 | ``` 67 | 68 | # Command Line Options 69 | 70 | ## Help 71 | Show this help message and exit 72 | ``` 73 | -h, --help 74 | ``` 75 | 76 | ### Build documentation 77 | ``` 78 | cd docs 79 | make html 80 | ``` 81 | 82 | 83 | ## Extensions 84 | Further functionalities improving your aptly CI workflow. 85 | 86 | 87 | #### Create configuration file 88 | Creates standard config file (aptly-cli.conf) at $HOME directory. 89 | 90 | ``` 91 | aptly_api_cli --create_config 92 | ``` 93 | Please Note: You can configure the following keys: 94 | 95 | - basic_url: the basic url 96 | - port: the port 97 | - prefixes_mirrors: prefixes for all mirrors that you have created 98 | - repos_to_clean: reponames, which should be searched for to be cleaned out 99 | - package_prefixes: package-prefixes, which should be searched for to be cleaned out 100 | - save_last_pkg: Number of package-versions for each prefix, that should be kept 101 | - save_last_snap: Number of snapshot-versions for each prefix, that should be kept 102 | 103 | See an working example (aptly-cli.conf): 104 | 105 | ``` 106 | # aptly-cli config file 107 | [general] 108 | basic_url=http://localhost 109 | port=:9003 110 | prefixes_mirrors=cloudera, erlang, mongodb2, mongodb_, nginx, puppetmaster, rabbitmq, redis, saltstack2014.7, saltstack2015.5, saltstack_, git 111 | repos_to_clean=unstable-repo, stable-repo 112 | package_prefixes=cluster-manager, puppet-config, ingest-api, camus-etl, aptly-cli 113 | save_last_pkg=10 114 | save_last_snap=3 115 | 116 | [3rd_party] 117 | # 3rd party s3 buckets to publish switch to 118 | repos=3rdparty-eu-west-1, 3rdparty-us-east-1 119 | # Pre and postfix of the staging snapshots 120 | staging_snap_pre_post=3rdparty-s3-repo, 3rdparty-staging_snapshot 121 | ``` 122 | 123 | 124 | 125 | #### Get last n snapshots sorted by prefix 126 | Returns the last n snapshots by prefix or optional postfix. 127 | ``` 128 | aptly_api_cli --get_last_snapshots=PREFIX NR_OF_VERS [POSTFIX] 129 | ``` 130 | 131 | #### Clean out last n snapshots by prefix 132 | Cleans the last n snapshots by prefix or optional postfix. 133 | ``` 134 | aptly_api_cli --clean_last_snapshots=PREFIX NR_OF_VERS [POSTFIX] 135 | ``` 136 | 137 | 138 | #### List all repos and packages 139 | List all repos with their containing packages. 140 | ``` 141 | aptly_api_cli --list_repos_and_packages 142 | ``` 143 | 144 | #### Get last n packages by reponame and sorted by prefix 145 | Returns the last n packages by reponame, prefix or optional postfix. 146 | ``` 147 | aptly_api_cli --get_last_packages=REPO_NAME PREFIX NR_OF_VERS [POSTFIX] 148 | ``` 149 | 150 | #### Clean last n packages by reponame and sorted by prefix 151 | Delete the last n packages by reponame, prefix or optional postfix. 152 | ``` 153 | --clean_last_packages=REPO_NAME PREFIX NR_OF_VERS [POSTFIX] 154 | ``` 155 | 156 | #### Diff all mirror snapshots 157 | Sorts list of snapshots and makes a diff between the last two. 158 | ``` 159 | aptly_api_cli --diff_both_last_snapshots_mirrors 160 | 161 | ``` 162 | 163 | #### Clean all mirror snapshots 164 | Cleans out snapshots, which were taken from mirrors (from config) 165 | ``` 166 | aptly_api_cli --clean_mirrored_snapshots 167 | 168 | ``` 169 | 170 | #### Clean all packages from repos 171 | Cleans out packages globally, which were taken from repo names (from config) 172 | ``` 173 | aptly_api_cli --clean_repo_packages 174 | 175 | ``` 176 | 177 | #### Publish 3rd party staging snapshot to production 178 | Publish the last 3rd party staging snapshot to production (e.g. s3 bucket), only if new content is available. The taken snapshot 179 | stems from the script 'update-3rdparty-staging.sh'. 180 | 181 | ``` 182 | aptly_api_cli --publish_switch_3rdparty_production 183 | 184 | ``` 185 | 186 | 187 | ## CI - Scripts 188 | 189 | This script is called by other scripts, just to check if aptly server is alive. 190 | ``` 191 | aptly-check-port-running.sh 192 | ``` 193 | 194 | This script is called, whenever we release new software as debian package to staging or production repositories (No 3rd Party). 195 | ``` 196 | publish.sh 'cluster-manager' 'unstable' 197 | ``` 198 | 199 | This script starts the 3rdPartyMirror update chain beginning from staging: 200 | ``` 201 | update-3rdparty-staging.sh 202 | ``` 203 | 204 | ## Local Repos API 205 | Local repositories management via REST API. 206 | 207 | #### List 208 | List all local repos 209 | ``` 210 | aptly_api_cli --repo_list 211 | ``` 212 | 213 | #### Create 214 | Create empty local repository with specified parameters. REPO_NAME is the name of the repository to create. COMMENT, DISTRIBUTION (e.g.: precise) and COMPONENT (e.g.: main) are optional. 215 | 216 | ``` 217 | aptly_api_cli --repo_create=REPO_NAME [COMMENT] [DISTRIBUTION] [COMPONENT] 218 | ``` 219 | 220 | #### Show 221 | Show basic information about a local repository. REPO_NAME is the name of the repository. 222 | 223 | ``` 224 | aptly_api_cli --repo_show=REPO_NAME 225 | ``` 226 | 227 | #### Show Package 228 | Show all packages of a local repository. REPO_NAME is the name of the repository. PACKAGE_TO_SEARCH (Name of the Package to search for), WITH_DEPS (e.g.: 0 or 1), FORMAT (e.g.: compact or detail) are optional. Please see http://www.aptly.info/doc/api/ for more details. 229 | 230 | ``` 231 | aptly_api_cli --repo_show_packages=REPO_NAME [PACKAGE_TO_SEARCH] [WITH_DEPS] [FORMAT] 232 | ``` 233 | 234 | #### Edit 235 | Edit information of a local repository. 236 | 237 | ``` 238 | aptly_api_cli --repo_edit=REPO_NAME COMMENT DISTRIBUTION COMPONENT 239 | ``` 240 | 241 | #### Delete 242 | Delete repository. 243 | 244 | ``` 245 | aptly_api_cli --repo_delete=REPO_NAME 246 | ``` 247 | 248 | #### Add Packages 249 | Add packages to local repo by key 250 | ``` 251 | aptly_api_cli --repo_add_packages_by_key=REPO_NAME PACKAGE_REFS 252 | ``` 253 | 254 | #### Delete Packages 255 | Delete packages from repository by key 256 | ``` 257 | aptly_api_cli --repo_delete_packages_by_key=REPO_NAME PACKAGE_REFS 258 | ``` 259 | 260 | ## File Upload API 261 | Upload package files temporarily to aptly service. These files could be added to local repositories using local repositories API. 262 | 263 | All uploaded files are stored under /upload directory (see configuration). This directory would be created automatically if it doesn’t exist. 264 | 265 | Uploaded files are grouped by directories to support concurrent uploads from multiple package sources. Local repos add API can operate on directory (adding all files from directory) or on individual package files. By default, all successfully added package files would be removed. 266 | 267 | #### List Directories 268 | Lists all upload-directories. 269 | ``` 270 | aptly_api_cli --file_list_dirs 271 | ``` 272 | 273 | #### Upload files 274 | Upload file to local upload-directory 275 | ``` 276 | aptly_api_cli --file_upload=UPLOAD_DIR FILE 277 | ``` 278 | 279 | #### Add Package 280 | Add package from upload folder to local repo 281 | ``` 282 | aptly_api_cli --repo_add_package_from_upload=REPO_NAME UPLOAD_DIR PACKAGE_NAME 283 | ``` 284 | 285 | #### List files 286 | List uploaded files 287 | ``` 288 | aptly_api_cli --file_list 289 | ``` 290 | 291 | #### Delete directory 292 | Delete upload directory 293 | ``` 294 | aptly_api_cli --file_delete_dir=UPLOAD_DIR 295 | ``` 296 | 297 | #### Delete file 298 | Delete a file in upload directory 299 | ``` 300 | aptly_api_cli --file_delete=UPLOAD_DIR FILE 301 | ``` 302 | 303 | ## Snapshot API 304 | Snapshot management APIs. 305 | 306 | Snapshot is a immutable package reference list taken from local repository, mirror or result of other snapshot processing. 307 | 308 | 309 | #### Create snapshot from local repo 310 | Create snapshot from local repo by giving the snapshot and repo name as parameter. A description is optional. 311 | ``` 312 | aptly_api_cli --snapshot_create_from_local_repo=SNAPSHOT_NAME REPO_NAME [DESCRIPTION] 313 | ``` 314 | 315 | #### Create snapshot by package references 316 | Create snapshot by package references. The snapshot name, a comma separated list of snapshots and package references should be given as parameter. A description is optional. 317 | ``` 318 | aptly_api_cli --snapshot_create_by_pack_refs=SNAPSHOT_NAME SOURCE_SNAPSHOTS PACKAGE_REF_LIST [DESCRIPTION] 319 | ``` 320 | 321 | #### Snapshot show 322 | Show basic information about snapshot 323 | ``` 324 | aptly_api_cli --snapshot_show=SNAPSHOT_NAME 325 | ``` 326 | 327 | #### Snapshot show packages 328 | Show all packages the snapshot is containing or optionally search for one. 329 | ``` 330 | aptly_api_cli --snapshot_show_packages=SNAPSHOT_NAME [PACKAGE_TO_SEARCH] [WITH_DEPS] [FORMAT] 331 | ``` 332 | 333 | #### Update snapshot 334 | Rename snapshot and optionally change description 335 | ``` 336 | aptly_api_cli --snapshot_update=OLD_SNAPSHOT_NAME NEW_SNAPSHOT_NAME [DESCRIPTION] 337 | ``` 338 | 339 | #### Snapshot list 340 | Lists all available snapshots 341 | ``` 342 | aptly_api_cli --snapshot_list 343 | ``` 344 | 345 | #### Snapshot diff 346 | List differences of two snapshots 347 | ``` 348 | aptly_api_cli --snapshot_diff=LEFT_SNAPSHOT_NAME RIGHT_SNAPSHOT_NAME 349 | ``` 350 | #### Snapshot delete 351 | Delete snapshot by name. Optionally force deletion. 352 | ``` 353 | aptly_api_cli --snapshot_delete=SNAPSHOT_NAME [FORCE_DELETION] 354 | ``` 355 | 356 | ## Publish API 357 | Manages published repositories. 358 | 359 | #### Publish list 360 | List all available repositories to publish to 361 | ``` 362 | aptly_api_cli --publish_list 363 | ``` 364 | 365 | #### Publish 366 | Publish snapshot or repository to storage 367 | ``` 368 | aptly_api_cli --publish=PREFIX SOURCES_KIND SOURCES_LIST DISTRIBUTION COMPONENT_LIST [LABEL] [ORIGIN] [FORCE_OVERWRITE] [ARCHITECTURES_LIST] 369 | ``` 370 | 371 | #### Publish drop 372 | Drop published repo content 373 | ``` 374 | aptly_api_cli --publish_drop=PREFIX DISTRIBUTION [FORCE_REMOVAL] 375 | ``` 376 | 377 | 378 | #### Publish switch 379 | Switching snapshots to published repo with minimal server down time. 380 | 381 | ``` 382 | aptly_api_cli --publish_switch=PREFIX SOURCES_LIST DISTRIBUTION [COMPONENT] [FORCE_OVERWRITE] 383 | ``` 384 | 385 | ## Misc API 386 | 387 | #### Returns aptly version 388 | ``` 389 | aptly_api_cli --get_version 390 | ``` 391 | 392 | ## Package API 393 | APIs related to packages on their own. 394 | 395 | #### Package show 396 | Show packages by key 397 | ``` 398 | aptly_api_cli --package_show_by_key=PACKAGE_KEY 399 | ``` 400 | 401 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ## Create Documentation with examples 4 | ## Extend functionality 5 | ## setup proper structure for procject 6 | -------------------------------------------------------------------------------- /aptly_api_cli: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | This is the corresponding client 5 | for the REST API. Call it like: 6 | $ python client.py -h 7 | """ 8 | 9 | import os 10 | # if 'REST_API_TESTING' not in os.environ: 11 | # os.environ['REST_API_TESTING'] = '1' 12 | # if 'REST_API_DEBUG' not in os.environ: 13 | # os.environ['REST_API_DEBUG'] = '1' 14 | 15 | from aptly_cli.cli.cli import main 16 | 17 | if __name__ == '__main__': 18 | main() 19 | -------------------------------------------------------------------------------- /aptly_cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimSusa/aptly_api_cli/011ba8e7f464726b336b53f6b2cbdc4490b5180c/aptly_cli/__init__.py -------------------------------------------------------------------------------- /aptly_cli/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimSusa/aptly_api_cli/011ba8e7f464726b336b53f6b2cbdc4490b5180c/aptly_cli/api/__init__.py -------------------------------------------------------------------------------- /aptly_cli/api/api.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ AptlyApiRequests 5 | Instances of this class will be able to talk 6 | to the Aptly REST API remotely . 7 | """ 8 | 9 | import json 10 | import requests 11 | import os 12 | from ConfigParser import ConfigParser 13 | 14 | 15 | class AptlyApiRequests(object): 16 | 17 | """ AptlyApiRequests 18 | Instances of this class will be able to talk 19 | to the Aptly REST API remotely. 20 | """ 21 | 22 | def __init__(self): 23 | """ 24 | Pass url and port to the constructor 25 | to initialize instance. 26 | """ 27 | self.configfile = None 28 | cfg_file = self.get_config_from_file() 29 | 30 | if cfg_file is not None: 31 | basic_url = cfg_file['basic_url'] 32 | port = cfg_file['port'] 33 | else: 34 | basic_url = 'http://localhost' 35 | port = ':9003' 36 | print "No Config file found, take default values" 37 | 38 | self.headers = {'content-type': 'application/json'} 39 | 40 | url = basic_url + port 41 | 42 | # self values 43 | self.cfg = { 44 | # Routes 45 | 'route_snap': url + '/api/snapshots/', 46 | 'route_repo': url + '/api/repos/', 47 | 'route_file': url + '/api/files/', 48 | 'route_pack': url + '/api/packages/', 49 | 'route_pub': url + '/api/publish/', 50 | 'route_graph': url + '/api/graph/', 51 | 'route_vers': url + '/api/version/', 52 | 53 | # Number of packages to have left 54 | # 'save_last_pkg': 10, 55 | 56 | # Number of snapshots to have left 57 | # 'save_last_snap': 3 58 | } 59 | 60 | @staticmethod 61 | def _out(arg_list): 62 | """ _out 63 | Will give beautified output of a list. 64 | """ 65 | for y in arg_list: 66 | print json.dumps(y, indent=2) 67 | 68 | @staticmethod 69 | def get_config_from_file(): 70 | """ 71 | Returns a dictonary of config values read out from file 72 | """ 73 | home = os.path.expanduser("~") 74 | name = home + '/aptly-cli.conf' 75 | 76 | config_file = ConfigParser() 77 | if not config_file.read(name): 78 | cfg_file = None 79 | else: 80 | cfg_file = { 81 | # general 82 | 'basic_url': config_file.get('general', 'basic_url'), 83 | 'port': config_file.get('general', 'port'), 84 | 'prefixes_mirrors': config_file.get('general', 'prefixes_mirrors'), 85 | 'save_last_snap': config_file.get('general', 'save_last_snap'), 86 | 'save_last_pkg': config_file.get('general', 'save_last_pkg'), 87 | 'repos_to_clean': config_file.get('general', 'repos_to_clean'), 88 | 'package_prefixes': config_file.get('general', 'package_prefixes'), 89 | # 3rd party 90 | 'repos': config_file.get('3rd_party', 'repos'), 91 | 'staging_snap_pre_post': config_file.get('3rd_party', 'staging_snap_pre_post') 92 | } 93 | return cfg_file 94 | 95 | ################### 96 | # LOCAL REPOS API # 97 | ################### 98 | def repo_create(self, repo_name, data=None): 99 | """ 100 | POST /api/repos 101 | Create empty local repository with specified parameters ( see also aptly repo create). 102 | 103 | JSON body params: 104 | Name: required, [string] - local repository name 105 | Comment: [string] - text describing local repository, for the user 106 | DefaultDistribution: [string] - default distribution when publishing from this local repo 107 | DefaultComponent: [string] - default component when publishing from this local repo 108 | 109 | HTTP Errors: 110 | Code Description 111 | 400 repository with such name already exists 112 | curl -X POST -H 'Content-Type: application/json' --data '{"Name": "aptly-repo"}' http://localhost:8080/api/repos 113 | """ 114 | 115 | if data is None: 116 | post_data = { 117 | 'Name': repo_name 118 | } 119 | else: 120 | post_data = { 121 | 'Name': repo_name, 122 | 'Comment': data.comment, 123 | 'DefaultDistribution': data.default_distribution, 124 | 'DefaultComponent': data.default_component 125 | } 126 | 127 | r = requests.post(self.cfg['route_repo'][:-1], 128 | data=json.dumps(post_data), 129 | headers=self.headers) 130 | # r.raise_for_status() 131 | resp_data = json.loads(r.content) 132 | # print resp_data 133 | return resp_data 134 | 135 | def repo_show(self, repo_name): 136 | """ 137 | SHOW 138 | GET /api/repos/:name 139 | Returns basic information about local repository. 140 | 141 | HTTP Errors: 142 | Code Description 143 | 404 repository with such name doesn’t exist 144 | 145 | Response: 146 | Name: [string] local repository name 147 | Comment: [string] text describing local repository, for the user 148 | DefaultDistribution: [string] default distribution when publishing from this local repo 149 | DefaultComponent: [string] default component when publishing from this local repo 150 | 151 | Example: 152 | $ curl http://localhost:8080/api/repos/aptly-repo 153 | """ 154 | r = requests.get(self.cfg['route_repo'] + repo_name, headers=self.headers) 155 | resp_data = json.loads(r.content) 156 | # print resp_data 157 | return resp_data 158 | 159 | def repo_show_packages(self, repo_name, pkg_to_search=None, with_deps=0, detail='compact'): 160 | """ 161 | SHOW PACKAGES/SEARCH 162 | GET /api/repos/:name/packages 163 | List all packages in local repository or perform search on repository contents and return result. 164 | 165 | Query params: 166 | q - package query, if missing - return all packages 167 | withDeps - set to 1 to include dependencies when evaluating package query 168 | detail - result format, compact by default ( only package keys), details to return full information about each package ( might be slow on large repos) 169 | 170 | Example: 171 | $ curl http://localhost:8080/api/repos/aptly-repo/packages 172 | """ 173 | 174 | if pkg_to_search is None: 175 | param = { 176 | 'withDeps': with_deps, 177 | 'format': detail 178 | } 179 | else: 180 | param = { 181 | 'q': pkg_to_search, 182 | 'withDeps': with_deps, 183 | 'format': detail 184 | } 185 | url = str(self.cfg['route_repo']) + str(repo_name) + '/packages' 186 | 187 | r = requests.get(url, params=param, headers=self.headers) 188 | # raise_for_status() 189 | resp_data = json.loads(r.content) 190 | # print json.dumps(resp_data) 191 | return resp_data 192 | 193 | def repo_edit(self, repo_name, data=None): 194 | """ 195 | EDIT 196 | PUT /api/repos/:name 197 | Update local repository meta information. 198 | 199 | JSON body params: 200 | Comment: [string] text describing local repository, for the user 201 | DefaultDistribution: [string] default distribution when publishing from this local repo 202 | DefaultComponent: [string] default component when publishing from this local repo 203 | 204 | HTTP Errors: 205 | Code Description 206 | 404 repository with such name doesn’t exist 207 | Response is the same as for GET /api/repos/:name API. 208 | 209 | Example:: 210 | 211 | $ curl -X PUT -H 'Content-Type: application/json' 212 | --data '{"DefaultDistribution": "trusty"}' http://localhost:8080/api/repos/local1 213 | """ 214 | 215 | if data is None: 216 | data = {} 217 | else: 218 | data = { 219 | 'Comment': data.comment, 220 | 'DefaultDistribution': data.default_distribution, 221 | 'DefaultComponent': data.default_component 222 | } 223 | 224 | r = requests.put(self.cfg['route_repo'] + repo_name, 225 | data=json.dumps(data), 226 | headers=self.headers) 227 | # r.raise_for_status() 228 | resp_data = json.loads(r.content) 229 | # print resp_data 230 | return resp_data 231 | 232 | def repo_list(self): 233 | """ 234 | LIST 235 | GET /api/repos 236 | Show list of currently available local repositories. Each repository is returned as in “show” API. 237 | 238 | Example: 239 | $ curl http://localhost:8080/api/repos 240 | """ 241 | r = requests.get(self.cfg['route_repo'], headers=self.headers) 242 | # r.raise_for_status() 243 | resp_data = json.loads(r.content) 244 | # print json.dumps(resp_data) 245 | return resp_data 246 | 247 | def repo_delete(self, repo_name): 248 | """ 249 | DELETE 250 | DELETE /api/repos/:name 251 | Delete local repository. 252 | Local repository can’t be deleted if it is published. If local repository has snapshots, 253 | aptly would refuse to delete it by default, but that can be overridden with force flag. 254 | 255 | Query params: 256 | force when value is set to 1, delete local repository even if it has snapshots 257 | 258 | HTTP Errors: 259 | Code Description 260 | 404 repository with such name doesn’t exist 261 | 409 repository can’t be dropped ( self, reason in the message) 262 | """ 263 | r = requests.delete(self.cfg['route_repo'] + repo_name, 264 | headers=self.headers) 265 | # r.raise_for_status() 266 | resp_data = json.loads(r.content) 267 | # print json.dumps(resp_data) 268 | return resp_data 269 | 270 | def repo_add_package_from_upload(self, repo_name, dir_name, file_name=None, params=None): 271 | """ 272 | ADD PACKAGES FROM UPLOADED FILE/DIRECTORY 273 | POST /api/repos/:name/file/:dir 274 | POST /api/repos/:name/file/:dir/:file 275 | Import packages from files ( uploaded using File Upload API) to the local repository. 276 | If directory specified, aptly would discover package files automatically. 277 | Adding same package to local repository is not an error. 278 | By default aptly would try to remove every successfully processed file and directory :dir 279 | ( if it becomes empty after import). 280 | 281 | Query params: 282 | noRemove - when value is set to 1, don’t remove any files 283 | forceReplace - when value is set to 1, remove packages conflicting with package being added 284 | (in local repository). 285 | 286 | HTTP Errors: 287 | 404 repository with such name doesn’t exist 288 | 289 | Response: 290 | FailedFiles [][string] list of files that failed to be processed 291 | Report object operation report ( self, see below) 292 | 293 | Report structure: 294 | Warnings - [][string] list of warnings 295 | Added -[][string] list of messages related to packages being added 296 | 297 | Example ( file upload, add package to repo): 298 | $ curl -X POST -F file=@aptly_0.9~dev+217+ge5d646c_i386.deb http://localhost:8080/api/files/aptly-0.9 299 | """ 300 | if file_name is None: 301 | url = self.cfg['route_repo'] + repo_name + '/file/' + dir_name 302 | else: 303 | url = self.cfg['route_repo'] + repo_name + \ 304 | '/file/' + dir_name + '/' + file_name 305 | 306 | if params is not None: 307 | query_param = { 308 | 'noRemove': params.no_remove, 309 | 'forceReplace': params.force_replace 310 | } 311 | else: 312 | query_param = { 313 | 'noRemove': 0, 314 | 'forceReplace': 0 315 | } 316 | 317 | r = requests.post(url, 318 | params=query_param, 319 | headers=self.headers) 320 | # r.raise_for_status() 321 | resp_data = json.loads(r.content) 322 | # print resp_data 323 | return resp_data 324 | 325 | def repo_add_packages_by_key(self, repo_name, package_key_list): 326 | """ 327 | ADD PACKAGES BY KEY 328 | POST /api/repos/:name/packages 329 | Add packages to local repository by package keys. 330 | Any package could be added, it should be part of aptly database ( it could come from any mirror, snapshot, 331 | other local repository). This API combined with package list ( search) APIs allows to implement importing, 332 | copying, moving packages around. API verifies that packages actually exist in aptly database and checks 333 | constraint that conflicting packages can’t be part of the same local repository. 334 | 335 | JSON body params: 336 | PackageRefs [][string] list of package references ( package keys) 337 | 338 | HTTP Errors: 339 | Code Description 340 | 400 added package conflicts with already exists in repository 341 | 404 repository with such name doesn’t exist 342 | 404 package with specified key doesn’t exist 343 | Response is the same as for GET /api/repos/:name API. 344 | 345 | Example 346 | $ curl -X POST -H 'Content-Type: application/json' --data '{"PackageRefs": 347 | ["Psource pyspi 0.6.1-1.4 f8f1daa806004e89","Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378"]}' 348 | http://localhost:8080/api/repos/repo2/packages 349 | """ 350 | if len(package_key_list) <= 0: 351 | print 'No packages were given... aborting' 352 | return 353 | 354 | url = self.cfg['route_repo'] + repo_name + '/packages' 355 | param = { 356 | 'PackageRefs': package_key_list 357 | } 358 | r = requests.post(url, data=json.dumps(param), headers=self.headers) 359 | resp_data = json.loads(r.content) 360 | # print resp_data 361 | return resp_data 362 | 363 | def repo_delete_packages_by_key(self, repo_name, package_key_list): 364 | """ 365 | DELETE PACKAGES BY KEY 366 | DELETE /api/repos/:name/packages 367 | Remove packages from local repository by package keys. 368 | Any package could be removed from local repository. List package references in local repository could be 369 | retrieved with GET /repos/:name/packages. 370 | 371 | JSON body params: 372 | PackageRefs [][string] list of package references ( package keys) 373 | 374 | HTTP Errors: 375 | 404 repository with such name doesn’t exist 376 | Response is the same as for GET /api/repos/:name API. 377 | 378 | Example: 379 | $ curl -X DELETE -H 'Content-Type: application/json' --data '{"PackageRefs": 380 | ["Pi386 libboost-program-options-dev 1.49.0.1 918d2f433384e378"]}' 381 | http://localhost:8080/api/repos/repo2/packages 382 | """ 383 | url = self.cfg['route_repo'] + repo_name + '/packages' 384 | data = { 385 | 'PackageRefs': package_key_list 386 | } 387 | r = requests.delete(url, data=json.dumps(data), headers=self.headers) 388 | resp_data = json.loads(r.content) 389 | # print resp_data 390 | return resp_data 391 | 392 | ################### 393 | # FILE UPLOAD API # 394 | ################### 395 | 396 | def file_list_directories(self): 397 | """ 398 | LIST DIRECTORIES 399 | GET /api/files 400 | List all directories. 401 | Response: list of directory names. 402 | 403 | Example: 404 | $ curl http://localhost:8080/api/files 405 | """ 406 | r = requests.get(self.cfg['route_file'], headers=self.headers) 407 | # r.raise_for_status() 408 | resp_data = json.loads(r.content) 409 | # print json.dumps(resp_data) 410 | return resp_data 411 | 412 | def file_upload(self, dir_name, file_path): 413 | """ 414 | UPLOAD FILE 415 | POST /api/files/:dir 416 | Parameter :dir is upload directory name. Directory would be created if it doesn’t exist. 417 | Any number of files can be uploaded in one call, aptly would preserve filenames. 418 | No check is performed if existing uploaded would be overwritten. 419 | Response: list of uploaded files as :dir/:file. 420 | 421 | Example: 422 | $ curl -X POST -F file=@aptly_0.9~dev+217+ge5d646c_i386.deb http://localhost:8080/api/files/aptly-0.9 423 | """ 424 | 425 | f = { 426 | 'file': open(file_path, 'rb') 427 | } 428 | 429 | r = requests.post(self.cfg['route_file'] + dir_name, 430 | files=f) 431 | 432 | # r.raise_for_status() 433 | resp_data = json.loads(r.content) 434 | # print resp_data 435 | return resp_data 436 | 437 | def file_list(self, dir_name=None): 438 | """ 439 | LIST FILES IN DIRECTORY 440 | GET /api/files/:dir 441 | Returns list of files in directory. 442 | Response: list of filenames. 443 | 444 | HTTP Errors: 445 | 404 - directory doesn’t exist 446 | 447 | Example: 448 | $ curl http://localhost:8080/api/files/aptly-0.9 449 | """ 450 | if dir_name is None: 451 | dir_name = '' 452 | 453 | r = requests.get(self.cfg['route_file'] + 454 | dir_name, headers=self.headers) 455 | # r.raise_for_status() 456 | resp_data = json.loads(r.content) 457 | # print json.dumps(resp_data) 458 | return resp_data 459 | 460 | def file_delete_directory(self, dir_name): 461 | """ 462 | DELETE DIRECTORY 463 | DELETE /api/files/:dir 464 | Deletes all files in upload directory and directory itself. 465 | 466 | Example: 467 | $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9 468 | """ 469 | r = requests.delete( 470 | self.cfg['route_file'] + dir_name, headers=self.headers) 471 | # r.raise_for_status() 472 | resp_data = json.loads(r.content) 473 | # print json.dumps(resp_data) 474 | return resp_data 475 | 476 | def file_delete(self, dir_name, file_name): 477 | """ 478 | DELETE FILE IN DIRECTORY 479 | DELETE /api/files/:dir/:file 480 | Delete single file in directory. 481 | 482 | Example: 483 | $ curl -X DELETE http://localhost:8080/api/files/aptly-0.9/aptly_0.9~dev+217+ge5d646c_i386.deb 484 | """ 485 | r = requests.delete( 486 | self.cfg['route_file'] + dir_name + '/' + file_name, headers=self.headers) 487 | # r.raise_for_status() 488 | resp_data = json.loads(r.content) 489 | # print json.dumps(resp_data) 490 | return resp_data 491 | 492 | ################ 493 | # SNAPSHOT API # 494 | ################ 495 | 496 | def snapshot_list(self, sort='time'): 497 | """ 498 | LIST 499 | GET /api/snapshots 500 | Return list of all snapshots created in the system. 501 | 502 | Query params: 503 | sort snapshot order, defaults to name, set to time to display in creation order 504 | 505 | Example: 506 | $ curl -v http://localhost:8080/api/snapshots 507 | """ 508 | params = { 509 | 'sort': sort 510 | } 511 | r = requests.get(self.cfg['route_snap'], 512 | headers=self.headers, params=params) 513 | # r.raise_for_status() 514 | resp_data = json.loads(r.content) 515 | # self._out(resp_data) 516 | return resp_data 517 | 518 | def snapshot_create_from_local_repo(self, snapshot_name, repo_name, description=None): 519 | """ 520 | CREATE SNAPSHOT FROM LOCAL REPO 521 | POST /api/repos/:name/snapshots 522 | Create snapshot of current local repository :name contents as new snapshot with name :snapname. 523 | 524 | JSON body params: 525 | Name - [string], required snapshot name 526 | Description - [string] free-format description how snapshot has been created 527 | 528 | HTTP Errors: 529 | Code Description 530 | 400 snapshot with name Name already exists 531 | 404 local repo with name :name doesn’t exist 532 | 533 | Example: 534 | $ curl -X POST -H 'Content-Type: application/json' 535 | --data '{"Name":"snap9"}' http://localhost:8080/api/repos/local-repo/snapshots 536 | """ 537 | url = self.cfg['route_repo'] + repo_name + '/snapshots' 538 | if description is None: 539 | description = 'Description for ' + snapshot_name 540 | 541 | data = { 542 | 'Name': snapshot_name, 543 | 'Description': description 544 | } 545 | 546 | r = requests.post(url, data=json.dumps(data), headers=self.headers) 547 | # r.raise_for_status() 548 | resp_data = json.loads(r.content) 549 | # print resp_data 550 | return resp_data 551 | 552 | def snapshot_create_from_package_refs(self, snapshot_name, source_snapshot_list, package_refs_list, descr=None): 553 | """ 554 | CREATE SNAPSHOT FROM PACKAGE REFS 555 | POST /api/snapshots 556 | Create snapshot from list of package references. 557 | This API creates snapshot out of any list of package references. 558 | Package references could be obtained from other snapshots, local repos or mirrors. 559 | 560 | Name - [string], required snapshot name 561 | Description - [string] free-format description how snapshot has been created 562 | SourceSnapshots - [][string] list of source snapshot names (only for tracking purposes) 563 | PackageRefs - [][string] list of package keys which would be contents of the repository 564 | Sending request without SourceSnapshots and PackageRefs would create empty snapshot. 565 | 566 | HTTP Errors: 567 | 400 snapshot with name Name already exists, package conflict 568 | 404 source snapshot doesn’t exist, package doesn’t exist 569 | 570 | Example: 571 | $ curl -X POST -H 'Content-Type: application/json' --data '{"Name":"empty"}' http://localhost:8080/api/snapshots 572 | $ curl -X POST -H 'Content-Type: application/json' 573 | --data '{"Name":"snap10", "SourceSnapshots": ["snap9"], "Description": "Custom", "PackageRefs": 574 | ["Psource pyspi 0.6.1-1.3 3a8b37cbd9a3559e"]}' http://localhost:8080/api/snapshots 575 | """ 576 | url = self.cfg['route_snap'][:-1] 577 | if descr is None: 578 | descr = 'Description for ' + snapshot_name 579 | 580 | data = { 581 | 'Name': snapshot_name, 582 | 'Description': descr, 583 | 'SourceSnapshots': source_snapshot_list, 584 | 'PackageRefs': package_refs_list 585 | } 586 | 587 | r = requests.post(url, data=json.dumps(data), headers=self.headers) 588 | resp_data = json.loads(r.content) 589 | # print resp_data 590 | return resp_data 591 | 592 | def snapshot_update(self, old_snapshot_name, new_snapshot_name, description=None): 593 | """ 594 | UPDATE 595 | PUT /api/snapshots/:name 596 | Update snapshot’s description or name. 597 | 598 | JSON body params: 599 | Name - [string] new snapshot name 600 | Description - [string] free-format description how snapshot has been created 601 | 602 | HTTP Errors: 603 | 404 snapshot with such name doesn’t exist 604 | 409 rename is not possible: name already used by another snapshot 605 | 606 | Example: 607 | $ curl -X PUT -H 'Content-Type: application/json' --data '{"Name": "snap-wheezy"}' 608 | http://localhost:8080/api/snapshots/snap1 609 | """ 610 | url = self.cfg['route_snap'] + old_snapshot_name 611 | if description is None: 612 | description = 'Description for ' + new_snapshot_name 613 | 614 | data = { 615 | 'Name': new_snapshot_name, 616 | 'Description': description 617 | } 618 | 619 | r = requests.put(url, data=json.dumps(data), headers=self.headers) 620 | resp_data = json.loads(r.content) 621 | # print resp_data 622 | return resp_data 623 | 624 | def snapshot_show(self, snapshot_name): 625 | """ 626 | SHOW 627 | GET /api/snapshots/:name 628 | Get information about snapshot by name. 629 | 630 | HTTP Errors: 631 | Code Description 632 | 404 snapshot with such name doesn’t exist 633 | 634 | Example: 635 | $ curl http://localhost:8080/api/snapshots/snap1 636 | """ 637 | url = self.cfg['route_snap'] + snapshot_name 638 | r = requests.get(url, headers=self.headers) 639 | resp_data = json.loads(r.content) 640 | # print resp_data 641 | return resp_data 642 | 643 | def snapshot_delete(self, snapshot_name, force='0'): 644 | """ 645 | DELETE 646 | DELETE /api/snapshots/:name 647 | Delete snapshot. Snapshot can’t be deleted if it is published. aptly would refuse to delete snapshot if it has 648 | been used as source to create other snapshots, but that could be overridden with force parameter. 649 | 650 | Query params: 651 | force - when value is set to 1, delete snapshot even if it has been used as source snapshot 652 | 653 | HTTP Errors: 654 | 404 snapshot with such name doesn’t exist 655 | 409 snapshot can’t be dropped (reason in the message) 656 | 657 | Example: 658 | $ curl -X DELETE http://localhost:8080/api/snapshots/snap-wheezy 659 | $ curl -X DELETE 'http://localhost:8080/api/snapshots/snap-wheezy?force=1' 660 | """ 661 | url = self.cfg['route_snap'] + snapshot_name 662 | if force == '1': 663 | print 'Forcing removal of snapshot' 664 | 665 | param = { 666 | 'force': force 667 | } 668 | 669 | r = requests.delete(url, params=param, headers=self.headers) 670 | print r.url 671 | resp_data = json.loads(r.content) 672 | # print resp_data 673 | return resp_data 674 | 675 | def snapshot_show_packages(self, snapshot_name, package_to_search=None, with_deps=0, detail='compact'): 676 | """ 677 | SHOW PACKAGES/SEARCH 678 | GET /api/snapshots/:name/packages 679 | List all packages in snapshot or perform search on snapshot contents and return result. 680 | 681 | Query params: 682 | q - package query, if missing - return all packages 683 | withDeps - set to 1 to include dependencies when evaluating package query 684 | format - result format, compact by default ( only package keys), details to return full 685 | information about each package ( might be slow on large snapshots) 686 | 687 | Example: 688 | $ curl http://localhost:8080/api/snapshots/snap2/packages 689 | $ curl http://localhost:8080/api/snapshots/snap2/packages?q='Name%20( ~%20matlab)' 690 | """ 691 | url = self.cfg['route_snap'] + snapshot_name + '/packages' 692 | 693 | if package_to_search is None: 694 | param = { 695 | 'withDeps': with_deps, 696 | 'format': detail 697 | } 698 | else: 699 | param = { 700 | 'q': package_to_search, 701 | 'withDeps': with_deps, 702 | 'format': detail 703 | } 704 | 705 | r = requests.get(url, params=param, headers=self.headers) 706 | resp_data = json.loads(r.content) 707 | # print resp_data 708 | return resp_data 709 | 710 | def snapshot_diff(self, snapshot_left, snapshot_right): 711 | """ 712 | DIFFERENCE BETWEEN SNAPSHOTS 713 | GET /api/snapshots/:name/diff/:withSnapshot 714 | Calculate difference between two snapshots :name (left) and :withSnapshot (right). 715 | Response is a list of elements: 716 | 717 | Left - package reference present only in left snapshot 718 | Right - package reference present only in right snapshot 719 | 720 | If two snapshots are identical, response would be empty list. 721 | null - package reference right - snapshot has package missing in left 722 | package reference - null - left snapshot has package missing in right 723 | package reference - package reference snapshots have different packages 724 | 725 | Example: 726 | $ curl http://localhost:8080/api/snapshots/snap2/diff/snap3 727 | """ 728 | url = self.cfg['route_snap'] + \ 729 | snapshot_left + '/diff/' + snapshot_right 730 | r = requests.get(url, headers=self.headers) 731 | resp = json.loads(r.content) 732 | # print resp 733 | return resp 734 | 735 | ############### 736 | # PUBLISH API # 737 | ############### 738 | 739 | def publish_list(self): 740 | """ 741 | LIST 742 | GET /api/publish 743 | List published repositories. 744 | 745 | Example: 746 | $ curl http://localhost:8080/api/publish 747 | """ 748 | url = self.cfg['route_pub'] 749 | r = requests.get(url, headers=self.headers) 750 | resp = json.loads(r.content) 751 | # print resp 752 | return resp 753 | 754 | def publish(self, prefix, src_kind, src_list, dist, comp_list, label=None, orig=None, overwrite=None, arch_list=None): 755 | """ 756 | PUBLISH SNAPSHOT/LOCAL REPO 757 | POST /api/publish/:prefix 758 | Publish local repository or snapshot under specified prefix. Storage might be passed in prefix as well, 759 | e.g. s3:packages/. To supply empty prefix, just remove last part (POST /api/publish) 760 | 761 | JSON body params: 762 | SourceKind - [string], required source kind: local for local repositories and snapshot for snapshots 763 | Sources -[]Source, required list of Component/Name objects, Name is either local repository or snpashot name 764 | Distribution - [string] distribution name, if missing aptly would try to guess from sources 765 | Label [string] - value of Label: field in published repository stanza 766 | Origin [string] - value of Origin: field in published repository stanza 767 | ForceOverwrite - bool when publishing, overwrite files in pool/ directory without notice 768 | Architectures - [][string] override list of published architectures 769 | 770 | Notes on Sources field: 771 | when publishing single component repository, Component may be omitted, it would be guessed from source or 772 | set to default value main 773 | for multiple component published repository, Component would be guessed from source if not set 774 | GPG signing would happen in aptly server, using local to server gpg binary, keyrings. 775 | It’s not possible to configure publishing endpoints via API, they should be set in configuration and 776 | require aptly server restart. 777 | 778 | HTTP errors: 779 | 400 prefix/distribution is already used by another published repository 780 | 404 source snapshot/repo hasn’t been found 781 | 782 | Example: 783 | $ curl -X POST -H 'Content-Type: application/json' 784 | --data '{"SourceKind": "local", "Sources": [{"Name": "local-repo"}], 785 | "Architectures": ["i386", "amd64"], "Distribution": "wheezy"}' 786 | http://localhost:8080/api/publish 787 | 788 | $ curl -X POST -H 'Content-Type: application/json' 789 | --data '{"SourceKind": "local", "Sources": [{"Name": "0XktRe6qMFp4b8C", "Component": "contrib"}, 790 | {"Name": "EqmoTZiVx8MGN65", "Component": "non-free"}], 791 | "Architectures": ["i386", "amd64"], "Distribution": "wheezy"}' 792 | http://localhost:8080/api/publish/debian_testing/ 793 | """ 794 | url = self.cfg['route_pub'] + prefix 795 | 796 | # Prepare list of sources 797 | sources = [] 798 | if len(comp_list) != len(src_list): 799 | print "ERROR: sources list and components list should have same length" 800 | return 801 | 802 | for x in src_list: 803 | for y in comp_list: 804 | row = { 805 | 'Name': x, 806 | 'Component': y 807 | } 808 | sources.append(row) 809 | 810 | dat = {} 811 | if label is None: 812 | if orig is None: 813 | if overwrite is None: 814 | if arch_list is None: 815 | print 'simple publish' 816 | dat = { 817 | 'SourceKind': src_kind, 818 | 'Sources': sources, 819 | 'Distribution': dist 820 | } 821 | else: 822 | print 'multi publish' 823 | if int(overwrite) <= 0: 824 | fo = False 825 | else: 826 | fo = True 827 | print fo 828 | dat = { 829 | 'SourceKind': src_kind, 830 | 'Sources': sources, 831 | 'Distribution': dist, 832 | 'Architectures': arch_list, 833 | 'Label': label, 834 | 'Origin': orig, 835 | 'ForceOverwrite': fo 836 | } 837 | 838 | # print dat 839 | r = requests.post(url, data=json.dumps(dat), headers=self.headers) 840 | # print r.url 841 | resp = json.loads(r.content) 842 | # print resp 843 | return resp 844 | 845 | def publish_switch(self, prefix, snapshot_list, dist, component=None, force_overwrite=0): 846 | """ 847 | UPDATE PUBLISHED LOCAL REPO/SWITCH PUBLISHED SNAPSHOT 848 | PUT /api/publish/:prefix/:distribution 849 | API action depends on published repository contents: 850 | if local repository has been published, published repository would be updated to match local repository contents 851 | if snapshots have been been published, it is possible to switch each component to new snapshot 852 | 853 | JSON body params: 854 | Snapshots - []Source only when updating published snapshots, list of objects Component/Name 855 | ForceOverwrite - bool when publishing, overwrite files in pool/ directory without notice 856 | 857 | Example: 858 | $ curl -X PUT -H 'Content-Type: application/json' --data '{"Snapshots": 859 | [{"Component": "main", "Name": "8KNOnIC7q900L5v"}]}' http://localhost:8080/api/publish//wheezy 860 | """ 861 | if prefix is None: 862 | prefix = '' 863 | 864 | if int(force_overwrite) <= 0: 865 | fo = False 866 | else: 867 | fo = True 868 | 869 | url = self.cfg['route_pub'] + 's3:' + prefix + ':/' + dist 870 | 871 | snap_list_obj = [] 872 | is_array = isinstance(snapshot_list, list) 873 | 874 | if not is_array: 875 | print "convert snapshot param to array.. " 876 | tmp_val = snapshot_list 877 | snapshot_list = tmp_val.split(', ') 878 | 879 | for x in snapshot_list: 880 | if component is not None: 881 | snap_obj = { 882 | 'Component': component, 883 | 'Name': x 884 | } 885 | else: 886 | snap_obj = { 887 | 'Name': x 888 | } 889 | snap_list_obj.append(snap_obj) 890 | print snap_list_obj 891 | data = { 892 | 'Snapshots': snap_list_obj, 893 | 'ForceOverwrite': fo 894 | } 895 | r = requests.put(url, data=json.dumps(data), headers=self.headers) 896 | resp = json.loads(r.content) 897 | # print resp 898 | return resp 899 | 900 | def publish_drop(self, prefix, distribution, force=0): 901 | """ 902 | DROP PUBLISHED REPOSITORY 903 | DELETE /api/publish/:prefix/:distribution 904 | Delete published repository, clean up files in published directory. 905 | 906 | Query params: 907 | force - force published repository removal even if component cleanup fails 908 | Usually ?force=1 isn’t required, but if due to some corruption component cleanup fails, 909 | ?force=1 could be used to drop published repository. 910 | This might leave some published repository files left under public/ directory. 911 | 912 | Example: 913 | $ curl -X DELETE http://localhost:8080/api/publish//wheezy 914 | """ 915 | 916 | url = self.cfg['route_pub'] + prefix + '/' + distribution 917 | 918 | param = { 919 | 'force': force 920 | } 921 | 922 | r = requests.delete(url, params=param, headers=self.headers) 923 | resp = json.loads(r.content) 924 | # print resp 925 | return resp 926 | 927 | ############### 928 | # PACKAGE API # 929 | ############### 930 | 931 | def package_show_by_key(self, package_key): 932 | """ 933 | SHOW 934 | GET /api/packages/:key 935 | Show information about package by package key. 936 | Package keys could be obtained from various GET .../packages APIs. 937 | 938 | Response: 939 | Key - [sitring] package key (unique package identifier) 940 | ShortKey - [string] short package key (should be unique in one package list: snapshot, mirror, 941 | local repository) 942 | FilesHash - [string] hash of package files 943 | Package Stanza Fields - [string] all package stanza fields, e.g. Package, Architecture, … 944 | 945 | HTTP Errors: 946 | Code Description 947 | 404 package with such key doesn’t exist 948 | 949 | Example: 950 | $ curl http://localhost:8080/api/packages/'Pi386%20libboost-program-options-dev%201.49.0.1%20918d2f433384e378' 951 | Hint: %20 is url-encoded space. 952 | """ 953 | url = self.cfg['route_pack'] + package_key 954 | r = requests.get(url, headers=self.headers) 955 | resp = json.loads(r.content) 956 | # print resp 957 | return resp 958 | 959 | ############# 960 | # GRAPH API # 961 | ############# 962 | 963 | def graph(self, file_ext='.png'): 964 | """ 965 | GET /api/graph.:ext 966 | Generate graph of aptly objects ( same as in aptly graph command). 967 | :ext specifies desired file extension, e.g. .png, .svg. 968 | 969 | Example: 970 | open url http://localhost:8080/api/graph.svg in browser (hint: aptly database should be non-empty) 971 | """ 972 | url = self.cfg['route_graph'][:-1] + file_ext 973 | print url 974 | r = requests.get(url, headers=self.headers) 975 | resp = json.loads(r.content) 976 | # print resp 977 | return resp 978 | 979 | ############### 980 | # VERSION API # 981 | ############### 982 | 983 | def get_version(self): 984 | """ 985 | GET /api/version 986 | Return current aptly version. 987 | 988 | Example: 989 | $ curl http://localhost:8080/api/version 990 | """ 991 | url = self.cfg['route_vers'] 992 | r = requests.get(url, headers=self.headers) 993 | resp = json.loads(r.content) 994 | # print resp 995 | return resp 996 | -------------------------------------------------------------------------------- /aptly_cli/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimSusa/aptly_api_cli/011ba8e7f464726b336b53f6b2cbdc4490b5180c/aptly_cli/cli/__init__.py -------------------------------------------------------------------------------- /aptly_cli/cli/cli.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | The CLI with option parser 6 | """ 7 | 8 | import sys 9 | import json 10 | import os 11 | from optparse import OptionParser 12 | from aptly_cli.util.util import Util 13 | 14 | 15 | def main(): 16 | """ 17 | Main entry point for cli. 18 | """ 19 | util = Util() 20 | parser = _get_parser_opts() 21 | (opts, args) = parser.parse_args() 22 | _execute_opts(opts, args, util) 23 | 24 | if len(sys.argv) == 1: 25 | parser.print_help() 26 | home = os.path.expanduser("~") 27 | name = home + '/aptly-cli.conf' 28 | if not os.path.exists(name): 29 | print "No config file (aptly-cli.conf) found at $HOME. Please create one by --create_config option" 30 | sys.exit(0) 31 | 32 | 33 | def _get_parser_opts(): 34 | """ _get_parser_opts 35 | Create parser, options and return object. 36 | """ 37 | parser = OptionParser() 38 | 39 | parser.add_option('--repo_list', 40 | action='store_true', 41 | help='List all local repos') 42 | 43 | parser.add_option('--repo_create', 44 | nargs=1, 45 | help='Create local repo', 46 | metavar='REPO_NAME [COMMENT] [DISTRIBUTION] [COMPONENT]') 47 | 48 | parser.add_option('--repo_show_packages', 49 | nargs=1, 50 | help='Shows packages from repo', 51 | metavar='REPO_NAME [PACKAGE_TO_SEARCH] [WITH_DEPS] [FORMAT]') 52 | 53 | parser.add_option('--repo_show', 54 | nargs=1, 55 | help='Show basic repo-information', 56 | metavar='REPO_NAME') 57 | 58 | parser.add_option('--repo_edit', 59 | nargs=1, 60 | help='Edit repo-information', 61 | metavar='REPO_NAME COMMENT DISTRIBUTION COMPONENT') 62 | 63 | parser.add_option('--repo_delete', 64 | nargs=1, 65 | help='Delete repository', 66 | metavar='REPO_NAME') 67 | 68 | parser.add_option('--repo_add_packages_by_key', 69 | nargs=2, 70 | help='Add packages to local repo by key', 71 | metavar='REPO_NAME PACKAGE_REFS') 72 | 73 | parser.add_option('--repo_delete_packages_by_key', 74 | nargs=2, 75 | help='Delete packages from repository by key', 76 | metavar='REPO_NAME PACKAGE_REFS') 77 | 78 | parser.add_option('--file_list_dirs', 79 | action='store_true', 80 | help='Lists all upload-directories') 81 | 82 | parser.add_option('--file_upload', 83 | nargs=2, 84 | help='Upload file to local upload-directory', 85 | metavar='UPLOAD_DIR FILE') 86 | 87 | parser.add_option('--repo_add_package_from_upload', 88 | nargs=3, 89 | help='Add package from upload folder to local repo', 90 | metavar='REPO_NAME UPLOAD_DIR PACKAGE_NAME') 91 | 92 | parser.add_option('--file_list', 93 | action='store_true', 94 | help='List uploaded files') 95 | 96 | parser.add_option('--file_delete_dir', 97 | nargs=1, 98 | help='Delete upload directory', 99 | metavar='UPLOAD_DIR') 100 | 101 | parser.add_option('--file_delete', 102 | nargs=2, 103 | help='Delete a file in upload directory', 104 | metavar='UPLOAD_DIR FILE') 105 | 106 | parser.add_option('--snapshot_create_from_local_repo', 107 | nargs=2, 108 | help='Create snapshot from local repo', 109 | metavar='SNAPSHOT_NAME REPO_NAME [DESCRIPTION]') 110 | 111 | parser.add_option('--snapshot_create_by_pack_refs', 112 | nargs=3, 113 | help='Create snapshot by package references', 114 | metavar='SNAPSHOT_NAME SOURCE_SNAPSHOTS PACKAGE_REF_LIST [DESCRIPTION]') 115 | 116 | parser.add_option('--snapshot_show', 117 | nargs=1, 118 | help='Show basic information about snapshot', 119 | metavar='SNAPSHOT_NAME') 120 | 121 | parser.add_option('--snapshot_show_packages', 122 | nargs=1, 123 | help='Show all packages the snapshot is containing or optionally search for one.', 124 | metavar='SNAPSHOT_NAME [PACKAGE_TO_SEARCH] [WITH_DEPS] [FORMAT]') 125 | 126 | parser.add_option('--snapshot_update', 127 | nargs=2, 128 | help='Rename snapshot and optionally change description', 129 | metavar='OLD_SNAPSHOT_NAME NEW_SNAPSHOT_NAME [DESCRIPTION]') 130 | 131 | parser.add_option('--snapshot_list', 132 | action='store_true', 133 | help='Lists all available snapshots', 134 | metavar='[SORT_BY_NAME_OR_TIME]') 135 | 136 | parser.add_option('--snapshot_diff', 137 | nargs=2, 138 | help='List differences of two snapshots', 139 | metavar='LEFT_SNAPSHOT_NAME RIGHT_SNAPSHOT_NAME') 140 | 141 | parser.add_option('--snapshot_delete', 142 | nargs=1, 143 | help='Delete snapshot by name. Optionally force deletion.', 144 | metavar='SNAPSHOT_NAME [FORCE_DELETION]') 145 | 146 | parser.add_option('--publish_list', 147 | action='store_true', 148 | help='List all available repositories to publish to') 149 | 150 | parser.add_option('--publish', 151 | nargs=5, 152 | help='Publish snapshot or repository to storage', 153 | metavar='PREFIX SOURCES_KIND SOURCES_LIST DISTRIBUTION COMPONENT_LIST [LABEL] [ORIGIN] \ 154 | [FORCE_OVERWRITE] [ARCHITECTURES_LIST]') 155 | 156 | parser.add_option('--publish_drop', 157 | nargs=2, 158 | help='Drop published repo content', 159 | metavar='PREFIX DISTRIBUTION [FORCE_REMOVAL]') 160 | 161 | parser.add_option('--publish_switch', 162 | nargs=3, 163 | help='Switching snapshots to published repo with minimal server down time.', 164 | metavar='PREFIX SOURCES_LIST DISTRIBUTION [COMPONENT] [FORCE_OVERWRITE]') 165 | 166 | parser.add_option('--get_version', 167 | action='store_true', 168 | help='Returns aptly version') 169 | 170 | parser.add_option('--package_show_by_key', 171 | nargs=1, 172 | help='Show packages by key', 173 | metavar='PACKAGE_KEY') 174 | 175 | parser.add_option('--create_config', 176 | action='store_true', 177 | help='Creates standard config file (aptly-cli.conf) in $HOME') 178 | 179 | parser.add_option('--get_last_snapshots', 180 | nargs=2, 181 | help='Returns the last n snapshots by prefix or optional postfix.', 182 | metavar='PREFIX NR_OF_VERS [POSTFIX]') 183 | 184 | parser.add_option('--clean_last_snapshots', 185 | nargs=2, 186 | help='Cleans the last n snapshots by prefix or optional postfix.', 187 | metavar='PREFIX NR_OF_VERS [POSTFIX]') 188 | 189 | parser.add_option('--clean_mirrored_snapshots', 190 | action='store_true', 191 | help='Cleans out snapshots, which were taken from mirrors (from config)') 192 | 193 | parser.add_option('--clean_repo_packages', 194 | action='store_true', 195 | help='Cleans out packages, which were taken from repos (from config)') 196 | 197 | parser.add_option('--list_repos_and_packages', 198 | action='store_true', 199 | help='List all repos with their containing packages.') 200 | 201 | parser.add_option('--get_last_packages', 202 | nargs=3, 203 | help='Returns the last n packages by reponame, prefix or optional postfix.', 204 | metavar='REPO_NAME PREFIX NR_OF_VERS [POSTFIX]') 205 | 206 | parser.add_option('--clean_last_packages', 207 | nargs=3, 208 | help='Cleans the last n packages by reponame, prefix or optional postfix.', 209 | metavar='REPO_NAME PREFIX NR_OF_VERS [POSTFIX]') 210 | 211 | parser.add_option('--diff_both_last_snapshots_mirrors', 212 | action='store_true', 213 | help='Sorts list of snapshots and makes a diff between the last two.') 214 | 215 | parser.add_option('--publish_switch_3rdparty_production', 216 | action='store_true', 217 | help='Publish the last 3rd party staging snapshot to s3 production, if new content is available') 218 | return parser 219 | 220 | 221 | def _execute_opts(opts, args, util): 222 | """ _execute_opts 223 | Execute functions due to options and arguments. 224 | """ 225 | class Data(object): 226 | """ 227 | Create dat object and use it as argument. 228 | """ 229 | def __init__(self): 230 | pass 231 | # 232 | # Basic API functionalities 233 | # 234 | if opts.repo_list: 235 | resp = util.api.repo_list() 236 | print json.dumps(resp, indent=2) 237 | 238 | if opts.repo_create: 239 | if len(args) >= 3: 240 | Data.comment = args[0] 241 | Data.default_distribution = args[1] 242 | Data.default_component = args[2] 243 | resp = util.api.repo_create(opts.repo_create, Data) 244 | else: 245 | resp = util.api.repo_create(opts.repo_create) 246 | print json.dumps(resp, indent=2) 247 | 248 | if opts.repo_show_packages: 249 | resp = None 250 | if len(args) >= 3: 251 | resp = util.api.repo_show_packages( 252 | opts.repo_show_packages, args[0], args[1], args[2]) 253 | else: 254 | resp = util.api.repo_show_packages(opts.repo_show_packages) 255 | print json.dumps(resp, indent=2) 256 | 257 | if opts.repo_show: 258 | resp = util.api.repo_show(opts.repo_show) 259 | print json.dumps(resp, indent=2) 260 | 261 | if opts.repo_edit: 262 | if len(args) >= 3: 263 | Data.comment = args[0] 264 | Data.default_distribution = args[1] 265 | Data.default_component = args[2] 266 | resp = util.api.repo_edit(opts.repo_edit, Data) 267 | print json.dumps(resp, indent=2) 268 | else: 269 | print 'Wrong usage!' 270 | 271 | if opts.repo_delete: 272 | resp = util.api.repo_delete(opts.repo_delete) 273 | print json.dumps(resp, indent=2) 274 | 275 | if opts.file_list_dirs: 276 | resp = util.api.file_list_directories() 277 | print json.dumps(resp, indent=2) 278 | 279 | if opts.file_upload: 280 | resp = util.api.file_upload(opts.file_upload[0], opts.file_upload[1]) 281 | print json.dumps(resp, indent=2) 282 | 283 | if opts.repo_add_package_from_upload: 284 | o = opts.repo_add_package_from_upload 285 | resp = util.api.repo_add_package_from_upload(o[0], o[1], o[2]) 286 | print json.dumps(resp, indent=2) 287 | 288 | if opts.repo_add_packages_by_key: 289 | print 'repo_add_packages_by_key' 290 | o = opts.repo_add_packages_by_key 291 | key_list = o[1].split(', ') 292 | resp = util.api.repo_add_packages_by_key(o[0], key_list) 293 | print json.dumps(resp, indent=2) 294 | 295 | if opts.repo_delete_packages_by_key: 296 | print 'repo_delete_packages_by_key' 297 | o = opts.repo_delete_packages_by_key 298 | key_list = o[1].split(', ') 299 | resp = util.api.repo_delete_packages_by_key(o[0], key_list) 300 | print json.dumps(resp, indent=2) 301 | 302 | if opts.file_list: 303 | resp = util.api.file_list() 304 | 305 | if opts.file_delete_dir: 306 | resp = util.api.file_delete_directory(opts.file_delete_dir) 307 | print json.dumps(resp, indent=2) 308 | 309 | if opts.file_delete: 310 | resp = util.api.file_delete(opts.file_delete[0], opts.file_delete[1]) 311 | 312 | if opts.snapshot_create_from_local_repo: 313 | o = opts.snapshot_create_from_local_repo 314 | resp = None 315 | if len(args) >= 1: 316 | resp = util.api.snapshot_create_from_local_repo(o[0], o[1], args[0]) 317 | else: 318 | resp = util.api.snapshot_create_from_local_repo(o[0], o[1]) 319 | print json.dumps(resp, indent=2) 320 | 321 | if opts.snapshot_create_by_pack_refs: 322 | o = opts.snapshot_create_by_pack_refs 323 | l = o[2].split(', ') 324 | resp = None 325 | if len(args) >= 1: 326 | resp = util.api.snapshot_create_from_package_refs(o[0], o[1].split(', '), l, args[0]) 327 | else: 328 | resp = util.api.snapshot_create_from_package_refs(o[0], o[1].split(', '), l) 329 | print json.dumps(resp, indent=2) 330 | 331 | if opts.snapshot_show_packages: 332 | o = opts.snapshot_show_packages 333 | resp = None 334 | if len(args) >= 3: 335 | resp = util.api.snapshot_show_packages(o, args[0], args[1], args[2]) 336 | else: 337 | resp = util.api.snapshot_show_packages(o) 338 | print json.dumps(resp, indent=2) 339 | 340 | if opts.snapshot_update: 341 | o = opts.snapshot_update 342 | if len(args) >= 1: 343 | resp = util.api.snapshot_update(o[0], o[1], args[0]) 344 | print json.dumps(resp, indent=2) 345 | 346 | if opts.snapshot_list: 347 | if len(args) >= 1: 348 | print json.dumps(util.api.snapshot_list(args[0]), indent=2) 349 | else: 350 | print json.dumps(util.api.snapshot_list(), indent=2) 351 | 352 | if opts.snapshot_diff: 353 | print json.dumps(util.api.snapshot_diff(opts.snapshot_diff[0], opts.snapshot_diff[1]), indent=2) 354 | 355 | if opts.snapshot_delete: 356 | resp = None 357 | if len(args) >= 1: 358 | print args[0] 359 | resp = util.api.snapshot_delete(opts.snapshot_delete, args[0]) 360 | else: 361 | resp = util.api.snapshot_delete(opts.snapshot_delete) 362 | print json.dumps(resp, indent=2) 363 | 364 | if opts.publish_list: 365 | resp = util.api.publish_list() 366 | print json.dumps(resp, indent=2) 367 | 368 | if opts.publish: 369 | o = opts.publish 370 | resp = None 371 | if len(args) >= 5: 372 | resp = util.api.publish( 373 | o[0], o[1], o[2].split(', '), o[3], o[4].split(', '), args[1], args[2], args[3], args[4].split(', ')) 374 | else: 375 | resp = util.api.publish(o[0], o[1], o[2].split(', '), o[3], o[4].split(', ')) 376 | print json.dumps(resp, indent=2) 377 | 378 | if opts.publish_switch: 379 | o = opts.publish_switch 380 | res = None 381 | if len(args) >= 2: 382 | res = util.api.publish_switch(o[0], o[1], o[2], args[0], args[1]) 383 | else: 384 | res = util.api.publish_switch(o[0], o[1], o[2]) 385 | print json.dumps(res, indent=2) 386 | 387 | if opts.publish_drop: 388 | o = opts.publish_drop 389 | resp = None 390 | if len(args) >= 1: 391 | resp = util.api.publish_drop(o[0], o[1], args[0]) 392 | else: 393 | resp = util.api.publish_drop(o[0], o[1]) 394 | print json.dumps(resp, indent=2) 395 | 396 | if opts.package_show_by_key: 397 | resp = util.api.package_show_by_key(opts.package_show_by_key) 398 | print json.dumps(resp, indent=2) 399 | 400 | if opts.get_version: 401 | resp = util.api.get_version() 402 | print json.dumps(resp, indent=2) 403 | 404 | # 405 | # Extended functionalities 406 | # 407 | if opts.create_config: 408 | # package prefix, reponame 409 | util.create_init_file() 410 | 411 | if opts.get_last_snapshots: 412 | o = opts.get_last_snapshots 413 | if len(args) >= 1: 414 | res = util.get_last_snapshots(o[0], o[1], args[0]) 415 | else: 416 | res = util.get_last_snapshots(o[0], o[1]) 417 | 418 | if len(res) == 1: 419 | print ''.join(res) 420 | else: 421 | print json.dumps(res, indent=2) 422 | 423 | if opts.clean_last_snapshots: 424 | o = opts.clean_last_snapshots 425 | if len(args) >= 1: 426 | res = util.clean_last_snapshots(o[0], o[1], args[0]) 427 | else: 428 | res = util.clean_last_snapshots(o[0], o[1]) 429 | 430 | print json.dumps(res, indent=2) 431 | 432 | if opts.diff_both_last_snapshots_mirrors: 433 | # package prefix, reponame 434 | util.diff_both_last_snapshots_mirrors() 435 | 436 | if opts.clean_mirrored_snapshots: 437 | # package prefix, reponame 438 | util.clean_mirrored_snapshots() 439 | 440 | if opts.clean_repo_packages: 441 | # package prefix, reponame 442 | util.clean_repo_packages() 443 | 444 | if opts.list_repos_and_packages: 445 | # package prefix, reponame 446 | util.list_all_repos_and_packages() 447 | 448 | if opts.get_last_packages: 449 | o = opts.get_last_packages 450 | if len(args) >= 1: 451 | res = util.get_last_packages(o[0], o[1], o[2], args[0]) 452 | else: 453 | res = util.get_last_packages(o[0], o[1], o[2]) 454 | 455 | if len(res) == 1: 456 | print ''.join(res) 457 | else: 458 | print json.dumps(res, indent=2) 459 | 460 | if opts.clean_last_packages: 461 | o = opts.clean_last_packages 462 | if len(args) >= 1: 463 | res = util.clean_last_packages(o[0], o[1], o[2], args[0]) 464 | else: 465 | res = util.clean_last_packages(o[0], o[1], o[2]) 466 | 467 | if opts.publish_switch_3rdparty_production: 468 | # package prefix, reponame 469 | util.publish_switch_3rdparty_production() 470 | 471 | if __name__ == "__main__": 472 | sys.exit(main()) 473 | -------------------------------------------------------------------------------- /aptly_cli/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimSusa/aptly_api_cli/011ba8e7f464726b336b53f6b2cbdc4490b5180c/aptly_cli/util/__init__.py -------------------------------------------------------------------------------- /aptly_cli/util/util.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ Util 5 | Instance for utils and tools. 6 | """ 7 | 8 | import re 9 | from os.path import expanduser, exists 10 | from aptly_cli.api.api import AptlyApiRequests 11 | 12 | 13 | class Util(object): 14 | 15 | """ Util 16 | Instance for utils and tools. 17 | """ 18 | 19 | def __init__(self): 20 | """ 21 | Init contstructor 22 | """ 23 | self.api = AptlyApiRequests() 24 | 25 | @staticmethod 26 | def _atoi(text): 27 | """ _atoi 28 | Converts asci to int 29 | """ 30 | return int(text) if text.isdigit() else text 31 | 32 | @staticmethod 33 | def create_init_file(): 34 | """ create_init_file 35 | Will create a config file at home folder, if it does not exist. 36 | """ 37 | home = expanduser("~") 38 | name = home + '/aptly-cli.conf' 39 | 40 | print "Look for already existing file..." 41 | 42 | if not exists(name): 43 | print 'Create_init_file' 44 | try: 45 | conf = open(name, 'a') 46 | conf.write( 47 | '[general]\nbasic_url=http://localhost\nport=:9003\nsave_last_snap=3\nsave_last_pkg=10\n \ 48 | prefixes_mirrors=\npackage_prefixes=\nrepos_to_clean=\n[general]\nrepos=\nstaging_snap_pre_post=') 49 | conf.close() 50 | 51 | except: 52 | print('Something went wrong! Can\'t tell what?') 53 | 54 | else: 55 | print "File already exists! Stop action" 56 | print name 57 | 58 | def _natural_keys(self, text): 59 | """ _natural_keys 60 | Split up string at int. 61 | """ 62 | return [self._atoi(c) for c in re.split('(\\d+)', text)] 63 | 64 | def _sort_out_last_n_snap(self, snaplist, prefix, nr_of_leftover, postfix=None): 65 | """ _sort_out_last_n_snap 66 | Returns n sorted items from given input list by prefix. 67 | """ 68 | snapstaginglist = [] 69 | for x in snaplist: 70 | if prefix in x[u'Name']: 71 | if postfix is None: 72 | # end of string contains 73 | snapstaginglist.append(x[u'Name']) 74 | else: 75 | if postfix in x[u'Name']: 76 | snapstaginglist.append(x[u'Name']) 77 | 78 | slen = len(snapstaginglist) 79 | snapstaginglist.sort(key=self._natural_keys) 80 | ret = [] 81 | nr_o = int(nr_of_leftover) 82 | if slen > (nr_o - 1): 83 | for a in snapstaginglist[-nr_o:]: 84 | ret.append(a) 85 | else: 86 | ret = snapstaginglist 87 | 88 | return ret 89 | 90 | def get_last_snapshots(self, prefix, nr_of_vers, postfix=None): 91 | """ get_last_snapshots 92 | Returns n versions of snapshots, sorted out by a prefix and optional postfix. 93 | """ 94 | snaplist = self.api.snapshot_list() 95 | if postfix is None: 96 | res = self._sort_out_last_n_snap(snaplist, prefix, nr_of_vers) 97 | else: 98 | res = self._sort_out_last_n_snap( 99 | snaplist, prefix, nr_of_vers, postfix) 100 | 101 | return res 102 | 103 | def clean_last_snapshots(self, prefix, nr_of_vers, postfix=None): 104 | """ clean_last_snapshots 105 | Cleans n versions of snapshots, sorted out by a prefix and optional postfix. 106 | """ 107 | if postfix is None: 108 | items_to_delete = self.get_last_snapshots(prefix, nr_of_vers) 109 | else: 110 | items_to_delete = self.get_last_snapshots( 111 | prefix, nr_of_vers, postfix) 112 | 113 | nr_to_left_over = int( 114 | self.api.get_config_from_file()['save_last_snap']) 115 | 116 | if len(items_to_delete) > nr_to_left_over: 117 | for item in items_to_delete[:-nr_to_left_over]: 118 | if item: 119 | # force removal 120 | self.api.snapshot_delete(item, '1') 121 | else: 122 | print prefix 123 | print "Nothing to delete...." 124 | 125 | def diff_both_last_snapshots_mirrors(self): 126 | """ diff_both_last_snapshots_mirrors 127 | Fetches out last two versions of snapshots from a given list of mirrors and diffs both. 128 | Return, if all mirrors have new content to update or not (EMPTY). 129 | """ 130 | local_cfg = self.api.get_config_from_file() 131 | if local_cfg['prefixes_mirrors']: 132 | prefix_list = local_cfg['prefixes_mirrors'].split(', ') 133 | else: 134 | print "Error: Prefix list is empty: please add prefixes_mirrors to your configfile!" 135 | 136 | snaplist = self.api.snapshot_list() 137 | results = [] 138 | result = "" 139 | 140 | for x in prefix_list: 141 | res_list = self._sort_out_last_n_snap(snaplist, x, 2) 142 | if len(res_list) >= 2: 143 | res = self.api.snapshot_diff(res_list[0], res_list[1]) 144 | if not res: 145 | results.append("EMPTY") 146 | else: 147 | results.append(res) 148 | break 149 | else: 150 | results.append("EMPTY") 151 | 152 | # print results 153 | result = "" 154 | for y in results: 155 | if y == "EMPTY": 156 | result = "EMPTY" 157 | else: 158 | result = y 159 | break 160 | 161 | print result 162 | return result 163 | 164 | def list_all_repos_and_packages(self): 165 | """ list_all_repos_and_packages 166 | """ 167 | repos = self.api.repo_list() 168 | for repo in repos: 169 | print repo[u'Name'] 170 | packs = self.api.repo_show_packages(repo[u'Name']) 171 | for pack in packs: 172 | print pack 173 | 174 | def get_last_packages(self, repo_name, pack_prefix, nr_of_leftover, postfix=None): 175 | """ get_last_packages 176 | """ 177 | resp = None 178 | packs = self.api.repo_show_packages(repo_name) 179 | if postfix: 180 | resp = self._sort_out_last_n_packages(packs, pack_prefix, nr_of_leftover, postfix) 181 | else: 182 | resp = self._sort_out_last_n_packages(packs, pack_prefix, nr_of_leftover) 183 | return resp 184 | 185 | def clean_last_packages(self, repo_name, pack_prefix, nr_of_leftover, postfix=None): 186 | """ clean_last_packages 187 | """ 188 | items_to_delete = None 189 | if postfix: 190 | items_to_delete = self.get_last_packages(repo_name, pack_prefix, nr_of_leftover, postfix) 191 | else: 192 | items_to_delete = self.get_last_packages(repo_name, pack_prefix, nr_of_leftover) 193 | 194 | nr_to_left_over = int( 195 | self.api.get_config_from_file()['save_last_pkg']) 196 | 197 | print nr_to_left_over 198 | 199 | if len(items_to_delete) > nr_to_left_over: 200 | worklist = [] 201 | for item in items_to_delete[:-nr_to_left_over]: 202 | if item: 203 | print "Will remove..." 204 | print item 205 | worklist.append(item) 206 | 207 | self.api.repo_delete_packages_by_key(repo_name, worklist) 208 | else: 209 | print "Nothing to delete..." 210 | 211 | def _sort_out_last_n_packages(self, packlist, prefix, nr_of_leftover, postfix=None): 212 | """ _sort_out_last_n_snap 213 | Returns n sorted items from given input list by prefix. 214 | """ 215 | # print packlist 216 | worklist = [] 217 | for pack_blob in packlist: 218 | pack_tmp = pack_blob.split(' ') 219 | if pack_tmp[1] in prefix: 220 | worklist.append(pack_blob) 221 | # print pack_tmp[1] 222 | 223 | slen = len(worklist) 224 | worklist.sort(key=self._natural_keys) 225 | ret = [] 226 | nr_o = int(nr_of_leftover) 227 | if slen > (nr_o - 1): 228 | for a in worklist[-nr_o:]: 229 | ret.append(a) 230 | else: 231 | ret = worklist 232 | 233 | return ret 234 | 235 | def clean_mirrored_snapshots(self): 236 | """ clean_mirrored_snapshots 237 | Clean out all snapshots that were taken from mirrors. The mirror entries are taken from config file. 238 | """ 239 | print "clean mirrored snapshots" 240 | local_cfg = self.api.get_config_from_file() 241 | if local_cfg['prefixes_mirrors']: 242 | prefix_list = local_cfg['prefixes_mirrors'].split(', ') 243 | else: 244 | print "Error: Prefix list is empty: please add prefixes_mirrors to your configfile!" 245 | 246 | for x in prefix_list: 247 | self.clean_last_snapshots(x, 100) 248 | 249 | def clean_repo_packages(self): 250 | """ clean_repo_snapshots 251 | Clean out all snapshots that were taken from repos. The repo entries are taken from config file. 252 | """ 253 | print "clean snapshots from repos" 254 | local_cfg = self.api.get_config_from_file() 255 | if local_cfg['repos_to_clean']: 256 | repo_list = local_cfg['repos_to_clean'].split(', ') 257 | else: 258 | print "Error: Prefix list is empty: please add repos_to_clean to your configfile!" 259 | 260 | if local_cfg['package_prefixes']: 261 | pack_pref_list = local_cfg['package_prefixes'].split(', ') 262 | else: 263 | print "Error: Prefix list is empty: please add package_prefixes to your configfile!" 264 | 265 | for repo_name in repo_list: 266 | print repo_name 267 | for pack_prefix in pack_pref_list: 268 | print pack_prefix 269 | self.clean_last_packages(repo_name, pack_prefix, 100) 270 | 271 | def publish_switch_3rdparty_production(self): 272 | """ publish_switch_s3_3rd_party_production 273 | Publish the latest 3rd party snapshot from staging to production, only if there is new content available. 274 | """ 275 | print "publish_switch_s3_3rd_party_production" 276 | 277 | # Get Config 278 | local_cfg = self.api.get_config_from_file() 279 | if local_cfg['repos']: 280 | s3_list = local_cfg['repos'].split(', ') 281 | else: 282 | print "Error: Prefix list is empty: please add s3 buckets to your configfile!" 283 | 284 | if local_cfg['staging_snap_pre_post']: 285 | prefix_postfix = local_cfg['staging_snap_pre_post'].split(', ') 286 | else: 287 | print "Error: Prefix list is empty: please add staging_snap_pre_post to your configfile!" 288 | 289 | # Diff snapshots from mirrors 290 | # Temphack 291 | res = 'something' # self.diff_both_last_snapshots_mirrors() 292 | 293 | # Decide if it should be released to production 294 | if res == "EMPTY": 295 | print "New snapshot has no new packages. No need to release to production!" 296 | 297 | else: 298 | print "New packages were found...", res 299 | 300 | # Get most actual snapshot from 3rdparty staging 301 | last_snap = self.get_last_snapshots(prefix_postfix[0], 1, prefix_postfix[1]) 302 | print "This is the new snapshot: ", last_snap 303 | 304 | # publish snapshots to production on s3 305 | print ("Publish ", last_snap, s3_list[0]) 306 | self.api.publish_switch(s3_list[0], last_snap, "precise", "main", 0) 307 | 308 | print ("Publish ", last_snap, s3_list[1]) 309 | self.api.publish_switch(s3_list[1], last_snap, "precise", "main", 0) 310 | 311 | # clean out 312 | self.clean_mirrored_snapshots() 313 | -------------------------------------------------------------------------------- /ci-scripts/aptly-check-port-running.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | nc -z localhost 9003 4 | #pgrep aptly 5 | lastRes=$(echo $?) 6 | test $lastRes -gt 0 && printf "Aptly server not running, at specified port. So it will be started now..\n" && sudo start -n aptly & 7 | sleep 5 8 | exit 0 9 | -------------------------------------------------------------------------------- /ci-scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Restart aptly server if it is not running 4 | $HOME/bin/aptly-check-port-running.sh 5 | 6 | # If no argument is given, spit out help 7 | if [ -z "$1" ]; then 8 | echo "This script should be used along with jenkins versioning plugin and it will add a package to the local repo, snapshot the repo and publish it to s3." 9 | echo "Usage: $(basename $0) " 10 | echo "For example: $(basename $0) ../ cluster-manager unstable" 11 | else 12 | 13 | 14 | PATH_TO=$1 15 | DEB_PACKAGE_PREFIX=$2 16 | REPO_PREFIX=$3 17 | 18 | echo "Uploading File ${DEB_PACKAGE_PREFIX}_${CUSTOM_PACKAGE_VERSION}_amd64.deb to internal repo" 19 | aptly-cli --file_upload ${REPO_PREFIX}-uploads ${PATH_TO}${DEB_PACKAGE_PREFIX}_${CUSTOM_PACKAGE_VERSION}_amd64.deb 20 | 21 | echo "Adding File to internal repo ${REPO_PREFIX}-repo" 22 | aptly-cli --repo_add_package_from_upload ${REPO_PREFIX}-repo ${REPO_PREFIX}-uploads ${DEB_PACKAGE_PREFIX}_${CUSTOM_PACKAGE_VERSION}_amd64.deb 23 | 24 | echo "Creating a snapshot from internal repo ${DEB_PACKAGE_PREFIX}_${CUSTOM_PACKAGE_VERSION}_snapshot" 25 | aptly-cli --snapshot_create_from_local_repo ${DEB_PACKAGE_PREFIX}_${CUSTOM_PACKAGE_VERSION}_snapshot ${REPO_PREFIX}-repo 26 | 27 | echo "Publish snapshot to ${REPO_PREFIX}-eu-west-1" 28 | aptly-cli --publish_switch "${REPO_PREFIX}-eu-west-1" "${DEB_PACKAGE_PREFIX}_${CUSTOM_PACKAGE_VERSION}_snapshot" "precise" "main" 0 29 | 30 | echo "Publish snapshot to ${REPO_PREFIX}-us-east-1" 31 | aptly-cli --publish_switch "${REPO_PREFIX}-us-east-1" "${DEB_PACKAGE_PREFIX}_${CUSTOM_PACKAGE_VERSION}_snapshot" "precise" "main" 0 32 | 33 | echo "Cleaning up... " 34 | echo "Remove local package, because it is not needed anymore" 35 | rm ${PATH_TO}${DEB_PACKAGE_PREFIX}_${CUSTOM_PACKAGE_VERSION}* 36 | 37 | echo "Delete old snapshots" 38 | aptly-cli --clean_last_snapshots "${DEB_PACKAGE_PREFIX}" 100 "${REPO_PREFIX}" 39 | 40 | echo "Delete old packages" 41 | aptly-cli --clean_last_packages "${REPO_PREFIX}-repo" "${DEB_PACKAGE_PREFIX}" 100 42 | fi 43 | -------------------------------------------------------------------------------- /ci-scripts/update-3rdparty-staging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # This script starts the update chain beginning from staging. 4 | 5 | # If no argument is given, spit out help 6 | if [ -z "$1" ]; then 7 | echo "What does this script do?" 8 | echo "All 3rdParty mirrors will get updated and snapshots will be created." 9 | echo "Afterwards the snapshots will be merged with that of jenkins local 3rdParty repo and published to s3." 10 | echo "Usage: $(basename $0) " 11 | echo "For example: $(basename $0) cloudera saltstack mongodb puppetmaster" 12 | else 13 | 14 | PACKAGES=() 15 | for ARG in "$@" 16 | do 17 | # hack, because of nginx repo issue 18 | if [ "${ARG}" != "nginx" ]; then 19 | echo "Updating mirror ${ARG}" 20 | aptly mirror update ${ARG}-mirror 21 | fi 22 | 23 | echo "Create snapshot ${ARG}_${CUSTOM_PACKAGE_VERSION}_snapshot from mirror ${ARG}-mirror" 24 | aptly snapshot create ${ARG}_${CUSTOM_PACKAGE_VERSION}_snapshot from mirror ${ARG}-mirror 25 | 26 | PACKAGES+=(${ARG}) 27 | done 28 | 29 | # Create a joined string of all mirrored snapshot names 30 | PACKAGES=( "${PACKAGES[@]/%/_${CUSTOM_PACKAGE_VERSION}_snapshot}" ) 31 | bar=$(printf " %s" "${PACKAGES[@]}") 32 | bar=${bar:1} 33 | 34 | echo "mirrored snapshots.." 35 | echo $bar 36 | 37 | # snapshot local repo 38 | echo "Create snapshto from local repo 3rdparty-repo" 39 | aptly snapshot create 3rdparty-repo_${CUSTOM_PACKAGE_VERSION}_snapshot from repo 3rdparty-repo 40 | 41 | # merge all snapshots 42 | echo "Merge all snapshots together to 3rdparty-s3-repo_${CUSTOM_PACKAGE_VERSION}_snapshot" 43 | aptly snapshot merge 3rdparty-s3-repo_${CUSTOM_PACKAGE_VERSION}_snapshot 3rdparty-repo_${CUSTOM_PACKAGE_VERSION}_snapshot $bar 44 | 45 | # publish snapshots 46 | echo "Publish snapshots to s3:3rdparty-staging:" 47 | aptly publish switch precise s3:3rdparty-staging-eu-west-1: 3rdparty-s3-repo_${CUSTOM_PACKAGE_VERSION}_snapshot 48 | 49 | # old repo 50 | aptly publish switch precise s3:3rdparty-staging: 3rdparty-s3-repo_${CUSTOM_PACKAGE_VERSION}_snapshot 51 | # create dependency graph 52 | echo "Create dependency graph" 53 | aptly graph 54 | 55 | echo "Cleanup Database" 56 | # clean up database 57 | aptly db cleanup 58 | 59 | fi 60 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | aptly-cli (0.1.8-c4efef0ac0263ac3c8b55357f684aa01cf71e727-unstable) UNRELEASED; urgency=low 2 | 3 | * Initial release. (Closes: #XXXXXX) 4 | 5 | -- Jenkins Wed, 11 Nov 2015 15:32:06 +0000 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: aptly-cli 2 | Section: python 3 | Priority: extra 4 | Maintainer: Tim Susa 5 | Build-Depends: debhelper (>= 9), python, dh-virtualenv, python-dev 6 | Standards-Version: 3.9.5 7 | 8 | Package: aptly-cli 9 | Architecture: amd64 10 | Depends: ${python:Depends}, ${misc:Depends}, acl 11 | Description: a tool to remotely request aptly server calls 12 | -------------------------------------------------------------------------------- /debian/install: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimSusa/aptly_api_cli/011ba8e7f464726b336b53f6b2cbdc4490b5180c/debian/install -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | ln -fs /usr/share/python/aptly-cli/bin/aptly-cli /usr/bin/aptly-cli 4 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ --with python-virtualenv -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | Sphinx==1.3 3 | alabaster==0.7.3 4 | docutils==0.12 5 | importlib==1.0.3 6 | pep8==1.6.2 7 | pipdeptree==0.4.1 8 | pyflakes==0.8.1 9 | pylint==1.4.3 10 | python-dateutil==2.4.2 11 | python-magic==0.4.6 12 | setuptools-flakes==0.1 13 | setuptools-lint==0.3 14 | setuptools-pep8==0.9.0 15 | 16 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/AptlyAPICLI.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/AptlyAPICLI.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/AptlyAPICLI" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/AptlyAPICLI" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | Aptly API Requests 4 | ================== 5 | 6 | .. automodule:: api.api 7 | .. autoclass:: api.api.AptlyApiRequests 8 | :members: 9 | :undoc-members: 10 | :inherited-members: 11 | :show-inheritance: 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/cli.rst: -------------------------------------------------------------------------------- 1 | .. _cli: 2 | 3 | 4 | CLI Options 5 | =========== 6 | 7 | .. automodule:: cli 8 | :members: 9 | :undoc-members: 10 | :inherited-members: 11 | :show-inheritance: 12 | 13 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Aptly API CLI documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Nov 11 14:09:47 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | sys.path.append(os.path.abspath('../aptly_cli')) 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.todo', 35 | 'sphinx.ext.coverage', 36 | 'sphinx.ext.viewcode', 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | #source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = u'Aptly API CLI' 55 | copyright = u'2015, Tim Susa' 56 | author = u'Tim Susa' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = '0.1' 64 | # The full version, including alpha/beta/rc tags. 65 | release = '0.1' 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to some 75 | # non-false value, then it is used: 76 | #today = '' 77 | # Else, today_fmt is used as the format for a strftime call. 78 | #today_fmt = '%B %d, %Y' 79 | 80 | # List of patterns, relative to source directory, that match files and 81 | # directories to ignore when looking for source files. 82 | exclude_patterns = ['_build'] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | #add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | # If true, keep warnings as "system message" paragraphs in the built documents. 106 | #keep_warnings = False 107 | 108 | # If true, `todo` and `todoList` produce output, else they produce nothing. 109 | todo_include_todos = True 110 | 111 | 112 | # -- Options for HTML output ---------------------------------------------- 113 | 114 | # The theme to use for HTML and HTML Help pages. See the documentation for 115 | # a list of builtin themes. 116 | html_theme = 'alabaster' 117 | 118 | # Theme options are theme-specific and customize the look and feel of a theme 119 | # further. For a list of options available for each theme, see the 120 | # documentation. 121 | #html_theme_options = {} 122 | 123 | # Add any paths that contain custom themes here, relative to this directory. 124 | #html_theme_path = [] 125 | 126 | # The name for this set of Sphinx documents. If None, it defaults to 127 | # " v documentation". 128 | #html_title = None 129 | 130 | # A shorter title for the navigation bar. Default is the same as html_title. 131 | #html_short_title = None 132 | 133 | # The name of an image file (relative to this directory) to place at the top 134 | # of the sidebar. 135 | #html_logo = None 136 | 137 | # The name of an image file (within the static path) to use as favicon of the 138 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 139 | # pixels large. 140 | #html_favicon = None 141 | 142 | # Add any paths that contain custom static files (such as style sheets) here, 143 | # relative to this directory. They are copied after the builtin static files, 144 | # so a file named "default.css" will overwrite the builtin "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # Add any extra paths that contain custom files (such as robots.txt or 148 | # .htaccess) here, relative to this directory. These files are copied 149 | # directly to the root of the documentation. 150 | #html_extra_path = [] 151 | 152 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 153 | # using the given strftime format. 154 | #html_last_updated_fmt = '%b %d, %Y' 155 | 156 | # If true, SmartyPants will be used to convert quotes and dashes to 157 | # typographically correct entities. 158 | #html_use_smartypants = True 159 | 160 | # Custom sidebar templates, maps document names to template names. 161 | #html_sidebars = {} 162 | 163 | # Additional templates that should be rendered to pages, maps page names to 164 | # template names. 165 | #html_additional_pages = {} 166 | 167 | # If false, no module index is generated. 168 | #html_domain_indices = True 169 | 170 | # If false, no index is generated. 171 | #html_use_index = True 172 | 173 | # If true, the index is split into individual pages for each letter. 174 | #html_split_index = False 175 | 176 | # If true, links to the reST sources are added to the pages. 177 | #html_show_sourcelink = True 178 | 179 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 180 | #html_show_sphinx = True 181 | 182 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 183 | #html_show_copyright = True 184 | 185 | # If true, an OpenSearch description file will be output, and all pages will 186 | # contain a tag referring to it. The value of this option must be the 187 | # base URL from which the finished HTML is served. 188 | #html_use_opensearch = '' 189 | 190 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 191 | #html_file_suffix = None 192 | 193 | # Language to be used for generating the HTML full-text search index. 194 | # Sphinx supports the following languages: 195 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 196 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 197 | #html_search_language = 'en' 198 | 199 | # A dictionary with options for the search language support, empty by default. 200 | # Now only 'ja' uses this config value 201 | #html_search_options = {'type': 'default'} 202 | 203 | # The name of a javascript file (relative to the configuration directory) that 204 | # implements a search results scorer. If empty, the default will be used. 205 | #html_search_scorer = 'scorer.js' 206 | 207 | # Output file base name for HTML help builder. 208 | htmlhelp_basename = 'AptlyAPICLIdoc' 209 | 210 | # -- Options for LaTeX output --------------------------------------------- 211 | 212 | latex_elements = { 213 | # The paper size ('letterpaper' or 'a4paper'). 214 | #'papersize': 'letterpaper', 215 | 216 | # The font size ('10pt', '11pt' or '12pt'). 217 | #'pointsize': '10pt', 218 | 219 | # Additional stuff for the LaTeX preamble. 220 | #'preamble': '', 221 | 222 | # Latex figure (float) alignment 223 | #'figure_align': 'htbp', 224 | } 225 | 226 | # Grouping the document tree into LaTeX files. List of tuples 227 | # (source start file, target name, title, 228 | # author, documentclass [howto, manual, or own class]). 229 | latex_documents = [ 230 | (master_doc, 'AptlyAPICLI.tex', u'Aptly API CLI Documentation', 231 | u'Tim Susa', 'manual'), 232 | ] 233 | 234 | # The name of an image file (relative to this directory) to place at the top of 235 | # the title page. 236 | #latex_logo = None 237 | 238 | # For "manual" documents, if this is true, then toplevel headings are parts, 239 | # not chapters. 240 | #latex_use_parts = False 241 | 242 | # If true, show page references after internal links. 243 | #latex_show_pagerefs = False 244 | 245 | # If true, show URL addresses after external links. 246 | #latex_show_urls = False 247 | 248 | # Documents to append as an appendix to all manuals. 249 | #latex_appendices = [] 250 | 251 | # If false, no module index is generated. 252 | #latex_domain_indices = True 253 | 254 | 255 | # -- Options for manual page output --------------------------------------- 256 | 257 | # One entry per manual page. List of tuples 258 | # (source start file, name, description, authors, manual section). 259 | man_pages = [ 260 | (master_doc, 'aptlyapicli', u'Aptly API CLI Documentation', 261 | [author], 1) 262 | ] 263 | 264 | # If true, show URL addresses after external links. 265 | #man_show_urls = False 266 | 267 | 268 | # -- Options for Texinfo output ------------------------------------------- 269 | 270 | # Grouping the document tree into Texinfo files. List of tuples 271 | # (source start file, target name, title, author, 272 | # dir menu entry, description, category) 273 | texinfo_documents = [ 274 | (master_doc, 'AptlyAPICLI', u'Aptly API CLI Documentation', 275 | author, 'AptlyAPICLI', 'One line description of project.', 276 | 'Miscellaneous'), 277 | ] 278 | 279 | # Documents to append as an appendix to all manuals. 280 | #texinfo_appendices = [] 281 | 282 | # If false, no module index is generated. 283 | #texinfo_domain_indices = True 284 | 285 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 286 | #texinfo_show_urls = 'footnote' 287 | 288 | # If true, do not generate a @detailmenu in the "Top" node's menu. 289 | #texinfo_no_detailmenu = False 290 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Aptly API CLI documentation master file, created by 2 | sphinx-quickstart on Wed Nov 11 14:09:47 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Aptly API CLI's documentation! 7 | ========================================= 8 | 9 | .. aptly_cli 10 | 11 | Contents: 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | api 17 | cli 18 | util 19 | 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | -------------------------------------------------------------------------------- /docs/util.rst: -------------------------------------------------------------------------------- 1 | .. _util: 2 | 3 | 4 | Util 5 | =========== 6 | 7 | .. automodule:: util 8 | :members: 9 | :undoc-members: 10 | :inherited-members: 11 | :show-inheritance: 12 | 13 | -------------------------------------------------------------------------------- /etc/aptly-cli.conf: -------------------------------------------------------------------------------- 1 | # aptly-cli config file 2 | [general] 3 | basic_url=http://localhost 4 | port=:9003 5 | prefixes_mirrors=cloudera, erlang, mongodb2, mongodb_, nginx, puppetmaster, rabbitmq, redis, saltstack2014.7, saltstack2015.5, saltstack_, git 6 | repos_to_clean=unstable-repo, stable-repo 7 | package_prefixes=cluster-manager, puppet-config, ingest-api, camus-etl, aptly-cli 8 | save_last_pkg=10 9 | save_last_snap=3 10 | 11 | [3rd_party] 12 | # 3rd party s3 buckets to publish switch to 13 | repos=3rdparty-eu-west-1, 3rdparty-us-east-1 14 | # Pre and postfix of the staging snapshots 15 | staging_snap_pre_post=3rdparty-s3-repo, 3rdparty-staging_snapshot 16 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | 25 | [MESSAGES CONTROL] 26 | 27 | # Enable the message, report, category or checker with the given id(s). You can 28 | # either give multiple identifier separated by comma (,) or put this option 29 | # multiple time. See also the "--disable" option for examples. 30 | #enable= 31 | 32 | # Disable the message, report, category or checker with the given id(s). You 33 | # can either give multiple identifiers separated by comma (,) or put this 34 | # option multiple times (only on the command line, not in the configuration 35 | # file where it should appear only once).You can also use "--disable=all" to 36 | # disable everything first and then reenable specific checks. For example, if 37 | # you want to run only the similarities checker, you can use "--disable=all 38 | # --enable=similarities". If you want to run only the classes checker, but have 39 | # no Warning level messages displayed, use"--disable=all --enable=classes 40 | # --disable=W" 41 | #disable= 42 | 43 | 44 | [REPORTS] 45 | 46 | # Set the output format. Available formats are text, parseable, colorized, msvs 47 | # (visual studio) and html. You can also give a reporter class, eg 48 | # mypackage.mymodule.MyReporterClass. 49 | output-format=text 50 | 51 | # Put messages in a separate file for each module / package specified on the 52 | # command line instead of printing them on stdout. Reports (if any) will be 53 | # written in a file name "pylint_global.[txt|html]". 54 | files-output=no 55 | 56 | # Tells whether to display a full report or only the messages 57 | reports=yes 58 | 59 | # Python expression which should return a note less than 10 (10 is the highest 60 | # note). You have access to the variables errors warning, statement which 61 | # respectively contain the number of errors / warnings messages and the total 62 | # number of statements analyzed. This is used by the global evaluation report 63 | # (RP0004). 64 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 65 | 66 | # Add a comment according to your evaluation note. This is used by the global 67 | # evaluation report (RP0004). 68 | comment=no 69 | 70 | # Template used to display messages. This is a python new-style format string 71 | # used to format the message information. See doc for all details 72 | msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} 73 | 74 | 75 | [BASIC] 76 | 77 | # Required attributes for module, separated by a comma 78 | required-attributes= 79 | 80 | # List of builtins function names that should not be used, separated by a comma 81 | bad-functions=map,filter,apply,input 82 | 83 | # Regular expression which should only match correct module names 84 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 85 | 86 | # Regular expression which should only match correct module level names 87 | const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ 88 | 89 | # Regular expression which should only match correct class names 90 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 91 | 92 | # Regular expression which should only match correct function names 93 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 94 | 95 | # Regular expression which should only match correct method names 96 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 97 | 98 | # Regular expression which should only match correct instance attribute names 99 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 100 | 101 | # Regular expression which should only match correct argument names 102 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 103 | 104 | # Regular expression which should only match correct variable names 105 | variable-rgx=[a-z_][a-z0-9_]{0,30}$ 106 | 107 | # Regular expression which should only match correct attribute names in class 108 | # bodies 109 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 110 | 111 | # Regular expression which should only match correct list comprehension / 112 | # generator expression variable names 113 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 114 | 115 | # Good variable names which should always be accepted, separated by a comma 116 | good-names=i,j,k,ex,Run,_ 117 | 118 | # Bad variable names which should always be refused, separated by a comma 119 | bad-names=foo,bar,baz,toto,tutu,tata 120 | 121 | # Regular expression which should only match function or class names that do 122 | # not require a docstring. 123 | no-docstring-rgx=__.*__ 124 | 125 | # Minimum line length for functions/classes that require docstrings, shorter 126 | # ones are exempt. 127 | docstring-min-length=-1 128 | 129 | 130 | [FORMAT] 131 | 132 | # Maximum number of characters on a single line. 133 | max-line-length=120 134 | 135 | # Regexp for a line that is allowed to be longer than the limit. 136 | ignore-long-lines=^\s*(# )??$ 137 | 138 | # Allow the body of an if to be on the same line as the test if there is no 139 | # else. 140 | single-line-if-stmt=no 141 | 142 | # List of optional constructs for which whitespace checking is disabled 143 | no-space-check=trailing-comma,dict-separator 144 | 145 | # Maximum number of lines in a module 146 | max-module-lines=1000 147 | 148 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 149 | # tab). 150 | indent-string=' ' 151 | 152 | 153 | [MISCELLANEOUS] 154 | 155 | # List of note tags to take in consideration, separated by a comma. 156 | notes=FIXME,XXX,TODO 157 | 158 | 159 | [SIMILARITIES] 160 | 161 | # Minimum lines number of a similarity. 162 | min-similarity-lines=4 163 | 164 | # Ignore comments when computing similarities. 165 | ignore-comments=yes 166 | 167 | # Ignore docstrings when computing similarities. 168 | ignore-docstrings=yes 169 | 170 | # Ignore imports when computing similarities. 171 | ignore-imports=no 172 | 173 | 174 | [TYPECHECK] 175 | 176 | # Tells whether missing members accessed in mixin class should be ignored. A 177 | # mixin class is detected if its name ends with "mixin" (case insensitive). 178 | ignore-mixin-members=yes 179 | 180 | # List of classes names for which member attributes should not be checked 181 | # (useful for classes with attributes dynamically set). 182 | ignored-classes= 183 | 184 | # When zope mode is activated, add a predefined set of Zope acquired attributes 185 | # to generated-members. 186 | zope=no 187 | 188 | # List of members which are set dynamically and missed by pylint inference 189 | # system, and so shouldn't trigger E0201 when accessed. Python regular 190 | # expressions are accepted. 191 | generated-members=REQUEST,acl_users,aq_parent,query,commit,render 192 | 193 | 194 | [VARIABLES] 195 | 196 | # Tells whether we should check for unused import in __init__ files. 197 | init-import=no 198 | 199 | # A regular expression matching the beginning of the name of dummy variables 200 | # (i.e. not used). 201 | dummy-variables-rgx=_$|dummy 202 | 203 | # List of additional names supposed to be defined in builtins. Remember that 204 | # you should avoid to define new builtins when possible. 205 | additional-builtins= 206 | 207 | 208 | [CLASSES] 209 | 210 | # List of interface methods to ignore, separated by a comma. This is used for 211 | # instance to not check methods defines in Zope's Interface base class. 212 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 213 | 214 | # List of method names used to declare (i.e. assign) instance attributes. 215 | defining-attr-methods=__init__,__new__,setUp 216 | 217 | # List of valid names for the first argument in a class method. 218 | valid-classmethod-first-arg=cls 219 | 220 | # List of valid names for the first argument in a metaclass class method. 221 | valid-metaclass-classmethod-first-arg=mcs 222 | 223 | 224 | [DESIGN] 225 | 226 | # Maximum number of arguments for function / method 227 | max-args=7 228 | 229 | # Argument names that match this expression will be ignored. Default to name 230 | # with leading underscore 231 | ignored-argument-names=_.* 232 | 233 | # Maximum number of locals for function / method body 234 | max-locals=15 235 | 236 | # Maximum number of return / yield for function / method body 237 | max-returns=6 238 | 239 | # Maximum number of branch for function / method body 240 | max-branches=12 241 | 242 | # Maximum number of statements in function / method body 243 | max-statements=50 244 | 245 | # Maximum number of parents for a class (see R0901). 246 | max-parents=7 247 | 248 | # Maximum number of attributes for a class (see R0902). 249 | max-attributes=7 250 | 251 | # Minimum number of public methods for a class (see R0903). 252 | min-public-methods=1 253 | 254 | # Maximum number of public methods for a class (see R0904). 255 | max-public-methods=20 256 | 257 | 258 | [IMPORTS] 259 | 260 | # Deprecated modules which should not be used, separated by a comma 261 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 262 | 263 | # Create a graph of every (i.e. internal and external) dependencies in the 264 | # given file (report RP0402 must not be disabled) 265 | import-graph= 266 | 267 | # Create a graph of external dependencies in the given file (report RP0402 must 268 | # not be disabled) 269 | ext-import-graph= 270 | 271 | # Create a graph of internal dependencies in the given file (report RP0402 must 272 | # not be disabled) 273 | int-import-graph= 274 | 275 | 276 | [EXCEPTIONS] 277 | 278 | # Exceptions that will emit a warning when being caught. Defaults to 279 | # "Exception" 280 | overgeneral-exceptions=Exception 281 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.6.0 2 | simplejson==3.3.2 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pep8] 2 | ignore=E501,E402 3 | exclude=build,debian,docs 4 | 5 | [lint] 6 | lint-exclude-packages=tests 7 | lint-disable=C0301,R0902 8 | 9 | [build_sphinx] 10 | source-dir = docs 11 | build-dir = docs/_build 12 | all_files = 1 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup, find_packages 3 | from pkg_resources import Requirement, resource_filename 4 | except ImportError: 5 | from distutils.core import setup, find_packages 6 | 7 | setup( 8 | name='Aptly-Api-Cli', 9 | version='0.1', 10 | url='https://github.com/TimSusa/aptly_api_cli', 11 | license='MIT', 12 | keywords="aptly aptly-server debian", 13 | author='Tim Susa', 14 | author_email='timsusa@gmx.de', 15 | description='This cli executes remote calls to the Aptly server, without blocking the Aptly database.', 16 | long_description=__doc__, 17 | packages=find_packages(), 18 | package_dir={'aptly_cli': 'aptly_cli'}, 19 | # packages=['aptly_cli', 'aptly_cli.api', 'aptly_cli.cli', 'aptly_cli.util'], 20 | # py_modules=['aptly_cli.api.api', 'cli'], 21 | entry_points={ 22 | 'console_scripts': [ 23 | 'aptly-cli=aptly_cli.cli.cli:main' 24 | ] 25 | }, 26 | # data_files=[ 27 | # ('configs', ['configs/aptly-cli.conf']), 28 | # ], 29 | # package_data={'configs': ['aptly_cli/configs/aptly-cli.conf']}, 30 | platforms='any' 31 | ) 32 | 33 | filename = resource_filename(Requirement.parse("Aptly-Api-Cli"), "configs/aptly-cli.conf") 34 | --------------------------------------------------------------------------------