1""" 2Module to import docker-compose via saltstack 3 4.. versionadded:: 2016.3.0 5 6:maintainer: Jean Praloran <jeanpralo@gmail.com> 7:maturity: new 8:depends: docker-compose>=1.5 9:platform: all 10 11Introduction 12------------ 13This module allows one to deal with docker-compose file in a directory. 14 15This is a first version only, the following commands are missing at the moment 16but will be built later on if the community is interested in this module: 17 18- run 19- logs 20- port 21- scale 22 23Installation Prerequisites 24-------------------------- 25 26This execution module requires at least version 1.4.0 of both docker-compose_ and 27Docker_. docker-compose can easily be installed using :py:func:`pip.install 28<salt.modules.pip.install>`: 29 30.. code-block:: bash 31 32 salt myminion pip.install docker-compose>=1.5.0 33 34.. _docker-compose: https://pypi.python.org/pypi/docker-compose 35.. _Docker: https://www.docker.com/ 36 37 38How to use this module? 39----------------------- 40In order to use the module if you have no docker-compose file on the server you 41can issue the command create, it takes two arguments the path where the 42docker-compose.yml will be stored and the content of this latter: 43 44.. code-block:: text 45 46 # salt-call -l debug dockercompose.create /tmp/toto ' 47 database: 48 image: mongo:3.0 49 command: mongod --smallfiles --quiet --logpath=/dev/null 50 ' 51 52Then you can execute a list of method defined at the bottom with at least one 53argument (the path where the docker-compose.yml will be read) and an optional 54python list which corresponds to the services names: 55 56.. code-block:: bash 57 58 # salt-call -l debug dockercompose.up /tmp/toto 59 # salt-call -l debug dockercompose.restart /tmp/toto '[database]' 60 # salt-call -l debug dockercompose.stop /tmp/toto 61 # salt-call -l debug dockercompose.rm /tmp/toto 62 63Docker-compose method supported 64------------------------------- 65- up 66- restart 67- stop 68- start 69- pause 70- unpause 71- kill 72- rm 73- ps 74- pull 75- build 76 77Functions 78--------- 79- docker-compose.yml management 80 - :py:func:`dockercompose.create <salt.modules.dockercompose.create>` 81 - :py:func:`dockercompose.get <salt.modules.dockercompose.get>` 82- Manage containers 83 - :py:func:`dockercompose.restart <salt.modules.dockercompose.restart>` 84 - :py:func:`dockercompose.stop <salt.modules.dockercompose.stop>` 85 - :py:func:`dockercompose.pause <salt.modules.dockercompose.pause>` 86 - :py:func:`dockercompose.unpause <salt.modules.dockercompose.unpause>` 87 - :py:func:`dockercompose.start <salt.modules.dockercompose.start>` 88 - :py:func:`dockercompose.kill <salt.modules.dockercompose.kill>` 89 - :py:func:`dockercompose.rm <salt.modules.dockercompose.rm>` 90 - :py:func:`dockercompose.up <salt.modules.dockercompose.up>` 91- Manage containers image: 92 - :py:func:`dockercompose.pull <salt.modules.dockercompose.pull>` 93 - :py:func:`dockercompose.build <salt.modules.dockercompose.build>` 94- Gather information about containers: 95 - :py:func:`dockercompose.ps <salt.modules.dockercompose.ps>` 96- Manage service definitions: 97 - :py:func:`dockercompose.service_create <salt.modules.dockercompose.ps>` 98 - :py:func:`dockercompose.service_upsert <salt.modules.dockercompose.ps>` 99 - :py:func:`dockercompose.service_remove <salt.modules.dockercompose.ps>` 100 - :py:func:`dockercompose.service_set_tag <salt.modules.dockercompose.ps>` 101 102Detailed Function Documentation 103------------------------------- 104""" 105 106 107import inspect 108import logging 109import os 110import re 111from operator import attrgetter 112 113import salt.utils.files 114import salt.utils.stringutils 115from salt.serializers import json 116from salt.utils import yaml 117 118try: 119 import compose 120 from compose.cli.command import get_project 121 from compose.service import ConvergenceStrategy 122 123 HAS_DOCKERCOMPOSE = True 124except ImportError: 125 HAS_DOCKERCOMPOSE = False 126 127try: 128 from compose.project import OneOffFilter 129 130 USE_FILTERCLASS = True 131except ImportError: 132 USE_FILTERCLASS = False 133 134MIN_DOCKERCOMPOSE = (1, 5, 0) 135VERSION_RE = r"([\d.]+)" 136 137log = logging.getLogger(__name__) 138debug = False 139 140__virtualname__ = "dockercompose" 141DEFAULT_DC_FILENAMES = ("docker-compose.yml", "docker-compose.yaml") 142 143 144def __virtual__(): 145 if HAS_DOCKERCOMPOSE: 146 match = re.match(VERSION_RE, str(compose.__version__)) 147 if match: 148 version = tuple([int(x) for x in match.group(1).split(".")]) 149 if version >= MIN_DOCKERCOMPOSE: 150 return __virtualname__ 151 return ( 152 False, 153 "The dockercompose execution module not loaded: " 154 "compose python library not available.", 155 ) 156 157 158def __standardize_result(status, message, data=None, debug_msg=None): 159 """ 160 Standardizes all responses 161 162 :param status: 163 :param message: 164 :param data: 165 :param debug_msg: 166 :return: 167 """ 168 result = {"status": status, "message": message} 169 170 if data is not None: 171 result["return"] = data 172 173 if debug_msg is not None and debug: 174 result["debug"] = debug_msg 175 176 return result 177 178 179def __get_docker_file_path(path): 180 """ 181 Determines the filepath to use 182 183 :param path: 184 :return: 185 """ 186 if os.path.isfile(path): 187 return path 188 for dc_filename in DEFAULT_DC_FILENAMES: 189 file_path = os.path.join(path, dc_filename) 190 if os.path.isfile(file_path): 191 return file_path 192 # implicitly return None 193 194 195def __read_docker_compose_file(file_path): 196 """ 197 Read the compose file if it exists in the directory 198 199 :param file_path: 200 :return: 201 """ 202 if not os.path.isfile(file_path): 203 return __standardize_result( 204 False, "Path {} is not present".format(file_path), None, None 205 ) 206 try: 207 with salt.utils.files.fopen(file_path, "r") as fl: 208 file_name = os.path.basename(file_path) 209 result = {file_name: ""} 210 for line in fl: 211 result[file_name] += salt.utils.stringutils.to_unicode(line) 212 except OSError: 213 return __standardize_result( 214 False, "Could not read {}".format(file_path), None, None 215 ) 216 return __standardize_result( 217 True, "Reading content of {}".format(file_path), result, None 218 ) 219 220 221def __load_docker_compose(path): 222 """ 223 Read the compose file and load its' contents 224 225 :param path: 226 :return: 227 """ 228 file_path = __get_docker_file_path(path) 229 if file_path is None: 230 msg = "Could not find docker-compose file at {}".format(path) 231 return None, __standardize_result(False, msg, None, None) 232 if not os.path.isfile(file_path): 233 return ( 234 None, 235 __standardize_result( 236 False, "Path {} is not present".format(file_path), None, None 237 ), 238 ) 239 try: 240 with salt.utils.files.fopen(file_path, "r") as fl: 241 loaded = yaml.load(fl) 242 except OSError: 243 return ( 244 None, 245 __standardize_result( 246 False, "Could not read {}".format(file_path), None, None 247 ), 248 ) 249 except yaml.YAMLError as yerr: 250 msg = "Could not parse {} {}".format(file_path, yerr) 251 return None, __standardize_result(False, msg, None, None) 252 if not loaded: 253 msg = "Got empty compose file at {}".format(file_path) 254 return None, __standardize_result(False, msg, None, None) 255 if "services" not in loaded: 256 loaded["services"] = {} 257 result = {"compose_content": loaded, "file_name": os.path.basename(file_path)} 258 return result, None 259 260 261def __dump_docker_compose(path, content, already_existed): 262 """ 263 Dumps 264 265 :param path: 266 :param content: the not-yet dumped content 267 :return: 268 """ 269 try: 270 dumped = yaml.safe_dump(content, indent=2, default_flow_style=False) 271 return __write_docker_compose(path, dumped, already_existed) 272 except TypeError as t_err: 273 msg = "Could not dump {} {}".format(content, t_err) 274 return __standardize_result(False, msg, None, None) 275 276 277def __write_docker_compose(path, docker_compose, already_existed): 278 """ 279 Write docker-compose to a path 280 in order to use it with docker-compose ( config check ) 281 282 :param path: 283 284 docker_compose 285 contains the docker-compose file 286 287 :return: 288 """ 289 if path.lower().endswith((".yml", ".yaml")): 290 file_path = path 291 dir_name = os.path.dirname(path) 292 else: 293 dir_name = path 294 file_path = os.path.join(dir_name, DEFAULT_DC_FILENAMES[0]) 295 if os.path.isdir(dir_name) is False: 296 os.mkdir(dir_name) 297 try: 298 with salt.utils.files.fopen(file_path, "w") as fl: 299 fl.write(salt.utils.stringutils.to_str(docker_compose)) 300 except OSError: 301 return __standardize_result( 302 False, "Could not write {}".format(file_path), None, None 303 ) 304 project = __load_project_from_file_path(file_path) 305 if isinstance(project, dict): 306 if not already_existed: 307 os.remove(file_path) 308 return project 309 return file_path 310 311 312def __load_project(path): 313 """ 314 Load a docker-compose project from path 315 316 :param path: 317 :return: 318 """ 319 file_path = __get_docker_file_path(path) 320 if file_path is None: 321 msg = "Could not find docker-compose file at {}".format(path) 322 return __standardize_result(False, msg, None, None) 323 return __load_project_from_file_path(file_path) 324 325 326def __load_project_from_file_path(file_path): 327 """ 328 Load a docker-compose project from file path 329 330 :param path: 331 :return: 332 """ 333 try: 334 project = get_project( 335 project_dir=os.path.dirname(file_path), 336 config_path=[os.path.basename(file_path)], 337 ) 338 except Exception as inst: # pylint: disable=broad-except 339 return __handle_except(inst) 340 return project 341 342 343def __load_compose_definitions(path, definition): 344 """ 345 Will load the compose file located at path 346 Then determines the format/contents of the sent definition 347 348 err or results are only set if there were any 349 350 :param path: 351 :param definition: 352 :return tuple(compose_result, loaded_definition, err): 353 """ 354 compose_result, err = __load_docker_compose(path) 355 if err: 356 return None, None, err 357 if isinstance(definition, dict): 358 return compose_result, definition, None 359 elif definition.strip().startswith("{"): 360 try: 361 loaded_definition = json.deserialize(definition) 362 except json.DeserializationError as jerr: 363 msg = "Could not parse {} {}".format(definition, jerr) 364 return None, None, __standardize_result(False, msg, None, None) 365 else: 366 try: 367 loaded_definition = yaml.load(definition) 368 except yaml.YAMLError as yerr: 369 msg = "Could not parse {} {}".format(definition, yerr) 370 return None, None, __standardize_result(False, msg, None, None) 371 return compose_result, loaded_definition, None 372 373 374def __dump_compose_file(path, compose_result, success_msg, already_existed): 375 """ 376 Utility function to dump the compose result to a file. 377 378 :param path: 379 :param compose_result: 380 :param success_msg: the message to give upon success 381 :return: 382 """ 383 ret = __dump_docker_compose( 384 path, compose_result["compose_content"], already_existed 385 ) 386 if isinstance(ret, dict): 387 return ret 388 return __standardize_result( 389 True, success_msg, compose_result["compose_content"], None 390 ) 391 392 393def __handle_except(inst): 394 """ 395 Handle exception and return a standard result 396 397 :param inst: 398 :return: 399 """ 400 return __standardize_result( 401 False, 402 "Docker-compose command {} failed".format(inspect.stack()[1][3]), 403 "{}".format(inst), 404 None, 405 ) 406 407 408def _get_convergence_plans(project, service_names): 409 """ 410 Get action executed for each container 411 412 :param project: 413 :param service_names: 414 :return: 415 """ 416 ret = {} 417 plans = project._get_convergence_plans( 418 project.get_services(service_names), ConvergenceStrategy.changed 419 ) 420 for cont in plans: 421 (action, container) = plans[cont] 422 if action == "create": 423 ret[cont] = "Creating container" 424 elif action == "recreate": 425 ret[cont] = "Re-creating container" 426 elif action == "start": 427 ret[cont] = "Starting container" 428 elif action == "noop": 429 ret[cont] = "Container is up to date" 430 return ret 431 432 433def get(path): 434 """ 435 Get the content of the docker-compose file into a directory 436 437 path 438 Path where the docker-compose file is stored on the server 439 440 CLI Example: 441 442 .. code-block:: bash 443 444 salt myminion dockercompose.get /path/where/docker-compose/stored 445 """ 446 file_path = __get_docker_file_path(path) 447 if file_path is None: 448 return __standardize_result( 449 False, "Path {} is not present".format(path), None, None 450 ) 451 salt_result = __read_docker_compose_file(file_path) 452 if not salt_result["status"]: 453 return salt_result 454 project = __load_project(path) 455 if isinstance(project, dict): 456 salt_result["return"]["valid"] = False 457 else: 458 salt_result["return"]["valid"] = True 459 return salt_result 460 461 462def create(path, docker_compose): 463 """ 464 Create and validate a docker-compose file into a directory 465 466 path 467 Path where the docker-compose file will be stored on the server 468 469 docker_compose 470 docker_compose file 471 472 CLI Example: 473 474 .. code-block:: bash 475 476 salt myminion dockercompose.create /path/where/docker-compose/stored content 477 """ 478 if docker_compose: 479 ret = __write_docker_compose(path, docker_compose, already_existed=False) 480 if isinstance(ret, dict): 481 return ret 482 else: 483 return __standardize_result( 484 False, 485 "Creating a docker-compose project failed, you must send a valid" 486 " docker-compose file", 487 None, 488 None, 489 ) 490 return __standardize_result( 491 True, 492 "Successfully created the docker-compose file", 493 {"compose.base_dir": path}, 494 None, 495 ) 496 497 498def pull(path, service_names=None): 499 """ 500 Pull image for containers in the docker-compose file, service_names is a 501 python list, if omitted pull all images 502 503 path 504 Path where the docker-compose file is stored on the server 505 service_names 506 If specified will pull only the image for the specified services 507 508 CLI Example: 509 510 .. code-block:: bash 511 512 salt myminion dockercompose.pull /path/where/docker-compose/stored 513 salt myminion dockercompose.pull /path/where/docker-compose/stored '[janus]' 514 """ 515 516 project = __load_project(path) 517 if isinstance(project, dict): 518 return project 519 else: 520 try: 521 project.pull(service_names) 522 except Exception as inst: # pylint: disable=broad-except 523 return __handle_except(inst) 524 return __standardize_result( 525 True, "Pulling containers images via docker-compose succeeded", None, None 526 ) 527 528 529def build(path, service_names=None): 530 """ 531 Build image for containers in the docker-compose file, service_names is a 532 python list, if omitted build images for all containers. Please note 533 that at the moment the module does not allow you to upload your Dockerfile, 534 nor any other file you could need with your docker-compose.yml, you will 535 have to make sure the files you need are actually in the directory specified 536 in the `build` keyword 537 538 path 539 Path where the docker-compose file is stored on the server 540 service_names 541 If specified will pull only the image for the specified services 542 543 CLI Example: 544 545 .. code-block:: bash 546 547 salt myminion dockercompose.build /path/where/docker-compose/stored 548 salt myminion dockercompose.build /path/where/docker-compose/stored '[janus]' 549 """ 550 551 project = __load_project(path) 552 if isinstance(project, dict): 553 return project 554 else: 555 try: 556 project.build(service_names) 557 except Exception as inst: # pylint: disable=broad-except 558 return __handle_except(inst) 559 return __standardize_result( 560 True, "Building containers images via docker-compose succeeded", None, None 561 ) 562 563 564def restart(path, service_names=None): 565 """ 566 Restart container(s) in the docker-compose file, service_names is a python 567 list, if omitted restart all containers 568 569 path 570 Path where the docker-compose file is stored on the server 571 572 service_names 573 If specified will restart only the specified services 574 575 CLI Example: 576 577 .. code-block:: bash 578 579 salt myminion dockercompose.restart /path/where/docker-compose/stored 580 salt myminion dockercompose.restart /path/where/docker-compose/stored '[janus]' 581 """ 582 583 project = __load_project(path) 584 debug_ret = {} 585 result = {} 586 if isinstance(project, dict): 587 return project 588 else: 589 try: 590 project.restart(service_names) 591 if debug: 592 for container in project.containers(): 593 if ( 594 service_names is None 595 or container.get("Name")[1:] in service_names 596 ): 597 container.inspect_if_not_inspected() 598 debug_ret[container.get("Name")] = container.inspect() 599 result[container.get("Name")] = "restarted" 600 except Exception as inst: # pylint: disable=broad-except 601 return __handle_except(inst) 602 return __standardize_result( 603 True, "Restarting containers via docker-compose", result, debug_ret 604 ) 605 606 607def stop(path, service_names=None): 608 """ 609 Stop running containers in the docker-compose file, service_names is a python 610 list, if omitted stop all containers 611 612 path 613 Path where the docker-compose file is stored on the server 614 service_names 615 If specified will stop only the specified services 616 617 CLI Example: 618 619 .. code-block:: bash 620 621 salt myminion dockercompose.stop /path/where/docker-compose/stored 622 salt myminion dockercompose.stop /path/where/docker-compose/stored '[janus]' 623 """ 624 625 project = __load_project(path) 626 debug_ret = {} 627 result = {} 628 if isinstance(project, dict): 629 return project 630 else: 631 try: 632 project.stop(service_names) 633 if debug: 634 for container in project.containers(stopped=True): 635 if ( 636 service_names is None 637 or container.get("Name")[1:] in service_names 638 ): 639 container.inspect_if_not_inspected() 640 debug_ret[container.get("Name")] = container.inspect() 641 result[container.get("Name")] = "stopped" 642 except Exception as inst: # pylint: disable=broad-except 643 return __handle_except(inst) 644 return __standardize_result( 645 True, "Stopping containers via docker-compose", result, debug_ret 646 ) 647 648 649def pause(path, service_names=None): 650 """ 651 Pause running containers in the docker-compose file, service_names is a python 652 list, if omitted pause all containers 653 654 path 655 Path where the docker-compose file is stored on the server 656 service_names 657 If specified will pause only the specified services 658 659 CLI Example: 660 661 .. code-block:: bash 662 663 salt myminion dockercompose.pause /path/where/docker-compose/stored 664 salt myminion dockercompose.pause /path/where/docker-compose/stored '[janus]' 665 """ 666 667 project = __load_project(path) 668 debug_ret = {} 669 result = {} 670 if isinstance(project, dict): 671 return project 672 else: 673 try: 674 project.pause(service_names) 675 if debug: 676 for container in project.containers(): 677 if ( 678 service_names is None 679 or container.get("Name")[1:] in service_names 680 ): 681 container.inspect_if_not_inspected() 682 debug_ret[container.get("Name")] = container.inspect() 683 result[container.get("Name")] = "paused" 684 except Exception as inst: # pylint: disable=broad-except 685 return __handle_except(inst) 686 return __standardize_result( 687 True, "Pausing containers via docker-compose", result, debug_ret 688 ) 689 690 691def unpause(path, service_names=None): 692 """ 693 Un-Pause containers in the docker-compose file, service_names is a python 694 list, if omitted unpause all containers 695 696 path 697 Path where the docker-compose file is stored on the server 698 service_names 699 If specified will un-pause only the specified services 700 701 CLI Example: 702 703 .. code-block:: bash 704 705 salt myminion dockercompose.pause /path/where/docker-compose/stored 706 salt myminion dockercompose.pause /path/where/docker-compose/stored '[janus]' 707 """ 708 709 project = __load_project(path) 710 debug_ret = {} 711 result = {} 712 if isinstance(project, dict): 713 return project 714 else: 715 try: 716 project.unpause(service_names) 717 if debug: 718 for container in project.containers(): 719 if ( 720 service_names is None 721 or container.get("Name")[1:] in service_names 722 ): 723 container.inspect_if_not_inspected() 724 debug_ret[container.get("Name")] = container.inspect() 725 result[container.get("Name")] = "unpaused" 726 except Exception as inst: # pylint: disable=broad-except 727 return __handle_except(inst) 728 return __standardize_result( 729 True, "Un-Pausing containers via docker-compose", result, debug_ret 730 ) 731 732 733def start(path, service_names=None): 734 """ 735 Start containers in the docker-compose file, service_names is a python 736 list, if omitted start all containers 737 738 path 739 Path where the docker-compose file is stored on the server 740 service_names 741 If specified will start only the specified services 742 743 CLI Example: 744 745 .. code-block:: bash 746 747 salt myminion dockercompose.start /path/where/docker-compose/stored 748 salt myminion dockercompose.start /path/where/docker-compose/stored '[janus]' 749 """ 750 751 project = __load_project(path) 752 debug_ret = {} 753 result = {} 754 if isinstance(project, dict): 755 return project 756 else: 757 try: 758 project.start(service_names) 759 if debug: 760 for container in project.containers(): 761 if ( 762 service_names is None 763 or container.get("Name")[1:] in service_names 764 ): 765 container.inspect_if_not_inspected() 766 debug_ret[container.get("Name")] = container.inspect() 767 result[container.get("Name")] = "started" 768 except Exception as inst: # pylint: disable=broad-except 769 return __handle_except(inst) 770 return __standardize_result( 771 True, "Starting containers via docker-compose", result, debug_ret 772 ) 773 774 775def kill(path, service_names=None): 776 """ 777 Kill containers in the docker-compose file, service_names is a python 778 list, if omitted kill all containers 779 780 path 781 Path where the docker-compose file is stored on the server 782 service_names 783 If specified will kill only the specified services 784 785 CLI Example: 786 787 .. code-block:: bash 788 789 salt myminion dockercompose.kill /path/where/docker-compose/stored 790 salt myminion dockercompose.kill /path/where/docker-compose/stored '[janus]' 791 """ 792 793 project = __load_project(path) 794 debug_ret = {} 795 result = {} 796 if isinstance(project, dict): 797 return project 798 else: 799 try: 800 project.kill(service_names) 801 if debug: 802 for container in project.containers(stopped=True): 803 if ( 804 service_names is None 805 or container.get("Name")[1:] in service_names 806 ): 807 container.inspect_if_not_inspected() 808 debug_ret[container.get("Name")] = container.inspect() 809 result[container.get("Name")] = "killed" 810 except Exception as inst: # pylint: disable=broad-except 811 return __handle_except(inst) 812 return __standardize_result( 813 True, "Killing containers via docker-compose", result, debug_ret 814 ) 815 816 817def rm(path, service_names=None): 818 """ 819 Remove stopped containers in the docker-compose file, service_names is a python 820 list, if omitted remove all stopped containers 821 822 path 823 Path where the docker-compose file is stored on the server 824 service_names 825 If specified will remove only the specified stopped services 826 827 CLI Example: 828 829 .. code-block:: bash 830 831 salt myminion dockercompose.rm /path/where/docker-compose/stored 832 salt myminion dockercompose.rm /path/where/docker-compose/stored '[janus]' 833 """ 834 835 project = __load_project(path) 836 if isinstance(project, dict): 837 return project 838 else: 839 try: 840 project.remove_stopped(service_names) 841 except Exception as inst: # pylint: disable=broad-except 842 return __handle_except(inst) 843 return __standardize_result( 844 True, "Removing stopped containers via docker-compose", None, None 845 ) 846 847 848def ps(path): 849 """ 850 List all running containers and report some information about them 851 852 path 853 Path where the docker-compose file is stored on the server 854 855 CLI Example: 856 857 .. code-block:: bash 858 859 salt myminion dockercompose.ps /path/where/docker-compose/stored 860 """ 861 862 project = __load_project(path) 863 result = {} 864 if isinstance(project, dict): 865 return project 866 else: 867 if USE_FILTERCLASS: 868 containers = sorted( 869 project.containers(None, stopped=True) 870 + project.containers(None, OneOffFilter.only), 871 key=attrgetter("name"), 872 ) 873 else: 874 containers = sorted( 875 project.containers(None, stopped=True) 876 + project.containers(None, one_off=True), 877 key=attrgetter("name"), 878 ) 879 for container in containers: 880 command = container.human_readable_command 881 if len(command) > 30: 882 command = "{} ...".format(command[:26]) 883 result[container.name] = { 884 "id": container.id, 885 "name": container.name, 886 "command": command, 887 "state": container.human_readable_state, 888 "ports": container.human_readable_ports, 889 } 890 return __standardize_result(True, "Listing docker-compose containers", result, None) 891 892 893def up(path, service_names=None): 894 """ 895 Create and start containers defined in the docker-compose.yml file 896 located in path, service_names is a python list, if omitted create and 897 start all containers 898 899 path 900 Path where the docker-compose file is stored on the server 901 service_names 902 If specified will create and start only the specified services 903 904 CLI Example: 905 906 .. code-block:: bash 907 908 salt myminion dockercompose.up /path/where/docker-compose/stored 909 salt myminion dockercompose.up /path/where/docker-compose/stored '[janus]' 910 """ 911 912 debug_ret = {} 913 project = __load_project(path) 914 if isinstance(project, dict): 915 return project 916 else: 917 try: 918 result = _get_convergence_plans(project, service_names) 919 ret = project.up(service_names) 920 if debug: 921 for container in ret: 922 if ( 923 service_names is None 924 or container.get("Name")[1:] in service_names 925 ): 926 container.inspect_if_not_inspected() 927 debug_ret[container.get("Name")] = container.inspect() 928 except Exception as inst: # pylint: disable=broad-except 929 return __handle_except(inst) 930 return __standardize_result( 931 True, "Adding containers via docker-compose", result, debug_ret 932 ) 933 934 935def service_create(path, service_name, definition): 936 """ 937 Create the definition of a docker-compose service 938 This fails when the service already exists 939 This does not pull or up the service 940 This wil re-write your yaml file. Comments will be lost. Indentation is set to 2 spaces 941 942 path 943 Path where the docker-compose file is stored on the server 944 service_name 945 Name of the service to create 946 definition 947 Service definition as yaml or json string 948 949 CLI Example: 950 951 .. code-block:: bash 952 953 salt myminion dockercompose.service_create /path/where/docker-compose/stored service_name definition 954 """ 955 compose_result, loaded_definition, err = __load_compose_definitions( 956 path, definition 957 ) 958 if err: 959 return err 960 services = compose_result["compose_content"]["services"] 961 if service_name in services: 962 msg = "Service {} already exists".format(service_name) 963 return __standardize_result(False, msg, None, None) 964 services[service_name] = loaded_definition 965 return __dump_compose_file( 966 path, 967 compose_result, 968 "Service {} created".format(service_name), 969 already_existed=True, 970 ) 971 972 973def service_upsert(path, service_name, definition): 974 """ 975 Create or update the definition of a docker-compose service 976 This does not pull or up the service 977 This wil re-write your yaml file. Comments will be lost. Indentation is set to 2 spaces 978 979 path 980 Path where the docker-compose file is stored on the server 981 service_name 982 Name of the service to create 983 definition 984 Service definition as yaml or json string 985 986 CLI Example: 987 988 .. code-block:: bash 989 990 salt myminion dockercompose.service_upsert /path/where/docker-compose/stored service_name definition 991 """ 992 compose_result, loaded_definition, err = __load_compose_definitions( 993 path, definition 994 ) 995 if err: 996 return err 997 services = compose_result["compose_content"]["services"] 998 if service_name in services: 999 msg = "Service {} already exists".format(service_name) 1000 return __standardize_result(False, msg, None, None) 1001 services[service_name] = loaded_definition 1002 return __dump_compose_file( 1003 path, 1004 compose_result, 1005 "Service definition for {} is set".format(service_name), 1006 already_existed=True, 1007 ) 1008 1009 1010def service_remove(path, service_name): 1011 """ 1012 Remove the definition of a docker-compose service 1013 This does not rm the container 1014 This wil re-write your yaml file. Comments will be lost. Indentation is set to 2 spaces 1015 1016 path 1017 Path where the docker-compose file is stored on the server 1018 service_name 1019 Name of the service to remove 1020 1021 CLI Example: 1022 1023 .. code-block:: bash 1024 1025 salt myminion dockercompose.service_remove /path/where/docker-compose/stored service_name 1026 """ 1027 compose_result, err = __load_docker_compose(path) 1028 if err: 1029 return err 1030 services = compose_result["compose_content"]["services"] 1031 if service_name not in services: 1032 return __standardize_result( 1033 False, "Service {} did not exists".format(service_name), None, None 1034 ) 1035 del services[service_name] 1036 return __dump_compose_file( 1037 path, 1038 compose_result, 1039 "Service {} is removed from {}".format(service_name, path), 1040 already_existed=True, 1041 ) 1042 1043 1044def service_set_tag(path, service_name, tag): 1045 """ 1046 Change the tag of a docker-compose service 1047 This does not pull or up the service 1048 This wil re-write your yaml file. Comments will be lost. Indentation is set to 2 spaces 1049 1050 path 1051 Path where the docker-compose file is stored on the server 1052 service_name 1053 Name of the service to remove 1054 tag 1055 Name of the tag (often used as version) that the service image should have 1056 1057 CLI Example: 1058 1059 .. code-block:: bash 1060 1061 salt myminion dockercompose.service_create /path/where/docker-compose/stored service_name tag 1062 """ 1063 compose_result, err = __load_docker_compose(path) 1064 if err: 1065 return err 1066 services = compose_result["compose_content"]["services"] 1067 if service_name not in services: 1068 return __standardize_result( 1069 False, "Service {} did not exists".format(service_name), None, None 1070 ) 1071 if "image" not in services[service_name]: 1072 return __standardize_result( 1073 False, 1074 'Service {} did not contain the variable "image"'.format(service_name), 1075 None, 1076 None, 1077 ) 1078 image = services[service_name]["image"].split(":")[0] 1079 services[service_name]["image"] = "{}:{}".format(image, tag) 1080 return __dump_compose_file( 1081 path, 1082 compose_result, 1083 'Service {} is set to tag "{}"'.format(service_name, tag), 1084 already_existed=True, 1085 ) 1086