1# -*- coding: utf-8 -*- #
2# Copyright 2020 Google LLC. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Wraps a Cloud Run job message with convenience methods."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import unicode_literals
20
21import enum
22from googlecloudsdk.api_lib.run import container_resource
23from googlecloudsdk.api_lib.run import k8s_object
24
25AUTHOR_ANNOTATION = k8s_object.RUN_GROUP + '/creator'
26
27STARTED_CONDITION = 'Started'
28COMPLETED_CONDITION = 'Completed'
29
30
31class RestartPolicy(enum.Enum):
32  NEVER = 'Never'
33  ON_FAILURE = 'OnFailure'
34
35
36class Job(k8s_object.KubernetesObject):
37  """Wraps a Cloud Run job message, making fields more convenient."""
38
39  API_CATEGORY = 'run.googleapis.com'
40  KIND = 'Job'
41  READY_CONDITION = COMPLETED_CONDITION
42  TERMINAL_CONDITIONS = frozenset({STARTED_CONDITION, READY_CONDITION})
43
44  @classmethod
45  def New(cls, client, namespace):
46    """Produces a new Job object.
47
48    Args:
49      client: The Cloud Run API client.
50      namespace: str, The serving namespace.
51
52    Returns:
53      A new Job object to be deployed.
54    """
55    ret = super(Job, cls).New(client, namespace)
56    ret.spec.template.spec.containers = [client.MESSAGES_MODULE.Container()]
57    return ret
58
59  class InstanceTemplateSpec(container_resource.ContainerResource):
60    """Wrapper class for Job subfield InstanceTemplateSpec."""
61
62    KIND = 'InstanceTemplateSpec'
63
64    @classmethod
65    def SpecAndAnnotationsOnly(cls, job):
66      """Special wrapper for spec only that also covers metadata annotations.
67
68      For a message type without its own metadata, like InstanceTemplateSpec,
69      metadata fields should either raise AttributeErrors or refer to the
70      metadata of a different message depending on use case. This method handles
71      the annotations of metadata by referencing the parent job's annotations.
72      All other metadata fields will fall through to k8s_object which will
73      lead to AttributeErrors.
74
75      Args:
76        job: The parent job for this InstanceTemplateSpec
77
78      Returns:
79        A new k8s_object to wrap the InstanceTemplateSpec with only the spec
80        fields and the metadata annotations.
81      """
82      spec_wrapper = super(Job.InstanceTemplateSpec,
83                           cls).SpecOnly(job.spec.template.spec,
84                                         job.MessagesModule())
85      # pylint: disable=protected-access
86      spec_wrapper._annotations = job.annotations
87      return spec_wrapper
88
89    @property
90    def annotations(self):
91      """Override to return the parent job's annotations."""
92      try:
93        return self._annotations
94      except AttributeError:
95        raise ValueError(
96            'Job templates do not have their own annotations. Initialize the '
97            'wrapper with SpecAndAnnotationsOnly to be able to use annotations.'
98        )
99
100    @property
101    def restart_policy(self):
102      """Returns the enum version of the restart policy."""
103      return RestartPolicy(self.spec.restartPolicy)
104
105    @restart_policy.setter
106    def restart_policy(self, enum_value):
107      self.spec.restartPolicy = enum_value.value
108
109    @property
110    def service_account(self):
111      """The service account to use as the container identity."""
112      return self.spec.serviceAccountName
113
114    @service_account.setter
115    def service_account(self, value):
116      self.spec.serviceAccountName = value
117
118  @property
119  def template(self):
120    return Job.InstanceTemplateSpec.SpecAndAnnotationsOnly(self)
121
122  @property
123  def author(self):
124    return self.annotations.get(AUTHOR_ANNOTATION)
125
126  @property
127  def image(self):
128    return self.template.image
129
130  @image.setter
131  def image(self, value):
132    self.template.image = value
133
134  @property
135  def parallelism(self):
136    return self.spec.parallelism
137
138  @parallelism.setter
139  def parallelism(self, value):
140    self.spec.parallelism = value
141
142  @property
143  def completions(self):
144    return self.spec.completions
145
146  @completions.setter
147  def completions(self, value):
148    self.spec.completions = value
149
150  @property
151  def backoff_limit(self):
152    return self.spec.backoffLimit
153
154  @backoff_limit.setter
155  def backoff_limit(self, value):
156    self.spec.backoffLimit = value
157