├── tests ├── logs │ └── .empty ├── redhat-credentials.dist ├── suse-credentials.dist ├── run_vagrant.sh ├── __init__.py ├── run_vagrant_all.sh ├── cid_initcmd_test.py ├── cid_migrate_test.py ├── cid_misc_test.py └── cid_packagecmd_test.py ├── futoin ├── cid │ ├── contrib │ │ └── __init__.py │ ├── details │ │ └── __init__.py │ ├── misc │ │ └── __init__.py │ ├── mixins │ │ ├── __init__.py │ │ ├── log.py │ │ ├── data.py │ │ ├── lock.py │ │ └── ondemand.py │ ├── tool │ │ ├── __init__.py │ │ ├── bashtool.py │ │ ├── exetool.py │ │ ├── ziptool.py │ │ ├── gulptool.py │ │ ├── curltool.py │ │ ├── xztool.py │ │ ├── curltoolmixin.py │ │ ├── elixirtool.py │ │ ├── grunttool.py │ │ ├── erlangtool.py │ │ ├── javatoolmixin.py │ │ ├── unziptool.py │ │ ├── bashtoolmixin.py │ │ ├── scalatool.py │ │ ├── bzip2tool.py │ │ ├── tartool.py │ │ ├── futointool.py │ │ ├── sshtool.py │ │ ├── gpgtool.py │ │ ├── ctesttool.py │ │ ├── maketool.py │ │ ├── gcctool.py │ │ ├── yarntool.py │ │ ├── binutilstool.py │ │ ├── dockercomposetool.py │ │ ├── bowertool.py │ │ ├── anttool.py │ │ ├── maventool.py │ │ ├── rusttool.py │ │ ├── sbttool.py │ │ ├── twinetool.py │ │ ├── npmtoolmixin.py │ │ ├── cargotool.py │ │ ├── gemtool.py │ │ ├── webpacktool.py │ │ ├── sdkmantool.py │ │ ├── nvmtool.py │ │ ├── setuptoolstool.py │ │ ├── gemtoolmixin.py │ │ ├── phpbuildtool.py │ │ ├── gradletool.py │ │ ├── rustuptool.py │ │ ├── piptool.py │ │ ├── jfrogtool.py │ │ ├── gvmtool.py │ │ ├── pumatool.py │ │ ├── piptoolmixin.py │ │ ├── bundlertool.py │ │ ├── phoenixtool.py │ │ ├── cmaketool.py │ │ ├── rvmtool.py │ │ ├── cidtool.py │ │ ├── bundlermixin.py │ │ ├── pythontool.py │ │ ├── awstool.py │ │ ├── gziptool.py │ │ ├── gotool.py │ │ ├── mixtool.py │ │ ├── brewtool.py │ │ └── flywaytool.py │ ├── __main__.py │ ├── runenvtool.py │ ├── testtool.py │ ├── migrationtool.py │ ├── __init__.py │ ├── util │ │ ├── install │ │ │ ├── pacman.py │ │ │ ├── ebuild.py │ │ │ ├── java.py │ │ │ ├── apk.py │ │ │ ├── __init__.py │ │ │ └── deb.py │ │ ├── versionutil.py │ │ ├── log.py │ │ ├── __init__.py │ │ ├── github.py │ │ └── configutil.py │ ├── buildtool.py │ ├── coloring.py │ ├── runtimetool.py │ ├── cte.py │ └── vcstool.py └── __init__.py ├── setup.cfg ├── .gitignore ├── bin ├── cid └── update_copyright.sh ├── .travis.yml ├── futoin.json └── setup.py /tests/logs/.empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futoin/cid/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /tests/redhat-credentials.dist: -------------------------------------------------------------------------------- 1 | RHDEV_USER=some_user 2 | RHDEV_PASS=some_pass 3 | -------------------------------------------------------------------------------- /tests/suse-credentials.dist: -------------------------------------------------------------------------------- 1 | SLESDEV_EMAIL=some_email 2 | SLESDEV_RCODE=some_code 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | testrun 3 | .vagrant 4 | futoin_cid.egg-info 5 | build 6 | dist 7 | tests/logs 8 | /redhat-credentials 9 | /suse-credentials 10 | 11 | -------------------------------------------------------------------------------- /tests/run_vagrant.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | vm=$1 4 | shift 1 5 | 6 | if [ -z "$vm" ]; then 7 | echo "Usage: $(basename $0) " 8 | exit 1 9 | fi 10 | 11 | echo "Running up $vm" 12 | vagrant up $vm >/dev/null 13 | vagrant rsync $vm #>/dev/null 14 | vagrant ssh $vm -c "cd /vagrant && tests/run.sh $*" 15 | vagrant ssh $vm -- cat /testrun/stdout.log > tests/logs/${vm}.log 16 | res=$? 17 | #vagrant 18 | exit $res 19 | -------------------------------------------------------------------------------- /bin/cid: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function, absolute_import 4 | 5 | import sys 6 | import os 7 | 8 | if sys.version_info < (2,7): 9 | print( 'Sorry, but only Python version >= 2.7 is supported!', file=sys.stderr ) 10 | sys.exit( 1 ) 11 | 12 | pypath = os.path.join(os.path.dirname( __file__ ), '..') 13 | pypath = os.path.realpath(pypath) 14 | sys.path.insert(0, pypath) 15 | 16 | from futoin.cid.cli import run 17 | 18 | run() 19 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | -------------------------------------------------------------------------------- /futoin/cid/details/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | -------------------------------------------------------------------------------- /futoin/cid/misc/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | -------------------------------------------------------------------------------- /futoin/cid/mixins/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | -------------------------------------------------------------------------------- /futoin/cid/tool/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | -------------------------------------------------------------------------------- /futoin/cid/__main__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | from .cli import run 19 | run() 20 | -------------------------------------------------------------------------------- /futoin/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | try: 19 | __import__('pkg_resources').declare_namespace(__name__) 20 | except ImportError: 21 | pass 22 | -------------------------------------------------------------------------------- /futoin/cid/runenvtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .subtool import SubTool 18 | 19 | __all__ = ['RunEnvTool'] 20 | 21 | 22 | class RunEnvTool(SubTool): 23 | __slots__ = () 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | sudo: required 4 | 5 | dist: trusty 6 | 7 | os: 8 | - linux 9 | - osx 10 | 11 | #install: 12 | #- python setup.py -q install 13 | 14 | script: 15 | - >- 16 | tests/run.sh nocompile \ 17 | tests/cid_vcs_test.py \ 18 | tests/cid_install_test.py \ 19 | tests/cid_buildtool_test.py \ 20 | tests/cid_runcmd_test.py \ 21 | tests/cid_initcmd_test.py \ 22 | tests/cid_misc_test.py \ 23 | tests/cid_deploy_test.py \ 24 | tests/cid_service_test.py 25 | 26 | # allow failures, as is quite depends on changing app versions 27 | - >- 28 | [ "${TRAVIS_OS_NAME}" = "linux" ] && \ 29 | tests/run.sh rmshost && \ 30 | tests/run.sh \ 31 | tests/cid_realapp_test.py \ 32 | tests/cid_rms_test.py \ 33 | tests/cid_migrate_test.py \ 34 | || true 35 | -------------------------------------------------------------------------------- /futoin/cid/testtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .subtool import SubTool 18 | 19 | __all__ = ['TestTool'] 20 | 21 | 22 | class TestTool(SubTool): 23 | __slots__ = () 24 | 25 | def onCheck(self, config): 26 | pass 27 | -------------------------------------------------------------------------------- /futoin/cid/migrationtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .subtool import SubTool 18 | 19 | __all__ = ['MigrationTool'] 20 | 21 | 22 | class MigrationTool(SubTool): 23 | __slots__ = () 24 | 25 | def onMigrate(self, config): 26 | pass 27 | -------------------------------------------------------------------------------- /tests/run_vagrant_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$VM_LIST" ]; then 4 | VM_LIST=$(vagrant status --machine-readable | grep metadata | cut -d, -f2) 5 | else 6 | VM_LIST="cid_rmshost $VM_LIST" 7 | fi 8 | CID_DESTROY=${CID_DESTROY:-0} 9 | CID_FAST=${CID_FAST:-fast} 10 | args= 11 | 12 | for vm in $VM_LIST; do 13 | if [ $vm = 'cid_rmshost' ]; then 14 | vagrant up cid_rmshost --provision 15 | continue 16 | fi 17 | 18 | $(dirname $0)/run_vagrant.sh $vm $args "$@"; 19 | 20 | [ $vm = 'cid_rhel_7' ] && \ 21 | [ $CID_DESTROY -eq 1 ] && \ 22 | vagrant ssh $vm -- sudo subscription-manager unregister 23 | 24 | [ $vm = 'cid_sles_12' ] && \ 25 | [ $CID_DESTROY -eq 1 ] && \ 26 | vagrant ssh $vm -- sudo SUSEConnect --de-register 27 | 28 | [ $CID_DESTROY -eq 1 ] && vagrant destroy -f $vm 29 | 30 | args=$CID_FAST 31 | done 32 | -------------------------------------------------------------------------------- /futoin/cid/tool/bashtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class bashTool(RunEnvTool): 21 | """Bash is an sh-compatible command language interpreter. 22 | 23 | Mostly used for internal purposes. 24 | """ 25 | __slots__ = () 26 | -------------------------------------------------------------------------------- /futoin/cid/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | __version__ = '0.9.1' 19 | 20 | from .cli import run 21 | from .subtool import SubTool 22 | from .buildtool import BuildTool 23 | from .rmstool import RmsTool 24 | from .runenvtool import RunEnvTool 25 | from .vcstool import VcsTool 26 | -------------------------------------------------------------------------------- /futoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "futoin-cid", 3 | "version": "0.9.1", 4 | "plugins": { 5 | "release": "futoin.cid.misc.releasetool" 6 | }, 7 | "toolTune": { 8 | "release": { 9 | "python": "futoin/cid/__init__.py" 10 | } 11 | }, 12 | "vcs": "git", 13 | "rms": "twine", 14 | "rmsRepo": "ignored", 15 | "tools": { 16 | "release": "embedded", 17 | "python": "*", 18 | "setuptools": true 19 | }, 20 | "entryPoints": { 21 | "main": { 22 | "tool": "exe", 23 | "path": "bin/cid" 24 | } 25 | }, 26 | "actions": { 27 | "autopep8": [ 28 | "@cte pip install -q autopep8", 29 | "autopep8 -i -r futoin" 30 | ], 31 | "yapf": [ 32 | "@cte pip install -q yapf", 33 | "yapf -i -r futoin" 34 | ], 35 | "check": [ 36 | "@default", 37 | "@cte pip install -q pylint nose", 38 | "pylint -E futoin --ignore=contrib", 39 | "pylint -E tests/" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /futoin/cid/tool/exetool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runtimetool import RuntimeTool 18 | 19 | 20 | class exeTool(RuntimeTool): 21 | """Dummy tool to execute files directly""" 22 | __slots__ = () 23 | 24 | def initEnv(self, env): 25 | self._have_tool = True 26 | 27 | def onRun(self, config, svc, args): 28 | env = config['env'] 29 | self._executil.callInteractive([svc['path']] + args) 30 | -------------------------------------------------------------------------------- /futoin/cid/tool/ziptool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class zipTool(RunEnvTool): 21 | """package and compress (archive) files. 22 | """ 23 | __slots__ = () 24 | 25 | def _installTool(self, env): 26 | self._install.debrpm(['zip']) 27 | self._install.emerge(['app-arch/zip']) 28 | self._install.pacman(['zip']) 29 | self._install.apk(['zip']) 30 | -------------------------------------------------------------------------------- /futoin/cid/util/install/pacman.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ...mixins.ondemand import ext as _ext 18 | 19 | _pacman = _ext.pathutil.which('pacman') 20 | 21 | 22 | def pacman(packages): 23 | if _pacman: 24 | packages = _ext.configutil.listify(packages) 25 | 26 | _ext.executil.trySudoCall( 27 | [_pacman, '-S', '--noconfirm', '--needed'] + packages, 28 | errmsg='you may need to install the build deps manually !' 29 | ) 30 | -------------------------------------------------------------------------------- /futoin/cid/buildtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | from .subtool import SubTool 19 | 20 | __all__ = ['BuildTool'] 21 | 22 | 23 | class BuildTool(SubTool): 24 | __slots__ = () 25 | 26 | def onPrepare(self, config): 27 | pass 28 | 29 | def onBuild(self, config): 30 | pass 31 | 32 | def onPackage(self, config): 33 | pass 34 | 35 | def _isDefaultPackage(self, config): 36 | return ('package' not in config) or (config['package'] == '.') 37 | -------------------------------------------------------------------------------- /futoin/cid/tool/gulptool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from .npmtoolmixin import NpmToolMixIn 19 | 20 | 21 | class gulpTool(NpmToolMixIn, BuildTool): 22 | """Automate and enhance your workflow (Node.js). 23 | 24 | Home: http://gulpjs.com/ 25 | """ 26 | __slots__ = () 27 | 28 | def autoDetectFiles(self): 29 | return 'gulpfile.js' 30 | 31 | def onBuild(self, config): 32 | cmd = [config['env']['gulpBin']] 33 | self._executil.callMeaningful(cmd) 34 | -------------------------------------------------------------------------------- /futoin/cid/tool/curltool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class curlTool(RunEnvTool): 21 | """Command line tool and library for transferring data with URLs. 22 | 23 | Home: https://curl.haxx.se/ 24 | """ 25 | __slots__ = () 26 | 27 | def _installTool(self, env): 28 | self._install.debrpm(['curl']) 29 | self._install.emerge(['net-misc/curl']) 30 | self._install.pacman(['curl']) 31 | self._install.apk(['curl']) 32 | self._install.brew('curl') 33 | -------------------------------------------------------------------------------- /futoin/cid/util/versionutil.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..mixins.ondemand import ext as _ext 18 | 19 | 20 | def sort(verioned_list): 21 | def castver(v): 22 | res = _ext.re.split(r'[\W_]+', v) 23 | for (i, vc) in enumerate(res): 24 | try: 25 | res[i] = int(vc, 10) 26 | except: 27 | pass 28 | return res 29 | 30 | verioned_list.sort(key=castver) 31 | 32 | 33 | def latest(verioned_list): 34 | sort(verioned_list) 35 | return verioned_list[-1] 36 | -------------------------------------------------------------------------------- /futoin/cid/tool/xztool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class xzTool(RunEnvTool): 21 | """Free general-purpose data compression software with a high compression ratio. 22 | 23 | Home: https://tukaani.org/xz/ 24 | """ 25 | __slots__ = () 26 | 27 | def _installTool(self, env): 28 | self._install.debrpm(['xz-utils']) 29 | self._install.emerge(['app-arch/xz-utils']) 30 | self._install.pacman(['xz-utils']) 31 | self._install.apk(['xz']) 32 | self._install.brew('xz') 33 | -------------------------------------------------------------------------------- /futoin/cid/mixins/log.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..util.log import * 18 | 19 | __all__ = ('LogMixIn',) 20 | 21 | 22 | class LogMixIn(object): 23 | __slots__ = () 24 | 25 | def _info(self, msg, label=None): 26 | if label: # for backward compatibility 27 | infoLabel(label, msg) 28 | else: 29 | info(msg) 30 | 31 | def _infoLabel(self, label, msg): 32 | infoLabel(label, msg) 33 | 34 | def _warn(self, msg): 35 | warn(msg) 36 | 37 | def _errorExit(self, msg): 38 | errorExit(msg) 39 | -------------------------------------------------------------------------------- /futoin/cid/tool/curltoolmixin.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # pylint: disable=no-member 18 | 19 | class CurlToolMixIn(object): 20 | __slots__ = () 21 | 22 | def getDeps(self): 23 | return ['curl'] 24 | 25 | def _callCurl(self, env, curl_args, *args, **nargs): 26 | cmd = [env['curlBin'], '-fsSL'] 27 | cmd += self._configutil.timeouts(env, 'curl') 28 | cmd += ['-A', 'FutoIn CID'] 29 | cmd += curl_args 30 | 31 | return self._executil.callExternal( 32 | cmd, 33 | *args, **nargs 34 | ) 35 | -------------------------------------------------------------------------------- /futoin/cid/tool/elixirtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class elixirTool(RunEnvTool): 21 | """ 22 | Elixir. 23 | 24 | Home: https://elixir-lang.org/ 25 | """ 26 | 27 | __slots__ = () 28 | 29 | def getDeps(self): 30 | return ['erlang'] 31 | 32 | def _installTool(self, env): 33 | install = self._install 34 | 35 | install.generic(['elixir']) 36 | 37 | def uninstallTool(self, env): 38 | pass 39 | 40 | def initEnv(self, env): 41 | super(elixirTool, self).initEnv(env, 'iex') 42 | -------------------------------------------------------------------------------- /futoin/cid/tool/grunttool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from .npmtoolmixin import NpmToolMixIn 19 | 20 | 21 | class gruntTool(NpmToolMixIn, BuildTool): 22 | """Grunt: The JavaScript Task Runner. 23 | 24 | Home: https://gruntjs.com/ 25 | """ 26 | __slots__ = () 27 | 28 | def autoDetectFiles(self): 29 | return ['Gruntfile.js', 'Gruntfile.coffee'] 30 | 31 | def _npmName(self): 32 | return 'grunt-cli' 33 | 34 | def onBuild(self, config): 35 | cmd = [config['env']['gruntBin']] 36 | self._executil.callMeaningful(cmd) 37 | -------------------------------------------------------------------------------- /futoin/cid/util/install/ebuild.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ...mixins.ondemand import ext as _ext 18 | 19 | 20 | def emerge(packages): 21 | if not _ext.detect.isEmerge(): 22 | return 23 | 24 | emerge = _ext.pathutil.which('emerge') 25 | 26 | packages = _ext.configutil.listify(packages) 27 | 28 | _ext.executil.trySudoCall( 29 | [emerge] + packages, 30 | errmsg='you may need to install the build deps manually !' 31 | ) 32 | 33 | 34 | def emergeDepsOnly(packages): 35 | packages = _ext.configutil.listify(packages) 36 | emerge(['--onlydeps'] + packages) 37 | -------------------------------------------------------------------------------- /futoin/cid/tool/erlangtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class erlangTool(RunEnvTool): 21 | """ 22 | Erlang OTP. 23 | 24 | Home: https://www.erlang.org/ 25 | """ 26 | 27 | __slots__ = () 28 | 29 | def _installTool(self, env): 30 | install = self._install 31 | 32 | install.dpkg( 33 | env, 'erlang-solutions', 34 | 'https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb', 35 | True) 36 | 37 | install.generic(['erlang']) 38 | 39 | def uninstallTool(self, env): 40 | pass 41 | 42 | def initEnv(self, env): 43 | super(erlangTool, self).initEnv(env, 'erl') 44 | -------------------------------------------------------------------------------- /futoin/cid/util/log.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from __future__ import print_function 18 | 19 | from ..coloring import Coloring 20 | from ..mixins.ondemand import ext as _ext 21 | 22 | 23 | def info(msg, label=None): 24 | if label: # for backward compatibility 25 | infoLabel(label, msg) 26 | return 27 | 28 | line = Coloring.info('INFO: ' + msg) 29 | print(line, file=_ext.sys.stderr) 30 | 31 | 32 | def infoLabel(label, msg): 33 | line = Coloring.infoLabel(label) + Coloring.info(msg) 34 | print(line, file=_ext.sys.stderr) 35 | 36 | 37 | def warn(msg): 38 | print(Coloring.warn('WARNING: ' + msg), file=_ext.sys.stderr) 39 | 40 | 41 | def errorExit(msg): 42 | raise RuntimeError(msg) 43 | -------------------------------------------------------------------------------- /futoin/cid/tool/javatoolmixin.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # pylint: disable=no-member 18 | 19 | class JavaToolMixIn(object): 20 | __slots__ = () 21 | 22 | _LATEST_JAVA = '8' 23 | 24 | def _minJava(self): 25 | return False 26 | 27 | def envDeps(self, env): 28 | min_java = self._minJava() 29 | 30 | if not min_java: 31 | return 32 | 33 | for ek in ['javaVer', 'jdkVer']: 34 | if env.get(ek, None): 35 | if int(env[ek]) < int(min_java): 36 | self._errorExit('{0} requires minimal {1}={2}' 37 | .format(self._name, ek, min_java)) 38 | else: 39 | env[ek] = str(self._LATEST_JAVA) 40 | -------------------------------------------------------------------------------- /futoin/cid/tool/unziptool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class unzipTool(RunEnvTool): 21 | """list, test and extract compressed files in a ZIP archive. 22 | """ 23 | __slots__ = () 24 | 25 | def _installTool(self, env): 26 | self._install.debrpm(['unzip']) 27 | self._install.emerge(['app-arch/unzip']) 28 | self._install.pacman(['unzip']) 29 | self._install.apk(['unzip']) 30 | 31 | def initEnv(self, env, bin_name=None): 32 | # Busybox's version is not enough for SDKMan 33 | if self._detect.isAlpineLinux() and self._ospath.islink('/usr/bin/unzip'): 34 | return 35 | 36 | super(unzipTool, self).initEnv(env, bin_name) 37 | -------------------------------------------------------------------------------- /futoin/cid/tool/bashtoolmixin.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # pylint: disable=no-member 18 | 19 | class BashToolMixIn(object): 20 | __slots__ = () 21 | 22 | def getDeps(self): 23 | return ['bash'] 24 | 25 | def _callBash(self, env, cmd=None, bash_args=[], *args, **nargs): 26 | if cmd: 27 | cmd_args = ['-c', cmd] 28 | else: 29 | cmd_args = ['-s'] 30 | 31 | return self._executil.callExternal( 32 | [env['bashBin'], '--noprofile', '--norc'] + cmd_args + bash_args, 33 | *args, **nargs) 34 | 35 | def _callBashInteractive(self, env, cmd, replace=True): 36 | return self._executil.callInteractive([ 37 | env['bashBin'], 38 | '--noprofile', '--norc', 39 | '-c', cmd, 40 | ], replace=replace) 41 | -------------------------------------------------------------------------------- /futoin/cid/tool/scalatool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runtimetool import RuntimeTool 18 | from .sdkmantoolmixin import SdkmanToolMixIn 19 | 20 | 21 | class scalaTool(SdkmanToolMixIn, RuntimeTool): 22 | """The Scala Programming Language. 23 | 24 | Home: https://www.scala-lang.org/ 25 | 26 | Installed via SDKMan! 27 | 28 | Requires Java >= 8. 29 | """ 30 | __slots__ = () 31 | 32 | def _minJava(self): 33 | return '8' 34 | 35 | def tuneDefaults(self, env): 36 | return { 37 | 'minMemory': '256M', 38 | 'debugOverhead': '128M', 39 | 'connMemory': '100K', 40 | 'debugConnOverhead': '1M', 41 | 'socketType': 'tcp', 42 | 'scalable': False, 43 | 'reloadable': False, 44 | 'multiCore': True, 45 | } 46 | -------------------------------------------------------------------------------- /futoin/cid/util/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from functools import wraps as _wraps 18 | 19 | 20 | def simple_memo(f): 21 | @_wraps(f) 22 | def wrap(): 23 | fdict = f.__dict__ 24 | 25 | if 'cached_val' in fdict: 26 | return fdict['cached_val'] 27 | 28 | val = f() 29 | fdict['cached_val'] = val 30 | return val 31 | 32 | return wrap 33 | 34 | 35 | def complex_memo(f): 36 | f.cached = {} 37 | 38 | @_wraps(f) 39 | def wrap(*args, **kwargs): 40 | cached = f.cached 41 | key = str(args) + str(kwargs) 42 | if key in cached: 43 | return cached[key] 44 | 45 | val = f(*args, **kwargs) 46 | 47 | if val is not None: 48 | cached[key] = val 49 | 50 | return val 51 | 52 | return wrap 53 | -------------------------------------------------------------------------------- /futoin/cid/tool/bzip2tool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class bzip2Tool(RunEnvTool): 21 | """Freely available, patent free (see below), high-quality data compressor. 22 | 23 | Home: http://www.bzip.org/ 24 | """ 25 | __slots__ = () 26 | 27 | def _installTool(self, env): 28 | self._install.debrpm(['bzip2']) 29 | self._install.emerge(['app-arch/bzip2']) 30 | self._install.pacman(['bzip2']) 31 | self._install.apk(['bzip2']) 32 | self._install.brew('bzip2') 33 | 34 | def initEnv(self, env, bin_name=None): 35 | # Busybox's version is not enough for SDKMan 36 | if self._detect.isAlpineLinux() and self._ospath.islink('/usr/bin/bzip2'): 37 | return 38 | 39 | super(bzip2Tool, self).initEnv(env, bin_name) 40 | -------------------------------------------------------------------------------- /futoin/cid/tool/tartool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class tarTool(RunEnvTool): 21 | """The GNU version of the tar archiving utility. 22 | 23 | Tool tune: 24 | * .compressor=gzip ("bzip2", "gzip", "xz" or plain "tar") 25 | """ 26 | __slots__ = () 27 | 28 | def _installTool(self, env): 29 | self._install.debrpm(['tar']) 30 | self._install.emerge(['app-arch/tar']) 31 | self._install.pacman(['tar']) 32 | self._install.apk(['tar']) 33 | self._install.brew('gnu-tar') 34 | 35 | def initEnv(self, env, bin_name=None): 36 | # Busybox's version is not enough for SDKMan 37 | if self._detect.isAlpineLinux() and self._ospath.islink('/bin/tar'): 38 | return 39 | 40 | super(tarTool, self).initEnv(env, bin_name) 41 | -------------------------------------------------------------------------------- /futoin/cid/tool/futointool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class futoinTool(RunEnvTool): 21 | """futoin.json updater as defined in FTN16. 22 | 23 | Home: https://github.com/futoin/specs/blob/master/draft/ftn16_cid_tool.md 24 | 25 | futoin.json is the only file read by FutoIn CID itself. 26 | """ 27 | __slots__ = () 28 | _FUTOIN_JSON = 'futoin.json' 29 | 30 | def autoDetectFiles(self): 31 | return self._FUTOIN_JSON 32 | 33 | def initEnv(self, env): 34 | self._have_tool = True 35 | 36 | def updateProjectConfig(self, config, updates): 37 | def updater(json): 38 | for f in ('name', 'version'): 39 | if f in updates: 40 | json[f] = updates[f] 41 | 42 | return self._pathutil.updateJSONConfig(self._FUTOIN_JSON, updater) 43 | -------------------------------------------------------------------------------- /futoin/cid/tool/sshtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class sshTool(RunEnvTool): 21 | """Secure Shell client. 22 | 23 | Home: https://www.openssh.com/ 24 | 25 | Note: 26 | * sshStrictHostKeyChecking is set to "yes" by default. 27 | It is used by tools depending on ssh. 28 | """ 29 | __slots__ = () 30 | 31 | def envNames(self): 32 | return ['sshBin', 'sshStrictHostKeyChecking'] 33 | 34 | def _installTool(self, env): 35 | self._install.deb(['openssh-client']) 36 | self._install.rpm(['openssh']) 37 | self._install.emerge(['virtual/ssh']) 38 | self._install.pacman(['openssh']) 39 | self._install.apk('openssh-client') 40 | 41 | def initEnv(self, env): 42 | env.setdefault('sshStrictHostKeyChecking', 'yes') 43 | super(sshTool, self).initEnv(env) 44 | -------------------------------------------------------------------------------- /futoin/cid/tool/gpgtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class gpgTool(RunEnvTool): 21 | """The GNU Privacy Guard. 22 | 23 | Home: https://www.gnupg.org/ 24 | 25 | gpgKeyServer is hkp://keyserver.ubuntu.com:80 by default 26 | """ 27 | __slots__ = () 28 | 29 | def envNames(self): 30 | return ['gpgBin', 'gpgKeyServer'] 31 | 32 | def _installTool(self, env): 33 | self._install.debrpm('gnupg') 34 | self._install.debrpm('gnupg2') 35 | self._install.debrpm('dirmngr') 36 | 37 | self._install.emerge(['app-crypt/gnupg']) 38 | self._install.pacman(['gnupg']) 39 | self._install.apk(['gnupg']) 40 | self._install.brew('gnupg') 41 | 42 | def initEnv(self, env): 43 | super(gpgTool, self).initEnv(env) 44 | env.setdefault('gpgKeyServer', 'hkp://keyserver.ubuntu.com:80') 45 | 46 | if self._have_tool and self._detect.isDeb(): 47 | self._have_tool = self._pathutil.which('dirmngr') 48 | -------------------------------------------------------------------------------- /futoin/cid/tool/ctesttool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2018-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..testtool import TestTool 18 | 19 | 20 | class ctestTool(TestTool): 21 | """Build, Test and Package Your Software With CMake. 22 | 23 | Home: https://cmake.org/ 24 | 25 | CTest runs tests in CMake build folder, if any. 26 | """ 27 | __slots__ = () 28 | 29 | def autoDetectFiles(self): 30 | return 'CMakeLists.txt' 31 | 32 | def getDeps(self): 33 | return ['cmake'] 34 | 35 | def envNames(self): 36 | return ['ctestBin'] 37 | 38 | def initEnv(self, env): 39 | self._environ.setdefault('CTEST_OUTPUT_ON_FAILURE', '1') 40 | super(ctestTool, self).initEnv(env) 41 | 42 | def onCheck(self, config): 43 | ospath = self._ospath 44 | os = self._os 45 | build_dir = config['env']['cmakeBuildDir'] 46 | 47 | if ospath.exists(build_dir): 48 | os.chdir(build_dir) 49 | cmd = [config['env']['ctestBin']] 50 | self._executil.callMeaningful(cmd) 51 | os.chdir(config['wcDir']) 52 | -------------------------------------------------------------------------------- /futoin/cid/mixins/data.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .ondemand import OnDemandMixIn 18 | 19 | 20 | class DataSlots(OnDemandMixIn): 21 | __slots__ = ( 22 | # ConfigMixIn 23 | '_startup_env', 24 | '_env', 25 | '_config', 26 | '_overrides', 27 | '_global_config', 28 | '_user_config', 29 | '_deploy_config', 30 | '_project_config', 31 | 32 | # DeployMixIn 33 | '_current_dir', 34 | '_devserve_mode', 35 | 36 | # LockMixIn 37 | '_deploy_lock', 38 | '_master_lock', 39 | '_global_lock', 40 | 41 | # ToolMixIn 42 | '_tool_impl', 43 | 44 | '_lastPackages', 45 | 46 | # ServiceMixIn 47 | '_running', 48 | '_reload_services', 49 | '_interruptable', 50 | ) 51 | 52 | _FUTOIN_JSON = 'futoin.json' 53 | _FUTOIN_USER_JSON = '.futoin.json' 54 | _FUTOIN_MERGED_JSON = '.futoin.merged.json' 55 | 56 | _DEPLOY_LOCK_FILE = '.futoin-deploy.lock' 57 | _MASTER_LOCK_FILE = '.futoin-master.lock' 58 | _GLOBAL_LOCK_FILE = '.futoin-global.lock' 59 | -------------------------------------------------------------------------------- /futoin/cid/coloring.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | import sys 19 | 20 | 21 | class Coloring: 22 | __slots__ = () 23 | _color = sys.stdout.isatty() 24 | 25 | @classmethod 26 | def enable(cls, val): 27 | cls._color = val 28 | 29 | @classmethod 30 | def wrap(cls, text, escape): 31 | if cls._color: 32 | ESC = chr(0x1B) 33 | return ESC + escape + str(text) + ESC + '[0m' 34 | else: 35 | return text 36 | 37 | @classmethod 38 | def error(cls, text): 39 | # bright red on black 40 | return cls.wrap(text, '[1;31m') 41 | 42 | @classmethod 43 | def warn(cls, text): 44 | # yellow on black 45 | return cls.wrap(text, '[33m') 46 | 47 | @classmethod 48 | def info(cls, text): 49 | # cyan on black 50 | return cls.wrap(text, '[36m') 51 | 52 | @classmethod 53 | def infoLabel(cls, text): 54 | # bright cyan on black 55 | return cls.wrap(text, '[1;36m') 56 | 57 | @classmethod 58 | def label(cls, text): 59 | # bright 60 | return cls.wrap(text, '[1m') 61 | -------------------------------------------------------------------------------- /futoin/cid/tool/maketool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | 19 | 20 | class makeTool(BuildTool): 21 | """GNU Make. 22 | 23 | Home: https://www.gnu.org/software/make/ 24 | 25 | Expects presence of "clean" target. 26 | Build uses the default target. 27 | """ 28 | __slots__ = () 29 | 30 | def autoDetectFiles(self): 31 | return [ 32 | 'GNUmakefile', 33 | 'makefile', 34 | 'Makefile', 35 | ] 36 | 37 | def _installTool(self, env): 38 | self._install.debrpm(['make']) 39 | self._install.emerge(['sys-devel/make']) 40 | self._install.pacman(['make']) 41 | self._install.apk(['make']) 42 | 43 | def onPrepare(self, config): 44 | cmd = [config['env']['makeBin'], 'clean'] 45 | self._executil.callMeaningful(cmd) 46 | 47 | def onBuild(self, config): 48 | target = self._getTune(config, 'build') 49 | 50 | if target: 51 | args = [target] 52 | else: 53 | args = [] 54 | 55 | cmd = [config['env']['makeBin']] + args 56 | self._executil.callMeaningful(cmd) 57 | -------------------------------------------------------------------------------- /futoin/cid/tool/gcctool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class gccTool(RunEnvTool): 21 | """GNU Compiler Collection. 22 | 23 | Home: https://gcc.gnu.org/ 24 | 25 | If gccBin is set it must point to bin folder. 26 | """ 27 | __slots__ = () 28 | 29 | def envNames(self): 30 | return ['gccBin', 'gccPrefix', 'gccPostfix'] 31 | 32 | def _installTool(self, env): 33 | self._install.deb(['gcc']) 34 | self._install.rpm(['gcc']) 35 | self._install.emerge(['sys-devel/gcc']) 36 | self._install.pacman(['gcc']) 37 | self._install.apk(['gcc']) 38 | 39 | def initEnv(self, env): 40 | gcc_bin = env.get('gccBin', None) 41 | 42 | pref = env.setdefault('gccPrefix', '') 43 | postf = env.setdefault('gccPostfix', '') 44 | 45 | if gcc_bin: 46 | if self._ospath.exists(gcc_bin): 47 | self._have_tool = True 48 | else: 49 | gcc = pref + 'gcc' + postf 50 | gcc = self._pathutil.which(gcc) 51 | 52 | if gcc: 53 | env['gccBin'] = gcc 54 | self._have_tool = True 55 | -------------------------------------------------------------------------------- /futoin/cid/util/github.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..mixins.ondemand import ext as _ext 18 | from . import log as _log 19 | 20 | _api_url = 'https://api.github.com' 21 | _api_ver = 'application/vnd.github.v3+json' 22 | 23 | 24 | def api_call(env, method, path, **kwargs): 25 | url = _api_url + path 26 | _ext.configutil.requestsOptions(env, kwargs) 27 | 28 | headers = kwargs.setdefault('headers', {}) 29 | headers.setdefault('Accept', _api_ver) 30 | 31 | return _ext.requests.request(method, url, **kwargs) 32 | 33 | 34 | def listReleases(env, repo): 35 | res = api_call(env, 'GET', 36 | '/repos/{0}/releases'.format(repo)) 37 | res.raise_for_status() 38 | return res.json() 39 | 40 | 41 | def releaseInfo(env, repo, ver): 42 | res = api_call(env, 'GET', 43 | '/repos/{0}/releases/{1}'.format(repo, ver)) 44 | res.raise_for_status() 45 | return res.json() 46 | 47 | 48 | def findAsset(assets, content_type): 49 | for asset in assets: 50 | if asset['content_type'] == content_type: 51 | return asset 52 | else: 53 | _log.errorExit('Failed to find .tar.gz asset') 54 | -------------------------------------------------------------------------------- /futoin/cid/tool/yarntool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from .npmtoolmixin import NpmToolMixIn 19 | 20 | 21 | class yarnTool(NpmToolMixIn, BuildTool): 22 | """YARN: fast, reliable, and secure dependency management. 23 | 24 | Home: https://yarnpkg.com 25 | 26 | Note: auto-detected only if yarn.lock is present 27 | """ 28 | __slots__ = () 29 | 30 | def getOrder(self): 31 | # required to run before other npm-based tools 32 | return -10 33 | 34 | def autoDetectFiles(self): 35 | return 'yarn.lock' 36 | 37 | def onPrepare(self, config): 38 | node_env = self._environ['NODE_ENV'] 39 | 40 | try: 41 | self._environ['NODE_ENV'] = 'development' 42 | yarnBin = config['env']['yarnBin'] 43 | self._executil.callExternal( 44 | [yarnBin, 'install', '--production=false']) 45 | finally: 46 | self._environ['NODE_ENV'] = node_env 47 | 48 | def onPackage(self, config): 49 | if not self._isDefaultPackage(config): 50 | return 51 | 52 | yarnBin = config['env']['yarnBin'] 53 | cmd = [yarnBin, 'install', '--production'] 54 | self._executil.callMeaningful(cmd) 55 | -------------------------------------------------------------------------------- /bin/update_copyright.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mpath=$(dirname $0)/.. 4 | 5 | if test -z "$mpath"; then 6 | echo "Usage: $(basename $0) module path)" 7 | fi 8 | 9 | author="Andrey Galkin " 10 | year=$(date +"%Y") 11 | 12 | function update_license_file() { 13 | update_py_file $1 14 | } 15 | 16 | function update_py_file() { 17 | local f=$1 18 | local ftmp=${f}.tmp 19 | 20 | if egrep -q "Copyright [0-9]{4}(-[0-9]{4})? $author" $f; then 21 | awk " 22 | /Copyright $year $author/ { print; next } 23 | /Copyright ([0-9]{4})-$year $author/ { print; next } 24 | /Copyright ([0-9]{4})(-[0-9]{4})? $author/ { 25 | print gensub(/([0-9]{4})(-[0-9]{4})?/, \"\\\\1-$year\", \"g\") 26 | next 27 | } 28 | {print} 29 | " $f > $ftmp 30 | 31 | if diff -q $f $ftmp; then 32 | rm -f $ftmp 33 | else 34 | mv -f $ftmp $f 35 | fi 36 | return 37 | fi 38 | 39 | cat >$ftmp <>$ftmp 59 | mv $ftmp $f 60 | } 61 | 62 | #update_license_file $mpath/LICENSE 63 | 64 | for f in $(find $mpath -type f -name '*.py' | grep -v '/contrib/'); do 65 | update_py_file $f 66 | done 67 | 68 | 69 | -------------------------------------------------------------------------------- /futoin/cid/tool/binutilstool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class binutilsTool(RunEnvTool): 21 | """GNU Binutils. 22 | 23 | Home: https://www.gnu.org/software/binutils/ 24 | 25 | If binutilsDir is set it must point to bin folder. 26 | """ 27 | __slots__ = () 28 | 29 | def envNames(self): 30 | return ['binutilsDir', 'binutilsPrefix', 'binutilsPostfix'] 31 | 32 | def _installTool(self, env): 33 | self._install.deb(['binutils']) 34 | self._install.rpm(['binutils']) 35 | self._install.emerge(['sys-devel/binutils']) 36 | self._install.pacman(['binutils']) 37 | self._install.apk(['binutils']) 38 | 39 | def initEnv(self, env): 40 | ospath = self._ospath 41 | pref = env.setdefault('binutilsPrefix', '') 42 | postf = env.setdefault('binutilsPostfix', '') 43 | 44 | bu_dir = env.get('binutilsDir', None) 45 | ld = pref + 'ld' + postf 46 | 47 | if bu_dir: 48 | ld = ospath.join(bu_dir, ld) 49 | self._have_tool = ospath.exists(bu_dir) 50 | else: 51 | ld = self._pathutil.which(ld) 52 | 53 | if ld: 54 | env['binutilsDir'] = ospath.dirname(ld) 55 | self._have_tool = True 56 | -------------------------------------------------------------------------------- /futoin/cid/tool/dockercomposetool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from ..runenvtool import RunEnvTool 19 | from .piptoolmixin import PipToolMixIn 20 | 21 | 22 | class dockercomposeTool(PipToolMixIn, BuildTool, RunEnvTool): 23 | """Compose is a tool for defining and running multi-container Docker applications. 24 | 25 | Home: https://docs.docker.com/compose/ 26 | 27 | Experimental support. 28 | """ 29 | __slots__ = () 30 | 31 | def autoDetectFiles(self): 32 | return ['docker-compose.yml', 'docker-compose.yaml'] 33 | 34 | def getDeps(self): 35 | return ['pip', 'docker'] 36 | 37 | def getOrder(self): 38 | return 20 39 | 40 | def _pipName(self): 41 | return 'docker-compose' 42 | 43 | def _installTool(self, env): 44 | self._requirePythonDev(env) 45 | super(dockercomposeTool, self)._installTool(env) 46 | 47 | def onBuild(self, config): 48 | cmd = [config['env']['dockercomposeBin'], 'build'] 49 | self._executil.callMeaningful(cmd) 50 | 51 | def onPackage(self, config): 52 | cmd = [config['env']['dockercomposeBin'], 'bundle'] 53 | self._executil.callMeaningful(cmd) 54 | 55 | def initEnv(self, env): 56 | super(dockercomposeTool, self).initEnv(env, 'docker-compose') 57 | -------------------------------------------------------------------------------- /futoin/cid/runtimetool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .subtool import SubTool 18 | 19 | __all__ = ['RuntimeTool'] 20 | 21 | 22 | class RuntimeTool(SubTool): 23 | __slots__ = () 24 | DEFAULT_EXIT_TIMEOUT = 5000 25 | 26 | def __init__(self, name): 27 | super(RuntimeTool, self).__init__(name) 28 | 29 | def onRun(self, config, svc, args): 30 | env = config['env'] 31 | self._executil.callInteractive([ 32 | env[self._name + 'Bin'], svc['path'] 33 | ] + args) 34 | 35 | def tuneDefaults(self, env): 36 | return { 37 | 'minMemory': '1M', 38 | 'connMemory': '1M', 39 | 'scalable': False, 40 | 'reloadable': False, 41 | 'multiCore': False, 42 | 'exitTimeoutMS': self.DEFAULT_EXIT_TIMEOUT, 43 | 'maxRequestSize': '1M', 44 | 'socketProtocol': 'custom', 45 | } 46 | 47 | def onStop(self, config, pid, tune): 48 | self._signalPID(pid, self._sigStop()) 49 | 50 | def onReload(self, config, pid, tune): 51 | if tune['reloadable']: 52 | self._signalPID(pid, self._sigReload()) 53 | else: 54 | self.onStop(config, pid, tune) 55 | 56 | def onPreConfigure(self, config, runtime_dir, svc, cfg_svc_tune): 57 | pass 58 | 59 | def _signalPID(self, pid, sig): 60 | self._os.kill(pid, sig) 61 | 62 | def _sigReload(self): 63 | return self._ext.signal.SIGHUP 64 | 65 | def _sigStop(self): 66 | return self._ext.signal.SIGTERM 67 | -------------------------------------------------------------------------------- /futoin/cid/util/install/java.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ...mixins.ondemand import ext as _ext 18 | from .. import log as _log 19 | 20 | 21 | def mavenCentral(env): 22 | return env.get('mavenCentral', 23 | 'https://repo.maven.apache.org/maven2') 24 | 25 | 26 | def ensureJDBC(env, lib_dir, drivers): 27 | ospath = _ext.ospath 28 | pathutil = _ext.pathutil 29 | ver = env.get('javaVer', '8') 30 | 31 | maven_central = mavenCentral(env) 32 | 33 | psql_ver = '42.1.3' 34 | 35 | if int(ver) == 7: 36 | psql_ver += '.jdk7' 37 | elif int(ver) == 6: 38 | psql_ver += '.jdk6' 39 | 40 | driverMap = { 41 | 'mysql': ('mysql', 'mysql-connector-java', '5.1.43'), 42 | 'postgresql': ('org/postgresql', 'postgresql', psql_ver), 43 | 'mssql': ('com/microsoft/sqlserver', 'mssql-jdbc', '6.2.1.jre' + ver), 44 | 'sqlite': ('org/xerial', 'sqlite-jdbc', '3.19.3'), 45 | } 46 | 47 | drivers = drivers or driverMap.keys() 48 | 49 | for d in drivers: 50 | try: 51 | src = driverMap[d] 52 | except KeyError: 53 | _log.errorExit('Unknown JDBC driver "{0}'.format(d)) 54 | 55 | dst = ospath.join(lib_dir, '{0}.jar'.format(d)) 56 | 57 | if ospath.exists(dst): 58 | continue 59 | 60 | if type(src) == tuple: 61 | src = '{0}/{1}/{2}/{3}/{2}-{3}.jar'.format( 62 | maven_central, 63 | src[0], src[1], src[2] 64 | ) 65 | 66 | pathutil.downloadFile(env, src, dst) 67 | -------------------------------------------------------------------------------- /futoin/cid/util/install/apk.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ...mixins.ondemand import ext as _ext 18 | 19 | 20 | def apk(packages): 21 | if _ext.detect.isApk(): 22 | packages = _ext.configutil.listify(packages) 23 | 24 | apk = '/sbin/apk' 25 | 26 | _ext.executil.trySudoCall( 27 | [apk, 'add'] + packages, 28 | errmsg='you may need to install the build deps manually !' 29 | ) 30 | 31 | 32 | def apkRepo(url, tag=None): 33 | if not _ext.detect.isApk(): 34 | return 35 | 36 | apk = '/sbin/apk' 37 | repo_file = '/etc/apk/repositories' 38 | 39 | # version 40 | releasever = _ext.pathutil.readTextFile('/etc/alpine-release') 41 | releasever = releasever.strip().split('.') 42 | releasever = '.'.join(releasever[:2]) 43 | repoline = url.replace('$releasever', releasever) 44 | 45 | # tag 46 | if tag: 47 | repoline = '@{0} {1}'.format(tag, repoline) 48 | 49 | repos = _ext.pathutil.readTextFile(repo_file).split("\n") 50 | repos = [r.strip() for r in repos] 51 | 52 | if repoline not in repos: 53 | _ext.executil.trySudoCall( 54 | ['/usr/bin/tee', '-a', repo_file], 55 | errmsg='you may need to add the repo manually!', 56 | input=repoline 57 | ) 58 | _ext.executil.trySudoCall( 59 | [apk, 'update'], 60 | errmsg='you may need to update manually!' 61 | ) 62 | 63 | 64 | def apkCommunity(): 65 | _ext.install.apkRepo( 66 | 'http://dl-cdn.alpinelinux.org/alpine/v$releasever/community') 67 | -------------------------------------------------------------------------------- /futoin/cid/tool/bowertool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from .npmtoolmixin import NpmToolMixIn 19 | 20 | 21 | class bowerTool(NpmToolMixIn, BuildTool): 22 | """Bower: a package manager for the web. 23 | 24 | Home: https://bower.io/ 25 | """ 26 | __slots__ = () 27 | 28 | BOWER_JSON = 'bower.json' 29 | 30 | def autoDetectFiles(self): 31 | return self.BOWER_JSON 32 | 33 | def loadConfig(self, config): 34 | content = self._pathutil.loadJSONConfig(self.BOWER_JSON) 35 | if content is None: 36 | return 37 | 38 | f = 'name' 39 | if f in content and f not in config: 40 | config[f] = content[f] 41 | 42 | def updateProjectConfig(self, config, updates): 43 | def updater(json): 44 | f = 'name' 45 | if f in updates: 46 | json[f] = updates[f] 47 | 48 | # version is deprecated 49 | if 'version' in json: 50 | del json['version'] 51 | 52 | return self._pathutil.updateJSONConfig(self.BOWER_JSON, updater) 53 | 54 | def onPrepare(self, config): 55 | bowerBin = config['env']['bowerBin'] 56 | self._executil.callExternal([bowerBin, 'install']) 57 | 58 | def onPackage(self, config): 59 | if not self._isDefaultPackage(config): 60 | return 61 | 62 | # Bower does not remove dev deps by itself 63 | self._pathutil.rmTree('bower_components') 64 | 65 | bowerBin = config['env']['bowerBin'] 66 | self._executil.callExternal([bowerBin, 'install', '--production']) 67 | -------------------------------------------------------------------------------- /futoin/cid/tool/anttool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from .sdkmantoolmixin import SdkmanToolMixIn 19 | 20 | 21 | class antTool(SdkmanToolMixIn, BuildTool): 22 | """Ant build tool for Java applications. 23 | 24 | Home: http://ant.apache.org/ 25 | 26 | The tool assumes the following targets: clean, compile, jar, run 27 | 28 | Ant is setup through SDKMan! 29 | 30 | Note: If detected Java version is less than 8 then Ant 1.9.8 is used. 31 | 32 | Build targets: 33 | prepare -> clean 34 | build -> compile 35 | package -> jar 36 | Override targets with .config.toolTune. 37 | 38 | """ 39 | __slots__ = () 40 | 41 | def autoDetectFiles(self): 42 | return ['build.xml'] 43 | 44 | def initEnv(self, env): 45 | if self._javaVersion(env) < 8: 46 | env['antVer'] = '1.9.8' 47 | 48 | super(antTool, self).initEnv(env) 49 | 50 | def _callAnt(self, config, target): 51 | cmd = [config['env']['antBin'], target] 52 | self._executil.callMeaningful(cmd) 53 | 54 | def onPrepare(self, config): 55 | target = self._getTune(config, 'prepare', 'clean') 56 | self._callAnt(config, target) 57 | 58 | def onBuild(self, config): 59 | target = self._getTune(config, 'build', 'compile') 60 | self._callAnt(config, target) 61 | 62 | def onPackage(self, config): 63 | target = self._getTune(config, 'package', 'jar') 64 | self._callAnt(config, target) 65 | 66 | self._pathutil.addPackageFiles(config, 'build/jar/*.jar') 67 | 68 | def onRunDev(self, config): 69 | self._callAnt(config, 'run') 70 | -------------------------------------------------------------------------------- /futoin/cid/tool/maventool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from ..testtool import TestTool 19 | from .sdkmantoolmixin import SdkmanToolMixIn 20 | 21 | 22 | class mavenTool(SdkmanToolMixIn, BuildTool, TestTool): 23 | """Apache Maven is a software project management and comprehension tool. 24 | 25 | Home: https://maven.apache.org/ 26 | 27 | Expects clean, compile, package and test targets. 28 | 29 | Build targets: 30 | prepare -> clean 31 | build -> compile 32 | package -> package 33 | check -> test 34 | Override targets with .config.toolTune. 35 | 36 | Requires Java >= 7. 37 | """ 38 | __slots__ = () 39 | 40 | def _minJava(self): 41 | return '7' 42 | 43 | def autoDetectFiles(self): 44 | return 'pom.xml' 45 | 46 | def _binName(self): 47 | return 'mvn' 48 | 49 | def _callMaven(self, config, target): 50 | cmd = [config['env']['mavenBin'], target] 51 | self._executil.callMeaningful(cmd) 52 | 53 | def onPrepare(self, config): 54 | target = self._getTune(config, 'prepare', 'clean') 55 | self._callMaven(config, target) 56 | 57 | def onBuild(self, config): 58 | target = self._getTune(config, 'build', 'compile') 59 | self._callMaven(config, target) 60 | 61 | def onPackage(self, config): 62 | target = self._getTune(config, 'package', 'package') 63 | self._callMaven(config, target) 64 | self._pathutil.addPackageFiles(config, 'target/*.jar') 65 | 66 | def onCheck(self, config): 67 | target = self._getTune(config, 'check', 'test') 68 | self._callMaven(config, target) 69 | -------------------------------------------------------------------------------- /futoin/cid/tool/rusttool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class rustTool(RunEnvTool): 21 | """Rust is a systems programming language. 22 | 23 | Home: https://www.rust-lang.org 24 | """ 25 | __slots__ = () 26 | 27 | def getDeps(self): 28 | if self._isGlobalRust(): 29 | return [] 30 | 31 | return ['rustup'] 32 | 33 | def getVersionParts(self): 34 | return 3 35 | 36 | def _isGlobalRust(self): 37 | return self._detect.isAlpineLinux() 38 | 39 | def _installTool(self, env): 40 | if self._isGlobalRust(): 41 | self._install.apk('rust') 42 | return 43 | 44 | self._executil.callExternal([ 45 | env['rustupBin'], 'toolchain', 'install', env['rustVer'] 46 | ]) 47 | 48 | def _updateTool(self, env): 49 | self._installTool(env) 50 | 51 | def uninstallTool(self, env): 52 | if self._isGlobalRust(): 53 | return 54 | 55 | self._executil.callExternal([ 56 | env['rustupBin'], 'toolchain', 'uninstall', env['rustVer'] 57 | ]) 58 | self._have_tool = False 59 | 60 | def envNames(self): 61 | return ['rustBin', 'rustVer'] 62 | 63 | def initEnv(self, env): 64 | if not self._isGlobalRust(): 65 | ver = env.setdefault('rustVer', 'stable') 66 | self._environ['RUSTUP_TOOLCHAIN'] = ver 67 | 68 | try: 69 | res = self._executil.callExternal([ 70 | env['rustupBin'], 'which', 'rustc' 71 | ], verbose=False) 72 | except: 73 | return 74 | 75 | super(rustTool, self).initEnv(env, 'rustc') 76 | -------------------------------------------------------------------------------- /futoin/cid/tool/sbttool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from ..testtool import TestTool 19 | from .sdkmantoolmixin import SdkmanToolMixIn 20 | 21 | 22 | class sbtTool(SdkmanToolMixIn, BuildTool, TestTool): 23 | """The interactive build tool (Scala). 24 | 25 | Home: http://www.scala-sbt.org/ 26 | 27 | Installed via SDKMan! 28 | 29 | First run of SBT may consume a lot of time on post-installation steps. 30 | 31 | Build targets: 32 | prepare -> clean 33 | build -> compile 34 | package -> package 35 | check -> test 36 | Override targets with .config.toolTune. 37 | 38 | Requires Java >= 8. 39 | """ 40 | __slots__ = () 41 | 42 | def _minJava(self): 43 | return '8' 44 | 45 | def autoDetectFiles(self): 46 | return 'build.sbt' 47 | 48 | def _callSBT(self, config, target): 49 | cmd = [config['env']['sbtBin'], target] 50 | self._executil.callMeaningful(cmd) 51 | 52 | def onPrepare(self, config): 53 | target = self._getTune(config, 'prepare', 'clean') 54 | self._callSBT(config, target) 55 | 56 | def onBuild(self, config): 57 | target = self._getTune(config, 'build', 'compile') 58 | self._callSBT(config, target) 59 | 60 | def onPackage(self, config): 61 | target = self._getTune(config, 'package', 'package') 62 | self._callSBT(config, target) 63 | self._pathutil.addPackageFiles(config, 'target/scala-*/*.jar') 64 | 65 | def onRunDev(self, config): 66 | target = self._getTune(config, 'run', 'check') 67 | self._callSBT(config, target) 68 | 69 | def onCheck(self, config): 70 | target = self._getTune(config, 'check', 'test') 71 | self._callSBT(config, target) 72 | -------------------------------------------------------------------------------- /futoin/cid/tool/twinetool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..rmstool import RmsTool 18 | from .piptoolmixin import PipToolMixIn 19 | 20 | 21 | class twineTool(PipToolMixIn, RmsTool): 22 | """Collection of utilities for interacting with PyPI 23 | Home: https://pypi.python.org/pypi/twine 24 | 25 | Not a fully supported RMS. Supports only uploading of local packages. 26 | 27 | Note: rmsRepo is ignored and rmsPool is actual repo URL or uses ~/.pypirc entry 28 | """ 29 | __slots__ = () 30 | 31 | def getDeps(self): 32 | return super(twineTool, self).getDeps() + ['gpg'] 33 | 34 | def envNames(self): 35 | return [ 36 | 'twineBin', 37 | 'twineIdentity', 38 | 'twineUser', 39 | 'twinePassword', 40 | ] 41 | 42 | def rmsUpload(self, config, rms_pool, package_list): 43 | env = config['env'] 44 | 45 | identity = env.get('twineIdentity', None) 46 | user = env.get('twineUser', None) 47 | passwd = env.get('twinePassword', None) 48 | 49 | cmd = [env['twineBin'], 'upload'] 50 | cmd += ['--repository', rms_pool] 51 | 52 | if identity: 53 | cmd += ['--sign', '--identity', identity] 54 | elif self._getTune(config, 'requireSign', True): 55 | self._errorExit( 56 | 'Twine config requires GPG signature. Please set "twineIdentity"') 57 | 58 | if user: 59 | cmd += ['--username', user] 60 | 61 | if passwd: 62 | cmd += ['--password', passwd] 63 | 64 | cmd.append('--skip-existing') 65 | 66 | cmd += package_list 67 | 68 | self._executil.callExternal(cmd, user_interaction=True) 69 | 70 | def rmsPoolList(self, config): 71 | return [ 72 | 'pypi', 73 | 'pypitest', 74 | ] 75 | -------------------------------------------------------------------------------- /tests/cid_initcmd_test.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from __future__ import print_function, absolute_import 18 | 19 | from .cid_utbase import cid_UTBase 20 | from futoin.cid.rmstool import RmsTool 21 | 22 | import os, stat 23 | import subprocess 24 | import glob 25 | 26 | from collections import OrderedDict 27 | 28 | class cid_initcmd_Test ( cid_UTBase ) : 29 | __test__ = True 30 | TEST_DIR = os.path.join(cid_UTBase.TEST_RUN_DIR, 'initcmd') 31 | 32 | def setUp(self): 33 | self.setUpClass() 34 | 35 | os.mkdir(self.TEST_DIR) 36 | os.chdir(self.TEST_DIR) 37 | 38 | def test10_init(self): 39 | self._call_cid(['init']) 40 | cfg = self._readJSON('futoin.json') 41 | 42 | self.assertEquals(cfg, OrderedDict([ 43 | ('name', 'initcmd') 44 | ])) 45 | 46 | def test11_init_name(self): 47 | self._call_cid(['init', 'some_name']) 48 | cfg = self._readJSON('futoin.json') 49 | 50 | self.assertEquals(cfg, OrderedDict([ 51 | ('name', 'some_name') 52 | ])) 53 | 54 | def test12_init_vcs_npm(self): 55 | self._writeJSON('package.json', { 56 | "name": "futoin-cid-grunt-test", 57 | "version" : "0.0.1", 58 | "description": "Futoin CID grunt Test", 59 | "devDependencies": { 60 | "grunt": "*" 61 | }, 62 | }) 63 | 64 | self._call_cid(['init', '--vcsRepo=git:someRepo.git']) 65 | cfg = self._readJSON('futoin.json') 66 | 67 | self.assertEquals(cfg, OrderedDict([ 68 | ('name', 'futoin-cid-grunt-test'), 69 | ('version', '0.0.1'), 70 | ('vcs', 'git'), 71 | ('vcsRepo', 'someRepo.git'), 72 | ])) 73 | 74 | 75 | -------------------------------------------------------------------------------- /futoin/cid/tool/npmtoolmixin.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..subtool import SubTool 18 | 19 | 20 | class NpmToolMixIn(SubTool): 21 | __slots__ = () 22 | 23 | def getDeps(self): 24 | return ['node', 'npm'] 25 | 26 | def _npmName(self): 27 | return self._name 28 | 29 | def _isGlobalNpm(self): 30 | return self._detect.isAlpineLinux() 31 | 32 | def _installTool(self, env): 33 | cmd = [env['npmBin'], 'install', '-g', self._npmName()] 34 | 35 | if self._isGlobalNpm(): 36 | self._executil.trySudoCall(cmd + ['--unsafe-perm']) 37 | else: 38 | self._executil.callExternal(cmd) 39 | 40 | def _updateTool(self, env): 41 | cmd = [env['npmBin'], 'update', '-g', self._npmName()] 42 | 43 | if self._isGlobalNpm(): 44 | self._executil.trySudoCall(cmd + ['--unsafe-perm']) 45 | else: 46 | self._executil.callExternal(cmd) 47 | 48 | def uninstallTool(self, env): 49 | cmd = [env['npmBin'], 'uninstall', '-g', self._npmName()] 50 | 51 | if self._isGlobalNpm(): 52 | self._executil.trySudoCall(cmd + ['--unsafe-perm']) 53 | else: 54 | self._executil.callExternal(cmd) 55 | 56 | self._have_tool = False 57 | 58 | def initEnv(self, env, bin_name=None): 59 | name = self._name 60 | bin_env = name + 'Bin' 61 | 62 | if not env.get(bin_env, None): 63 | if bin_name is None: 64 | bin_name = name 65 | 66 | npmBinDir = env.get('npmBinDir', None) 67 | 68 | if npmBinDir: 69 | tool_path = self._pathutil.safeJoin(npmBinDir, bin_name) 70 | 71 | if self._ospath.exists(tool_path): 72 | env[bin_env] = tool_path 73 | self._have_tool = True 74 | else: 75 | self._have_tool = True 76 | -------------------------------------------------------------------------------- /futoin/cid/tool/cargotool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from ..testtool import TestTool 19 | from ..rmstool import RmsTool 20 | 21 | 22 | class cargoTool(BuildTool, TestTool, RmsTool): 23 | """Cargo, Rust;s Package Manager. 24 | 25 | Home: http://doc.crates.io/ 26 | 27 | Build targets: 28 | prepare -> clean 29 | build -> build 30 | check -> test 31 | Override targets with .config.toolTune. 32 | 33 | """ 34 | __slots__ = () 35 | 36 | def autoDetectFiles(self): 37 | return 'Cargo.toml' 38 | 39 | def getDeps(self): 40 | return ['rust'] 41 | 42 | def _installTool(self, env): 43 | if self._detect.isAlpineLinux(): 44 | self._install.apk('cargo') 45 | 46 | def uninstallTool(self, env): 47 | pass 48 | 49 | def onPrepare(self, config): 50 | self._executil.callExternal([ 51 | config['env']['cargoBin'], 'clean', 52 | ]) 53 | 54 | def onBuild(self, config): 55 | args = [] 56 | 57 | if not config.get('debugBuild', False): 58 | args.append('--release') 59 | 60 | self._executil.callExternal( 61 | [config['env']['cargoBin'], 'build'] + args) 62 | 63 | def onPackage(self, config): 64 | cmd = [config['env']['cargoBin'], 'package', '--allow-dirty'] 65 | self._executil.callMeaningful(cmd) 66 | self._pathutil.addPackageFiles(config, 'target/package/*.crate') 67 | 68 | def onCheck(self, config): 69 | cmd = [config['env']['cargoBin'], 'test'] 70 | self._executil.callMeaningful(cmd) 71 | 72 | def onRunDev(self, config): 73 | cmd = [config['env']['cargoBin'], 'run'] 74 | self._executil.callMeaningful(cmd) 75 | 76 | def rmsUpload(self, config, rms_pool, package_list): 77 | cmd = [config['env']['cargoBin'], 'publish'] 78 | self._executil.callMeaningful(cmd) 79 | -------------------------------------------------------------------------------- /futoin/cid/tool/gemtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | 19 | 20 | class gemTool(BuildTool): 21 | """RubyGems: Find, install, and publish RubyGems. 22 | 23 | Home: https://rubygems.org/ 24 | 25 | If rubyVer is equal to system then gems are installed in 26 | user's folder gemDir. 27 | 28 | gemDir is equal to ~/.gem by default. 29 | 30 | gemInstallArgs is forcibly set by tool depending on its version. 31 | """ 32 | __slots__ = () 33 | 34 | def getDeps(self): 35 | return ['ruby'] 36 | 37 | def uninstallTool(self, env): 38 | pass 39 | 40 | def envNames(self): 41 | return ['gemBin', 'gemDir', 'gemInstallArgs'] 42 | 43 | def initEnv(self, env): 44 | ospath = self._ospath 45 | environ = self._environ 46 | installArgs = [] 47 | 48 | if env['rubyVer'] == self.SYSTEM_VER or env['rubyBinOnly']: 49 | gemDir = ospath.join(self._pathutil.deployHome(), 50 | '.gem', env['rubyVer']) 51 | gemDir = env.setdefault('gemDir', gemDir) 52 | environ['GEM_HOME'] = gemDir 53 | environ['GEM_PATH'] = gemDir 54 | environ['GEM_SPEC_CACHE'] = ospath.join(gemDir, 'specs') 55 | 56 | # self._pathutil.addEnvPath('GEM_PATH', gemDir) 57 | self._pathutil.addBinPath(ospath.join(gemDir, 'bin'), True) 58 | installArgs += ['--no-user-install', '--no-format-executable'] 59 | 60 | super(gemTool, self).initEnv(env) 61 | 62 | if self._have_tool: 63 | version = self._executil.callExternal( 64 | [env['gemBin'], '--version'], verbose=False).strip() 65 | 66 | if version >= '2': 67 | installArgs += ['--no-document'] 68 | else: 69 | installArgs += ['--no-ri', '--no-rdoc'] 70 | 71 | env['gemInstallArgs'] = ' '.join(installArgs) 72 | -------------------------------------------------------------------------------- /futoin/cid/tool/webpacktool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from .npmtoolmixin import NpmToolMixIn 19 | 20 | 21 | class webpackTool(NpmToolMixIn, BuildTool): 22 | """webpack is a module bundler (JS world) 23 | 24 | Home: https://webpack.js.org 25 | """ 26 | __slots__ = () 27 | 28 | def autoDetectFiles(self): 29 | return 'webpack.config.js' 30 | 31 | def _installTool(self, env): 32 | try: 33 | self._name = 'webpack-cli' 34 | super(webpackTool, self)._installTool(env) 35 | self._name = 'webpack' 36 | super(webpackTool, self)._installTool(env) 37 | finally: 38 | self._name = 'webpack' 39 | 40 | def _updateTool(self, env): 41 | try: 42 | self._name = 'webpack-cli' 43 | super(webpackTool, self)._updateTool(env) 44 | self._name = 'webpack' 45 | super(webpackTool, self)._updateTool(env) 46 | finally: 47 | self._name = 'webpack' 48 | 49 | def uninstallTool(self, env): 50 | try: 51 | self._name = 'webpack-cli' 52 | super(webpackTool, self).uninstallTool(env) 53 | self._name = 'webpack' 54 | super(webpackTool, self).uninstallTool(env) 55 | finally: 56 | self._name = 'webpack' 57 | 58 | def initEnv(self, env, bin_name=None): 59 | super(webpackTool, self).initEnv(env, bin_name) 60 | 61 | if self._have_tool: 62 | cli_path = '{0}-cli'.format(env['webpackBin']) 63 | self._have_tool = self._ospath.exists(cli_path) 64 | 65 | if self._have_tool: 66 | env['webpackCliBin'] = cli_path 67 | 68 | def onBuild(self, config): 69 | webpackBin = config['env']['webpackCliBin'] 70 | cmd = [webpackBin, '-p', '--profile', '--display', '--verbose'] 71 | self._executil.callMeaningful(cmd) 72 | -------------------------------------------------------------------------------- /futoin/cid/tool/sdkmantool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | from ..runenvtool import RunEnvTool 19 | from .bashtoolmixin import BashToolMixIn 20 | from .curltoolmixin import CurlToolMixIn 21 | 22 | 23 | class sdkmanTool(BashToolMixIn, CurlToolMixIn, RunEnvTool): 24 | """SDK Man for Java. 25 | 26 | Home: http://sdkman.io/ 27 | """ 28 | __slots__ = () 29 | 30 | def getDeps(self): 31 | return ( 32 | ['unzip', 'zip'] + 33 | BashToolMixIn.getDeps(self) + 34 | CurlToolMixIn.getDeps(self)) 35 | 36 | def envNames(self): 37 | return ['sdkmanDir', 'sdkmanGet'] 38 | 39 | def _installTool(self, env): 40 | dir = env['sdkmanDir'] 41 | get = env.get('sdkmanGet', 'https://get.sdkman.io?rcupdate=false') 42 | 43 | installer = self._callCurl(env, [get]) 44 | 45 | environ = self._environ 46 | environ['SDKMAN_DIR'] = dir 47 | self._callBash(env, input=installer) 48 | del environ['SDKMAN_DIR'] 49 | 50 | def _updateTool(self, env): 51 | self._callBash(env, 52 | 'source {0} && sdk selfupgrade'.format( 53 | env['sdkmanInit']) 54 | ) 55 | 56 | def uninstallTool(self, env): 57 | dir = env['sdkmanDir'] 58 | self._pathutil.rmTree(dir) 59 | self._have_tool = False 60 | 61 | def initEnv(self, env): 62 | ospath = self._ospath 63 | dir = ospath.join(self._environ['HOME'], '.sdkman') 64 | dir = env.setdefault('sdkmanDir', dir) 65 | self._environ['SDKMAN_DIR'] = dir 66 | env_init = ospath.join(dir, 'bin', 'sdkman-init.sh') 67 | env['sdkmanInit'] = env_init 68 | self._have_tool = ospath.exists(env_init) 69 | 70 | def onExec(self, env, args, replace=True): 71 | cmd = '. {0} && sdk {1}'.format( 72 | env['sdkmanInit'], self._ext.subprocess.list2cmdline(args)) 73 | self._callBashInteractive(env, cmd, replace=replace) 74 | -------------------------------------------------------------------------------- /futoin/cid/tool/nvmtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | from .bashtoolmixin import BashToolMixIn 19 | 20 | 21 | class nvmTool(BashToolMixIn, RunEnvTool): 22 | """Node Version Manager. 23 | 24 | Home: https://github.com/creationix/nvm 25 | """ 26 | __slots__ = () 27 | 28 | NVM_LATEST = '$(git describe --abbrev=0 --tags --match "v[0-9]*" origin/master)' 29 | 30 | def getDeps(self): 31 | return ['bash', 'git'] 32 | 33 | def _installTool(self, env): 34 | nvm_dir = env['nvmDir'] 35 | nvm_git = env.get('nvmGit', 'https://github.com/creationix/nvm.git') 36 | nvm_ver = env.get('nvmVer', self.NVM_LATEST) 37 | 38 | self._callBash(env, 39 | 'git clone {1} {0}; \ 40 | cd {0} && git fetch && git reset --hard && git checkout {2}' 41 | .format(nvm_dir, nvm_git, nvm_ver)) 42 | 43 | def _updateTool(self, env): 44 | nvm_dir = env['nvmDir'] 45 | nvm_ver = env.get('nvmVer', self.NVM_LATEST) 46 | 47 | self._callBash(env, 48 | 'cd {0} && git fetch && git reset --hard && git checkout {1}' 49 | .format(nvm_dir, nvm_ver)) 50 | 51 | def uninstallTool(self, env): 52 | nvm_dir = env['nvmDir'] 53 | self._pathutil.rmTree(nvm_dir) 54 | self._have_tool = False 55 | 56 | def envNames(self): 57 | return ['nvmDir', 'nvmGit', 'nvmVer'] 58 | 59 | def initEnv(self, env): 60 | ospath = self._ospath 61 | nvm_dir = ospath.join(self._environ['HOME'], '.nvm') 62 | nvm_dir = env.setdefault('nvmDir', nvm_dir) 63 | env_init = ospath.join(nvm_dir, 'nvm.sh') 64 | env['nvmInit'] = env_init 65 | self._have_tool = ospath.exists(env_init) 66 | 67 | def onExec(self, env, args, replace=True): 68 | cmd = '. {0} && nvm {1}'.format( 69 | env['nvmInit'], self._ext.subprocess.list2cmdline(args)) 70 | self._callBashInteractive(env, cmd, replace=replace) 71 | -------------------------------------------------------------------------------- /futoin/cid/tool/setuptoolstool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from ..testtool import TestTool 19 | from .piptoolmixin import PipToolMixIn 20 | 21 | 22 | class setuptoolsTool(PipToolMixIn, BuildTool, TestTool): 23 | """Easily download, build, install, upgrade, and uninstall Python packages. 24 | 25 | Home: https://pypi.python.org/pypi/setuptools 26 | 27 | Not assumed to be used directly. 28 | 29 | Build targets: 30 | prepare -> {removes build & dist folders} 31 | build -> ['sdist', 'bdist_wheel'] 32 | package -> {uses result of build in dist/} 33 | Override targets with .config.toolTune. 34 | 35 | """ 36 | __slots__ = () 37 | 38 | def autoDetectFiles(self): 39 | return 'setup.py' 40 | 41 | def uninstallTool(self, env): 42 | pass 43 | 44 | def initEnv(self, env): 45 | ospath = self._ospath 46 | virtualenv_dir = env['virtualenvDir'] 47 | self._have_tool = self._checkPip(env, 'setuptools') 48 | 49 | def onPrepare(self, config): 50 | targets = self._getTune(config, 'prepare', ['build', 'dist']) 51 | targets = self._configutil.listify(targets) 52 | 53 | for d in targets: 54 | self._pathutil.rmTree(d) 55 | 56 | def onBuild(self, config): 57 | env = config['env'] 58 | self._requirePip(env, 'wheel') 59 | 60 | targets = self._getTune(config, 'build', ['sdist', 'bdist_wheel']) 61 | targets = self._configutil.listify(targets) 62 | 63 | cmd = [env['pythonBin'], 'setup.py'] + targets 64 | self._executil.callMeaningful(cmd) 65 | 66 | def onPackage(self, config): 67 | target = self._getTune(config, 'package', 'dist') 68 | self._pathutil.addPackageFiles( 69 | config, self._pathutil.safeJoin(target, '*')) 70 | 71 | def onCheck(self, config): 72 | env = config['env'] 73 | self._requirePip(env, 'docutils') 74 | self._requirePip(env, 'readme') 75 | cmd = [env['pythonBin'], 'setup.py', 'check', '-mrs'] 76 | self._executil.callMeaningful(cmd) 77 | -------------------------------------------------------------------------------- /futoin/cid/cte.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # Copyright 2015-2020 Andrey Galkin 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | """FutoIn CID v{0} 20 | 21 | This command is alias for "cid tool exec" of FutoIn Continuous Integration & Delivery Tool. 22 | 23 | Experience has shown that this command is used a lot in development process and 24 | requires a dedicated alias. 25 | 26 | Usage: 27 | cte [...] 28 | cte list 29 | """ 30 | 31 | from __future__ import print_function 32 | 33 | from .cidtool import CIDTool 34 | from .coloring import Coloring 35 | 36 | import os 37 | import sys 38 | 39 | from . import __version__ as version 40 | 41 | if sys.version_info < (2, 7): 42 | print('Sorry, but only Python version >= 2.7 is supported!', file=sys.stderr) 43 | sys.exit(1) 44 | 45 | __all__ = ['run'] 46 | 47 | 48 | def runInner(): 49 | argv = sys.argv 50 | 51 | try: 52 | tool = argv[1] 53 | except IndexError: 54 | tool = None 55 | 56 | if tool is None or tool in ('-h', '--help', 'help'): 57 | print(__doc__.format(version)) 58 | else: 59 | ospath = os.path 60 | 61 | if 'CID_COLOR' in os.environ: 62 | Coloring.enable(os.environ['CID_COLOR'] == 'yes') 63 | 64 | overrides = {} 65 | overrides['wcDir'] = ospath.realpath('.') 66 | 67 | # --- 68 | overrides['toolVer'] = None 69 | overrides['toolDetect'] = False 70 | 71 | # --- 72 | cit = CIDTool(overrides=overrides) 73 | cit.cte(tool, argv[2:]) 74 | 75 | 76 | def run(): 77 | try: 78 | runInner() 79 | except Exception as e: 80 | print(file=sys.stderr) 81 | print(Coloring.error('ERROR: ' + str(e)), file=sys.stderr) 82 | print(file=sys.stderr) 83 | import traceback 84 | print(Coloring.warn(traceback.format_exc()), file=sys.stderr) 85 | sys.exit(1) 86 | except KeyboardInterrupt as e: 87 | print(file=sys.stderr) 88 | print(Coloring.error('Exit on user abort'), file=sys.stderr) 89 | print(file=sys.stderr) 90 | sys.exit(1) 91 | 92 | 93 | if __name__ == "__main__": 94 | run() 95 | -------------------------------------------------------------------------------- /futoin/cid/tool/gemtoolmixin.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..subtool import SubTool 18 | 19 | 20 | class GemToolMixIn(SubTool): 21 | __slots__ = () 22 | 23 | def getDeps(self): 24 | return ['gem', 'ruby'] 25 | 26 | def _gemName(self): 27 | return self._name 28 | 29 | def _installTool(self, env): 30 | if self._detect.isExternalToolsSetup(env): 31 | self._executil.externalSetup(env, ['build-dep', 'ruby']) 32 | else: 33 | self._builddep.require(env, 'ruby') 34 | 35 | ver = env.get(self._name + 'Ver', None) 36 | version_arg = [] 37 | 38 | if ver: 39 | version_arg = ['--version', ver] 40 | 41 | self._executil.callExternal([env['gemBin'], 'install', self._gemName( 42 | )] + env['gemInstallArgs'].split(' ') + version_arg) 43 | 44 | def installTool(self, env): 45 | if not self._have_tool: 46 | if self._detect.isDisabledToolsSetup(env): 47 | self._errorExit( 48 | 'Tool "{0}" must be installed externally (env config)'.format(self._name)) 49 | else: 50 | self._warn( 51 | 'Auto-installing required "{0}" tool'.format(self._name)) 52 | self._installTool(env) 53 | 54 | self.initEnv(env) 55 | 56 | if not self._have_tool: 57 | self._errorExit('Failed to install "{0}"'.format(self._name)) 58 | 59 | def _updateTool(self, env): 60 | if env.get(self._name + 'Ver', None): 61 | self._installTool(env) 62 | else: 63 | self._executil.callExternal( 64 | [env['gemBin'], 'update', self._gemName()] + env['gemInstallArgs'].split(' ')) 65 | 66 | def updateTool(self, env): 67 | if self._detect.isDisabledToolsSetup(env): 68 | self._errorExit( 69 | 'Tool "{0}" must be updated externally (env config)'.format(self._name)) 70 | else: 71 | self._updateTool(env) 72 | 73 | def uninstallTool(self, env): 74 | self._executil.callExternal([env['gemBin'], 'uninstall', 75 | '--force', self._gemName()]) 76 | self._have_tool = False 77 | -------------------------------------------------------------------------------- /futoin/cid/tool/phpbuildtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | from .bashtoolmixin import BashToolMixIn 19 | 20 | 21 | class phpbuildTool(BashToolMixIn, RunEnvTool): 22 | """Builds PHP so that multiple versions can be used side by side. 23 | 24 | Home: https://github.com/php-build/php-build 25 | """ 26 | __slots__ = () 27 | 28 | PHPBUILD_LATEST = 'master' 29 | 30 | def getDeps(self): 31 | return ['bash', 'git'] 32 | 33 | def _installTool(self, env): 34 | phpbuild_dir = env['phpbuildDir'] 35 | phpbuild_git = env.get( 36 | 'phpbuildGit', 'https://github.com/php-build/php-build.git') 37 | phpbuild_ver = env.get('phpbuildVer', self.PHPBUILD_LATEST) 38 | 39 | self._callBash(env, 40 | 'git clone {1} {0}; \ 41 | cd {0} && git fetch && git reset --hard && git checkout {2}' 42 | .format(phpbuild_dir, phpbuild_git, phpbuild_ver)) 43 | 44 | def _updateTool(self, env): 45 | if env.get('phpBinOnly', False): 46 | return 47 | 48 | phpbuild_dir = env['phpbuildDir'] 49 | phpbuild_ver = env.get('phpbuildVer', self.PHPBUILD_LATEST) 50 | 51 | self._callBash(env, 52 | 'cd {0} && git fetch && git reset --hard && git checkout {1} && git pull --rebase' 53 | .format(phpbuild_dir, phpbuild_ver)) 54 | 55 | def uninstallTool(self, env): 56 | phpbuild_dir = env['phpbuildDir'] 57 | self._pathutil.rmTree(phpbuild_dir) 58 | self._have_tool = False 59 | 60 | def envNames(self): 61 | return ['phpbuildDir', 'phpbuildBin', 'phpbuildGit', 'phpbuildVer'] 62 | 63 | def initEnv(self, env): 64 | ospath = self._ospath 65 | phpbuild_dir = ospath.join(self._environ['HOME'], '.phpbuild') 66 | phpbuild_dir = env.setdefault('phpbuildDir', phpbuild_dir) 67 | phpbuild_bin = env.setdefault( 68 | 'phpbuildBin', ospath.join(phpbuild_dir, 'bin', 'php-build')) 69 | self._have_tool = ospath.exists(phpbuild_bin) 70 | 71 | # special case when environment required binaries only 72 | if env.get('phpBinOnly', False): 73 | self._have_tool = True 74 | -------------------------------------------------------------------------------- /futoin/cid/tool/gradletool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from .sdkmantoolmixin import SdkmanToolMixIn 19 | 20 | 21 | class gradleTool(SdkmanToolMixIn, BuildTool): 22 | """Gradle Build Tool. 23 | 24 | Home: https://gradle.org/ 25 | 26 | Build targets: 27 | prepare -> clean 28 | build -> without explicit target 29 | package -> dists 30 | packageGlob -> '*.jar' 31 | Override targets with .config.toolTune. 32 | 33 | Requires Java >= 7. 34 | """ 35 | __slots__ = () 36 | 37 | def _minJava(self): 38 | return '7' 39 | 40 | def autoDetectFiles(self): 41 | return 'build.gradle' 42 | 43 | def envDeps(self, env): 44 | super(gradleTool, self).envDeps(env) 45 | 46 | gradlew_prop = 'gradle/wrapper/gradle-wrapper.properties' 47 | 48 | if self._ospath.exists(gradlew_prop): 49 | with open(gradlew_prop, 'r') as f: 50 | props = f.read() 51 | gradleVer = self._ext.re.search( 52 | 'gradle-([0-9.]+)-(all|bin).zip', props) 53 | 54 | if gradleVer is None: 55 | self._errorExit( 56 | 'Unable to find gradle version in {0}'.format(gradlew_prop)) 57 | 58 | env['gradleVer'] = gradleVer.group(1) 59 | 60 | def onPrepare(self, config): 61 | target = self._getTune(config, 'prepare', 'clean') 62 | cmd = [config['env']['gradleBin'], 63 | '-q', '--no-daemon', target] 64 | self._executil.callMeaningful(cmd) 65 | 66 | def onBuild(self, config): 67 | target = self._getTune(config, 'build') 68 | 69 | if target: 70 | args = [target] 71 | else: 72 | args = [] 73 | 74 | cmd = [config['env']['gradleBin'], 75 | '-q', '--no-daemon'] + args 76 | self._executil.callMeaningful(cmd) 77 | 78 | def onPackage(self, config): 79 | target = self._getTune(config, 'package', 'dists') 80 | cmd = [config['env']['gradleBin'], 81 | '-q', '--no-daemon', target] 82 | self._executil.callMeaningful(cmd) 83 | 84 | packageGlob = self._getTune(config, 'packageGlob', 'build/libs/*.jar') 85 | self._pathutil.addPackageFiles(config, packageGlob) 86 | -------------------------------------------------------------------------------- /futoin/cid/tool/rustuptool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | 18 | from ..runenvtool import RunEnvTool 19 | from .bashtoolmixin import BashToolMixIn 20 | from .curltoolmixin import CurlToolMixIn 21 | 22 | 23 | class rustupTool(BashToolMixIn, CurlToolMixIn, RunEnvTool): 24 | """rustup is an installer for the systems programming language Rust. 25 | 26 | Home: https://www.rustup.rs/ 27 | """ 28 | __slots__ = () 29 | 30 | INSTALLER_DEFAULT = 'https://sh.rustup.rs' 31 | 32 | def getDeps(self): 33 | return ( 34 | BashToolMixIn.getDeps(self) + 35 | CurlToolMixIn.getDeps(self)) 36 | 37 | def _installTool(self, env): 38 | if self._detect.isAlpineLinux(): 39 | self._warn('Unfortunately, rustup does not support musl libc yet') 40 | 41 | installer = self._callCurl(env, [env['rustupInstaller']]) 42 | 43 | self._callBash( 44 | env, 45 | bash_args=['--', '-y', '--no-modify-path'], 46 | input=installer) 47 | 48 | def _updateTool(self, env): 49 | self._executil.callExternal([ 50 | env['rustupBin'], 'self', 'update' 51 | ]) 52 | 53 | def uninstallTool(self, env): 54 | ospath = self._ospath 55 | 56 | for v in ['rustupDir', 'cargoDir']: 57 | dir = env[v] 58 | self._pathutil.rmTree(dir) 59 | 60 | self._have_tool = False 61 | 62 | def envNames(self): 63 | return ['rustupBin', 'rustupDir', 'rustupInstaller'] 64 | 65 | def initEnv(self, env): 66 | ospath = self._ospath 67 | environ = self._environ 68 | dir = ospath.join(environ['HOME'], '.multirust') 69 | dir = environ.setdefault('RUSTUP_HOME', dir) 70 | cargo_dir = ospath.join(environ['HOME'], '.cargo') 71 | cargo_dir = environ.setdefault('CARGO_HOME', cargo_dir) 72 | 73 | dir = env.setdefault('rustupDir', dir) 74 | cargo_dir = env.setdefault('cargoDir', cargo_dir) 75 | 76 | environ['RUSTUP_HOME'] = dir 77 | environ['CARGO_HOME'] = cargo_dir 78 | 79 | env.setdefault('rustupInstaller', self.INSTALLER_DEFAULT) 80 | 81 | bin_dir = ospath.join(cargo_dir, 'bin') 82 | self._pathutil.addBinPath(bin_dir, True) 83 | 84 | if ospath.exists(ospath.join(bin_dir, 'rustup')): 85 | super(rustupTool, self).initEnv(env) 86 | -------------------------------------------------------------------------------- /futoin/cid/tool/piptool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | 19 | 20 | class pipTool(BuildTool): 21 | """The PyPA recommended tool for installing Python packages. 22 | 23 | Home: https://pypi.python.org/pypi/pip 24 | """ 25 | __slots__ = () 26 | 27 | REQUIREMENTS_FILES = [ 28 | 'requirements.txt', 29 | 'requirements.pip', 30 | ] 31 | 32 | def autoDetectFiles(self): 33 | return self.REQUIREMENTS_FILES 34 | 35 | def getDeps(self): 36 | return ['python', 'virtualenv'] 37 | 38 | def envNames(self): 39 | return ['pipBin', 'pipVer'] 40 | 41 | def _installTool(self, env): 42 | ospath = self._ospath 43 | 44 | if ospath.exists(env['pipBin']): 45 | self._updateTool(env) 46 | else: 47 | self._executil.callExternal([ 48 | ospath.join(env['virtualenvDir'], 'bin', 49 | 'easy_install'), 'pip' 50 | ]) 51 | 52 | def _updateTool(self, env): 53 | self._executil.callExternal([ 54 | env['pipBin'], 'install', '-q', 55 | '--upgrade', 56 | 'pip>={0}'.format(env['pipVer']), 57 | ]) 58 | 59 | def installTool(self, env): 60 | if not self._have_tool: 61 | self._installTool(env) 62 | self.initEnv(env) 63 | 64 | if not self._have_tool: 65 | self._errorExit('Failed to install "{0}"'.format(self._name)) 66 | 67 | def uninstallTool(self, env): 68 | pass 69 | 70 | def initEnv(self, env): 71 | ospath = self._ospath 72 | pipBin = ospath.join(env['virtualenvDir'], 'bin', 'pip') 73 | pipBin = env.setdefault('pipBin', pipBin) 74 | pipVer = env.setdefault('pipVer', '9.0.3') 75 | 76 | if ospath.exists(pipBin): 77 | pipFactVer = self._executil.callExternal( 78 | [pipBin, '--version'], verbose=False) 79 | pipFactVer = [int(v) for v in pipFactVer.split(' ')[1].split('.')] 80 | pipNeedVer = [int(v) for v in pipVer.split('.')] 81 | 82 | self._have_tool = pipNeedVer <= pipFactVer 83 | 84 | def onPrepare(self, config): 85 | for rf in self.REQUIREMENTS_FILES: 86 | if self._ospath.exists(rf): 87 | cmd = [config['env']['pipBin'], 'install', '-r', rf] 88 | self._executil.callMeaningful(cmd) 89 | -------------------------------------------------------------------------------- /futoin/cid/vcstool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .subtool import SubTool 18 | 19 | __all__ = ['VcsTool'] 20 | 21 | 22 | class VcsTool(SubTool): 23 | __slots__ = () 24 | 25 | def autoDetect(self, config): 26 | return self._autoDetectVCS(config, self.autoDetectFiles()) 27 | 28 | def vcsGetRepo(self, config, wc_dir=None): 29 | raise NotImplementedError(self._name) 30 | 31 | def vcsCheckout(self, config, branch): 32 | raise NotImplementedError(self._name) 33 | 34 | def vcsCommit(self, config, message, files): 35 | raise NotImplementedError(self._name) 36 | 37 | def vcsTag(self, config, tag, message): 38 | raise NotImplementedError(self._name) 39 | 40 | def vcsPush(self, config, refs): 41 | raise NotImplementedError(self._name) 42 | 43 | def vcsGetRevision(self, config): 44 | raise NotImplementedError(self._name) 45 | 46 | def vcsGetRefRevision(self, config, vcs_cache_dir, branch): 47 | raise NotImplementedError(self._name) 48 | 49 | def vcsListTags(self, config, vcs_cache_dir, tag_hint): 50 | raise NotImplementedError(self._name) 51 | 52 | def vcsListBranches(self, config, vcs_cache_dir, branch_hint): 53 | raise NotImplementedError(self._name) 54 | 55 | def vcsExport(self, config, vcs_cache_dir, vcs_ref, dst_path): 56 | raise NotImplementedError(self._name) 57 | 58 | def vcsBranch(self, config, vcs_ref): 59 | raise NotImplementedError(self._name) 60 | 61 | def vcsMerge(self, config, vcs_ref, cleanup): 62 | raise NotImplementedError(self._name) 63 | 64 | def vcsDelete(self, config, vcs_cache_dir, vcs_ref): 65 | raise NotImplementedError(self._name) 66 | 67 | def vcsRevert(self, config): 68 | raise NotImplementedError(self._name) 69 | 70 | def vcsIsMerged(self, config, vcs_ref): 71 | raise NotImplementedError(self._name) 72 | 73 | def vcsClean(self, config): 74 | raise NotImplementedError(self._name) 75 | 76 | def _autoDetectVCS(self, config, vcsDir): 77 | if config.get('vcs', None) == self._name: 78 | return True 79 | 80 | if vcsDir in config['projectRootSet']: 81 | if config.get('vcs', self._name) != self._name: 82 | self._errorExit( 83 | 'Another VCS type {0} has been already detected!'.format(config['vcs'])) 84 | config['vcs'] = self._name 85 | return True 86 | 87 | return False 88 | -------------------------------------------------------------------------------- /futoin/cid/tool/jfrogtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | from .curltoolmixin import CurlToolMixIn 19 | 20 | 21 | class jfrogTool(CurlToolMixIn, RunEnvTool): 22 | """JFrog: Command Line Interface for Artifactory and Bintray 23 | 24 | Home: https://www.jfrog.com/confluence/display/CLI/JFrog+CLI 25 | """ 26 | __slots__ = () 27 | 28 | def _installTool(self, env): 29 | ospath = self._ospath 30 | os = self._os 31 | 32 | if self._detect.isMacOS(): 33 | self._install.brew('jfrog-cli-go') 34 | return 35 | 36 | dst_dir = env['jfrogDir'] 37 | get_url = env['jfrogGet'] 38 | jfrog_bin = ospath.join(dst_dir, 'jfrog') 39 | 40 | if not ospath.exists(dst_dir): 41 | os.makedirs(dst_dir) 42 | 43 | self._callCurl(env, [get_url, '-o', jfrog_bin]) 44 | stat = self._ext.stat 45 | os.chmod(jfrog_bin, stat.S_IRWXU | stat.S_IRGRP | 46 | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) 47 | 48 | def _updateTool(self, env): 49 | if self._detect.isMacOS(): 50 | return 51 | 52 | self.uninstallTool(env) 53 | self._installTool(env) 54 | 55 | def uninstallTool(self, env): 56 | if self._detect.isMacOS(): 57 | return 58 | 59 | jfrog_bin = env['jfrogBin'] 60 | if self._ospath.exists(jfrog_bin): 61 | self._os.remove(jfrog_bin) 62 | self._have_tool = False 63 | 64 | def envNames(self): 65 | return ['jfrogDir', 'jfrogBin', 'jfrogGet'] 66 | 67 | def initEnv(self, env): 68 | bin_dir = env.setdefault('jfrogDir', env['binDir']) 69 | 70 | pkg = None 71 | url_base = 'https://api.bintray.com/content/jfrog/jfrog-cli-go/$latest' 72 | 73 | detect = self._detect 74 | 75 | if detect.isMacOS(): 76 | pass 77 | elif detect.isAMD64(): 78 | pkg = 'jfrog-cli-linux-amd64' 79 | else: 80 | pkg = 'jfrog-cli-linux-386' 81 | 82 | if pkg: 83 | env.setdefault( 84 | 'jfrogGet', 85 | 'https://api.bintray.com/content/jfrog/jfrog-cli-go/$latest/{0}/jfrog?bt_package={0}'.format( 86 | pkg) 87 | ) 88 | 89 | self._pathutil.addBinPath(bin_dir) 90 | 91 | super(jfrogTool, self).initEnv(env) 92 | 93 | if self._have_tool: 94 | env['jfrogDir'] = self._ospath.dirname(env['jfrogBin']) 95 | -------------------------------------------------------------------------------- /futoin/cid/tool/gvmtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | from .bashtoolmixin import BashToolMixIn 19 | from .curltoolmixin import CurlToolMixIn 20 | 21 | 22 | class gvmTool(BashToolMixIn, CurlToolMixIn, RunEnvTool): 23 | """Go Version Manager. 24 | 25 | Home: https://github.com/moovweb/gvm 26 | """ 27 | __slots__ = () 28 | 29 | GVM_VERSION_DEFAULT = 'master' 30 | GVM_INSTALLER_DEFAULT = 'https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer' 31 | 32 | def getDeps(self): 33 | return ( 34 | ['git', 'hg', 'make', 'binutils'] + 35 | BashToolMixIn.getDeps(self) + 36 | CurlToolMixIn.getDeps(self)) 37 | 38 | def _installTool(self, env): 39 | self._install.deb(['bison', 'gcc', 'build-essential']) 40 | self._install.rpm(['bison', 'gcc', 'glibc-devel']) 41 | self._install.emergeDepsOnly(['dev-lang/go']) 42 | self._install.pacman(['bison', 'gcc', 'glibc', ]) 43 | self._install.apk('bison') 44 | self._builddep.essential() 45 | 46 | gvm_installer = self._callCurl(env, [env['gvmInstaller']]) 47 | self._callBash( 48 | env, 49 | bash_args=['master', self._environ['GVM_DEST']], 50 | input=gvm_installer, 51 | suppress_fail=True) # error when Go is not yet installed 52 | 53 | def _updateTool(self, env): 54 | self._installTool(env) 55 | 56 | def uninstallTool(self, env): 57 | gvm_dir = env['gvmDir'] 58 | self._pathutil.rmTree(gvm_dir) 59 | self._have_tool = False 60 | 61 | def envNames(self): 62 | return ['gvmDir', 'gvmInstaller'] 63 | 64 | def initEnv(self, env): 65 | ospath = self._ospath 66 | os = self._os 67 | environ = self._environ 68 | gvm_dir = ospath.join(environ['HOME'], '.gvm') 69 | gvm_dir = env.setdefault('gvmDir', gvm_dir) 70 | environ['GVM_DEST'] = ospath.dirname(gvm_dir) 71 | environ['GVM_NAME'] = ospath.basename(gvm_dir) 72 | environ['GVM_NO_UPDATE_PROFILE'] = '1' 73 | 74 | env.setdefault('gvmVer', self.GVM_VERSION_DEFAULT) 75 | env.setdefault('gvmInstaller', self.GVM_INSTALLER_DEFAULT) 76 | 77 | env_init = ospath.join(gvm_dir, 'scripts', 'gvm') 78 | env['gvmInit'] = env_init 79 | 80 | self._have_tool = ospath.exists(env_init) 81 | 82 | def onExec(self, env, args, replace=True): 83 | cmd = '. {0} && gvm {1}'.format( 84 | env['gvmInit'], self._ext.subprocess.list2cmdline(args)) 85 | self._callBashInteractive(env, cmd, replace=replace) 86 | -------------------------------------------------------------------------------- /futoin/cid/tool/pumatool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runtimetool import RuntimeTool 18 | from .bundlermixin import BundlerMixIn 19 | 20 | 21 | class pumaTool(BundlerMixIn, RuntimeTool): 22 | """A ruby web server built for concurrency http://puma.io 23 | 24 | NOTE: the tool can only be used in scope of Ruby project through 25 | bundler 26 | """ 27 | __slots__ = () 28 | 29 | def _sigReload(self): 30 | return self._ext.signal.SIGUSR2 31 | 32 | def getDeps(self): 33 | return ['bundler'] 34 | 35 | def tuneDefaults(self, env): 36 | return { 37 | 'internal': True, 38 | 'minMemory': '128M', 39 | 'connMemory': '8M', 40 | 'connFD': 8, 41 | 'socketTypes': ['unix', 'tcp'], 42 | 'socketType': 'unix', 43 | 'socketProtocol': 'http', 44 | 'scalable': True, 45 | 'reloadable': True, 46 | 'multiCore': False, # use workers on service level 47 | 'maxRequestSize': '1M', 48 | } 49 | 50 | def onRun(self, config, svc, args): 51 | svc_tune = svc['tune'] 52 | 53 | # --- 54 | socket_type = svc_tune['socketType'] 55 | name_id = '{0}-{1}'.format(svc['name'], svc['instanceId']) 56 | 57 | if socket_type == 'unix': 58 | socket = 'unix://{0}'.format(svc_tune['socketPath']) 59 | elif socket_type == 'tcp': 60 | socket = 'tcp://{0}:{1}'.format( 61 | svc_tune['socketAddress'], svc_tune['socketPort']) 62 | else: 63 | self._errorExit( 64 | 'Unsupported socket type "{0}" for "{1}"'.format(socket_type, name_id)) 65 | 66 | # --- 67 | resource = self._ext.resource 68 | heap_limit = self._configutil.parseMemory(svc_tune['maxMemory']) 69 | # both limit RAM and HEAP (not the same) 70 | resource.setrlimit(resource.RLIMIT_RSS, (heap_limit, heap_limit)) 71 | resource.setrlimit(resource.RLIMIT_DATA, (heap_limit, heap_limit)) 72 | 73 | # --- 74 | threads = svc_tune['maxConnections'] 75 | 76 | puma_args = [ 77 | '-b', socket, 78 | '-e', self._environ['RUBY_ENV'], 79 | '-t', '{0}:{0}'.format(threads), 80 | '--dir', self._os.getcwd(), # force chdir() on reload 81 | ] 82 | 83 | # --- 84 | env = config['env'] 85 | 86 | cmd = [ 87 | env['bundlerBin'], 88 | 'exec', env['pumaBin'], 89 | svc['path'] 90 | ] + puma_args + args 91 | 92 | self._ensureInstalled(env) 93 | self._executil.callInteractive(cmd) 94 | -------------------------------------------------------------------------------- /futoin/cid/util/configutil.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from collections import OrderedDict 18 | from ..mixins.ondemand import ext as _ext 19 | 20 | # ================== 21 | 22 | 23 | def timeouts(env, fmt): 24 | timeouts = env['timeouts'] 25 | connect_to = int(timeouts['connect']) 26 | read_to = int(timeouts['read']) 27 | total_to = int(timeouts['total']) 28 | 29 | if fmt == 'requests': 30 | return (connect_to, read_to) 31 | elif fmt == 'curl': 32 | return [ 33 | '--connect-timeout', '{0}'.format(int(connect_to)), 34 | '--max-time', '{0}'.format(int(total_to)), 35 | ] 36 | 37 | raise NotImplementedError('Unknown format: {0}'.format(fmt)) 38 | 39 | 40 | def requestsOptions(env, kwargs): 41 | kwargs['timeout'] = timeouts(env, 'requests') 42 | headers = kwargs.setdefault('headers', {}) 43 | headers['User-Agent'] = 'FutoIn CID' 44 | 45 | 46 | # ================== 47 | _memory_mult_table = { 48 | 'B': 1, 49 | 'K': (1 << 10), 50 | 'M': (1 << 20), 51 | 'G': (1 << 30), 52 | } 53 | 54 | 55 | def parseMemory(val): 56 | b = int(val[:-1]) 57 | m = _memory_mult_table[val[-1]] 58 | 59 | if b <= 0: 60 | raise ValueError('Memory must be positive') 61 | 62 | return b * m 63 | 64 | 65 | def toMemory(val): 66 | res = None 67 | old_v = 0 68 | 69 | for (k, v) in _memory_mult_table.items(): 70 | if val % v: 71 | continue 72 | elif v > old_v: 73 | res = '{0}{1}'.format(int(val / v), k) 74 | old_v = v 75 | 76 | return res 77 | # ================== 78 | 79 | 80 | def listify(val): 81 | if isinstance(val, list): 82 | return val 83 | 84 | return [val] 85 | 86 | 87 | # ================== 88 | 89 | 90 | def parseJSON(val): 91 | return _ext.json.loads(val, object_pairs_hook=lambda pairs: OrderedDict(pairs)) 92 | 93 | # ================== 94 | 95 | 96 | def deepMerge(target, source): 97 | for k, v in source.items(): 98 | if isinstance(v, dict): 99 | # get node or create one 100 | t = target.setdefault(k, OrderedDict()) 101 | deepMerge(t, v) 102 | elif isinstance(v, list): 103 | t = target.setdefault(k, []) 104 | t.extend(v) 105 | else: 106 | target[k] = v 107 | 108 | return target 109 | 110 | # ================== 111 | 112 | 113 | def syslogTag(env, name): 114 | tag = env.get('syslogTag', None) 115 | 116 | if tag: 117 | return '{0}-{1}'.format(tag, name) 118 | 119 | return name 120 | -------------------------------------------------------------------------------- /tests/cid_migrate_test.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .cid_utbase import cid_Tool_UTBase 18 | from nose.plugins.attrib import attr 19 | import os, re 20 | 21 | 22 | #============================================================================= 23 | class cid_MigrateTool_UTBase(cid_Tool_UTBase): 24 | RMS_HOST = os.environ.get('RMS_HOST', '10.11.1.11') 25 | 26 | @classmethod 27 | def setUpClass(cls): 28 | cls.TOOL_NAME = re.match(r'^cid_(.+)_Test$', cls.__name__).group(1) 29 | super(cid_MigrateTool_UTBase, cls).setUpClass() 30 | cls.setUpTool() 31 | 32 | @classmethod 33 | def setUpTool(cls): 34 | pass 35 | 36 | 37 | #============================================================================= 38 | @attr(tool='liquibase') 39 | class cid_liquibase_Test(cid_MigrateTool_UTBase): 40 | __test__ = True 41 | 42 | @classmethod 43 | def setUpTool(cls): 44 | cls._writeFile('changelog.sql', 45 | """--liquibase formatted sql 46 | 47 | --changeset test:1 runAlways:true failOnError:true 48 | CREATE TABLE IF NOT EXISTS liquibaseTest(tid int auto_increment primary key); 49 | """) 50 | 51 | def test_migrate(self): 52 | self._writeFile('liquibase.properties', """ 53 | driver: com.mysql.jdbc.Driver 54 | url: jdbc:mysql://{0}/liquibase 55 | username: liquibase 56 | password: liquibase 57 | changeLogFile: changelog.sql 58 | """.format(self.RMS_HOST)) 59 | self._call_cid(['migrate']) 60 | 61 | def test_migrate_fail(self): 62 | self._writeFile('liquibase.properties', """ 63 | driver: com.mysql.jdbc.Driver 64 | url: jdbc:mysql://{0}/invalid 65 | username: liquibase 66 | password: liquibase 67 | changeLogFile: changelog.sql 68 | """.format(self.RMS_HOST)) 69 | self._call_cid(['migrate'], returncode=1) 70 | 71 | #============================================================================= 72 | @attr(tool='flyway') 73 | class cid_flyway_Test(cid_MigrateTool_UTBase): 74 | __test__ = True 75 | 76 | @classmethod 77 | def setUpTool(cls): 78 | os.makedirs('sql') 79 | cls._writeFile('sql/R__test.sql', 80 | """ 81 | CREATE TABLE IF NOT EXISTS flywayTest(tid int auto_increment primary key); 82 | """) 83 | 84 | def test_migrate(self): 85 | self._writeFile('flyway.conf', """ 86 | flyway.url=jdbc:mysql://{0}/flyway 87 | flyway.user=flyway 88 | flyway.password=flyway 89 | """.format(self.RMS_HOST)) 90 | self._call_cid(['migrate']) 91 | 92 | def test_migrate_fail(self): 93 | self._writeFile('flyway.conf', """ 94 | flyway.url=jdbc:mysql://{0}/invalid 95 | flyway.user=flyway 96 | flyway.password=flyway 97 | """.format(self.RMS_HOST)) 98 | self._call_cid(['migrate'], returncode=1) 99 | -------------------------------------------------------------------------------- /futoin/cid/util/install/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ...mixins.ondemand import ext as _ext 18 | from .. import complex_memo as _complex_memo 19 | 20 | from .apk import * 21 | from .deb import * 22 | from .ebuild import * 23 | from .macos import * 24 | from .pacman import * 25 | from .rpm import * 26 | from .java import * 27 | 28 | 29 | def debrpm(packages): 30 | deb(packages) 31 | rpm(packages) 32 | 33 | 34 | def generic(packages): 35 | apk(packages) 36 | deb(packages) 37 | brew(packages) 38 | pacman(packages) 39 | rpm(packages) 40 | 41 | # too different 42 | # emerge(packages) 43 | 44 | 45 | @_complex_memo 46 | def search(pattern): 47 | detect = _ext.detect 48 | pathutil = _ext.pathutil 49 | 50 | found = [] 51 | 52 | # -- 53 | apt_cache = _ext.pathutil.which('apt-cache') 54 | yum = _ext.pathutil.which('yum') 55 | dnf = _ext.pathutil.which('dnf') 56 | zypper = _ext.pathutil.which('zypper') 57 | 58 | if apt_cache: 59 | found = _ext.executil.callExternal( 60 | [apt_cache, '--names-only', 'search', pattern], 61 | suppress_fail=True) 62 | found = found.strip().split('\n') 63 | 64 | elif dnf or yum: 65 | found = _ext.executil.callExternal( 66 | [dnf or yum, 'search', '-q', pattern], 67 | suppress_fail=True) 68 | found = (found or '').strip().split('\n') 69 | found = [(f + '.').split('.')[0] for f in found] 70 | 71 | elif zypper: 72 | zypper = _ext.pathutil.which('zypper') 73 | res = _ext.executil.callExternal( 74 | [zypper, '--non-interactive', '--no-refresh', 75 | 'search', '-t', 'package', pattern], 76 | suppress_fail=False) # make user aware of failures @SLES 77 | 78 | found = [] 79 | 80 | for f in (res or '').split('\n'): 81 | f = f.split('|') 82 | 83 | if len(f) > 2 and f[1] and f[1][0] != '-': 84 | f = f[1].strip() 85 | found.append(f) 86 | 87 | elif detect.isMacOS(): 88 | found = _ext.install.brewSearch(pattern) 89 | 90 | elif detect.isAlpineLinux(): 91 | apk = '/sbin/apk' 92 | found = _ext.executil.callExternal( 93 | [apk, 'search', pattern], 94 | suppress_fail=True) 95 | found = found.strip().split('\n') 96 | found = ['-'.join(f.split('-')[:-2]) for f in found] 97 | 98 | res = [] 99 | 100 | for r in found: 101 | r = r.split() 102 | 103 | if r: 104 | r = r[0].strip() 105 | if r: 106 | res.append(r) 107 | 108 | return res 109 | -------------------------------------------------------------------------------- /futoin/cid/tool/piptoolmixin.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..subtool import SubTool 18 | 19 | 20 | class PipToolMixIn(SubTool): 21 | __slots__ = () 22 | 23 | def getDeps(self): 24 | return ['pip', 'virtualenv'] 25 | 26 | def _pipName(self): 27 | return self._name 28 | 29 | def _installTool(self, env): 30 | self._builddep.require(env, 'python') 31 | 32 | self._executil.callExternal( 33 | [env['pipBin'], 'install', '-q', self._pipName()]) 34 | 35 | def installTool(self, env): 36 | if not self._have_tool: 37 | self._installTool(env) 38 | self.initEnv(env) 39 | 40 | if not self._have_tool: 41 | self._errorExit('Failed to install "{0}"'.format(self._name)) 42 | 43 | def _updateTool(self, env): 44 | self._executil.callExternal([env['pipBin'], 'install', '-q', 45 | '--upgrade', self._pipName()]) 46 | 47 | def uninstallTool(self, env): 48 | self._executil.callExternal([env['pipBin'], 'uninstall', 49 | '--yes', '-q', self._pipName()]) 50 | self._have_tool = False 51 | 52 | def initEnv(self, env, bin_name=None): 53 | name = self._name 54 | bin_env = name + 'Bin' 55 | 56 | if not env.get(bin_env, None): 57 | if bin_name is None: 58 | bin_name = name 59 | 60 | ospath = self._ospath 61 | tool_path = ospath.join(env['virtualenvDir'], 'bin', bin_name) 62 | 63 | if ospath.exists(tool_path): 64 | env[bin_env] = tool_path 65 | self._have_tool = True 66 | else: 67 | self._have_tool = True 68 | 69 | def _requirePythonDev(self, env): 70 | if int(env['pythonVer'].split('.')[0]) == 3: 71 | self._install.deb(['python3-dev']) 72 | self._install.zypper(['python3-devel']) 73 | self._install.yumEPEL() 74 | self._install.yum(['python34-devel']) 75 | self._install.pacman(['python']) 76 | self._install.apk('python3-dev') 77 | else: 78 | self._install.deb(['python-dev']) 79 | self._install.zypper(['python-devel']) 80 | self._install.yumEPEL() 81 | self._install.yum(['python-devel']) 82 | self._install.pacman(['python2']) 83 | self._install.apk('python2-dev') 84 | 85 | self._install.emergeDepsOnly(['dev-lang/python']) 86 | 87 | def _requirePip(self, env, package): 88 | self._executil.callExternal([env['pipBin'], 'install', '-q', package]) 89 | 90 | def _checkPip(self, env, package): 91 | return self._executil.callExternal([env['pipBin'], 'install', '-q', package], suppress_fail=True) is not None 92 | -------------------------------------------------------------------------------- /futoin/cid/tool/bundlertool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | from .gemtoolmixin import GemToolMixIn 19 | 20 | 21 | class bundlerTool(GemToolMixIn, BuildTool): 22 | """Bundler: The best way to manage a Ruby application's gems. 23 | 24 | Home: http://bundler.io/ 25 | 26 | Note: 27 | 1. It will use per-project BUNDLE_PATH=vendor/bundle by default 28 | 2. In deployment with missing vendor/bundle, it uses per-deployment BUNDLE_PATH 29 | 3. If run outside of project/deployment, standard ~/.bundle is used 30 | """ 31 | __slots__ = () 32 | 33 | def envNames(self): 34 | return ['bundlePath', 'bundlerVer'] 35 | 36 | def autoDetectFiles(self): 37 | return 'Gemfile' 38 | 39 | def initEnv(self, env): 40 | ospath = self._ospath 41 | 42 | if ospath.exists('vendor/bundle'): 43 | # packed deps 44 | bundlePath = self._ospath.realpath('vendor/bundle') 45 | elif ospath.exists('current/vendor/bundle'): 46 | # packed deps 47 | bundlePath = self._ospath.realpath('current/vendor/bundle') 48 | elif not ospath.exists('Gemfile'): 49 | # global / deployment 50 | bundlePath = ospath.join(self._pathutil.deployHome(), '.bundle') 51 | else: 52 | # per project 53 | bundlePath = self._ospath.realpath('vendor/bundle') 54 | 55 | bundlePath = env.setdefault('bundlePath', bundlePath) 56 | self._environ['BUNDLE_PATH'] = bundlePath 57 | 58 | super(bundlerTool, self).initEnv(env, 'bundle') 59 | 60 | def onPrepare(self, config): 61 | env = config['env'] 62 | 63 | # Dirty hack 64 | # --- 65 | bundlerTools = env.get('bundlerTools', {}) 66 | do_bundler_hack = len(bundlerTools) > 0 67 | 68 | for (k, v) in bundlerTools.items(): 69 | tcmd = [env['bundlerBin'], 'add', k] 70 | 71 | if v: 72 | tcmd.append('--version={0}'.format(v)) 73 | 74 | try: 75 | self._executil.callExternal(tcmd, suppress_fail=True) 76 | except Exception as e: 77 | self._warn(str(e)) 78 | 79 | if len(bundlerTools) > 0: 80 | cmd = [env['bundlerBin'], 'install'] 81 | self._executil.callExternal(cmd) 82 | 83 | # Main install 84 | # --- 85 | cmd = [env['bundlerBin'], 'install'] 86 | 87 | if self._ospath.exists('Gemfile.lock'): 88 | cmd.append('--deployment') 89 | 90 | self._executil.callMeaningful(cmd) 91 | 92 | def onPackage(self, config): 93 | if not self._isDefaultPackage(config): 94 | return 95 | 96 | cmd = [config['env']['bundlerBin'], 'install', 97 | '--deployment', '--clean'] 98 | self._executil.callMeaningful(cmd) 99 | -------------------------------------------------------------------------------- /futoin/cid/tool/phoenixtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runtimetool import RuntimeTool 18 | from ..buildtool import BuildTool 19 | 20 | 21 | class phoenixTool(RuntimeTool, BuildTool): 22 | """ 23 | Phoenix - a productive web framework that does not compromise speed or maintainability. 24 | 25 | Home: https://phoenixframework.org/ 26 | 27 | Notes on tuning: 28 | * .tune.prepare 29 | * .tune.build 30 | 31 | Note: MIX_ENV is set based on mixEnv or .env.type 32 | 33 | It is expected that the server is installed as dependency in the project. 34 | """ 35 | 36 | __slots__ = () 37 | 38 | def getDeps(self): 39 | return ['mix'] 40 | 41 | def _installTool(self, env): 42 | pass 43 | 44 | def initEnv(self, env): 45 | self._have_tool = True 46 | 47 | try: 48 | self._executil.callExternal( 49 | [env['mixBin'], 'help', 'phx'], verbose=False) 50 | except: 51 | self._info( 52 | 'Tool "{0}" must be installed as project dependency'.format(self._name)) 53 | 54 | def onPrepare(self, config): 55 | target = self._getTune( 56 | config, 'prepare', ['phx.digest.clean']) 57 | cmd = [config['env']['mixBin']] + target 58 | self._executil.callMeaningful(cmd) 59 | 60 | def onBuild(self, config): 61 | target = self._getTune( 62 | config, 'build', ['phx.digest']) 63 | cmd = [config['env']['mixBin']] + target 64 | self._executil.callMeaningful(cmd) 65 | 66 | def tuneDefaults(self, env): 67 | return { 68 | 'internal': True, 69 | 'minMemory': '128M', 70 | 'debugOverhead': '32M', 71 | 'connMemory': '32K', 72 | 'debugConnOverhead': '64K', 73 | 'socketTypes': ['tcp', 'tcp6'], 74 | 'socketType': 'tcp', 75 | 'scalable': False, 76 | 'reloadable': False, 77 | 'multiCore': True, 78 | 'maxRequestSize': '1M', 79 | 'socketProtocol': 'http', 80 | } 81 | 82 | def onRun(self, config, svc, args): 83 | svc_tune = svc['tune'] 84 | 85 | # --- 86 | mix_env = {} 87 | 88 | try: 89 | mix_env['PORT'] = str(svc_tune['socketPort']) 90 | mix_env['HOST'] = svc_tune['socketAddress'] 91 | except KeyError: 92 | pass 93 | 94 | self._environ.update(mix_env) 95 | 96 | # --- 97 | mix_args = [ 98 | '--no-compile', 99 | ] 100 | 101 | # --- 102 | 103 | cmd = [ 104 | config['env']['mixBin'], 105 | 'phx.server', 106 | ] + mix_args + args 107 | 108 | self._executil.callInteractive(cmd) 109 | -------------------------------------------------------------------------------- /tests/cid_misc_test.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from __future__ import print_function, absolute_import 18 | 19 | from .cid_utbase import cid_UTBase 20 | from futoin.cid.rmstool import RmsTool 21 | 22 | import os, stat 23 | import subprocess 24 | import glob 25 | 26 | from collections import OrderedDict 27 | 28 | class cid_misc_Test ( cid_UTBase ) : 29 | __test__ = True 30 | TEST_DIR = os.path.join(cid_UTBase.TEST_RUN_DIR, 'misc') 31 | ORIG_HOME = os.environ['HOME'] 32 | 33 | def setUp(self): 34 | self.setUpClass() 35 | 36 | os.mkdir(self.TEST_DIR) 37 | os.chdir(self.TEST_DIR) 38 | 39 | # Done in run.sh 40 | #subprocess.check_call('sudo mkdir -p /etc/futoin && sudo chmod 777 /etc/futoin', shell=True) 41 | 42 | home = os.path.join(self.TEST_DIR, 'home') 43 | os.mkdir(home) 44 | os.environ['HOME'] = home 45 | 46 | def tearDown(self): 47 | os.environ['HOME'] = self.ORIG_HOME 48 | #subprocess.call('sudo rm /etc/futoin -rf', shell=True) 49 | subprocess.call('rm -rf /etc/futoin/*', shell=True, 50 | stdout=self._stdout_log, stderr=self._stderr_log) 51 | 52 | def test_global_config(self): 53 | self._writeJSON('/etc/futoin/futoin.json', { 54 | 'env' : {} 55 | }) 56 | self._call_cid(['tool', 'list']) 57 | 58 | 59 | self._writeJSON('/etc/futoin/futoin.json', { 60 | 'invalid' : {} 61 | }) 62 | 63 | self._call_cid(['tool', 'list'], returncode=1) 64 | 65 | def test_user_config_notdot(self): 66 | self._writeJSON(os.path.join(os.environ['HOME'], 'futoin.json'), { 67 | 'env' : {} 68 | }) 69 | self._call_cid(['tool', 'list']) 70 | 71 | 72 | self._writeJSON(os.path.join(os.environ['HOME'], 'futoin.json'), { 73 | 'invalid' : {} 74 | }) 75 | 76 | self._call_cid(['tool', 'list'], returncode=1) 77 | 78 | def test_user_dot_config(self): 79 | self._writeJSON(os.path.join(os.environ['HOME'], '.futoin.json'), { 80 | 'env' : {} 81 | }) 82 | self._call_cid(['tool', 'list']) 83 | 84 | 85 | self._writeJSON(os.path.join(os.environ['HOME'], '.futoin.json'), { 86 | 'invalid' : {} 87 | }) 88 | 89 | self._call_cid(['tool', 'list'], returncode=1) 90 | 91 | def test_unknown_tool(self): 92 | self._call_cid(['tool', 'install']) 93 | 94 | self._writeJSON(os.path.join(self.TEST_DIR, 'futoin.json'), { 95 | 'tools' : { 96 | 'unknown_tool' : True, 97 | } 98 | }) 99 | self._call_cid(['tool', 'install'], returncode=1) 100 | 101 | 102 | -------------------------------------------------------------------------------- /tests/cid_packagecmd_test.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from __future__ import print_function, absolute_import 18 | from nose.plugins.attrib import attr 19 | 20 | from .cid_utbase import cid_UTBase 21 | from futoin.cid.rmstool import RmsTool 22 | 23 | import os, stat, fcntl 24 | import subprocess 25 | import glob 26 | import shutil 27 | 28 | from futoin.cid.util import executil 29 | 30 | class cid_packagecmd_Test ( cid_UTBase ) : 31 | __test__ = True 32 | TEST_DIR = os.path.join(cid_UTBase.TEST_RUN_DIR, 'packagecmd') 33 | _create_test_dir = True 34 | 35 | def setUp( self ): 36 | shutil.rmtree( self.TEST_DIR ) 37 | os.mkdir(self.TEST_DIR) 38 | self._goToBase() 39 | 40 | def _test_common(self, compressor, ext): 41 | cfg = { 42 | "name": "cid-packagecmd-test", 43 | "version": "0.0.1", 44 | } 45 | if compressor: 46 | cfg.update({ 47 | "toolTune" : { 48 | "tar": { 49 | "compressor": compressor, 50 | } 51 | } 52 | }) 53 | 54 | self._writeJSON('futoin.json', cfg) 55 | 56 | self._call_cid(['package']) 57 | 58 | res = glob.glob('cid-packagecmd-test-CI-0.0.1-*.*') 59 | self.assertEqual(len(res), 1) 60 | self.assertEqual(os.path.splitext(res[0])[1], ext) 61 | 62 | def test_tar(self): 63 | self._test_common('tar', '.tar') 64 | 65 | def test_tgz(self): 66 | self._test_common('gzip', '.tgz') 67 | 68 | def test_tz2(self): 69 | self._test_common('bzip2', '.tbz2') 70 | 71 | def test_xz(self): 72 | self._test_common('xz', '.txz') 73 | 74 | def test_default(self): 75 | self._test_common(None, '.tgz') 76 | 77 | def test_subdir(self): 78 | self._writeJSON('futoin.json', { 79 | "name": "cid-packagecmd-test", 80 | "version": "0.0.1", 81 | 'package': [ 82 | 'subdir1', 83 | 'subdir2', 84 | ] 85 | }) 86 | 87 | os.mkdir('subdir1') 88 | self._writeFile('subdir1/file', 'file') 89 | os.mkdir('subdir2') 90 | self._call_cid(['package']) 91 | 92 | pkg = glob.glob('cid-packagecmd-test-CI-0.0.1-*.*')[0] 93 | content = subprocess.check_output('tar tzf {0}'.format(pkg), shell=True) 94 | content = executil.toString(content).strip().split('\n') 95 | 96 | req_content=[ 97 | '.package.checksums', 98 | 'futoin.json', 99 | 'subdir1/', 100 | 'subdir1/file', 101 | 'subdir2/', 102 | ] 103 | prefix = os.path.splitext(pkg)[0] 104 | req_content = [ '{0}/{1}'.format(prefix, v) for v in req_content ] 105 | 106 | req_content.sort() 107 | content.sort() 108 | 109 | self.maxDiff = 1024 110 | self.assertEqual( content, req_content ) 111 | 112 | -------------------------------------------------------------------------------- /futoin/cid/tool/cmaketool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | 19 | 20 | class cmakeTool(BuildTool): 21 | """Build, Test and Package Your Software With CMake. 22 | 23 | Home: https://cmake.org/ 24 | 25 | CMake creates a build folder and does all processing in it. 26 | Build folder is configurable through cmakeBuildDir env. 27 | 28 | On release tagging, CMakeLists.txt the following replacements are done: 29 | 1. 'VERSION ".*" # AUTO-REPLACE' -> new version 30 | """ 31 | __slots__ = () 32 | 33 | def getOrder(self): 34 | return -10 35 | 36 | def autoDetectFiles(self): 37 | return 'CMakeLists.txt' 38 | 39 | def envNames(self): 40 | return ['cmakeBin', 'cmakeBuildDir', 'cmakeBuildType'] 41 | 42 | def _installTool(self, env): 43 | self._install.deb(['build-essential']) 44 | self._install.rpm(['gcc', 'gcc-c++']) 45 | self._install.pacman(['gcc']) 46 | self._builddep.essential() 47 | 48 | self._install.debrpm(['cmake']) 49 | self._install.emerge(['dev-util/cmake']) 50 | self._install.pacman(['cmake']) 51 | self._install.apk(['cmake']) 52 | self._install.brew('cmake') 53 | 54 | def initEnv(self, env): 55 | env.setdefault('cmakeBuildDir', 'build') 56 | env.setdefault('cmakeBuildType', 'Debug' if env['type'] == 'dev' else 'Release') 57 | super(cmakeTool, self).initEnv(env) 58 | 59 | def onPrepare(self, config): 60 | ospath = self._ospath 61 | os = self._os 62 | env = config['env'] 63 | build_dir = env['cmakeBuildDir'] 64 | build_type = env['cmakeBuildType'] 65 | self._pathutil.rmTree(build_dir) 66 | 67 | os.mkdir(build_dir) 68 | cmd = [config['env']['cmakeBin'], 69 | '-H' + config['wcDir'], 70 | '-B' + build_dir, 71 | '-DCMAKE_BUILD_TYPE=' + build_type] 72 | self._executil.callMeaningful(cmd) 73 | 74 | def onBuild(self, config): 75 | ospath = self._ospath 76 | os = self._os 77 | build_dir = config['env']['cmakeBuildDir'] 78 | 79 | if ospath.exists(build_dir): 80 | cmd = [config['env']['cmakeBin'], build_dir] 81 | self._executil.callMeaningful(cmd) 82 | else: 83 | self.onPrepare(config) 84 | 85 | cmd = [config['env']['cmakeBin'], '--build', build_dir] 86 | self._executil.callMeaningful(cmd) 87 | 88 | def updateProjectConfig(self, config, updates): 89 | re = self._ext.re 90 | 91 | def cmake_updater(content): 92 | if 'version' in updates: 93 | return re.sub( 94 | r'VERSION.*".*".*# AUTO-REPLACE', 95 | 'VERSION "{0}" # AUTO-REPLACE'.format(updates['version']), 96 | content, 97 | flags=re.MULTILINE 98 | ) 99 | 100 | return self._pathutil.updateTextFile( 101 | self.autoDetectFiles(), cmake_updater) 102 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from setuptools import setup, find_packages 18 | 19 | import os, sys 20 | 21 | project_path = os.path.dirname( __file__ ) 22 | sys.path.insert( 0, project_path ) 23 | from futoin.cid import __version__ as version 24 | 25 | with open(os.path.join(project_path, 'README.rst'), 'r') as f: 26 | long_description = f.read() 27 | 28 | config = { 29 | 'name': 'futoin-cid', 30 | 'version': version, 31 | 'namespace_packages': ['futoin'], 32 | 33 | 'description': 'FutoIn Continuous Integration & Delivery Tool', 34 | 'long_description': long_description, 35 | 36 | 'author': 'Andrey Galkin', 37 | 'author_email': 'andrey@futoin.org', 38 | 39 | 'url': 'https://github.com/futoin/cid-tool', 40 | 'download_url': 'https://github.com/futoin/cid-tool/archive/master.zip', 41 | 42 | 'install_requires': [ 43 | 'docopt', 44 | #'requests>=2.18.4', 45 | # be compatible with docker/docker-compose 46 | 'requests>=2.5.2', 47 | 'urllib3>=1.21.1', 48 | 'distro', 49 | ], 50 | # temporary disabled due to py3 failures on setup of pylint 51 | #'setup_requires': ['autopep8', 'pylint'], 52 | 'extras_require': { 53 | 'test': ['nose'], 54 | }, 55 | 'python_requires': '>=3.3', 56 | 'packages': find_packages(exclude=['bind', 'tests']), 57 | 58 | 'entry_points': { 59 | 'console_scripts': [ 60 | 'cid=futoin.cid.cli:run', 61 | 'cte=futoin.cid.cte:run', 62 | 'futoin-cid=futoin.cid.cli:run', 63 | ], 64 | }, 65 | 'classifiers': [ 66 | 'Development Status :: 5 - Production/Stable', 67 | 'Environment :: Console', 68 | 'Intended Audience :: Developers', 69 | 'Intended Audience :: Information Technology', 70 | 'Intended Audience :: System Administrators', 71 | 'License :: OSI Approved :: Apache Software License', 72 | 'Natural Language :: English', 73 | 'Programming Language :: C', 74 | 'Programming Language :: C++', 75 | 'Programming Language :: Java', 76 | 'Programming Language :: JavaScript', 77 | 'Programming Language :: Other', 78 | 'Programming Language :: PHP', 79 | 'Programming Language :: Python :: 3', 80 | 'Programming Language :: Python :: 3.3', 81 | 'Programming Language :: Python :: 3.4', 82 | 'Programming Language :: Python :: 3.5', 83 | 'Programming Language :: Python :: 3.6', 84 | 'Programming Language :: Python :: 3.7', 85 | 'Programming Language :: Python :: 3.8', 86 | 'Programming Language :: Python :: 3.9', 87 | 'Programming Language :: Ruby', 88 | 'Programming Language :: Unix Shell', 89 | 'Topic :: Software Development :: Build Tools', 90 | 'Topic :: System :: Installation/Setup', 91 | 'Topic :: Utilities', 92 | ], 93 | 'keywords': 'php ruby node nodejs npm gem rvm nvm grunt gulp bower \ 94 | puppet build deploy futoin cmake make gradle maven java composer bundler', 95 | 'license': 'Apache 2.0', 96 | } 97 | 98 | setup(**config) 99 | -------------------------------------------------------------------------------- /futoin/cid/mixins/lock.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .data import DataSlots 18 | 19 | 20 | class LockMixIn(DataSlots): 21 | __slots__ = () 22 | 23 | def __init__(self): 24 | super(LockMixIn, self).__init__() 25 | self._deploy_lock = None 26 | self._master_lock = None 27 | self._global_lock = None 28 | 29 | def __lockCommon(self, lock, file, flags): 30 | fcntl = self._ext.fcntl 31 | os = self._os 32 | 33 | assert getattr(self, lock) is None 34 | 35 | lockfd = os.open(file, os.O_WRONLY | os.O_CREAT) 36 | setattr(self, lock, lockfd) 37 | 38 | try: 39 | fcntl.flock(lockfd, flags) 40 | except Exception as e: 41 | self._errorExit('FAILED to acquire{0}: {1}'.format( 42 | lock.replace('_', ' '), e)) 43 | 44 | def __unlockCommon(self, lock): 45 | fcntl = self._ext.fcntl 46 | lockfd = getattr(self, lock) 47 | fcntl.flock(lockfd, fcntl.LOCK_UN) 48 | self._os.close(lockfd) 49 | setattr(self, lock, None) 50 | 51 | def _deployLock(self): 52 | fcntl = self._ext.fcntl 53 | self.__lockCommon( 54 | '_deploy_lock', 55 | self.__deployLockFile(), 56 | fcntl.LOCK_EX | fcntl.LOCK_NB 57 | ) 58 | 59 | def _deployUnlock(self): 60 | self.__unlockCommon('_deploy_lock') 61 | 62 | def _requireDeployLock(self): 63 | if self._deploy_lock is None: 64 | self._errorExit('Deploy lock must be already acquired') 65 | 66 | def _globalLock(self): 67 | fcntl = self._ext.fcntl 68 | self.__lockCommon( 69 | '_global_lock', 70 | self.__globalLockFile(), 71 | fcntl.LOCK_EX 72 | ) 73 | 74 | def _globalUnlock(self): 75 | self.__unlockCommon('_global_lock') 76 | 77 | def _masterLock(self): 78 | fcntl = self._ext.fcntl 79 | self.__lockCommon( 80 | '_master_lock', 81 | self.__masterLockFile(), 82 | fcntl.LOCK_EX | fcntl.LOCK_NB 83 | ) 84 | 85 | def _masterUnlock(self): 86 | self.__unlockCommon('_master_lock') 87 | 88 | def __deployLockFile(self): 89 | return self._pathutil.safeJoin(self._overrides['deployDir'], self._DEPLOY_LOCK_FILE) 90 | 91 | def __masterLockFile(self): 92 | return self._pathutil.safeJoin(self._overrides['deployDir'], self._MASTER_LOCK_FILE) 93 | 94 | def __globalLockFile(self): 95 | return self._pathutil.safeJoin(self._environ['HOME'], self._GLOBAL_LOCK_FILE) 96 | 97 | def _checkDeployLock(self): 98 | ospath = self._ospath 99 | placeholder = self.__deployLockFile() 100 | deploy_dir = self._overrides['deployDir'] 101 | 102 | if not ospath.exists(placeholder) and self._os.listdir(deploy_dir): 103 | self._errorExit( 104 | "Deployment dir '{0}' is missing safety placeholder {1}." 105 | .format(deploy_dir, ospath.basename(placeholder)) 106 | ) 107 | -------------------------------------------------------------------------------- /futoin/cid/mixins/ondemand.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | import importlib as _importlib 18 | import sys 19 | import os 20 | 21 | __all__ = [ 22 | 'tailor_ondemand', 23 | 'ext', 24 | 'OnDemandMixIn', 25 | ] 26 | 27 | # --- 28 | 29 | SHARED_ENV = dict(os.environ) 30 | 31 | 32 | def tailor_ondemand(demandMap): 33 | class TailoredDemander(object): 34 | __slots__ = tuple(demandMap.keys()) 35 | __demandMap = demandMap 36 | environ = SHARED_ENV 37 | 38 | def __getattr__(self, name): 39 | mod = TailoredDemander.__demandMap[name] 40 | 41 | if isinstance(mod, tuple): 42 | mod, memb = mod 43 | mod = _importlib.import_module(mod, 'futoin.cid') 44 | ret = getattr(mod, memb) 45 | else: 46 | ret = _importlib.import_module(mod, 'futoin.cid') 47 | 48 | setattr(self, name, ret) 49 | return ret 50 | 51 | return TailoredDemander 52 | 53 | 54 | # --- 55 | _ext_demand_map = { 56 | 'shlex': 'shlex', 57 | 'subprocess': 'subprocess', 58 | 'json': 'json', 59 | 'datetime': 'datetime', 60 | 're': 're', 61 | 'gzip': 'gzip', 62 | 'shutil': 'shutil', 63 | 'stat': 'stat', 64 | 'time': 'time', 65 | 'fnmatch': 'fnmatch', 66 | 'fcntl': 'fcntl', 67 | 'hashlib': 'hashlib', 68 | 'signal': 'signal', 69 | 'copy': 'copy', 70 | 'errno': 'errno', 71 | 'pwd': 'pwd', 72 | 'grp': 'grp', 73 | 'glob': 'glob', 74 | 'dir_util': 'distutils.dir_util', 75 | 'requests': 'requests', 76 | 'minidom': 'xml.dom.minidom', 77 | 'importlib': 'importlib', 78 | 'tempfile': 'tempfile', 79 | 'resource': 'resource', 80 | 'binascii': 'binascii', 81 | 'platform': 'platform', 82 | # 83 | 'os': 'os', 84 | 'ospath': 'os.path', 85 | 'sys': 'sys', 86 | 'detect': '.util.detect', 87 | 'executil': '.util.executil', 88 | 'install': '.util.install', 89 | 'pathutil': '.util.pathutil', 90 | 'builddep': '.util.builddep', 91 | 'versionutil': '.util.versionutil', 92 | 'configutil': '.util.configutil', 93 | 'phputil': '.util.phputil', 94 | 'github': '.util.github', 95 | } 96 | 97 | if sys.version_info >= (3, 0): 98 | _ext_demand_map['urllib'] = 'urllib.request' 99 | _ext_demand_map['urlparse'] = 'urllib.parse' 100 | else: 101 | _ext_demand_map['urllib'] = 'urllib2' 102 | _ext_demand_map['urlparse'] = 'urlparse' 103 | 104 | ext = tailor_ondemand(_ext_demand_map)() 105 | 106 | # --- 107 | _cid_demand_map = { 108 | '_os': 'os', 109 | '_ospath': 'os.path', 110 | '_sys': 'sys', 111 | '_detect': '.util.detect', 112 | '_executil': '.util.executil', 113 | '_install': '.util.install', 114 | '_pathutil': '.util.pathutil', 115 | '_builddep': '.util.builddep', 116 | '_versionutil': '.util.versionutil', 117 | '_configutil': '.util.configutil', 118 | '_phputil': '.util.phputil', 119 | } 120 | 121 | 122 | class OnDemandMixIn(tailor_ondemand(_cid_demand_map)): 123 | __slots__ = () 124 | _ext = ext 125 | _environ = SHARED_ENV 126 | -------------------------------------------------------------------------------- /futoin/cid/tool/rvmtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | from .bashtoolmixin import BashToolMixIn 19 | from .curltoolmixin import CurlToolMixIn 20 | 21 | 22 | class rvmTool(BashToolMixIn, CurlToolMixIn, RunEnvTool): 23 | """Ruby Version Manager. 24 | 25 | Home: https://rvm.io/ 26 | 27 | Stable RVM is used by default. 28 | """ 29 | __slots__ = () 30 | 31 | RVM_LATEST = 'stable' 32 | RVM_GPG_KEYS = [ 33 | '409B6B1796C275462A1703113804BB82D39DC0E3', 34 | '7D2BAF1CF37B13E2069D6956105BD0E739499BDB', 35 | ] 36 | RVM_GET = 'https://get.rvm.io' 37 | 38 | def getDeps(self): 39 | return ( 40 | ['gpg', 'tar', 'gzip'] + 41 | BashToolMixIn.getDeps(self) + 42 | CurlToolMixIn.getDeps(self)) 43 | 44 | def ensureGpgKeys(self, env): 45 | rvm_gpg_keys = env.get('rvmGpgKeys', '') 46 | 47 | if rvm_gpg_keys != '': 48 | rvm_gpg_keys = rvm_gpg_keys.split(' ') 49 | else: 50 | rvm_gpg_keys = self.RVM_GPG_KEYS 51 | 52 | self._executil.callExternal([ 53 | env['gpgBin'], '--keyserver', env['gpgKeyServer'], 54 | '--recv-keys'] + rvm_gpg_keys, 55 | suppress_fail=True) 56 | 57 | def _installTool(self, env): 58 | self.ensureGpgKeys(env) 59 | 60 | rvm_dir = env['rvmDir'] 61 | rvm_get = env.get('rvmGet', self.RVM_GET) 62 | 63 | environ = self._environ 64 | environ['rvm_user_install_flag'] = '1' 65 | environ['rvm_auto_dotfiles_flag'] = '0' 66 | 67 | installer = self._callCurl(env, [rvm_get]) 68 | 69 | # AlpineLinux - required for uninstall 70 | self._install.apk('procps') 71 | 72 | self._callBash( 73 | env, 74 | bash_args=['--', env['rvmVer'], '--path', rvm_dir], 75 | input=installer) 76 | 77 | def _updateTool(self, env): 78 | self._executil.callExternal([env['rvmBin'], 'get', env['rvmVer']]) 79 | 80 | def uninstallTool(self, env): 81 | try: 82 | self._executil.callExternal([env['rvmBin'], 'implode', '--force']) 83 | except: 84 | pass 85 | 86 | rvm_dir = env['rvmDir'] 87 | self._pathutil.rmTree(rvm_dir) 88 | self._have_tool = False 89 | 90 | def envNames(self): 91 | return ['rvmVer', 'rvmDir', 'rvmBin', 'rvmGet', 'rvmGpgKey'] 92 | 93 | def initEnv(self, env): 94 | ospath = self._ospath 95 | environ = self._environ 96 | 97 | for v in ['rvm_path', 'rvm_bin_path', 'rvm_prefix', 'rvm_version']: 98 | try: 99 | del environ[v] 100 | except: 101 | pass 102 | 103 | env.setdefault('rvmVer', self.RVM_LATEST) 104 | rvm_dir = ospath.join(environ['HOME'], '.rvm') 105 | rvm_dir = env.setdefault('rvmDir', rvm_dir) 106 | rvm_bin_dir = ospath.join(rvm_dir, 'bin') 107 | rvm_bin = env['rvmBin'] = ospath.join(rvm_bin_dir, 'rvm') 108 | env['rvmInit'] = ospath.join(rvm_dir, 'scripts', 'rvm') 109 | self._have_tool = ospath.exists(rvm_bin) 110 | -------------------------------------------------------------------------------- /futoin/cid/tool/cidtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from .. import __version__ as _version 18 | from ..buildtool import BuildTool 19 | from .piptoolmixin import PipToolMixIn 20 | 21 | _PROXY_BIN_TPL = """#!/bin/sh 22 | exec {0} -m{1} "$@" 23 | """ 24 | 25 | 26 | class cidTool(BuildTool): 27 | "Noop FutoIn-CID - a workaround to allow CID use from virtualenv" 28 | 29 | __slots__ = () 30 | 31 | def envNames(self): 32 | return ['cidVer'] 33 | 34 | def getDeps(self): 35 | return ['pip', 'virtualenv'] 36 | 37 | def _pipName(self): 38 | return 'futoin-cid' 39 | 40 | def _callPip(self, cmd, args): 41 | ext = self._ext 42 | os = ext.os 43 | need_sudo = False 44 | 45 | if ext.detect.isAdmin(): 46 | user_args = ['--system'] 47 | elif os.stat(__file__).st_uid != os.geteuid(): 48 | user_args = ['--system'] 49 | need_sudo = True 50 | else: 51 | user_args = ['--user'] 52 | 53 | if cmd == 'uninstall': 54 | user_args = [] 55 | 56 | cmd_args = [ext.sys.executable, '-mpip', cmd] + user_args + args 57 | 58 | if need_sudo: 59 | self._executil.trySudoCall(cmd_args) 60 | else: 61 | self._executil.callExternal(cmd_args, use_orig_env=True) 62 | 63 | def _installTool(self, env): 64 | source_dir = self._environ.get('CID_SOURCE_DIR', None) 65 | 66 | if source_dir: 67 | self._callPip('install', ['-q', '-e', source_dir]) 68 | else: 69 | self._callPip('install', ['-q', '--upgrade', self._pipName()]) 70 | 71 | def _updateTool(self, env): 72 | self._installTool(env) 73 | 74 | def uninstallTool(self, env): 75 | self._callPip('uninstall', ['-q', '--yes', self._pipName()]) 76 | self._have_tool = False 77 | 78 | def initEnv(self, env, bin_name=None): 79 | os = self._os 80 | ospath = self._ospath 81 | pathutil = self._pathutil 82 | sys = self._sys 83 | stat = self._ext.stat 84 | 85 | # Dirty hack, so proxy script deactives virtualenv 86 | venvDir = env['virtualenvDir'] 87 | 88 | cli_bin = ospath.join(venvDir, 'bin', 'cid') 89 | cli_cmd = _PROXY_BIN_TPL.format(sys.executable, 'futoin.cid') 90 | cte_bin = ospath.join(venvDir, 'bin', 'cte') 91 | cte_cmd = _PROXY_BIN_TPL.format(sys.executable, 'futoin.cid.cte') 92 | fperm = stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH 93 | 94 | if (not ospath.exists(cli_bin)) or os.stat(cli_bin).st_uid == os.geteuid(): 95 | pathutil.checkUpdateTextFile(cli_bin, lambda _: cli_cmd) 96 | os.chmod(cli_bin, fperm) 97 | 98 | if (not ospath.exists(cte_bin)) or os.stat(cte_bin).st_uid == os.geteuid(): 99 | pathutil.checkUpdateTextFile(cte_bin, lambda _: cte_cmd) 100 | os.chmod(cte_bin, fperm) 101 | 102 | # Handle version check 103 | ver = env.get('cidVer', _version) 104 | vers = [_version, ver] 105 | self._versionutil.sort(vers) 106 | self._have_tool = vers[0] == ver 107 | -------------------------------------------------------------------------------- /futoin/cid/tool/bundlermixin.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..subtool import SubTool 18 | 19 | 20 | class BundlerMixIn(SubTool): 21 | __slots__ = () 22 | 23 | def getDeps(self): 24 | return ['bundler'] 25 | 26 | def _gemName(self): 27 | return self._name 28 | 29 | def _installTool(self, env): 30 | self._have_tool = True 31 | tcmd = [env['bundlerBin'], 'add', self._gemName()] 32 | ver = env.get(self._name + 'Ver') 33 | 34 | if ver: 35 | tcmd.append('--version={0}'.format(ver)) 36 | 37 | try: 38 | self._executil.callExternal(tcmd, verbose=False) 39 | # self._executil.callMeaningful(tcmd) 40 | except Exception as e: 41 | self._warn(str(e)) 42 | bundlerTools = env.setdefault('bundlerTools', {}) 43 | bundlerTools[self._name] = ver 44 | return 45 | 46 | cmd = [env['bundlerBin'], 'install'] 47 | self._executil.callExternal(cmd, verbose=False) 48 | # self._executil.callMeaningful(cmd) 49 | 50 | def installTool(self, env): 51 | if not self._have_tool: 52 | if self._detect.isDisabledToolsSetup(env): 53 | self._errorExit( 54 | 'Tool "{0}" must be installed externally (env config)'.format(self._name)) 55 | else: 56 | self._warn( 57 | 'Auto-installing required "{0}" tool'.format(self._name)) 58 | self._installTool(env) 59 | 60 | self.initEnv(env) 61 | 62 | if not self._have_tool: 63 | self._errorExit('Failed to install "{0}"'.format(self._name)) 64 | 65 | def updateTool(self, env): 66 | # New version is installed, if not in project 67 | # Otherwise, the project is responsible for updates 68 | self._ensureInstalled(env) 69 | 70 | def initEnv(self, env): 71 | ospath = self._ospath 72 | 73 | # try newer layout first 74 | bin_path = ospath.join( 75 | env['bundlePath'], 76 | 'ruby', '{0}.0'.format(env['rubyVer']), 77 | 'bin', self._name) 78 | 79 | # fallback to legacy 80 | if not ospath.exists(bin_path): 81 | bin_path = ospath.join(env['bundlePath'], 'bin', self._name) 82 | 83 | env[self._name + 'Bin'] = bin_path 84 | 85 | if not ospath.exists('Gemfile'): 86 | # Fake to workaround being required outside of project root (e.g. deployment home) 87 | self._have_tool = True 88 | return 89 | 90 | self._have_tool = self._have_tool or ospath.exists(bin_path) 91 | 92 | def onRun(self, config, svc, args): 93 | env = config['env'] 94 | self._ensureInstalled(env) 95 | self._executil.callInteractive([ 96 | env['bundlerBin'], 'exec', self._gemName(), '--', svc['path'] 97 | ] + args) 98 | 99 | def _ensureInstalled(self, env): 100 | bin_path = env.get(self._name + 'Bin') 101 | 102 | if not bin_path or not self._ospath.exists(bin_path): 103 | self._have_tool = False 104 | self.installTool(env) 105 | -------------------------------------------------------------------------------- /futoin/cid/tool/pythontool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runtimetool import RuntimeTool 18 | 19 | 20 | class pythonTool(RuntimeTool): 21 | """Python is a programming language. 22 | 23 | Home: https://www.python.org/ 24 | 25 | Only the first part of pythonVer is used for installation of 26 | system packages OS-specific way. 27 | """ 28 | __slots__ = () 29 | 30 | VER_CMD = 'import sys; print( "%s.%s" % (sys.version_info.major, sys.version_info.minor) )' 31 | 32 | def getPostDeps(self): 33 | # hackish hack 34 | return ['virtualenv'] 35 | 36 | def envNames(self): 37 | return ['pythonBin', 'pythonVer'] 38 | 39 | def _installTool(self, env): 40 | if int(env['pythonVer'].split('.')[0]) == 3: 41 | self._install.deb(['python3']) 42 | self._install.zypper(['python3']) 43 | 44 | self._install.yumEPEL() 45 | self._install.yum(['python34']) 46 | self._install.pacman(['python']) 47 | self._install.apk('python3') 48 | self._install.brew('python3') 49 | else: 50 | self._install.debrpm(['python']) 51 | self._install.pacman(['python2']) 52 | self._install.apk('python2') 53 | self._install.brew('python') 54 | 55 | self._install.emerge( 56 | ['=dev-lang/python-{0}*'.format(env['pythonVer'])]) 57 | 58 | def uninstallTool(self, env): 59 | pass 60 | 61 | def initEnv(self, env): 62 | environ = self._environ 63 | detect = self._detect 64 | 65 | python_ver = env.setdefault('pythonVer', '3') 66 | bin_name = None 67 | 68 | if detect.isGentoo(): 69 | if len(python_ver) == 3: 70 | environ['EPYTHON'] = 'python{0}'.format(python_ver) 71 | elif int(python_ver.split('.')[0]) == 3: 72 | environ['EPYTHON'] = 'python3.4' 73 | else: 74 | environ['EPYTHON'] = 'python2.7' 75 | elif detect.isArchLinux(): 76 | if int(python_ver.split('.')[0]) == 2: 77 | bin_name = 'python2' 78 | elif int(python_ver.split('.')[0]) == 3: 79 | bin_name = 'python3' 80 | 81 | super(pythonTool, self).initEnv(env, bin_name) 82 | 83 | if self._have_tool and 'pythonRawBin' not in env: 84 | env['pythonRawBin'] = env['pythonBin'] 85 | python_ver_fact = self._executil.callExternal( 86 | [env['pythonRawBin'], '-c', self.VER_CMD], 87 | verbose=False 88 | ).strip() 89 | 90 | if python_ver.split('.') > python_ver_fact.split('.'): 91 | self._errorExit( 92 | 'Too old python version "{0}" when "{1}" is required' 93 | .format(python_ver, python_ver_fact) 94 | ) 95 | 96 | env['pythonFactVer'] = python_ver_fact 97 | 98 | def tuneDefaults(self, env): 99 | return { 100 | 'minMemory': '8M', 101 | 'socketType': 'none', 102 | 'scalable': False, 103 | 'reloadable': False, 104 | 'multiCore': True, 105 | } 106 | -------------------------------------------------------------------------------- /futoin/cid/tool/awstool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..rmstool import RmsTool 18 | from .piptoolmixin import PipToolMixIn 19 | 20 | 21 | class awsTool(PipToolMixIn, RmsTool): 22 | """Generic AWS CLI and S3 as RMS. 23 | 24 | Home: https://docs.aws.amazon.com/cli/index.html 25 | """ 26 | __slots__ = () 27 | 28 | def getDeps(self): 29 | return ['pip'] 30 | 31 | def _pipName(self): 32 | return 'awscli' 33 | 34 | def rmsUpload(self, config, rms_pool, package_list): 35 | ospath = self._ospath 36 | bucket, path = self._getS3Repo(config) 37 | 38 | for package in package_list: 39 | package_basename = ospath.basename(package) 40 | self.onExec(config['env'], [ 41 | 's3', 'cp', package, 42 | 's3://{0}{1}/{2}/{3}'.format(bucket, 43 | path, rms_pool, package_basename), 44 | ], False) 45 | 46 | def rmsPromote(self, config, src_pool, dst_pool, package_list): 47 | ospath = self._ospath 48 | bucket, path = self._getS3Repo(config) 49 | 50 | for package in package_list: 51 | package_basename = ospath.basename(package) 52 | self.onExec(config['env'], [ 53 | 's3', 'cp', 54 | 's3://{0}{1}/{2}/{3}'.format(bucket, 55 | path, src_pool, package_basename), 56 | 's3://{0}{1}/{2}/{3}'.format(bucket, 57 | path, dst_pool, package_basename), 58 | ], False) 59 | 60 | def rmsGetList(self, config, rms_pool, package_hint): 61 | bucket, path = self._getS3Repo(config) 62 | 63 | res = self._executil.callExternal([ 64 | config['env']['awsBin'], 65 | 's3', 'ls', 66 | 's3://{0}{1}/{2}/'.format(bucket, path, rms_pool)]) 67 | return [r.split()[-1] for r in res.rstrip().split('\n')] 68 | 69 | def rmsRetrieve(self, config, rms_pool, package_list): 70 | bucket, path = self._getS3Repo(config) 71 | 72 | for package in package_list: 73 | self.onExec(config['env'], [ 74 | 's3', 'cp', 75 | 's3://{0}{1}/{2}/{3}'.format(bucket, path, rms_pool, package), 76 | package, 77 | ], False) 78 | 79 | def rmsGetHash(self, config, rms_pool, package, hash_type): 80 | # Note: Etag is not always MD5, so we skip 81 | raise NotImplementedError(self._name) 82 | 83 | def rmsPoolCreate(self, config, rms_pool): 84 | pass 85 | 86 | def rmsPoolList(self, config): 87 | return self.rmsGetList(config, '', '') 88 | 89 | def _getS3Repo(self, config): 90 | rms_repo = config['rmsRepo'] 91 | parsed = self._ext.urlparse.urlparse(rms_repo) 92 | 93 | if parsed.scheme != 's3': 94 | self._errorExit( 95 | 'AWS S3 repository scheme must be s3://bucket/path') 96 | 97 | bucket = parsed.netloc 98 | path = parsed.path 99 | 100 | if len(path) > 1: 101 | path = path.rstrip('/') 102 | else: 103 | path = '' 104 | 105 | return bucket, path 106 | -------------------------------------------------------------------------------- /futoin/cid/util/install/deb.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ...mixins.ondemand import ext as _ext 18 | from .. import log as _log 19 | 20 | 21 | def deb(packages): 22 | if not _ext.detect.isDeb(): 23 | return 24 | 25 | apt_get = _ext.pathutil.which('apt-get') 26 | 27 | packages = _ext.configutil.listify(packages) 28 | 29 | _ext.environ['DEBIAN_FRONTEND'] = 'noninteractive' 30 | _ext.executil.trySudoCall( 31 | [apt_get, 'install', '-y', 32 | '--no-install-recommends', 33 | '-o', 'Dpkg::Options::=--force-confdef', 34 | '-o', 'Dpkg::Options::=--force-confold'] + packages, 35 | errmsg='you may need to install the packages manually !' 36 | ) 37 | 38 | 39 | def aptRepo(name, entry, gpg_key=None, codename_map=None, repo_base=None): 40 | if not _ext.detect.isDeb(): 41 | return 42 | 43 | deb([ 44 | 'software-properties-common', 45 | 'apt-transport-https', 46 | 'ca-certificates', 47 | 'lsb-release', 48 | ]) 49 | apt_add_repository = _ext.pathutil.which('apt-add-repository') 50 | 51 | if not apt_add_repository: 52 | return 53 | 54 | if gpg_key: 55 | tmp_dir = _ext.pathutil.tmpCacheDir(prefix='cidgpg') 56 | tf = _ext.ospath.join(tmp_dir, 'key.gpg') 57 | _ext.pathutil.writeBinaryFile(tf, gpg_key) 58 | 59 | _ext.executil.trySudoCall( 60 | ['apt-key', 'add', tf], 61 | errmsg='you may need to import the PGP key manually!' 62 | ) 63 | 64 | _ext.os.remove(tf) 65 | 66 | codename = _ext.detect.osCodeName() 67 | 68 | if codename_map: 69 | try: 70 | repo_info = _ext.urllib.urlopen( 71 | '{0}/{1}'.format(repo_base, codename)).read() 72 | except: 73 | fallback_codename = codename_map.get(codename, codename) 74 | _log.warn('Fallback to codename: {0}'.format( 75 | fallback_codename)) 76 | codename = fallback_codename 77 | 78 | entry = entry.replace('$codename$', codename) 79 | 80 | _ext.executil.trySudoCall( 81 | [apt_add_repository, '--yes', entry], 82 | errmsg='you may need to add the repo manually!' 83 | ) 84 | 85 | _ext.executil.trySudoCall( 86 | ['apt-get', 'update'], 87 | errmsg='you may need to update APT cache manually!' 88 | ) 89 | 90 | 91 | def dpkg(env, name, url, apt_update=False): 92 | if not _ext.detect.isDeb(): 93 | return 94 | 95 | pathutil = _ext.pathutil 96 | apt_cache = pathutil.which('apt-cache') 97 | 98 | if apt_cache and _ext.executil.callExternal([apt_cache, 'show', name], suppress_fail=True): 99 | _ext.install.deb(name) 100 | else: 101 | dpkg = pathutil.which('dpkg') 102 | 103 | if _ext.executil.callExternal([dpkg, '-s', name], suppress_fail=True): 104 | return 105 | 106 | cache_file = pathutil.cacheDownloadFile(env, url) 107 | _ext.executil.trySudoCall([dpkg, '-i', cache_file]) 108 | 109 | if apt_update: 110 | _ext.executil.trySudoCall( 111 | ['apt-get', 'update'], 112 | errmsg='you may need to update APT cache manually!' 113 | ) 114 | -------------------------------------------------------------------------------- /futoin/cid/tool/gziptool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..buildtool import BuildTool 18 | 19 | 20 | class gzipTool(BuildTool): 21 | """Compression utility designed to be a replacement for compress. 22 | 23 | Home: http://www.gzip.org/ 24 | 25 | Auto-detected, if webcfg has mount points serving static files with gzip 26 | static enabled. 27 | 28 | Tune: 29 | * toGzipRe = '\.(js|json|css|svg|txt|xml|html)$' 30 | """ 31 | __slots__ = () 32 | __TO_GZIP = '\.(js|json|css|svg|txt|xml|html|md)$' 33 | 34 | def autoDetect(self, config): 35 | if not config.get('packageGzipStatic', True): 36 | return False 37 | 38 | webcfg = config.get('webcfg', {}) 39 | webroot = webcfg.get('root', '.') 40 | 41 | for (m, v) in webcfg.get('mounts', {}).items(): 42 | if not isinstance(v, dict): 43 | continue 44 | 45 | if v.get('static', False): 46 | pass 47 | elif v.get('app', None) is None: 48 | pass 49 | else: 50 | continue 51 | 52 | if v.get('tune', {}).get('staticGzip', True): 53 | return True 54 | 55 | return False 56 | 57 | def _installTool(self, env): 58 | self._install.debrpm(['gzip']) 59 | self._install.emerge(['app-arch/gzip']) 60 | self._install.pacman(['gzip']) 61 | self._install.apk(['gzip']) 62 | self._install.brew('gzip') 63 | 64 | def initEnv(self, env, bin_name=None): 65 | # Busybox's version is not enough for SDKMan 66 | if self._detect.isAlpineLinux() and self._ospath.islink('/bin/gzip'): 67 | return 68 | 69 | super(gzipTool, self).initEnv(env, bin_name) 70 | 71 | def onBuild(self, config): 72 | if not self.autoDetect(config): 73 | return 74 | 75 | self._info('Generating GZip files of static content') 76 | webcfg = config.get('webcfg', {}) 77 | webroot = webcfg.get('root', '.') 78 | re = self._ext.re 79 | to_gzip_re = self._getTune(config, 'toGzipRe', self.__TO_GZIP) 80 | to_gzip_re = re.compile(to_gzip_re, re.I) 81 | 82 | ospath = self._ospath 83 | gzip = self._ext.gzip 84 | shutil = self._ext.shutil 85 | 86 | for (m, v) in webcfg.get('mounts', {}).items(): 87 | if not isinstance(v, dict): 88 | continue 89 | 90 | if v.get('static', False): 91 | pass 92 | elif v.get('app', None) is None: 93 | pass 94 | else: 95 | continue 96 | 97 | if not v.get('tune', {}).get('staticGzip', True): 98 | continue 99 | 100 | gzip_dir = ospath.join(webroot, m) 101 | gzip_dir = gzip_dir.lstrip('/') 102 | self._info('> ' + gzip_dir) 103 | 104 | for (path, dirs, files) in self._os.walk(gzip_dir): 105 | for f in files: 106 | if to_gzip_re.search(f): 107 | f = ospath.join(path, f) 108 | with open(f, 'rb') as f_in: 109 | with gzip.open(f + '.gz', 'wb', 9) as f_out: 110 | shutil.copyfileobj(f_in, f_out) 111 | -------------------------------------------------------------------------------- /futoin/cid/tool/gotool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runtimetool import RuntimeTool 18 | from .bashtoolmixin import BashToolMixIn 19 | 20 | 21 | class goTool(BashToolMixIn, RuntimeTool): 22 | """The Go Programming Language 23 | 24 | Home: https://golang.org/ 25 | 26 | All versions are installed through GVM. 27 | 28 | Only binary releases of Golang are supported for installation 29 | through CID, but you can install source releases through 30 | "cte gvm install sourcetag". 31 | """ 32 | __slots__ = () 33 | 34 | def getDeps(self): 35 | return ['gvm', 'bash', 'binutils', 'gcc'] 36 | 37 | def getVersionParts(self): 38 | return 3 39 | 40 | def _installTool(self, env): 41 | if self._detect.isAlpineLinux(): 42 | self._install.apkCommunity() 43 | self._install.apk('go') 44 | return 45 | 46 | # in case GVM is already installed without these deps 47 | self._install.deb(['bison', 'build-essential']) 48 | self._install.rpm(['bison', 'glibc-devel']) 49 | self._install.emergeDepsOnly(['dev-lang/go']) 50 | self._install.pacman(['bison', 'glibc', ]) 51 | self._install.apk('bison') 52 | self._builddep.essential() 53 | 54 | self._callBash(env, 55 | 'source {0} && gvm install go{1} --binary' 56 | .format(env['gvmInit'], env['goVer']) 57 | ) 58 | 59 | def _updateTool(self, env): 60 | self._installTool(env) 61 | 62 | def uninstallTool(self, env): 63 | self._callBash(env, 64 | 'source {0} && gvm uninstall go{1}' 65 | .format(env['gvmInit'], env['goVer']) 66 | ) 67 | self._have_tool = False 68 | 69 | def envNames(self): 70 | return ['goVer', 'goBin'] 71 | 72 | def initEnv(self, env): 73 | if self._detect.isAlpineLinux(): 74 | super(goTool, self).initEnv(env) 75 | return 76 | 77 | if not env.get('goVer', None): 78 | try: 79 | cmd = 'source {0} && gvm listall'.format(env['gvmInit']) 80 | ver_list = self._callBash(env, cmd, verbose=False) 81 | ver_list = ver_list.split("\n") 82 | 83 | rex = self._ext.re.compile('^go[0-9]+\.[0-9]+(\.[0-9]+)?$') 84 | 85 | ver_list = [v.strip() for v in ver_list] 86 | ver_list = filter(lambda x: x and rex.match(x), ver_list) 87 | 88 | ver = self._versionutil.latest(list(ver_list)) 89 | env['goVer'] = ver.replace('go', '') 90 | except Exception as e: 91 | self._warn(str(e)) 92 | return 93 | 94 | ver = env['goVer'] 95 | 96 | try: 97 | env_to_set = self._callBash(env, 98 | 'source {0} && \ 99 | gvm use {1} >/dev/null && \ 100 | env | egrep -i "(gvm|golang)"'.format(env['gvmInit'], ver), 101 | verbose=False 102 | ) 103 | except: 104 | return 105 | 106 | if env_to_set: 107 | self._pathutil.updateEnvFromOutput(env_to_set) 108 | super(goTool, self).initEnv(env) 109 | 110 | def onRun(self, config, svc, args): 111 | env = config['env'] 112 | self._executil.callInteractive([ 113 | env[self._name + 'Bin'], 'run', svc['path'] 114 | ] + args) 115 | -------------------------------------------------------------------------------- /futoin/cid/tool/mixtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runtimetool import RuntimeTool 18 | from ..buildtool import BuildTool 19 | 20 | 21 | class mixTool(RuntimeTool, BuildTool): 22 | """ 23 | Mix - elixir build tool. 24 | 25 | Home: https://elixir-lang.org/ 26 | 27 | Notes on tuning: 28 | * .tune.prepare 29 | * .tune.build 30 | * .tune.package 31 | * .tune.package 32 | 33 | Note: MIX_ENV is set based on mixEnv or .env.type 34 | """ 35 | 36 | __slots__ = () 37 | 38 | MIX_EXS = 'mix.exs' 39 | 40 | def autoDetectFiles(self): 41 | return self.MIX_EXS 42 | 43 | def getDeps(self): 44 | return ['elixir'] 45 | 46 | def envNames(self): 47 | return ['mixEnv'] 48 | 49 | def initEnv(self, env): 50 | mix_env = env.get('mixEnv', '') 51 | 52 | if mix_env: 53 | pass 54 | else: 55 | mix_env = env['type'] 56 | 57 | self._environ['MIX_ENV'] = mix_env 58 | # --- 59 | 60 | super(mixTool, self).initEnv(env, 'mix') 61 | 62 | def onPrepare(self, config): 63 | prepareHex = self._getTune( 64 | config, 'prepareHex', True) 65 | 66 | if prepareHex: 67 | cmd = [config['env']['mixBin'], 'local.hex', '--force'] 68 | self._executil.callMeaningful(cmd) 69 | 70 | prepareRebar = self._getTune( 71 | config, 'prepareRebar', True) 72 | 73 | if prepareRebar: 74 | cmd = [config['env']['mixBin'], 'local.rebar', '--force'] 75 | self._executil.callMeaningful(cmd) 76 | 77 | target = self._getTune( 78 | config, 'prepare', ['do', 'clean,', 'deps.get']) 79 | cmd = [config['env']['mixBin']] + target 80 | self._executil.callMeaningful(cmd) 81 | 82 | def onBuild(self, config): 83 | target = self._getTune( 84 | config, 'build', ['do', 'deps.compile,', 'compile']) 85 | cmd = [config['env']['mixBin']] + target 86 | self._executil.callMeaningful(cmd) 87 | 88 | def onPackage(self, config): 89 | target = self._getTune(config, 'package', ['release']) 90 | cmd = [config['env']['mixBin']] + target 91 | self._executil.callMeaningful(cmd) 92 | 93 | def tuneDefaults(self, env): 94 | return { 95 | 'internal': True, 96 | 'minMemory': '64M', 97 | 'debugOverhead': '32M', 98 | 'connMemory': '32K', 99 | 'debugConnOverhead': '64K', 100 | 'socketTypes': ['tcp', 'tcp6'], 101 | 'socketType': 'tcp', 102 | 'scalable': False, 103 | 'reloadable': False, 104 | 'multiCore': True, 105 | 'maxRequestSize': '1M', 106 | 'socketProtocol': 'http', 107 | } 108 | 109 | def onRun(self, config, svc, args): 110 | svc_tune = svc['tune'] 111 | 112 | # --- 113 | mix_env = {} 114 | 115 | try: 116 | mix_env['PORT'] = str(svc_tune['socketPort']) 117 | mix_env['HOST'] = svc_tune['socketAddress'] 118 | except KeyError: 119 | pass 120 | 121 | self._environ.update(mix_env) 122 | 123 | # --- 124 | mix_args = [ 125 | '--no-halt', 126 | '--no-compile', 127 | '--preload-modules', 128 | ] 129 | 130 | # --- 131 | 132 | cmd = [ 133 | config['env']['mixBin'], 134 | 'run', 135 | svc['path'] 136 | ] + mix_args + args 137 | 138 | self._executil.callInteractive(cmd) 139 | -------------------------------------------------------------------------------- /futoin/cid/tool/brewtool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..runenvtool import RunEnvTool 18 | 19 | 20 | class brewTool(RunEnvTool): 21 | """Homebrew. The missing package manager for macOS. 22 | 23 | Home: https://brew.sh/ 24 | 25 | brewInstall is use for admin user installation. 26 | brewDir & brewGit is used for local install. 27 | 28 | Hint: Unprivileged brew does not work well with many bottles, you may want to use 29 | brewSudo='/usr/bin/sudo -n -H -u adminaccount' with "cid sudoers" config. 30 | """ 31 | __slots__ = () 32 | 33 | _MACOS_ADMIN_GID = 80 # dirty hack for now 34 | _GLOBAL_BREW_DIR = '/usr/local' 35 | 36 | def envNames(self): 37 | return ['brewBin', 'brewDir', 'brewGit', 'brewInstall', 'brewSudo'] 38 | 39 | def _installTool(self, env): 40 | if self._isLocalBrew(env): 41 | self._warn( 42 | 'Unprivileged Homebrew install has many drawbacks. Check "cid tool describe brew"') 43 | homebrew_git = env['brewGit'] 44 | homebrew_dir = env['brewDir'] 45 | 46 | git = self._pathutil.which('git') 47 | 48 | if not git: 49 | xcode_select = self._pathutil.which('xcode-select') 50 | self._executil.callExternal( 51 | [xcode_select, '--install'], suppress_fail=True) 52 | git = self._pathutil.which('git') 53 | 54 | self._executil.callExternal( 55 | [git, 'clone', homebrew_git, homebrew_dir]) 56 | else: 57 | # should be system-available 58 | curl = self._pathutil.which('curl') 59 | ruby = self._pathutil.which('ruby') 60 | homebrew_install = env['brewInstall'] 61 | 62 | curl_args = self._configutil.timeouts(env, 'curl') 63 | 64 | brew_installer = self._executil.callExternal( 65 | [curl, '-fsSL', homebrew_install] + curl_args 66 | ) 67 | 68 | self._executil.callExternal([ruby, '-'], input=brew_installer) 69 | 70 | def _isLocalBrew(self, env): 71 | return env['brewDir'] != self._GLOBAL_BREW_DIR 72 | 73 | if self._MACOS_ADMIN_GID not in self._os.getgroups(): 74 | return True 75 | 76 | return False 77 | 78 | def initEnv(self, env, bin_name=None): 79 | ospath = self._ospath 80 | os = self._os 81 | brewSudo = env.get('brewSudo', '') 82 | 83 | if self._MACOS_ADMIN_GID not in os.getgroups() and not brewSudo: 84 | homebrew_dir = ospath.join(self._environ['HOME'], '.homebrew') 85 | env.setdefault('brewDir', homebrew_dir) 86 | else: 87 | env.setdefault('brewDir', self._GLOBAL_BREW_DIR) 88 | 89 | if brewSudo: 90 | self._environ['brewSudo'] = brewSudo 91 | 92 | self._environ['HOMEBREW_NO_GITHUB_API'] = '1' 93 | env.setdefault('brewGit', 94 | 'https://github.com/Homebrew/brew.git') 95 | env.setdefault('brewInstall', 96 | 'https://raw.githubusercontent.com/Homebrew/install/master/install') 97 | 98 | if self._isLocalBrew(env): 99 | homebrew_dir = env['brewDir'] 100 | bin_dir = ospath.join(homebrew_dir, 'bin') 101 | brew = ospath.join(bin_dir, 'brew') 102 | 103 | if ospath.exists(brew): 104 | self._pathutil.addBinPath(bin_dir, True) 105 | env['brewBin'] = brew 106 | self._have_tool = True 107 | else: 108 | env.setdefault('brewDir', self._GLOBAL_BREW_DIR) 109 | super(brewTool, self).initEnv(env, bin_name) 110 | -------------------------------------------------------------------------------- /futoin/cid/tool/flywaytool.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-2020 Andrey Galkin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from ..migrationtool import MigrationTool 18 | 19 | 20 | class flywayTool(MigrationTool): 21 | """Flyway - Evolve your Database Schema easily and 22 | reliably across all your instances. 23 | 24 | Home: https://flywaydb.org/ 25 | 26 | Migration is run automatically, if flyway.conf is found. 27 | 28 | FLYWAY_HOME is user/set similar to LIQUIBASE_HOME 29 | 30 | Note: for now, it gets the latest tag from GitHub and then uses 31 | maven repo for download. That obviously should be improved. 32 | """ 33 | __slots__ = () 34 | 35 | FLYWAY_CONF = 'flyway.conf' 36 | 37 | def autoDetectFiles(self): 38 | return self.FLYWAY_CONF 39 | 40 | def getDeps(self): 41 | return ['java', 'tar', 'gzip'] 42 | 43 | def getVersionParts(self): 44 | return 3 45 | 46 | def envNames(self): 47 | return ['flywayVer', 'flywayDir', 'flywayDrivers'] 48 | 49 | def _installTool(self, env): 50 | ospath = self._ospath 51 | os = self._os 52 | github = self._ext.github 53 | 54 | lb_dir = env['flywayDir'] 55 | ver = env.get('flywayVer', 'latest') 56 | 57 | if ver == 'latest': 58 | release = ver 59 | else: 60 | release = 'tags/flyway-{0}'.format(ver) 61 | 62 | info = github.releaseInfo(env, 'flyway/flyway', release) 63 | 64 | found_ver = info['tag_name'].replace('flyway-', '') 65 | dst = ospath.join(lb_dir, found_ver) 66 | 67 | if not ospath.exists(dst): 68 | maven_central = self._install.mavenCentral(env) 69 | asset_url = '{0}/org/flywaydb/flyway-commandline/{1}/flyway-commandline-{1}.tar.gz'.format( 70 | maven_central, found_ver 71 | ) 72 | self._pathutil.downloadExtract( 73 | env, asset_url, 74 | dst, 'z', 1) 75 | 76 | if ver == 'latest': 77 | latest = ospath.join(lb_dir, 'latest') 78 | try: 79 | os.unlink(latest) 80 | except OSError: 81 | pass 82 | os.symlink(found_ver, latest) 83 | 84 | def _updateTool(self, env): 85 | self._installTool(env) 86 | 87 | def uninstallTool(self, env): 88 | ver = env.get('flywayVer', 'latest') 89 | inst_dir = env['flywayDir'] 90 | 91 | if ver != 'latest': 92 | inst_dir = self._pathutil.safeJoin(inst_dir, ver) 93 | 94 | self._pathutil.rmTree(inst_dir) 95 | 96 | def initEnv(self, env): 97 | ospath = self._ospath 98 | 99 | # --- 100 | inst_dir = self._environ.get('FLYWAY_HOME', None) 101 | fail_on_missing = False 102 | 103 | if inst_dir: 104 | fail_on_missing = True 105 | else: 106 | lb_dir = ospath.join( 107 | self._pathutil.userHome(), 108 | '.local', 'flywaybin') 109 | lb_dir = env.setdefault('flywayDir', lb_dir) 110 | ver = env.get('flywayVer', 'latest') 111 | inst_dir = ospath.join(lb_dir, ver) 112 | 113 | lb_bin = ospath.join(inst_dir, 'flyway') 114 | 115 | if ospath.exists(lb_bin): 116 | env['flywayBin'] = lb_bin 117 | self._environ['FLYWAY_HOME'] = inst_dir 118 | self._have_tool = True 119 | elif fail_on_missing: 120 | self._errorExit('Unset FLYWAY_HOME or ' 121 | 'setup the tool manually.') 122 | 123 | def onMigrate(self, config): 124 | if self._ospath.exists(self.FLYWAY_CONF): 125 | self.onExec(config['env'], ['-n', 'migrate'], replace=False) 126 | --------------------------------------------------------------------------------