diff --git a/bin/compose_plantuml b/bin/compose_plantuml index 5f3f08f..d41a5b0 100755 --- a/bin/compose_plantuml +++ b/bin/compose_plantuml @@ -17,8 +17,13 @@ if __name__ == '__main__': default=False, ) parser.add_argument( - '--group', action='store_const', const=True, - help='group similar properties together', + '--port_boundaries', action='store_const', const=True, + help='prints the port system boundaries', + default=False, + ) + parser.add_argument( + '--volume_boundaries', action='store_const', const=True, + help='prints the port system boundaries', default=False, ) parser.add_argument( @@ -26,11 +31,16 @@ if __name__ == '__main__': help='utilize notes for displaying additional information', default=False, ) + parser.add_argument( + '--group', action='store_const', const=True, + help='group similar properties together', + default=False, + ) parser.add_argument('files', nargs=argparse.REMAINDER) args = parser.parse_args() plantuml = ComposePlantuml() - if not args.link_graph and not args.boundaries: + if not args.link_graph and not args.boundaries and not args.port_boundaries and not args.volume_boundaries: print('specify an output option. link_graph or boundaries') sys.exit(1) @@ -40,7 +50,11 @@ if __name__ == '__main__': if args.link_graph: print(plantuml.link_graph(parsed, notes=args.notes)) if args.boundaries: - print(plantuml.boundaries(parsed, group=args.group, notes=args.notes)) + print(plantuml.boundaries(parsed, args.group, notes=args.notes)) + if args.port_boundaries: + print(plantuml.boundaries(parsed, args.group, notes=args.notes, ports=True, volumes=False)) + if args.volume_boundaries: + print(plantuml.boundaries(parsed, args.group, notes=args.notes, ports=False, volumes=True)) if len(args.files) == 0: execute(sys.stdin.read()) diff --git a/compose_plantuml/__init__.py b/compose_plantuml/__init__.py index b40d708..f2badbe 100755 --- a/compose_plantuml/__init__.py +++ b/compose_plantuml/__init__.py @@ -29,58 +29,66 @@ def link_graph(self, compose, notes=False): result += 'note top of [{0}]\n {1}\nend note\n'.format(component_name, '\n '.join(labels)) return result.strip() - def boundaries(self, compose, group=False, notes=False): + def boundaries(self, compose, group=False, ports=True, volumes=True, notes=False): result = 'skinparam componentStyle uml2\n' result += 'cloud system {\n' for component in sorted(self.components(compose)): - if self.has_service_external_ports(compose, component) or self.has_service_volumes(compose, component): - result += ' [{0}]\n'.format(component) - result += '}\n' - volume_registry = {} - - volume_uml = '' - for volume in sorted(self.volumes(compose)): - if not self.is_volume_used(compose, volume): + relevant = False + if ports and self.has_service_external_ports(compose, component): + relevant = True + if volumes and self.has_service_volumes(compose, component): + relevant = True + if not relevant: continue - volume_uml += 'database {0}'.format(volume) + ' {\n' - for path in sorted(self.volume_usage(compose, volume)): - id = self.volume_identifier(volume, path) - - if id in volume_registry: - continue - volume_registry[id] = 'volume_{0}'.format(len(volume_registry.keys()) + 1) - volume_uml += ' [{0}] as {1}\n'.format(path, volume_registry[id]) - - volume_uml += '}\n' - result += self.group('volumes', volume_uml) if group else volume_uml - - port_uml = '' - port_links = '' - for service, host, container in sorted(self.ports(compose)): - port = host if container is None else '{0} : {1}'.format(host, container) - port_links += '[{0}] --> {1}\n'.format(service, port) - port_uml += 'interface {0}\n'.format(host) - result += self.group('ports', port_uml) if group else '' - result += port_links - - for volume in sorted(self.volumes(compose)): - for service, volume_path in sorted(self.service_using_path(compose, volume)): - name = volume_path - if '{0}.{1}'.format(volume, volume_path) in volume_registry: - name = volume_registry['{0}.{1}'.format(volume, volume_path)] - result += '[{0}] --> {1}\n'.format(service, name) + result += ' [{0}]\n'.format(component) + if not notes: + continue + if not self.labels(compose, component): + continue + labels = [] + for key, value in self.labels(compose, component).items(): + labels.append('{0}={1}'.format(key, value)) + result += ' note top of [{0}]\n {1}\n end note\n'.format(component, '\n '.join(labels)) + result += '}\n' + if volumes: + volume_registry = {} - if notes: - for component_name in sorted(self.components(compose)): - if not (self.has_service_external_ports(compose, component_name) or self.has_service_volumes(compose, component_name)): - continue - if not self.labels(compose, component_name): + volume_uml = '' + for volume in sorted(self.volumes(compose)): + if not self.is_volume_used(compose, volume): continue - labels = [] - for key, value in self.labels(compose, component_name).items(): - labels.append('{0}={1}'.format(key, value)) - result += 'note top of [{0}]\n {1}\nend note\n'.format(component_name, '\n '.join(labels)) + volume_uml += 'database {0}'.format(volume) + ' {\n' + for path in sorted(self.volume_usage(compose, volume)): + id = self.volume_identifier(volume, path) + + if id in volume_registry: + continue + volume_registry[id] = 'volume_{0}'.format(len(volume_registry.keys()) + 1) + volume_uml += ' [{0}] as {1}\n'.format(path, volume_registry[id]) + + volume_uml += '}\n' + result += self.group('volumes', volume_uml) if group else volume_uml + + if ports: + port_uml = '' + port_links = '' + for service, host, container in sorted(self.ports(compose)): + port = host + if container is not None: + port = '{0} : {1}'.format(host, container) + port_links += '[{0}] --> {1}\n'.format(service, port) + port_uml += 'interface {0}\n'.format(port) + result += self.group('ports', port_uml) if group else '' + result += port_links + + if volumes: + for volume in sorted(self.volumes(compose)): + for service, volume_path in sorted(self.service_using_path(compose, volume)): + name = volume_path + if '{0}.{1}'.format(volume, volume_path) in volume_registry: + name = volume_registry['{0}.{1}'.format(volume, volume_path)] + result += '[{0}] --> {1}\n'.format(service, name) return result.strip() diff --git a/features/boundaries.feature b/features/boundaries.feature index 0ec6096..7c110b9 100644 --- a/features/boundaries.feature +++ b/features/boundaries.feature @@ -104,7 +104,7 @@ Feature: Boundaries volumes: - service_log:/log ports: - - 8080:80 + - 8080 unused_service: {} volumes: service_log: {} @@ -125,7 +125,7 @@ Feature: Boundaries package ports { interface 8080 } - [service] --> 8080 : 80 + [service] --> 8080 [service] --> volume_1 """ @@ -149,14 +149,65 @@ Feature: Boundaries skinparam componentStyle uml2 cloud system { [service] + note top of [service] + key=value + end note } database service_log { [/log] as volume_1 } [service] --> volume_1 - note top of [service] - key=value - end note + """ + + Scenario: Ports only + Given a file named "compose.yml" with: + """ + version: "2" + services: + service: + volumes: + - service_log:/log + ports: + - 8080 + volumes: + service_log: {} + """ + When I run `bin/compose_plantuml --port_boundaries compose.yml` + Then it should pass with exactly: + """ + skinparam componentStyle uml2 + cloud system { + [service] + } + [service] --> 8080 + + """ + + Scenario: Volumes only + Given a file named "compose.yml" with: + """ + version: "2" + services: + service: + volumes: + - service_log:/log + ports: + - 8080 + volumes: + service_log: {} + """ + When I run `bin/compose_plantuml --volume_boundaries compose.yml` + Then it should pass with exactly: + """ + skinparam componentStyle uml2 + cloud system { + [service] + } + database service_log { + [/log] as volume_1 + } + [service] --> volume_1 + """ Scenario: Suppport for legacy docker-compose format diff --git a/setup.py b/setup.py index 0531f5a..4b11df0 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ def readme(): setup( name='compose_plantuml', - version='0.0.11', + version='0.0.12', description='converts docker-compose into plantuml', long_description=readme(), url='http://github.com/funkwerk/compose_plantuml',