grim/local-pipelines
Clone
Summary
Browse
Changes
Graph
pypi is verify specific about this
draft
2016-09-10, Gary Kramlich
40a54f95d0e4
pypi is verify specific about this
# vi:et:ts=4 sw=4 sts=4
#
# local-pipelines : run Bitbucket pipelines locally
# Copyright (C) 2016 Gary Kramlich <grim@reaperworld.com>
# Copyright (C) 2016 Sean Farley <sean@farley.io>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import
fnmatch
import
os
import
subprocess
from
pipelines.step
import
Step
class
Pipeline
(
object
):
def
__init__
(
self
,
config
,
path
,
repo
,
env
=
None
):
self
.
config
=
config
self
.
build_path
=
os
.
path
.
abspath
(
path
)
self
.
repo
=
repo
self
.
_env
=
env
or
{}
# add special variables
self
.
_env
[
"BITBUCKET_BRANCH"
]
=
self
.
repo
.
branch
self
.
_env
[
"BITBUCKET_COMMIT"
]
=
self
.
repo
.
node
self
.
_env
[
"BITBUCKET_REPO_OWNER"
]
=
self
.
repo
.
owner
self
.
_env
[
"BITBUCKET_REPO_SLUG"
]
=
self
.
repo
.
slug
@property
def
steps
(
self
):
ret
=
self
.
config
[
"pipelines"
][
"default"
]
if
"branches"
in
self
.
config
[
"pipelines"
]:
branches
=
self
.
config
[
"pipelines"
][
"branches"
]
for
pattern
,
steps
in
branches
.
items
():
if
fnmatch
.
fnmatch
(
self
.
repo
.
branch
,
pattern
):
ret
=
steps
break
return
[
Step
.
from_dict
(
s
[
"step"
])
for
s
in
ret
]
@property
def
public_env
(
self
):
# dict of env vars that are not none
return
{
k
:
v
for
k
,
v
in
self
.
_env
.
items
()
if
v
is
not
None
}
@property
def
private_env
(
self
):
# dict of env vars that are none (grabbed from os.environ)
return
{
k
:
os
.
environ
.
get
(
k
,
""
)
for
k
,
v
in
self
.
_env
.
items
()
if
v
is
None
}
def
_determine_image
(
self
,
step
):
return
step
.
image
or
self
.
config
.
get
(
"image"
)
def
_get_command
(
self
,
step
,
script
):
workdir
=
"/opt/atlassian/bitbucketci/agent/build"
labels
=
[
"com.atlassian.pipelines.agent=
\"
local
\"
"
,
]
# docker on windows needs to convert to a posix path
drive
,
spath
=
os
.
path
.
splitdrive
(
script
)
if
drive
:
# strip off the colon, e.g. 'C:' -> 'C'
drive
=
drive
[:
-
1
]
# assemble the pieces together
script
=
'/'
+
drive
+
spath
.
replace
(
'
\\
'
,
'/'
)
volumes
=
[
"
{filename}
:
{filename}
:ro"
.
format
(
filename
=
script
),
"
{path}
:
{workdir}
:rw"
.
format
(
path
=
self
.
build_path
,
workdir
=
workdir
,
),
]
cmd
=
[
"docker"
,
"run"
,
"--rm=true"
,
"--entrypoint=/bin/bash"
,
"--memory=2048m"
,
"-it"
,
" "
.
join
([
"-v "
+
v
for
v
in
volumes
]),
"-w
{}
"
.
format
(
workdir
),
" "
.
join
([
"--label "
+
l
for
l
in
labels
]),
" "
.
join
([
"-e
{}
=
{}
"
.
format
(
k
,
v
)
for
k
,
v
in
self
.
public_env
.
items
()]),
" "
.
join
([
"-e "
+
k
for
k
in
self
.
private_env
.
keys
()]),
self
.
_determine_image
(
step
),
script
,
]
return
" "
.
join
(
cmd
)
def
run
(
self
):
return_code
=
0
subenv
=
os
.
environ
.
copy
()
subenv
.
update
(
self
.
private_env
)
for
n
,
step
in
enumerate
(
self
.
steps
):
script
=
step
.
script_file
()
command
=
self
.
_get_command
(
step
,
script
)
try
:
print
(
'+
{}
'
.
format
(
command
))
proc
=
subprocess
.
Popen
(
command
.
split
(),
env
=
subenv
)
return_code
=
proc
.
wait
()
if
return_code
!=
0
:
return
return_code
except
KeyboardInterrupt
:
# let's not stacktrace on ctrl-c
proc
.
kill
()
return_code
=
1
break
finally
:
try
:
os
.
remove
(
script
)
except
OSError
:
pass
return
return_code