Add initial support for jobs authentication config

Change-Id: I6da03f7092bf9667d30b2fe2abbb2268bc0f4c58
diff --git a/tests/test_model.py b/tests/test_model.py
index 99cc57f..fa670a4 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -120,6 +120,81 @@
         self.assertEqual(job.name, 'python27')
         self.assertEqual(job.timeout, 50)
 
+    def test_job_auth_inheritance(self):
+        layout = model.Layout()
+        project = model.Project('project')
+
+        base = configloader.JobParser.fromYaml(layout, {
+            '_source_project': project,
+            'name': 'base',
+            'timeout': 30,
+        })
+        layout.addJob(base)
+        pypi_upload_without_inherit = configloader.JobParser.fromYaml(layout, {
+            '_source_project': project,
+            'name': 'pypi-upload-without-inherit',
+            'parent': 'base',
+            'timeout': 40,
+            'auth': {
+                'password': {
+                    'pypipassword': 'dummypassword'
+                }
+            }
+        })
+        layout.addJob(pypi_upload_without_inherit)
+        pypi_upload_with_inherit = configloader.JobParser.fromYaml(layout, {
+            '_source_project': project,
+            'name': 'pypi-upload-with-inherit',
+            'parent': 'base',
+            'timeout': 40,
+            'auth': {
+                'inherit': True,
+                'password': {
+                    'pypipassword': 'dummypassword'
+                }
+            }
+        })
+        layout.addJob(pypi_upload_with_inherit)
+        pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
+            layout, {
+                '_source_project': project,
+                'name': 'pypi-upload-with-inherit-false',
+                'parent': 'base',
+                'timeout': 40,
+                'auth': {
+                    'inherit': False,
+                    'password': {
+                        'pypipassword': 'dummypassword'
+                    }
+                }
+            })
+        layout.addJob(pypi_upload_with_inherit_false)
+        in_repo_job_without_inherit = configloader.JobParser.fromYaml(layout, {
+            '_source_project': project,
+            'name': 'in-repo-job-without-inherit',
+            'parent': 'pypi-upload-without-inherit',
+        })
+        layout.addJob(in_repo_job_without_inherit)
+        in_repo_job_with_inherit = configloader.JobParser.fromYaml(layout, {
+            '_source_project': project,
+            'name': 'in-repo-job-with-inherit',
+            'parent': 'pypi-upload-with-inherit',
+        })
+        layout.addJob(in_repo_job_with_inherit)
+        in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
+            layout, {
+                '_source_project': project,
+                'name': 'in-repo-job-with-inherit-false',
+                'parent': 'pypi-upload-with-inherit-false',
+            })
+        layout.addJob(in_repo_job_with_inherit_false)
+
+        self.assertNotIn('auth', in_repo_job_without_inherit.auth)
+        self.assertIn('password', in_repo_job_with_inherit.auth)
+        self.assertEquals(in_repo_job_with_inherit.auth['password'],
+                          {'pypipassword': 'dummypassword'})
+        self.assertNotIn('auth', in_repo_job_with_inherit_false.auth)
+
     def test_job_inheritance_job_tree(self):
         layout = model.Layout()
 
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 70a880d..65ec803 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -40,17 +40,23 @@
 class JobParser(object):
     @staticmethod
     def getSchema():
-        # TODOv3(jeblair, jhesketh): move to auth
-        swift = {vs.Required('name'): str,
-                 'container': str,
-                 'expiry': int,
-                 'max_file_size': int,
-                 'max-file-size': int,
-                 'max_file_count': int,
-                 'max-file-count': int,
-                 'logserver_prefix': str,
-                 'logserver-prefix': str,
-                 }
+        swift_tmpurl = {vs.Required('name'): str,
+                        'container': str,
+                        'expiry': int,
+                        'max_file_size': int,
+                        'max-file-size': int,
+                        'max_file_count': int,
+                        'max-file-count': int,
+                        'logserver_prefix': str,
+                        'logserver-prefix': str,
+                        }
+
+        password = {str: str}
+
+        auth = {'password': to_list(password),
+                'inherit': bool,
+                'swift-tmpurl': to_list(swift_tmpurl),
+                }
 
         node = {vs.Required('name'): str,
                 vs.Required('image'): str,
@@ -68,7 +74,7 @@
                'tags': to_list(str),
                'branches': to_list(str),
                'files': to_list(str),
-               'swift': to_list(swift),
+               'auth': to_list(auth),
                'irrelevant-files': to_list(str),
                'nodes': [node],
                'timeout': int,
@@ -81,6 +87,8 @@
     def fromYaml(layout, conf):
         JobParser.getSchema()(conf)
         job = model.Job(conf['name'])
+        if 'auth' in conf:
+            job.auth = conf.get('auth')
         if 'parent' in conf:
             parent = layout.getJob(conf['parent'])
             job.inheritFrom(parent)
diff --git a/zuul/launcher/client.py b/zuul/launcher/client.py
index 8448422..07a5e38 100644
--- a/zuul/launcher/client.py
+++ b/zuul/launcher/client.py
@@ -259,7 +259,7 @@
         # NOTE(jhesketh): The params need to stay in a key=value data pair
         # as workers cannot necessarily handle lists.
 
-        if job.swift and self.swift.connection:
+        if 'swift' in job.auth and self.swift.connection:
 
             for name, s in job.swift.items():
                 swift_instructions = {}
diff --git a/zuul/model.py b/zuul/model.py
index fd2f626..b85c80d 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -502,7 +502,7 @@
         timeout=None,
         # variables={},
         nodes=[],
-        # auth={},
+        auth={},
         workspace=None,
         pre_run=None,
         post_run=None,
@@ -516,7 +516,6 @@
         branch_matcher=None,
         file_matcher=None,
         irrelevant_file_matcher=None,  # skip-if
-        swift=None,  # TODOv3(jeblair): move to auth
         parameter_function=None,  # TODOv3(jeblair): remove
         success_pattern=None,  # TODOv3(jeblair): remove
         tags=set(),
@@ -555,8 +554,11 @@
         if not isinstance(other, Job):
             raise Exception("Job unable to inherit from %s" % (other,))
         for k, v in self.attributes.items():
-            if getattr(other, k) != v:
+            if getattr(other, k) != v and k != 'auth':
                 setattr(self, k, getattr(other, k))
+        # Inherit auth only if explicitly allowed
+        if other.auth and 'inherit' in other.auth and other.auth['inherit']:
+            setattr(self, 'auth', getattr(other, 'auth'))
 
     def changeMatches(self, change):
         if self.branch_matcher and not self.branch_matcher.matches(change):