Repository: ansible/ansible

Issue:
consul module: port should be optional when registering services
Opening this because #17031 has been closed because it needed to be submitted to https://github.com/ansible/ansible-modules-extras, but there it says you should open the issue here.

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.2.1.0
```
##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
not applicable

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
ubuntu 16.04

##### SUMMARY
<!--- Explain the problem briefly -->
Consul service definitions do not require a port. See https://www.consul.io/docs/agent/services.html
Still, the consul module requires me to provide the service_port parameter.

Some services do not listen on TCP ports.

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Register some service with consul
  consul:
    service_name: "myService"

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
It creates a service without a port. Creating a json service definition without a port and reloading consul works.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

The following error is returned
<!--- Paste verbatim command output between quotes below -->
```
service_name supplied but no service_port, a port is required to configure a service. Did you configure the 'port' argument meaning 'service_port'?
```
consul module: port should be optional when registering services
Opening this because #17031 has been closed because it needed to be submitted to https://github.com/ansible/ansible-modules-extras, but there it says you should open the issue here.

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.2.1.0
```
##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
not applicable

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
ubuntu 16.04

##### SUMMARY
<!--- Explain the problem briefly -->
Consul service definitions do not require a port. See https://www.consul.io/docs/agent/services.html
Still, the consul module requires me to provide the service_port parameter.

Some services do not listen on TCP ports.

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Register some service with consul
  consul:
    service_name: "myService"

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
It creates a service without a port. Creating a json service definition without a port and reloading consul works.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

The following error is returned
<!--- Paste verbatim command output between quotes below -->
```
service_name supplied but no service_port, a port is required to configure a service. Did you configure the 'port' argument meaning 'service_port'?
```


Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/clustering/consul.py b/lib/ansible/modules/clustering/consul.py
--- a/lib/ansible/modules/clustering/consul.py
+++ b/lib/ansible/modules/clustering/consul.py
@@ -95,9 +95,10 @@
         default: None
     service_port:
         description:
-          - the port on which the service is listening required for
+          - the port on which the service is listening. Can optionally be supplied for
             registration of a service, i.e. if service_name or service_id is set
         required: false
+        default: None
     service_address:
         description:
           - the address to advertise that the service will be listening on.
@@ -211,6 +212,13 @@
     service_name: nginx
     state: absent
 
+- name: register celery worker service
+  consul:
+    service_name: celery-worker
+    tags:
+      - prod
+      - worker
+
 - name: create a node level check to test disk usage
   consul:
     check_name: Disk usage
@@ -234,8 +242,8 @@
 except ImportError:
     python_consul_installed = False
 
-def register_with_consul(module):
 
+def register_with_consul(module):
     state = module.params.get('state')
 
     if state == 'present':
@@ -359,7 +367,6 @@ def get_service_by_id_or_name(consul_api, service_id_or_name):
 
 
 def parse_check(module):
-
     if len(filter(None, [module.params.get('script'), module.params.get('ttl'), module.params.get('http')])) > 1:
         module.fail_json(
             msg='check are either script, http or ttl driven, supplying more than one does not make sense')
@@ -382,8 +389,7 @@ def parse_check(module):
 
 
 def parse_service(module):
-
-    if module.params.get('service_name') and module.params.get('service_port'):
+    if module.params.get('service_name'):
         return ConsulService(
             module.params.get('service_id'),
             module.params.get('service_name'),
@@ -391,10 +397,8 @@ def parse_service(module):
             module.params.get('service_port'),
             module.params.get('tags'),
         )
-    elif module.params.get('service_name') and not module.params.get('service_port'):
-
-        module.fail_json(msg="service_name supplied but no service_port, a port is required to configure a service. Did you configure "
-                             "the 'port' argument meaning 'service_port'?")
+    elif module.params.get('service_port') and not module.params.get('service_name'):
+        module.fail_json(msg="service_port supplied but no service_name, a name is required to configure a service.")
 
 
 class ConsulService():
@@ -415,23 +419,20 @@ def __init__(self, service_id=None, name=None, address=None, port=-1,
             self.tags = loaded['Tags']
 
     def register(self, consul_api):
+        optional = {}
+
+        if self.port:
+            optional['port'] = self.port
+
         if len(self.checks) > 0:
-            check = self.checks[0]
-
-            consul_api.agent.service.register(
-                self.name,
-                service_id=self.id,
-                address=self.address,
-                port=self.port,
-                tags=self.tags,
-                check=check.check)
-        else:
-            consul_api.agent.service.register(
-                self.name,
-                service_id=self.id,
-                address=self.address,
-                port=self.port,
-                tags=self.tags)
+            optional['check'] = checks[0].check
+
+        consul_api.agent.service.register(
+            self.name,
+            service_id=self.id,
+            address=self.address,
+            tags=self.tags,
+            **optional)
 
     def add_check(self, check):
         self.checks.append(check)
@@ -443,11 +444,11 @@ def has_checks(self):
         return len(self.checks) > 0
 
     def __eq__(self, other):
-        return (isinstance(other, self.__class__)
-                and self.id == other.id
-                and self.name == other.name
-                and self.port == other.port
-                and self.tags == other.tags)
+        return (isinstance(other, self.__class__) and
+                self.id == other.id and
+                self.name == other.name and
+                self.port == other.port and
+                self.tags == other.tags)
 
     def __ne__(self, other):
         return not self.__eq__(other)
@@ -466,7 +467,7 @@ def to_dict(self):
 class ConsulCheck():
 
     def __init__(self, check_id, name, node=None, host='localhost',
-                    script=None, interval=None, ttl=None, notes=None, http=None, timeout=None, service_id=None):
+                 script=None, interval=None, ttl=None, notes=None, http=None, timeout=None, service_id=None):
         self.check_id = self.name = name
         if check_id:
             self.check_id = check_id
@@ -495,7 +496,6 @@ def __init__(self, check_id, name, node=None, host='localhost',
 
             self.check = consul.Check.http(http, self.interval, self.timeout)
 
-
     def validate_duration(self, name, duration):
         if duration:
             duration_units = ['ns', 'us', 'ms', 's', 'm', 'h']
@@ -509,12 +509,12 @@ def register(self, consul_api):
                                         check=self.check)
 
     def __eq__(self, other):
-        return (isinstance(other, self.__class__)
-                and self.check_id == other.check_id
-                and self.service_id == other.service_id
-                and self.name == other.name
-                and self.script == script
-                and self.interval == interval)
+        return (isinstance(other, self.__class__) and
+                self.check_id == other.check_id and
+                self.service_id == other.service_id and
+                self.name == other.name and
+                self.script == script and
+                self.interval == interval)
 
     def __ne__(self, other):
         return not self.__eq__(other)
@@ -542,10 +542,12 @@ def _add(self, data, key, attr=None):
         except:
             pass
 
+
 def test_dependencies(module):
     if not python_consul_installed:
         module.fail_json(msg="python-consul required for this module. see http://python-consul.readthedocs.org/en/latest/#installation")
 
+
 def main():
     module = AnsibleModule(
         argument_spec=dict(
@@ -562,7 +564,7 @@ def main():
             service_id=dict(required=False),
             service_name=dict(required=False),
             service_address=dict(required=False, type='str', default=None),
-            service_port=dict(required=False, type='int'),
+            service_port=dict(required=False, type='int', default=None),
             state=dict(default='present', choices=['present', 'absent']),
             interval=dict(required=False, type='str'),
             ttl=dict(required=False, type='str'),


Test patch:
diff --git a/test/integration/roles/test_consul_service/tasks/main.yml b/test/integration/roles/test_consul_service/tasks/main.yml
--- a/test/integration/roles/test_consul_service/tasks/main.yml
+++ b/test/integration/roles/test_consul_service/tasks/main.yml
@@ -5,6 +5,7 @@
   with_items:
       - service1
       - service2
+      - service3
       - http_check
       - with_check
       - with_tags
@@ -46,6 +47,20 @@
         - basic2_result.service_id == 'service2'
         - basic2_result.service_name == 'Basic Service'
 
+- name: register very basic service without service_port
+  consul:
+    service_name: Basic Service Without Port
+    service_id: service3
+  register: basic3_result
+
+- name: verify service3 registration
+  assert:
+    that:
+        - basic3_result.changed
+        - basic3_result.service_port == None
+        - basic3_result.service_id == 'service3'
+        - basic3_result.service_name == 'Basic Service Without Port'
+
 - name: register a service with an http check
   consul:
     service_name: http_check
diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt
--- a/test/sanity/pep8/legacy-files.txt
+++ b/test/sanity/pep8/legacy-files.txt
@@ -378,7 +378,6 @@ lib/ansible/modules/cloud/webfaction/webfaction_db.py
 lib/ansible/modules/cloud/webfaction/webfaction_domain.py
 lib/ansible/modules/cloud/webfaction/webfaction_mailbox.py
 lib/ansible/modules/cloud/webfaction/webfaction_site.py
-lib/ansible/modules/clustering/consul.py
 lib/ansible/modules/clustering/consul_acl.py
 lib/ansible/modules/clustering/consul_kv.py
 lib/ansible/modules/clustering/consul_session.py