2020-04-06 23:29:37 +03:00
#!/usr/bin/python3
2020-04-25 00:50:07 +03:00
from typing import Dict , List , Union , Optional
2020-04-06 23:29:37 +03:00
from pathlib import Path
2020-04-22 17:08:51 +03:00
2020-04-06 23:29:37 +03:00
import contextlib
import datetime
import fcntl
import logging
import os
import re
2020-10-30 14:48:12 +03:00
import shutil
2020-04-06 23:29:37 +03:00
import subprocess
2020-04-22 17:08:51 +03:00
import time
2020-04-06 23:29:37 +03:00
import yaml
import cloud_build . image_tests
PROG = ' cloud-build '
2020-04-16 18:24:36 +03:00
# types
PathLike = Union [ Path , str ]
2020-04-06 23:29:37 +03:00
2020-04-20 14:29:27 +03:00
class Error ( Exception ) :
pass
2020-04-21 22:28:41 +03:00
class BuildError ( Error ) :
def __init__ ( self , target : str , arch : str ) :
self . target = target
self . arch = arch
2021-03-19 18:25:39 +03:00
def __str__ ( self ) - > str :
2020-04-21 22:28:41 +03:00
return f ' Fail building of { self . target } { self . arch } '
class MultipleBuildErrors ( Error ) :
def __init__ ( self , build_errors : List [ BuildError ] ) :
self . build_errors = build_errors
2021-03-19 18:25:39 +03:00
def __str__ ( self ) - > str :
2020-06-19 19:27:12 +03:00
s = ' Fail building of the following targets: \n '
2020-04-21 22:28:41 +03:00
s + = ' \n ' . join ( f ' { be . target } { be . arch } ' for be in self . build_errors )
return s
2020-04-06 23:29:37 +03:00
class CB :
""" class for building cloud images """
2020-04-20 17:27:33 +03:00
def __init__ (
self ,
2021-03-19 18:25:39 +03:00
config : str ,
2020-04-20 18:55:00 +03:00
* ,
2021-03-19 18:25:39 +03:00
data_dir : Optional [ PathLike ] = None ,
tasks : Optional [ dict [ str , List [ str ] ] ] = None ,
2020-04-20 17:27:33 +03:00
) - > None :
2020-04-18 01:35:35 +03:00
self . initialized = False
2020-04-18 03:01:07 +03:00
self . _save_cwd = os . getcwd ( )
2020-05-06 15:24:48 +03:00
self . parse_config ( config )
2020-04-25 01:04:10 +03:00
if tasks is None :
self . tasks = { }
else :
self . tasks = tasks
2020-04-06 23:29:37 +03:00
2020-04-20 18:55:00 +03:00
if not data_dir :
data_dir = ( Path ( self . expand_path ( os . getenv ( ' XDG_DATA_HOME ' ,
' ~/.local/share ' ) ) )
/ f ' { PROG } ' )
else :
data_dir = Path ( data_dir )
2020-04-06 23:29:37 +03:00
self . data_dir = data_dir
self . checksum_command = ' sha256sum '
self . images_dir = data_dir / ' images '
self . work_dir = data_dir / ' work '
self . out_dir = data_dir / ' out '
self . service_default_state = ' enabled '
self . created_scripts : List [ Path ] = [ ]
2020-04-21 22:28:41 +03:00
self . _build_errors : List [ BuildError ] = [ ]
2020-04-06 23:29:37 +03:00
self . ensure_dirs ( )
logging . basicConfig (
filename = f ' { data_dir } / { PROG } .log ' ,
format = ' %(levelname)s : %(asctime)s - %(message)s ' ,
)
self . log = logging . getLogger ( PROG )
self . log . setLevel ( self . log_level )
2020-04-20 14:29:27 +03:00
self . ensure_run_once ( )
2020-04-06 23:29:37 +03:00
self . info ( f ' Start { PROG } ' )
2020-04-18 01:35:35 +03:00
self . initialized = True
2020-04-06 23:29:37 +03:00
def __del__ ( self ) - > None :
2020-04-18 01:35:35 +03:00
if not self . initialized :
2020-04-18 02:27:41 +03:00
if getattr ( self , ' lock_file ' , False ) :
self . lock_file . close ( )
2020-04-18 01:35:35 +03:00
return
2021-03-19 18:25:39 +03:00
def unlink ( path ) - > None :
2020-04-06 23:29:37 +03:00
try :
os . unlink ( path )
except FileNotFoundError :
pass
for name in self . created_scripts :
unlink ( name )
unlink ( self . work_dir / f ' mkimage-profiles/conf.d/ { PROG } .mk ' )
2020-04-18 03:01:07 +03:00
os . chdir ( self . _save_cwd )
2020-04-20 23:19:24 +03:00
try :
self . info ( f ' Finish { PROG } ' )
except FileNotFoundError :
pass
2020-04-18 02:27:41 +03:00
self . lock_file . close ( )
2020-04-06 23:29:37 +03:00
2020-04-16 18:24:36 +03:00
def expand_path ( self , path : PathLike ) :
result = os . path . expanduser ( os . path . expandvars ( path ) )
if isinstance ( path , Path ) :
return Path ( result )
else :
return result
2021-03-19 18:25:39 +03:00
def ensure_run_once ( self ) - > None :
2020-04-06 23:29:37 +03:00
self . lock_file = open ( self . data_dir / f ' { PROG } .lock ' , ' w ' )
try :
fcntl . flock ( self . lock_file . fileno ( ) , fcntl . LOCK_EX | fcntl . LOCK_NB )
except OSError : # already locked
2020-04-20 19:56:19 +03:00
dd = self . data_dir
msg = f ' Program { PROG } already running in ` { dd } ` directory '
self . error ( msg )
2020-04-06 23:29:37 +03:00
@contextlib.contextmanager
def pushd ( self , new_dir ) :
previous_dir = os . getcwd ( )
self . debug ( f ' Pushd to { new_dir } ' )
os . chdir ( new_dir )
yield
self . debug ( f ' Popd from { new_dir } ' )
os . chdir ( previous_dir )
def parse_config ( self , config : str ) - > None :
2020-04-18 01:35:35 +03:00
try :
with open ( config ) as f :
cfg = yaml . safe_load ( f )
2020-04-20 19:56:19 +03:00
except OSError as e :
msg = f ' Could not read config file ` { e . filename } `: { e . strerror } '
raise Error ( msg )
2020-04-06 23:29:37 +03:00
2020-04-16 18:24:36 +03:00
self . mkimage_profiles_git = self . expand_path (
2020-04-06 23:29:37 +03:00
cfg . get ( ' mkimage_profiles_git ' , ' ' )
)
self . log_level = getattr ( logging , cfg . get ( ' log_level ' , ' INFO ' ) . upper ( ) )
self . _repository_url = cfg . get ( ' repository_url ' ,
2020-04-25 00:07:45 +03:00
' copy:///space/ALT/ {branch} ' )
2020-04-06 23:29:37 +03:00
2020-04-21 22:28:41 +03:00
self . try_build_all = cfg . get ( ' try_build_all ' , False )
2020-05-06 16:03:06 +03:00
self . no_delete = cfg . get ( ' no_delete ' , True )
2020-04-06 23:29:37 +03:00
self . bad_arches = cfg . get ( ' bad_arches ' , [ ] )
self . external_files = cfg . get ( ' external_files ' )
if self . external_files :
2020-04-16 18:24:36 +03:00
self . external_files = self . expand_path ( Path ( self . external_files ) )
2020-04-06 23:29:37 +03:00
2020-04-22 17:08:51 +03:00
rebuild_after = cfg . get ( ' rebuild_after ' , { ' days ' : 1 } )
try :
self . rebuild_after = datetime . timedelta ( * * rebuild_after )
except TypeError as e :
m = re . match ( r " ' ([^ ' ]+) ' " , str ( e ) )
if m :
arg = m . groups ( ) [ 0 ]
raise Error ( f ' Invalid key ` { arg } ` passed to rebuild_after ' )
else :
raise
2020-04-06 23:29:37 +03:00
self . _packages = cfg . get ( ' packages ' , { } )
self . _services = cfg . get ( ' services ' , { } )
self . _scripts = cfg . get ( ' scripts ' , { } )
2021-04-13 16:02:58 +03:00
self . _after_sync_commands = cfg . get ( ' after_sync_commands ' , [ ] )
2021-04-13 01:01:01 +03:00
self . key = cfg . get ( ' key ' )
if isinstance ( self . key , int ) :
self . key = ' {:X} ' . format ( self . key )
2020-04-06 23:29:37 +03:00
try :
2020-04-16 18:24:36 +03:00
self . _remote = self . expand_path ( cfg [ ' remote ' ] )
2020-04-06 23:29:37 +03:00
self . _images = cfg [ ' images ' ]
self . _branches = cfg [ ' branches ' ]
for _ , branch in self . _branches . items ( ) :
branch [ ' arches ' ] = { k : { } if v is None else v
for k , v in branch [ ' arches ' ] . items ( ) }
except KeyError as e :
msg = f ' Required parameter { e } does not set in config '
2020-04-20 19:56:19 +03:00
raise Error ( msg )
2020-04-06 23:29:37 +03:00
def info ( self , msg : str ) - > None :
self . log . info ( msg )
def debug ( self , msg : str ) - > None :
self . log . debug ( msg )
2020-04-21 22:28:41 +03:00
def error ( self , arg : Union [ str , Error ] ) - > None :
if isinstance ( arg , Error ) :
err = arg
else :
err = Error ( arg )
self . log . error ( err )
raise err
2020-04-06 23:29:37 +03:00
def remote ( self , branch : str ) - > str :
return self . _remote . format ( branch = branch )
def repository_url ( self , branch : str , arch : str ) - > str :
url = self . _branches [ branch ] [ ' arches ' ] [ arch ] . get ( ' repository_url ' )
if url is None :
url = self . _branches [ branch ] . get ( ' repository_url ' ,
self . _repository_url )
return url . format ( branch = branch , arch = arch )
def call (
self ,
cmd : List [ str ] ,
* ,
stdout_to_file : str = ' ' ,
fail_on_error : bool = True ,
) - > None :
def maybe_fail ( string : str , rc : int ) - > None :
if fail_on_error :
if rc != 0 :
msg = ' Command ` {} ` failed with {} return code ' . format (
string ,
rc ,
)
self . error ( msg )
# just_print = True
just_print = False
string = ' ' . join ( cmd )
self . debug ( f ' Call ` { string } ` ' )
if just_print :
print ( string )
else :
if stdout_to_file :
2020-05-06 15:25:35 +03:00
# TODO rewrite using subprocess.run
2020-04-06 23:29:37 +03:00
p = subprocess . Popen ( cmd , stdout = subprocess . PIPE )
rc = p . wait ( )
maybe_fail ( string , rc )
2020-05-06 15:25:35 +03:00
# TODO rewrite by passing f as stdout value
2020-04-06 23:29:37 +03:00
with open ( stdout_to_file , ' w ' ) as f :
if p . stdout :
f . write ( p . stdout . read ( ) . decode ( ) )
2020-04-18 03:06:44 +03:00
if p . stdout is not None :
p . stdout . close ( )
2020-04-06 23:29:37 +03:00
else :
2020-05-06 15:25:35 +03:00
# TODO rewrite using subprocess.run
2020-04-06 23:29:37 +03:00
rc = subprocess . call ( cmd )
maybe_fail ( string , rc )
def ensure_dirs ( self ) - > None :
for attr in dir ( self ) :
if attr . endswith ( ' _dir ' ) :
value = getattr ( self , attr )
if isinstance ( value , str ) or isinstance ( value , os . PathLike ) :
os . makedirs ( value , exist_ok = True )
for branch in self . branches :
os . makedirs ( self . images_dir / branch , exist_ok = True )
def generate_apt_files ( self ) - > None :
apt_dir = self . work_dir / ' apt '
os . makedirs ( apt_dir , exist_ok = True )
for branch in self . branches :
for arch in self . arches_by_branch ( branch ) :
repo = self . repository_url ( branch , arch )
with open ( f ' { apt_dir } /apt.conf. { branch } . { arch } ' , ' w ' ) as f :
apt_conf = f '''
Dir : : Etc : : main " /dev/null " ;
Dir : : Etc : : parts " /var/empty " ;
Dir : : Etc : : SourceList " {apt_dir} /sources.list. {branch} . {arch} " ;
Dir : : Etc : : SourceParts " /var/empty " ;
Dir : : Etc : : preferences " /dev/null " ;
Dir : : Etc : : preferencesparts " /var/empty " ;
2020-04-15 20:02:54 +03:00
''' .lstrip()
2020-04-06 23:29:37 +03:00
f . write ( apt_conf )
with open ( f ' { apt_dir } /sources.list. { branch } . { arch } ' , ' w ' ) as f :
sources_list = f ' rpm { repo } { arch } classic \n '
if arch not in self . bad_arches :
sources_list + = f ' rpm { repo } noarch classic \n '
2020-04-25 01:04:10 +03:00
for task in self . tasks . get ( branch . lower ( ) , [ ] ) :
tr = ' http://git.altlinux.org '
sources_list + = f ' rpm { tr } repo/ { task } / { arch } task \n '
2020-04-06 23:29:37 +03:00
f . write ( sources_list )
def escape_branch ( self , branch : str ) - > str :
return re . sub ( r ' \ . ' , ' _ ' , branch )
def ensure_mkimage_profiles ( self , update : bool = False ) - > None :
""" Checks that mkimage-profiles exists or clones it """
def add_recipe ( variable : str , value : str ) - > str :
return f ' \n \t @$(call add, { variable } , { value } ) '
url = self . mkimage_profiles_git
if url == ' ' :
url = (
' git:// '
+ ' git.altlinux.org/ '
+ ' people/mike/packages/mkimage-profiles.git '
)
os . chdir ( self . work_dir )
if os . path . isdir ( ' mkimage-profiles ' ) :
if update :
with self . pushd ( ' mkimage-profiles ' ) :
self . info ( ' Updating mkimage-profiles ' )
self . call ( [ ' git ' , ' pull ' ] , fail_on_error = False )
else :
self . info ( ' Downloading mkimage-profiles ' )
self . call ( [ ' git ' , ' clone ' , url , ' mkimage-profiles ' ] )
# create file with proper brandings
with self . pushd ( ' mkimage-profiles ' ) :
with open ( f ' conf.d/ { PROG } .mk ' , ' w ' ) as f :
for image in self . images :
target = self . target_by_image ( image )
for branch in self . branches :
ebranch = self . escape_branch ( branch )
prerequisites = [ target ]
prerequisites . extend (
self . prerequisites_by_branch ( branch )
)
prerequisites . extend (
self . prerequisites_by_image ( image )
)
prerequisites_s = ' ' . join ( prerequisites )
branding = self . branding_by_branch ( branch )
if branding :
branding = f ' \n \t @$(call set,BRANDING, { branding } ) '
recipes = [ branding ]
for package in self . packages ( image , branch ) :
recipes . append (
add_recipe (
' BASE_PACKAGES ' ,
package ) )
for service in self . enabled_services ( image , branch ) :
recipes . append (
add_recipe (
' DEFAULT_SERVICES_ENABLE ' ,
service ) )
for service in self . disabled_services ( image , branch ) :
recipes . append (
add_recipe (
' DEFAULT_SERVICES_DISABLE ' ,
service ) )
recipes_s = ' ' . join ( recipes )
rule = f '''
{ target } _ { ebranch } : { prerequisites_s } ; @ : { recipes_s }
''' .strip()
print ( rule , file = f )
self . generate_apt_files ( )
@property
def branches ( self ) - > List [ str ] :
return list ( self . _branches . keys ( ) )
def arches_by_branch ( self , branch : str ) - > List [ str ] :
return list ( self . _branches [ branch ] [ ' arches ' ] . keys ( ) )
def branding_by_branch ( self , branch : str ) - > str :
return self . _branches [ branch ] . get ( ' branding ' , ' ' )
def prerequisites_by_branch ( self , branch : str ) - > List [ str ] :
return self . _branches [ branch ] . get ( ' prerequisites ' , [ ] )
@property
def images ( self ) - > List [ str ] :
return list ( self . _images . keys ( ) )
def kinds_by_image ( self , image : str ) - > List [ str ] :
return self . _images [ image ] [ ' kinds ' ]
2020-07-03 15:24:19 +03:00
def convert_size ( self , size : str ) - > Optional [ str ] :
result = None
multiplier = {
' ' : 1 ,
' k ' : 2 * * 10 ,
' m ' : 2 * * 20 ,
' g ' : 2 * * 30 ,
}
match = re . match (
r ' ^(?P<num> \ d+(:?. \ d+)? ) (?P<suff> [kmg] )?$ ' ,
size ,
re . IGNORECASE | re . VERBOSE ,
)
if not match :
self . error ( ' Bad size format ' )
else :
num = float ( match . group ( ' num ' ) )
suff = match . group ( ' suff ' )
if suff is None :
suff = ' '
mul = multiplier [ str . lower ( suff ) ]
result = str ( round ( num * mul ) )
return result
def size_by_image ( self , image : str ) - > Optional [ str ] :
size = self . _images [ image ] . get ( ' size ' )
if size is not None :
size = self . convert_size ( str ( size ) )
return size
2020-04-06 23:29:37 +03:00
def target_by_image ( self , image : str ) - > str :
return self . _images [ image ] [ ' target ' ]
def prerequisites_by_image ( self , image : str ) - > List [ str ] :
return self . _images [ image ] . get ( ' prerequisites ' , [ ] )
def tests_by_image ( self , image : str ) - > List [ Dict ] :
return self . _images [ image ] . get ( ' tests ' , [ ] )
def scripts_by_image ( self , image : str ) - > Dict [ str , str ] :
scripts = { }
for name , value in self . _scripts . items ( ) :
number = value . get ( ' number ' )
if (
value . get ( ' global ' , False )
and name not in self . _images [ image ] . get ( ' no_scripts ' , [ ] )
or name in self . _images [ image ] . get ( ' scripts ' , [ ] )
) :
if number is not None :
if isinstance ( number , int ) :
number = f ' { number : 02 } '
name = f ' { number } - { name } '
scripts [ name ] = value [ ' contents ' ]
return scripts
def skip_arch ( self , image : str , arch : str ) - > bool :
return arch in self . _images [ image ] . get ( ' exclude_arches ' , [ ] )
2020-04-17 13:48:00 +03:00
def skip_branch ( self , image : str , branch : str ) - > bool :
return branch in self . _images [ image ] . get ( ' exclude_branches ' , [ ] )
2020-04-06 23:29:37 +03:00
def get_items (
self ,
data : Dict ,
image : str ,
branch : str ,
state_re : str = None ,
default_state : str = None ,
) - > List [ str ] :
items = [ ]
if state_re is None :
state_re = ' '
if default_state is None :
default_state = state_re
for item , constraints in data . items ( ) :
2020-05-07 18:23:07 +03:00
if constraints is None :
constraints = { }
2020-04-06 23:29:37 +03:00
if (
image in constraints . get ( ' exclude_images ' , [ ] )
or branch in constraints . get ( ' exclude_branches ' , [ ] )
) :
continue
# Empty means no constraint: e.g. all images
images = constraints . get ( ' images ' , [ image ] )
branches = constraints . get ( ' branch ' , [ branch ] )
state = constraints . get ( ' state ' , default_state )
if (
image in images
and branch in branches
and re . match ( state_re , state )
) :
items . append ( item )
return items
def packages ( self , image : str , branch : str ) - > List [ str ] :
2021-03-03 17:23:54 +03:00
image_packages = self . _images [ image ] . get ( ' packages ' , [ ] )
return image_packages + self . get_items ( self . _packages , image , branch )
2020-04-06 23:29:37 +03:00
def enabled_services ( self , image : str , branch : str ) - > List [ str ] :
2021-03-03 17:23:54 +03:00
image_services = self . _images [ image ] . get ( ' services_enabled ' , [ ] )
return image_services + self . get_items (
2020-04-06 23:29:37 +03:00
self . _services ,
image ,
branch ,
' enabled? ' ,
self . service_default_state ,
)
def disabled_services ( self , image : str , branch : str ) - > List [ str ] :
2021-03-03 17:23:54 +03:00
image_services = self . _images [ image ] . get ( ' services_disabled ' , [ ] )
return image_services + self . get_items (
2020-04-06 23:29:37 +03:00
self . _services ,
image ,
branch ,
' disabled? ' ,
self . service_default_state ,
)
2020-04-21 22:28:41 +03:00
def build_failed ( self , target , arch ) :
if self . try_build_all :
self . _build_errors . append ( BuildError ( target , arch ) )
else :
self . error ( BuildError ( target , arch ) )
2020-04-22 17:08:51 +03:00
def should_rebuild ( self , tarball ) :
if not os . path . exists ( tarball ) :
rebuild = True
else :
lived = time . time ( ) - os . path . getmtime ( tarball )
delta = datetime . timedelta ( seconds = lived )
rebuild = delta > self . rebuild_after
if rebuild :
os . unlink ( tarball )
return rebuild
2020-04-06 23:29:37 +03:00
def build_tarball (
self ,
target : str ,
branch : str ,
arch : str ,
2020-07-03 15:24:19 +03:00
kind : str ,
size : str = None ,
2020-04-25 00:50:07 +03:00
) - > Optional [ Path ] :
2020-04-06 23:29:37 +03:00
target = f ' { target } _ { self . escape_branch ( branch ) } '
image = re . sub ( r ' .*/ ' , ' ' , target )
full_target = f ' { target } . { kind } '
2020-04-22 17:08:51 +03:00
tarball_name = f ' { image } - { arch } . { kind } '
tarball_path = self . out_dir / tarball_name
2020-05-05 21:20:50 +03:00
result : Optional [ Path ] = tarball_path
2020-04-06 23:29:37 +03:00
apt_dir = self . work_dir / ' apt '
with self . pushd ( self . work_dir / ' mkimage-profiles ' ) :
2020-04-22 17:08:51 +03:00
if not self . should_rebuild ( tarball_path ) :
2020-04-06 23:29:37 +03:00
self . info ( f ' Skip building of { full_target } { arch } ' )
else :
cmd = [
' make ' ,
f ' APTCONF= { apt_dir } /apt.conf. { branch } . { arch } ' ,
f ' ARCH= { arch } ' ,
f ' IMAGE_OUTDIR= { self . out_dir } ' ,
2020-04-22 17:08:51 +03:00
f ' IMAGE_OUTFILE= { tarball_name } ' ,
2020-04-06 23:29:37 +03:00
]
2020-07-03 15:24:19 +03:00
if size is not None :
cmd . append ( f ' VM_SIZE= { size } ' )
cmd . append ( full_target )
2020-04-06 23:29:37 +03:00
self . info ( f ' Begin building of { full_target } { arch } ' )
2020-05-15 14:17:43 +03:00
self . call ( cmd , fail_on_error = False )
2020-04-22 17:08:51 +03:00
if os . path . exists ( tarball_path ) :
2020-04-06 23:29:37 +03:00
self . info ( f ' End building of { full_target } { arch } ' )
else :
2020-05-05 21:20:50 +03:00
result = None
2020-04-21 22:28:41 +03:00
self . build_failed ( full_target , arch )
2020-04-06 23:29:37 +03:00
2020-04-25 00:50:07 +03:00
return result
2020-04-06 23:29:37 +03:00
def image_path (
self ,
image : str ,
branch : str ,
arch : str ,
kind : str
) - > Path :
path = (
self . images_dir
/ branch
/ f ' alt- { branch . lower ( ) } - { image } - { arch } . { kind } '
)
return path
def copy_image ( self , src : Path , dst : Path ) - > None :
os . link ( src , dst )
def clear_images_dir ( self ) :
for branch in self . branches :
directory = self . images_dir / branch
for path in directory . iterdir ( ) :
os . unlink ( path )
def remove_old_tarballs ( self ) :
with self . pushd ( self . out_dir ) :
for tb in os . listdir ( ) :
2020-04-22 17:08:51 +03:00
lived = time . time ( ) - os . path . getmtime ( tb )
delta = datetime . timedelta ( seconds = lived )
if delta > self . rebuild_after :
2020-04-06 23:29:37 +03:00
os . unlink ( tb )
def ensure_scripts ( self , image ) :
for name in self . created_scripts :
os . unlink ( name )
self . created_scripts = [ ]
target_type = re . sub ( r ' (?:( \ w+)/)?.* ' , r ' \ 1 ' ,
self . target_by_image ( image ) )
if not target_type :
target_type = ' distro '
scripts_path = (
self . work_dir
/ ' mkimage-profiles '
/ ' features.in '
/ f ' build- { target_type } '
/ ' image-scripts.d '
)
for name , content in self . scripts_by_image ( image ) . items ( ) :
script = scripts_path / name
self . created_scripts . append ( script )
script . write_text ( content )
os . chmod ( script , 0o755 )
2020-04-21 22:28:41 +03:00
def ensure_build_success ( self ) - > None :
if self . _build_errors :
self . error ( MultipleBuildErrors ( self . _build_errors ) )
2021-04-10 01:03:47 +03:00
def create_images ( self , no_tests : bool = False ) - > None :
2020-04-06 23:29:37 +03:00
self . clear_images_dir ( )
2021-03-03 17:03:57 +03:00
self . ensure_mkimage_profiles ( )
2020-04-06 23:29:37 +03:00
for branch in self . branches :
for image in self . images :
2020-04-17 13:48:00 +03:00
if self . skip_branch ( image , branch ) :
continue
2020-04-06 23:29:37 +03:00
self . ensure_scripts ( image )
target = self . target_by_image ( image )
for arch in self . arches_by_branch ( branch ) :
if self . skip_arch ( image , arch ) :
continue
for kind in self . kinds_by_image ( image ) :
2020-07-03 15:24:19 +03:00
size = self . size_by_image ( image )
2020-04-06 23:29:37 +03:00
tarball = self . build_tarball (
2020-07-03 15:24:19 +03:00
target , branch , arch , kind , size
2020-04-06 23:29:37 +03:00
)
2020-04-21 22:28:41 +03:00
if tarball is None :
continue
2020-04-06 23:29:37 +03:00
image_path = self . image_path ( image , branch , arch , kind )
self . copy_image ( tarball , image_path )
2021-04-10 01:03:47 +03:00
if not no_tests :
2020-04-06 23:29:37 +03:00
for test in self . tests_by_image ( image ) :
self . info ( f ' Test { image } { branch } { arch } ' )
if not cloud_build . image_tests . test (
image = image_path ,
branch = branch ,
arch = arch ,
* * test ,
) :
self . error ( f ' Test for { image } failed ' )
2020-04-21 22:28:41 +03:00
self . ensure_build_success ( )
2020-04-06 23:29:37 +03:00
self . remove_old_tarballs ( )
def copy_external_files ( self ) :
if self . external_files :
for branch in os . listdir ( self . external_files ) :
if branch not in self . branches :
self . error ( f ' Unknown branch { branch } in external_files ' )
with self . pushd ( self . external_files / branch ) :
for image in os . listdir ( ) :
self . info ( f ' Copy external image { image } in { branch } ' )
self . copy_image ( image ,
self . images_dir / branch / image )
def sign ( self ) :
2021-04-10 01:03:47 +03:00
if self . key is None :
self . error ( ' Pass key to config file for sign ' )
2020-05-06 15:24:48 +03:00
2020-04-06 23:29:37 +03:00
sum_file = self . checksum_command . upper ( )
for branch in self . branches :
with self . pushd ( self . images_dir / branch ) :
files = [ f
for f in os . listdir ( )
if not f . startswith ( sum_file ) ]
string = ' , ' . join ( files )
cmd = [ self . checksum_command ] + files
self . info ( f ' Calculate checksum of { string } ' )
self . call ( cmd , stdout_to_file = sum_file )
2020-10-30 14:48:12 +03:00
shutil . copyfile ( sum_file , ' SHA256SUMS ' )
2020-04-06 23:29:37 +03:00
self . info ( f ' Sign checksum of { string } ' )
self . call ( [ ' gpg2 ' , ' --yes ' , ' -basu ' , self . key , sum_file ] )
2020-10-30 14:48:12 +03:00
shutil . copyfile ( sum_file + ' .asc ' , ' SHA256SUMS.gpg ' )
2020-04-06 23:29:37 +03:00
2021-04-13 16:02:58 +03:00
def after_sync_commands ( self ) :
2020-04-06 23:29:37 +03:00
remote = self . _remote
colon = remote . find ( ' : ' )
if colon != - 1 :
host = remote [ : colon ]
2021-04-13 16:02:58 +03:00
def cmd ( command ) :
return [ ' ssh ' , host , command ]
else :
host = remote
def cmd ( command ) :
return [ command ]
for command in self . _after_sync_commands :
self . call ( cmd ( command ) )
2020-04-06 23:29:37 +03:00
2021-04-10 01:41:07 +03:00
def sync ( self , create_remote_dirs : bool = False ) - > None :
2020-04-06 23:29:37 +03:00
for branch in self . branches :
remote = self . remote ( branch )
2021-04-10 01:41:07 +03:00
if create_remote_dirs :
2020-04-16 16:17:44 +03:00
os . makedirs ( remote , exist_ok = True )
2020-04-06 23:29:37 +03:00
cmd = [
' rsync ' ,
f ' { self . images_dir } / { branch } / ' ,
' -rv ' ,
remote ,
]
2020-05-06 16:03:06 +03:00
if not self . no_delete :
cmd . append ( ' --delete ' )
2020-04-06 23:29:37 +03:00
self . call ( cmd )
2021-04-13 16:02:58 +03:00
self . after_sync_commands ( )