Init portainer-cli project, add version 0.1.0

This commit is contained in:
Douglas Paz 2018-10-19 14:29:13 -03:00
parent 1d4caa8de7
commit 4b7ff1e1f7
11 changed files with 524 additions and 0 deletions

11
.editorconfig Normal file
View file

@ -0,0 +1,11 @@
root = true
[*]
insert_final_newline = true
[*.py]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true

6
.gitignore vendored
View file

@ -102,3 +102,9 @@ venv.bak/
# mypy
.mypy_cache/
# ide
.vscode/
# portainer cli
.portainer-cli.json

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2018 Ilhasoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

8
Makefile Normal file
View file

@ -0,0 +1,8 @@
sdist:
pipenv run python setup.py sdist bdist_wheel
install:
pipenv install --dev
lint:
pipenv run flake8

17
Pipfile Normal file
View file

@ -0,0 +1,17 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
plac = ">=1.0.0"
requests = ">=2.20.0"
validators = ">=0.12.2"
[dev-packages]
"flake8" = "*"
setuptools = "*"
wheel = "*"
[requires]
python_version = "3.7"

124
Pipfile.lock generated Normal file
View file

@ -0,0 +1,124 @@
{
"_meta": {
"hash": {
"sha256": "2e28e1ee11aba53785a7bee2ec9b68d004035f148a7655dbdc0e537eeab9c2e3"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
"sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
],
"version": "==2018.10.15"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"decorator": {
"hashes": [
"sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82",
"sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c"
],
"version": "==4.3.0"
},
"idna": {
"hashes": [
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
],
"version": "==2.7"
},
"plac": {
"hashes": [
"sha256:879d3009bee474cc96b5d7a4ebdf6fa0c4931008ecb858caf09eed9ca302c8da",
"sha256:b03f967f535b3bf5a71b191fa5eb09872a5cfb1e3b377efc4138995e10ba36d7"
],
"index": "pypi",
"version": "==1.0.0"
},
"requests": {
"hashes": [
"sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c",
"sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279"
],
"index": "pypi",
"version": "==2.20.0"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"urllib3": {
"hashes": [
"sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae",
"sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59"
],
"version": "==1.24"
},
"validators": {
"hashes": [
"sha256:172ac45f7d1944ce4beca3c5c53ca7c83e9759e39fd3fedc1cf28e2130268706"
],
"index": "pypi",
"version": "==0.12.2"
}
},
"develop": {
"flake8": {
"hashes": [
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
],
"index": "pypi",
"version": "==3.5.0"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"pycodestyle": {
"hashes": [
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
],
"version": "==2.3.1"
},
"pyflakes": {
"hashes": [
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
],
"version": "==1.6.0"
},
"wheel": {
"hashes": [
"sha256:9fa1f772f1a2df2bd00ddb4fa57e1cc349301e1facb98fbe62329803a9ff1196",
"sha256:d215f4520a1ba1851a3c00ba2b4122665cd3d6b0834d2ba2816198b1e3024a0e"
],
"index": "pypi",
"version": "==0.32.1"
}
}
}

107
README.md Normal file
View file

@ -0,0 +1,107 @@
# Portainer CLI
Powered by [Ilhasoft's Web Team](http://www.ilhasoft.com.br/en/).
Portainer CLI is a Python software to use in command line. Use this command line interface to easy communicate to your [Portainer](https://portainer.io/) application, like in a continuous integration and continuous deploy environments.
## Install
```
pip install [--user] portainer-cli
```
## Usage
### Global flags
| Flag | Description |
|--|--|
| `-l` or `--local` | Save and load configuration file (`.portainer-cli.json`) in current directory. |
| `-d` or `--debug` | Enable DEBUG messages in stdout |
### configure command
Configure Portainer HTTP service base url.
```bash
portainer-cli configure base_url
```
**E.g:**
```bash
portainer-cli configure http://10.0.0.1:9000/
```
### login command
Identify yourself and take action.
```bash
portainer-cli login username password
```
**E.g:**
```bash
portainer-cli login douglas d1234
```
### update_stack command
Update stack.
```bash
portainer-cli update_stack id endpoint_id [stack_file]
```
**E.g:**
```bash
portainer-cli update_stack 2 1 docker-compose.yml
```
#### update_stack command environment variables arguments
```bash
portainer-cli update_stack id endpoint_id [stack_file] --env.var=value
```
Where `var` is environment variable name and `value` is the environment variable value.
#### Flags
| Flag | Description |
|--|--|
| `-p` or `--prune` | Prune services |
| `-c` or `--clear-env` | Clear all environment variables |
### request command
Make a request.
```bash
portainer-cli request path [method=GET] [data]
```
**E.g:**
```bash
portainer-cli request status
```
#### Flags
| Flag | Description |
|--|--|
| `-p` or `--printc` | Print response content in stdout. |
## Development
This project use [Pipenv](https://pipenv.readthedocs.io/en/latest/) to manager Python packages.
With Pipenv installed, run `make install` to install all development packages dependencies.
Run `make lint` to run [flake8](http://flake8.pycqa.org/en/latest/) following PEP8 rules.
Run `make` or `make sdist` to create/update `dist` directory.

6
portainer-cli Normal file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env python
import plac
from portainer_cli import PortainerCLI
p = PortainerCLI()
plac.call(p.main)

192
portainer_cli/__init__.py Executable file
View file

@ -0,0 +1,192 @@
#!/usr/bin/env python
import logging
import plac
import json
import re
import validators
from pathlib import Path
from requests import Request, Session
__version__ = '0.1.0'
logger = logging.getLogger('portainer-cli')
env_arg_regex = r'--env\.(.+)=(.+)'
def env_arg_to_dict(s):
split = re.split(env_arg_regex, s)
return {
'name': split[1],
'value': split[2],
}
class PortainerCLI:
COMMAND_CONFIGURE = 'configure'
COMMAND_LOGIN = 'login'
COMMAND_REQUEST = 'request'
COMMAND_UPDATE_STACK = 'update_stack'
COMMANDS = [
COMMAND_CONFIGURE,
COMMAND_LOGIN,
COMMAND_REQUEST,
COMMAND_UPDATE_STACK,
]
METHOD_GET = 'GET'
METHOD_POST = 'POST'
METHOD_PUT = 'PUT'
local = False
_base_url = 'http://localhost:9000/'
_jwt = None
@property
def base_url(self):
return self._base_url
@base_url.setter
def base_url(self, value):
if not validators.url(value):
raise Exception('Insert a valid base URL')
self._base_url = value if value.endswith('/') else f'{value}/'
self.persist()
@property
def jwt(self):
return self._jwt
@jwt.setter
def jwt(self, value):
self._jwt = value
self.persist()
@property
def data_path(self):
if self.local:
logger.debug('using local configuration file')
return '.portainer-cli.json'
logger.debug('using user configuration file')
return Path.joinpath(Path.home(), '.portainer-cli.json')
def persist(self):
data = {
'base_url': self.base_url,
'jwt': self.jwt,
}
logger.info(f'persisting configuration: {data}')
data_file = open(self.data_path, 'w+')
data_file.write(json.dumps(data))
def load(self):
try:
data_file = open(self.data_path)
except FileNotFoundError:
return
data = json.loads(data_file.read())
logger.info(f'configuration loaded: {data}')
self._base_url = data.get('base_url')
self._jwt = data.get('jwt')
def configure(self, base_url):
self.base_url = base_url
def login(self, username, password):
response = self.request(
'auth',
self.METHOD_POST,
{
'username': username,
'password': password,
}
)
r = response.json()
jwt = r.get('jwt')
logger.info(f'logged with jwt: {jwt}')
self.jwt = jwt
@plac.annotations(
prune=('Prune services', 'flag', 'p'),
clear_env=('Clear all env vars', 'flag', 'c'),
)
def update_stack(self, id, endpoint_id, stack_file='', prune=False,
clear_env=False, *args):
stack_url = f'stacks/{id}?endpointId={endpoint_id}'
current = self.request(stack_url).json()
stack_file_content = ''
if stack_file:
stack_file_content = open(stack_file).read()
else:
stack_file_content = self.request(
f'stacks/{id}/file?endpointId={endpoint_id}').json().get(
'StackFileContent')
env_args = filter(
lambda x: re.match(env_arg_regex, x),
args,
)
env = list(map(
lambda x: env_arg_to_dict(x),
env_args,
))
data = {
'Id': id,
'StackFileContent': stack_file_content,
'Prune': prune,
'Env': env if len(env) > 0 or clear_env else current.get('Env'),
}
self.request(
stack_url,
self.METHOD_PUT,
data,
)
@plac.annotations(
printc=('Print response content', 'flag', 'p'),
)
def request(self, path, method=METHOD_GET, data='', printc=False):
url = f'{self.base_url}api/{path}'
session = Session()
request = Request(method, url)
prepped = request.prepare()
if data:
prepped.headers['Content-Type'] = 'application/json'
try:
json.loads(data)
prepped.body = data
except Exception as e:
prepped.body = json.dumps(data)
prepped.headers['Content-Length'] = len(prepped.body)
if self.jwt:
prepped.headers['Authorization'] = f'Bearer {self.jwt}'
response = session.send(prepped)
logger.debug(f'request response: {response.content}')
response.raise_for_status()
if printc:
print(response.content.decode())
return response
@plac.annotations(
command=(
'Command',
'positional',
None,
str,
COMMANDS,
),
debug=('Enable debug mode', 'flag', 'd'),
local=('Use local/dir configuration', 'flag', 'l'),
)
def main(self, command, debug=False, local=local, *args):
if debug:
logging.basicConfig(level=logging.DEBUG)
self.local = local
self.load()
if command == self.COMMAND_CONFIGURE:
plac.call(self.configure, args)
elif command == self.COMMAND_LOGIN:
plac.call(self.login, args)
elif command == self.COMMAND_UPDATE_STACK:
plac.call(self.update_stack, args)
elif command == self.COMMAND_REQUEST:
plac.call(self.request, args)

2
setup.cfg Normal file
View file

@ -0,0 +1,2 @@
[bdist_wheel]
universal=1

32
setup.py Normal file
View file

@ -0,0 +1,32 @@
import setuptools
from portainer_cli import __version__
with open('README.md', 'r') as fh:
long_description = fh.read()
setuptools.setup(
name='portainer-cli',
version=__version__,
author='Ilhasoft\'s Web Team',
author_email='contato@ilhasoft.com.br',
description='Command line interface to easy communicate to your ' +
'Portainer application.',
long_description=long_description,
long_description_content_type='text/markdown',
url='https://github.com/Ilhasoft/portainer-cli',
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Programming Language :: Python :: 3',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
],
packages=setuptools.find_packages(),
scripts=['portainer-cli'],
install_requires=[
'plac>=1.0.0',
'requests>=2.20.0',
'validators>=0.12.2',
],
python_requires='>=3.6',
)