Repository: openshift/openshift-ansible

Issue:
master branch of openshift-ansible fails to deploy the OpenShift registry on OSE 3.2
#### Description

`openshift-ansible` should be able to deploy any major version of OpenShift since the major version of OpenShift is selected by the channel which is subscribed to.

However, deploying an OSE 3.2 cluster with the current `master` version of openshift-ansible fails.

##### Version

* OSE 3.2
* openshift-ansible master

##### Steps To Reproduce

1. Subscribe the machines to rhel-7-server-ose-3.2-rpms channel
2. Deploy an OpenShift cluster with the `master` version of openshift-ansible

##### Expected Results

The deployment completes and openshift-ansible exits without any error.

##### Observed Results

openshift-ansible fails at the followinq task:
```
TASK [openshift_hosted : Create OpenShift registry] ****************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: __main__.RegistryException: Could not perform registry preparation. {'returncode': 1, 'stderr': u'Error: unknown flag: --daemonset\n\nUsage:\n  oc adm registry [options]\n\nExamples:\n  # Check if default Docker registry ("docker-registry") has been created\n  $ oc adm registry --dry-run\n\n  # See what the registry will look like if created\n  $ oc adm registry -o yaml\n\n  # Create a registry with two replicas if it does not exist\n  $ oc adm registry --replicas=2\n\n  # Use a different registry image\n  $ oc adm registry --images=myrepo/docker-registry:mytag\n\nOptions:\n      --create=false: deprecated; this is now the default behavior\n      --credentials=\'\': Path to a .kubeconfig file that will contain the credentials the registry should use to contact the master.\n      --dry-run=false: Check if the registry exists instead of creating.\n      --images=\'openshift3/ose-${component}:${version}\': The image to base this registry on - ${component} will be replaced with --type\n      --labels=\'docker-registry=default\': A set of labels to uniquely identify the registry and its components.\n      --latest-images=false: If true, attempt to use the latest image for the registry instead of the latest release.\n      --mount-host=\'\': If set, the registry volume will be created as a host-mount at this path.\n      --no-headers=false: When using the default output, don\'t print headers.\n  -o, --output=\'\': Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/release-1.2/docs/user-guide/jsonpath.md].\n      --output-version=\'\': Output the formatted object with the given group version (for ex: \'extensions/v1beta1\').\n      --ports=\'5000\': A comma delimited list of ports or port pairs to expose on the registry pod. The default is set for 5000.\n      --replicas=1: The replication factor of the registry; commonly 2 when high availability is desired.\n      --selector=\'\': Selector used to filter nodes on deployment. Used to run registries on a specific set of nodes.\n      --service-account=\'registry\': Name of the service account to use to run the registry pod.\n  -a, --show-all=true: When printing, show all resources (false means hide terminated pods.)\n      --show-labels=false: When printing, show all labels as the last column (default hide labels column)\n      --sort-by=\'\': If non-empty, sort list types using this field specification.  The field specification is expressed as a JSONPath expression (e.g. \'{.metadata.name}\'). The field in the API resource specified by this JSONPath expression must be an integer or a string.\n  -t, --template=\'\': Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].\n      --tls-certificate=\'\': An optional path to a PEM encoded certificate (which may contain the private key) for serving over TLS\n      --tls-key=\'\': An optional path to a PEM encoded private key for serving over TLS\n      --type=\'docker-registry\': The registry image to use - if you specify --images this flag may be ignored.\n      --volume=\'/registry\': The volume path to use for registry storage; defaults to /registry which is the default for origin-docker-registry.\n\nUse "oc adm options" for a list of global command-line options (applies to all commands).\n\n', 'stdout': u'', 'cmd': '/bin/oc adm registry --daemonset=False --enforce-quota=False --images=openshift3/ose-${component}:${version} --latest-images=False --ports=5000 --replicas=1 --selector=type=infra --service-account=registry --dry-run=True -o json -n default', 'results': {}}
fatal: [lenaic-master-ebea6]: FAILED! => {"changed": false, "failed": true, "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_8Ri5NU/ansible_module_oc_adm_registry.py\", line 2670, in <module>\n    main()\n  File \"/tmp/ansible_8Ri5NU/ansible_module_oc_adm_registry.py\", line 2662, in main\n    results = Registry.run_ansible(module.params, module.check_mode)\n  File \"/tmp/ansible_8Ri5NU/ansible_module_oc_adm_registry.py\", line 2598, in run_ansible\n    api_rval = ocregistry.create()\n  File \"/tmp/ansible_8Ri5NU/ansible_module_oc_adm_registry.py\", line 2399, in create\n    self.needs_update()\n  File \"/tmp/ansible_8Ri5NU/ansible_module_oc_adm_registry.py\", line 2500, in needs_update\n    not Utils.check_def_equal(self.prepared_registry['service'].yaml_dict,\n  File \"/tmp/ansible_8Ri5NU/ansible_module_oc_adm_registry.py\", line 2289, in prepared_registry\n    results = self.prepare_registry()\n  File \"/tmp/ansible_8Ri5NU/ansible_module_oc_adm_registry.py\", line 2357, in prepare_registry\n    raise RegistryException('Could not perform registry preparation. {}'.format(results))\n__main__.RegistryException: Could not perform registry preparation. {'returncode': 1, 'stderr': u'Error: unknown flag: --daemonset\\n\\nUsage:\\n  oc adm registry [options]\\n\\nExamples:\\n  # Check if default Docker registry (\"docker-registry\") has been created\\n  $ oc adm registry --dry-run\\n\\n  # See what the registry will look like if created\\n  $ oc adm registry -o yaml\\n\\n  # Create a registry with two replicas if it does not exist\\n  $ oc adm registry --replicas=2\\n\\n  # Use a different registry image\\n  $ oc adm registry --images=myrepo/docker-registry:mytag\\n\\nOptions:\\n      --create=false: deprecated; this is now the default behavior\\n      --credentials=\\'\\': Path to a .kubeconfig file that will contain the credentials the registry should use to contact the master.\\n      --dry-run=false: Check if the registry exists instead of creating.\\n      --images=\\'openshift3/ose-${component}:${version}\\': The image to base this registry on - ${component} will be replaced with --type\\n      --labels=\\'docker-registry=default\\': A set of labels to uniquely identify the registry and its components.\\n      --latest-images=false: If true, attempt to use the latest image for the registry instead of the latest release.\\n      --mount-host=\\'\\': If set, the registry volume will be created as a host-mount at this path.\\n      --no-headers=false: When using the default output, don\\'t print headers.\\n  -o, --output=\\'\\': Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/release-1.2/docs/user-guide/jsonpath.md].\\n      --output-version=\\'\\': Output the formatted object with the given group version (for ex: \\'extensions/v1beta1\\').\\n      --ports=\\'5000\\': A comma delimited list of ports or port pairs to expose on the registry pod. The default is set for 5000.\\n      --replicas=1: The replication factor of the registry; commonly 2 when high availability is desired.\\n      --selector=\\'\\': Selector used to filter nodes on deployment. Used to run registries on a specific set of nodes.\\n      --service-account=\\'registry\\': Name of the service account to use to run the registry pod.\\n  -a, --show-all=true: When printing, show all resources (false means hide terminated pods.)\\n      --show-labels=false: When printing, show all labels as the last column (default hide labels column)\\n      --sort-by=\\'\\': If non-empty, sort list types using this field specification.  The field specification is expressed as a JSONPath expression (e.g. \\'{.metadata.name}\\'). The field in the API resource specified by this JSONPath expression must be an integer or a string.\\n  -t, --template=\\'\\': Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].\\n      --tls-certificate=\\'\\': An optional path to a PEM encoded certificate (which may contain the private key) for serving over TLS\\n      --tls-key=\\'\\': An optional path to a PEM encoded private key for serving over TLS\\n      --type=\\'docker-registry\\': The registry image to use - if you specify --images this flag may be ignored.\\n      --volume=\\'/registry\\': The volume path to use for registry storage; defaults to /registry which is the default for origin-docker-registry.\\n\\nUse \"oc adm options\" for a list of global command-line options (applies to all commands).\\n\\n', 'stdout': u'', 'cmd': '/bin/oc adm registry --daemonset=False --enforce-quota=False --images=openshift3/ose-${component}:${version} --latest-images=False --ports=5000 --replicas=1 --selector=type=infra --service-account=registry --dry-run=True -o json -n default', 'results': {}}\n", "module_stdout": "", "msg": "MODULE FAILURE"}
```

##### Additional Information

The reason of the above error is:
```
Error: unknown flag: --daemonset
```

The point is that `oc adm registry` didn’t have the `--daemonset` in OSE 3.2. It learned that flag in more recent versions.


Discussion:

Resolving patch:
diff --git a/roles/lib_openshift/library/oc_adm_registry.py b/roles/lib_openshift/library/oc_adm_registry.py
--- a/roles/lib_openshift/library/oc_adm_registry.py
+++ b/roles/lib_openshift/library/oc_adm_registry.py
@@ -2531,25 +2531,34 @@ def needs_update(self):
     def run_ansible(params, check_mode):
         '''run idempotent ansible code'''
 
+        registry_options = {'images': {'value': params['images'], 'include': True},
+                            'latest_images': {'value': params['latest_images'], 'include': True},
+                            'labels': {'value': params['labels'], 'include': True},
+                            'ports': {'value': ','.join(params['ports']), 'include': True},
+                            'replicas': {'value': params['replicas'], 'include': True},
+                            'selector': {'value': params['selector'], 'include': True},
+                            'service_account': {'value': params['service_account'], 'include': True},
+                            'mount_host': {'value': params['mount_host'], 'include': True},
+                            'env_vars': {'value': params['env_vars'], 'include': False},
+                            'volume_mounts': {'value': params['volume_mounts'], 'include': False},
+                            'edits': {'value': params['edits'], 'include': False},
+                            'tls_key': {'value': params['tls_key'], 'include': True},
+                            'tls_certificate': {'value': params['tls_certificate'], 'include': True},
+                           }
+
+        # Do not always pass the daemonset and enforce-quota parameters because they are not understood
+        # by old versions of oc.
+        # Default value is false. So, it's safe to not pass an explicit false value to oc versions which
+        # understand these parameters.
+        if params['daemonset']:
+            registry_options['daemonset'] = {'value': params['daemonset'], 'include': True}
+        if params['enforce_quota']:
+            registry_options['enforce_quota'] = {'value': params['enforce_quota'], 'include': True}
+
         rconfig = RegistryConfig(params['name'],
                                  params['namespace'],
                                  params['kubeconfig'],
-                                 {'images': {'value': params['images'], 'include': True},
-                                  'latest_images': {'value': params['latest_images'], 'include': True},
-                                  'labels': {'value': params['labels'], 'include': True},
-                                  'ports': {'value': ','.join(params['ports']), 'include': True},
-                                  'replicas': {'value': params['replicas'], 'include': True},
-                                  'selector': {'value': params['selector'], 'include': True},
-                                  'service_account': {'value': params['service_account'], 'include': True},
-                                  'mount_host': {'value': params['mount_host'], 'include': True},
-                                  'env_vars': {'value': params['env_vars'], 'include': False},
-                                  'volume_mounts': {'value': params['volume_mounts'], 'include': False},
-                                  'edits': {'value': params['edits'], 'include': False},
-                                  'enforce_quota': {'value': params['enforce_quota'], 'include': True},
-                                  'daemonset': {'value': params['daemonset'], 'include': True},
-                                  'tls_key': {'value': params['tls_key'], 'include': True},
-                                  'tls_certificate': {'value': params['tls_certificate'], 'include': True},
-                                 })
+                                 registry_options)
 
 
         ocregistry = Registry(rconfig, params['debug'])
diff --git a/roles/lib_openshift/src/class/oc_adm_registry.py b/roles/lib_openshift/src/class/oc_adm_registry.py
--- a/roles/lib_openshift/src/class/oc_adm_registry.py
+++ b/roles/lib_openshift/src/class/oc_adm_registry.py
@@ -331,25 +331,34 @@ def needs_update(self):
     def run_ansible(params, check_mode):
         '''run idempotent ansible code'''
 
+        registry_options = {'images': {'value': params['images'], 'include': True},
+                            'latest_images': {'value': params['latest_images'], 'include': True},
+                            'labels': {'value': params['labels'], 'include': True},
+                            'ports': {'value': ','.join(params['ports']), 'include': True},
+                            'replicas': {'value': params['replicas'], 'include': True},
+                            'selector': {'value': params['selector'], 'include': True},
+                            'service_account': {'value': params['service_account'], 'include': True},
+                            'mount_host': {'value': params['mount_host'], 'include': True},
+                            'env_vars': {'value': params['env_vars'], 'include': False},
+                            'volume_mounts': {'value': params['volume_mounts'], 'include': False},
+                            'edits': {'value': params['edits'], 'include': False},
+                            'tls_key': {'value': params['tls_key'], 'include': True},
+                            'tls_certificate': {'value': params['tls_certificate'], 'include': True},
+                           }
+
+        # Do not always pass the daemonset and enforce-quota parameters because they are not understood
+        # by old versions of oc.
+        # Default value is false. So, it's safe to not pass an explicit false value to oc versions which
+        # understand these parameters.
+        if params['daemonset']:
+            registry_options['daemonset'] = {'value': params['daemonset'], 'include': True}
+        if params['enforce_quota']:
+            registry_options['enforce_quota'] = {'value': params['enforce_quota'], 'include': True}
+
         rconfig = RegistryConfig(params['name'],
                                  params['namespace'],
                                  params['kubeconfig'],
-                                 {'images': {'value': params['images'], 'include': True},
-                                  'latest_images': {'value': params['latest_images'], 'include': True},
-                                  'labels': {'value': params['labels'], 'include': True},
-                                  'ports': {'value': ','.join(params['ports']), 'include': True},
-                                  'replicas': {'value': params['replicas'], 'include': True},
-                                  'selector': {'value': params['selector'], 'include': True},
-                                  'service_account': {'value': params['service_account'], 'include': True},
-                                  'mount_host': {'value': params['mount_host'], 'include': True},
-                                  'env_vars': {'value': params['env_vars'], 'include': False},
-                                  'volume_mounts': {'value': params['volume_mounts'], 'include': False},
-                                  'edits': {'value': params['edits'], 'include': False},
-                                  'enforce_quota': {'value': params['enforce_quota'], 'include': True},
-                                  'daemonset': {'value': params['daemonset'], 'include': True},
-                                  'tls_key': {'value': params['tls_key'], 'include': True},
-                                  'tls_certificate': {'value': params['tls_certificate'], 'include': True},
-                                 })
+                                 registry_options)
 
 
         ocregistry = Registry(rconfig, params['debug'])


Test patch:
diff --git a/roles/lib_openshift/src/test/unit/test_oc_adm_registry.py b/roles/lib_openshift/src/test/unit/test_oc_adm_registry.py
--- a/roles/lib_openshift/src/test/unit/test_oc_adm_registry.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_adm_registry.py
@@ -254,7 +254,7 @@ def test_state_present(self, mock_cmd, mock_tmpfile_copy, mock_write, mock_oc_bi
         mock_cmd.assert_has_calls([
             mock.call(['oc', 'get', 'dc', 'docker-registry', '-o', 'json', '-n', 'default'], None),
             mock.call(['oc', 'get', 'svc', 'docker-registry', '-o', 'json', '-n', 'default'], None),
-            mock.call(['oc', 'adm', 'registry', '--daemonset=False', '--enforce-quota=False',
+            mock.call(['oc', 'adm', 'registry',
                        '--ports=5000', '--replicas=1', '--selector=type=infra',
                        '--service-account=registry', '--dry-run=True', '-o', 'json', '-n', 'default'], None),
             mock.call(['oc', 'create', '-f', mock.ANY, '-n', 'default'], None),