-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathbuild.py
More file actions
136 lines (122 loc) · 5.1 KB
/
build.py
File metadata and controls
136 lines (122 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import logging
from django.db import models
from django.contrib.auth import get_user_model
from api.exceptions import DryccException
from .base import UuidAuditedModel, PTYPE_WEB
from .config import Config
User = get_user_model()
logger = logging.getLogger(__name__)
class Build(UuidAuditedModel):
"""
Instance of a software build used by runtime nodes
"""
owner = models.ForeignKey(User, on_delete=models.PROTECT)
app = models.ForeignKey('App', on_delete=models.CASCADE)
image = models.TextField()
stack = models.CharField(max_length=32)
# optional fields populated by builder
sha = models.CharField(max_length=40, blank=True)
procfile = models.JSONField(default=dict, blank=True)
dryccfile = models.JSONField(default=dict, blank=True)
dockerfile = models.TextField(blank=True)
class Meta:
get_latest_by = 'created'
ordering = ['-created']
unique_together = (('app', 'uuid'),)
@property
def type(self):
"""Figures out what kind of build type is being deal it with"""
if self.dockerfile:
return 'dockerfile'
elif self.sha:
return 'buildpack'
else:
# container image (or any sort of image) used via drycc pull
return 'image'
@property
def ptypes(self):
if self.dryccfile:
return list(self.dryccfile['deploy'].keys())
if self.procfile:
return list(self.procfile.keys())
return [PTYPE_WEB]
@property
def source_based(self):
"""
Checks if a build is source (has a sha) based or not
If True then the Build is coming from the drycc builder or something that
built from git / svn / hg / etc directly
"""
return self.sha != ''
@property
def version(self):
return 'git-{}'.format(self.sha) if self.source_based else 'latest'
def get_image(self, ptype, default_image=None):
docker = self.dryccfile.get('build', {}).get('docker', {})
if ptype in docker:
if ptype == 'web':
return self.image
else:
return f'{self.image}-{ptype}'
return default_image if default_image else self.image
def create_release(self, user, *args, **kwargs):
latest_release = self.app.release_set.filter(failed=False).latest()
try:
new_release = latest_release.new(
user,
build=self,
config=self._get_or_create_config(),
)
if self.app.appsettings_set.latest().autodeploy:
new_release.deploy(force_deploy=False)
return new_release
except Exception as e:
# check if the exception is during create or publish
latest_version = self.app.release_set.latest().version
if ('new_release' not in locals() and
self.app.release_set.latest().version == latest_version+1):
new_release = self.app.release_set.latest()
new_release.state = "crashed"
new_release.failed = True
if new_release.summary:
new_release.summary += " "
new_release.summary += "{} deployed {} which failed".format(
self.owner, str(self.uuid)[:7])
# Get the exception that has occured
new_release.exception = "error: {}".format(str(e))
# avoid overwriting other fields
new_release.save(update_fields=["state", "failed", "summary", "exception"])
if 'new_release' not in locals():
self.delete()
raise DryccException(str(e)) from e
def __str__(self):
return "{0}-{1}".format(self.app.id, str(self.uuid)[:7])
def _get_or_create_config(self):
"""
dryccfile to config
"""
latest_release = self.app.release_set.filter(failed=False).latest()
config_values, config_values_ref, config_healthcheck = [], {}, {}
for group, values in self.dryccfile.get('config', {}).items():
for value in values:
value['group'] = group
config_values.append(value)
for ptype, values in self.dryccfile.get('deploy', {}).items():
for value in values.get('config', {}).get('env', []):
value['ptype'] = ptype
config_values.append(value)
for config_ref in values.get('config', {}).get('ref', []):
if ptype not in config_values_ref:
config_values_ref[ptype] = [config_ref]
else:
config_values_ref[ptype].append(config_ref)
if 'healthcheck' in values:
config_healthcheck[ptype] = values.get('healthcheck')
if not config_values:
return latest_release.config
config = Config(
owner=self.owner, app=self.app, values=config_values, values_refs=config_values_ref,
healthcheck=config_healthcheck,
)
config.save(ignore_update_fields=["values", "values_refs", "healthcheck"])
return config