-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathbuild.py
More file actions
155 lines (135 loc) · 5.76 KB
/
build.py
File metadata and controls
155 lines (135 loc) · 5.76 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import logging
from django.conf import settings
from django.db import models
from django.contrib.auth import get_user_model
from api.exceptions import DryccException, Conflict
from api.tasks import run_pipeline
from .base import UuidAuditedModel
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 procfile_types(self):
if self.dryccfile:
return list(self.dryccfile['deploy'].keys())
return list(self.procfile.keys())
@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, procfile_type, default_image=None):
docker = self.dryccfile.get('build', {}).get('docker', {})
if procfile_type in docker:
if procfile_type == 'web':
return self.image
else:
return f'{self.image}-{procfile_type}'
return default_image if default_image else self.image
def create_release(self, user, *args, **kwargs):
app_settings = self.app.appsettings_set.latest()
latest_release = self.app.release_set.filter(failed=False).latest()
latest_version = self.app.release_set.latest().version
try:
new_release = latest_release.new(
user,
build=self,
config=latest_release.config,
canary=len(app_settings.canaries) > 0,
)
run_pipeline.delay(new_release)
return new_release
except Exception as e:
# check if the exception is during create or publish
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
new_release.summary = "{} deployed {} which failed".format(self.owner, str(self.uuid)[:7]) # noqa
# Get the exception that has occured
new_release.exception = "error: {}".format(str(e))
new_release.save()
if 'new_release' not in locals():
self.delete()
raise DryccException(str(e)) from e
def save(self, **kwargs):
previous_release = self.app.release_set.filter(failed=False).latest()
if (
settings.DRYCC_DEPLOY_REJECT_IF_PROCFILE_MISSING is True and
# previous release had a Procfile and the current one does not
(
previous_release.build is not None and
len(previous_release.procfile_types) > 0 and
len(self.procfile_types) == 0
)
):
# Reject deployment
raise Conflict(
'Last deployment had process types but is missing in this deploy. '
'For a successful deployment provide process types.'
)
# See if processes are permitted to be removed
remove_procs = (
# If set to True then contents of Procfile does not affect the outcome
settings.DRYCC_DEPLOY_PROCFILE_MISSING_REMOVE is True or
# previous release had a Procfile and the current one does as well
(
previous_release.build is not None and
len(previous_release.procfile_types) > 0 and
len(self.procfile_types) > 0
)
)
# spin down any proc type removed between the last procfile and the newest one
if remove_procs and previous_release.build is not None:
removed = {}
for proc in previous_release.procfile_types:
if proc not in self.procfile_types and self.app.structure.get(proc, 0) > 0:
# Scale proc type down to 0
removed[proc] = 0
self.app.scale(self.owner, removed)
# make sure the latest build has procfile if the intent is to
# allow empty Procfile without removals
if (
settings.DRYCC_DEPLOY_PROCFILE_MISSING_REMOVE is False and
previous_release.build is not None and
len(previous_release.procfile_types) > 0 and
len(self.procfile_types) == 0
):
self.procfile = previous_release.build.procfile
self.dryccfile = previous_release.build.dryccfile
return super(Build, self).save(**kwargs)
def __str__(self):
return "{0}-{1}".format(self.app.id, str(self.uuid)[:7])