def test_graph(self, root, expected): graph = {'a': ['c'], 'b': ['a'], 'c': [], 'd': ['a'], 'e': ['b', 'a']} assert len(YagocdUtil.graph_depth_walk( root, lambda x: graph.get(x))) == len(expected) assert sorted(YagocdUtil.graph_depth_walk( root, lambda x: graph.get(x))) == sorted(expected)
def test_tie_descendants(self, session_fixture): child_a = pipeline.PipelineEntity(session=session_fixture, data={ 'name': 'child1', 'materials': {} }) parent_a = pipeline.PipelineEntity(session=session_fixture, data={ 'name': 'parent1', 'materials': [{ 'description': 'child1', 'type': 'Pipeline' }] }) pipelines = [child_a, parent_a] YagocdUtil.build_graph( nodes=pipelines, dependencies=lambda parent: [material for material in parent.data.materials], compare=lambda candidate, child: candidate.description == child. data.name) assert child_a.descendants == [parent_a]
def list(self): """ List all available pipelines. :versionadded: 14.3.0. This method uses ``pipeline_groups`` API method call to list available pipelines. It also links them together, so later it's possible to refer to pipeline's descendants. :return: array of pipelines :rtype: list of yagocd.resources.pipeline.PipelineEntity """ response = self._session.get( path=self.GROUPS_RESOURCE_PATH.format(base_api=self.base_api), headers={'Accept': 'application/json'}, ) pipelines = list() for group in response.json(): for data in group['pipelines']: pipeline = PipelineEntity( session=self._session, data=data, group=group['name'] ) pipelines.append(pipeline) # build pipeline graph to link related nodes return YagocdUtil.build_graph( nodes=pipelines, dependencies=lambda parent: [material for material in parent.data.materials], compare=lambda candidate, child: candidate.description == child.data.name )
def list(self): """ List all available pipelines. :versionadded: 14.3.0. This method uses ``pipeline_groups`` API method call to list available pipelines. It also links them together, so later it's possible to refer to pipeline's descendants. :return: array of pipelines :rtype: list of yagocd.resources.pipeline.PipelineEntity """ response = self._session.get( path=self.GROUPS_RESOURCE_PATH.format(base_api=self.base_api), headers={'Accept': 'application/json'}, ) pipelines = list() for group in response.json(): for data in group['pipelines']: pipeline = PipelineEntity(session=self._session, data=data, group=group['name']) pipelines.append(pipeline) # build pipeline graph to link related nodes return YagocdUtil.build_graph( nodes=pipelines, dependencies=lambda parent: [material for material in parent.data.materials], compare=lambda candidate, child: candidate.description == child. data.name)
def _accept_header(self): """ Method for determining correct `Accept` header. Different resources and different GoCD version servers prefer a diverse headers. In order to manage all of them, this method tries to help: if `VERSION_TO_ACCEPT_HEADER` is not provided, if would simply return default `ACCEPT_HEADER`. Though if some manager specifies `VERSION_TO_ACCEPT_HEADER` class variable, then it should be a dictionary: keys should be a versions and values should be desired accept headers. Choosing is pessimistic: if version of a server is less or equal to one of the dictionary, the value of that key would be used. :return: accept header to use in request. """ if not self.VERSION_TO_ACCEPT_HEADER: return self.ACCEPT_HEADER return YagocdUtil.choose_option( version_to_options=self.VERSION_TO_ACCEPT_HEADER, default=self.ACCEPT_HEADER, server_version=self._session.server_version )
def test_tie_descendants(self, session_fixture): child_a = pipeline.PipelineEntity( session=session_fixture, data={'name': 'child1', 'materials': {}} ) parent_a = pipeline.PipelineEntity( session=session_fixture, data={'name': 'parent1', 'materials': [{'description': 'child1', 'type': 'Pipeline'}]} ) pipelines = [child_a, parent_a] YagocdUtil.build_graph( nodes=pipelines, dependencies=lambda parent: [material for material in parent.data.materials], compare=lambda candidate, child: candidate.description == child.data.name ) assert child_a.descendants == [parent_a]
def get_predecessors(self, transitive=False): """ Property for getting predecessors (parents) of current pipeline. This property automatically populates from API call :return: list of :class:`yagocd.resources.pipeline.PipelineEntity`. :rtype: list of yagocd.resources.pipeline.PipelineEntity """ result = self._predecessors if transitive: return YagocdUtil.graph_depth_walk(result, lambda v: v.predecessors) return result
def get_descendants(self, transitive=False): """ Property for getting descendants (children) of current pipeline. It's calculated by :meth:`yagocd.resources.pipeline.PipelineManager#tie_descendants` method during listing of all pipelines. :return: list of :class:`yagocd.resources.pipeline.PipelineEntity`. :rtype: list of yagocd.resources.pipeline.PipelineEntity """ result = self._descendants if transitive: return YagocdUtil.graph_depth_walk(result, lambda v: v.descendants) return result
def _accept_header(self): """ Method for determining correct `Accept` header. Different resources and different GoCD version servers prefer a diverse headers. In order to manage all of them, this method tries to help: if `VERSION_TO_ACCEPT_HEADER` is not provided, if would simply return default `ACCEPT_HEADER`. Though if some manager specifies `VERSION_TO_ACCEPT_HEADER` class variable, then it should be a dictionary: keys should be a versions and values should be desired accept headers. Choosing is pessimistic: if version of a server is less or equal to one of the dictionary, the value of that key would be used. :return: accept header to use in request. """ if not self.VERSION_TO_ACCEPT_HEADER: return self.ACCEPT_HEADER return YagocdUtil.choose_option( version_to_options=self.VERSION_TO_ACCEPT_HEADER, default=self.ACCEPT_HEADER, server_version=self._session.server_version)
def value_stream_map(self, name, counter): """ Method builds pipeline instance dependency graph. :param name: name of the pipeline. :param counter: pipeline counter. """ response = self._session.get( path=self._session.urljoin(self.VSM_RESOURCE_PATH, '{}.json'.format(counter)).format( base_api=self._session.base_api(api_path=''), name=name), headers={'Accept': 'application/json'}, ) data = EasyDict(response.json()) nodes = list() dependencies = dict() for level in data['levels']: for node_item in level.nodes: dependencies[node_item.id] = node_item.parents if node_item.node_type == 'DUMMY': # WTF?! continue elif node_item.node_type == 'PIPELINE': for instance in node_item.instances: pipeline_data = dict( id=node_item.id, name=node_item.name, counter=instance.counter, label=instance.label, type=node_item.node_type.capitalize(), stages=[] ) for stage in instance.stages: stage_data = dict( pipeline_name=node_item.name, pipeline_counter=instance.counter, name=stage.name, status=stage.status, counter=stage.locator.split('/')[-1] ) pipeline_data['stages'].append(stage_data) nodes.append(PipelineInstance(session=self._session, data=pipeline_data)) else: if LooseVersion(self._session.server_version) <= LooseVersion('16.5.0'): modifications = [m for m in node_item.instances] else: modifications = [m for sublist in node_item.material_revisions for m in sublist.modifications] for modification in modifications: modification['id'] = node_item.id modification['type'] = node_item.node_type.capitalize() nodes.append(ModificationEntity(session=self._session, data=modification)) return YagocdUtil.build_graph( nodes=nodes, dependencies=lambda parent: dependencies[parent.data.id], compare=lambda candidate, child: candidate == child.data.id )
def value_stream_map(self, name, counter): """ Method builds pipeline instance dependency graph. :param name: name of the pipeline. :param counter: pipeline counter. """ response = self._session.get( path=self._session.urljoin( self.VSM_RESOURCE_PATH, '{}.json'.format(counter)).format( base_api=self._session.base_api(api_path=''), name=name), headers={'Accept': 'application/json'}, ) data = EasyDict(response.json()) nodes = list() dependencies = dict() for level in data['levels']: for node_item in level.nodes: dependencies[node_item.id] = node_item.parents if node_item.node_type == 'DUMMY': # WTF?! continue elif node_item.node_type == 'PIPELINE': for instance in node_item.instances: pipeline_data = dict( id=node_item.id, name=node_item.name, counter=instance.counter, label=instance.label, type=node_item.node_type.capitalize(), stages=[]) for stage in instance.stages: stage_data = dict( pipeline_name=node_item.name, pipeline_counter=instance.counter, name=stage.name, status=stage.status, counter=stage.locator.split('/')[-1]) pipeline_data['stages'].append(stage_data) nodes.append( PipelineInstance(session=self._session, data=pipeline_data)) else: if LooseVersion(self._session.server_version ) <= LooseVersion('16.5.0'): modifications = [m for m in node_item.instances] else: modifications = [ m for sublist in node_item.material_revisions for m in sublist.modifications ] for modification in modifications: modification['id'] = node_item.id modification['type'] = node_item.node_type.capitalize() nodes.append( ModificationEntity(session=self._session, data=modification)) return YagocdUtil.build_graph( nodes=nodes, dependencies=lambda parent: dependencies[parent.data.id], compare=lambda candidate, child: candidate == child.data.id)
def test_graph(self, root, expected): graph = {'a': ['c'], 'b': ['a'], 'c': [], 'd': ['a'], 'e': ['b', 'a']} assert len(YagocdUtil.graph_depth_walk(root, lambda x: graph.get(x))) == len(expected) assert sorted(YagocdUtil.graph_depth_walk(root, lambda x: graph.get(x))) == sorted(expected)