(the beginning)
The algorithm is basically as follows:
for each dir:
if allowed:
for each file (of this dir):
if allowed: add
- The iteration starts with
.
. - Every subdirectory of
.
(at any level) is iterated (whether its parent directories are allowed or not). - A file or a directory is allowed if all its prefixes are allowed.
- Prefixes of
a/b/c
area
,a/b
anda/b/c
. Prefixes of./a
are.
and./a
.
The relevant parts of the source code are in:
/google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
(MakeZipFromDir()
)/google-cloud-sdk/lib/googlecloudsdk/command_lib/util/gcloudignore.py
(FileChooser.IsIncluded()
)/google-cloud-sdk/lib/googlecloudsdk/command_lib/util/glob.py
(Glob._MatchesHelper()
)
The tests below were run in a google/cloud-sdk:502.0.0-alpine
container:
$ docker run --rm -itv "$PWD:/app" -w /app google/cloud-sdk:502.0.0-alpine
For *.bats
tests you will need:
/app # git clone https://github.com/bats-core/bats-core
/app # git clone https://github.com/bats-core/bats-support
/app # git clone https://github.com/bats-core/bats-assert
/app # apk add patch ncurses
ncurses
provides tput
which makes the output more human-readable.
For tests that invoke gcloud
you will need to authenticate:
/app # gcloud auth login
replace PROJECT
and REGION
and the following patch:
archive.py.patch
:
--- /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
+++ /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
@@ -84,6 +84,8 @@
AddToArchive(zip_file, src_dir, file_path, True)
finally:
zip_file.close()
+ shutil.copy(dest_zip_file, "/tmp/out.zip")
+ import sys; sys.exit()
def AddToArchive(zip_file, src_dir, rel_path, is_file):
- Pattern anchoring
- Unignoring a file
- Unignoring a directory
- Unignoring a file in a directory
- Unignoring a directory in a directory
!a/
trick- Scripts for producing verbose output
gcloud
under the hood uses the Glob
class (see /google-cloud-sdk/lib/googlecloudsdk/command_lib/util/glob.py
).
a.py
:
"""
Patterns are anchored to the end of a path:
>>> Glob('b').Matches('a/b')
True
>>> not Glob('a').Matches('a/b')
True
Patterns the first segment of which is * are additionally anchored to a beginning:
>>> Glob('*').Matches('aa')
True
>>> not Glob('*').Matches('aa/bb')
True
>>> Glob('*/bb').Matches('aa/bb')
True
>>> not Glob('*/cc').Matches('aa/bb/cc')
True
>>> Glob('aa/*').Matches('aa/bb')
True
>>> Glob('bb/*').Matches('aa/bb/cc')
True
>>> Glob('*b/cc').Matches('aa/bb/cc')
True
But when such patterns get applied to a path that starts with `?/` (the first
segment is one character long), `*` can match the second segment of the path:
>>> Glob('*').Matches('a/b')
True
>>> not Glob('*').Matches('aa/b')
True
>>> Glob('*').Matches('a/bb')
True
>>> Glob('*/c').Matches('a/b/c')
True
>>> Glob('*/c/d').Matches('a/b/c/d')
True
>>> not Glob('*/d').Matches('a/b/c/d')
True
"""
import sys
sys.path.insert(0, '/venv/lib/python3.11/site-packages')
sys.path.insert(0, '/google-cloud-sdk/lib/third_party')
sys.path.insert(0, '/google-cloud-sdk/lib')
from googlecloudsdk.command_lib.util.glob import Glob
/app # python -m doctest a.py
a.bats
:
ml() { for l; do echo "$l"; done; }
setup() {
load 'bats-support/load'
load 'bats-assert/load'
(cd .. && patch -p0 < app/archive.py.patch)
}
teardown() {
(cd .. && patch -Rp0 < app/archive.py.patch)
rm -rf /tmp/gcloudignore
}
_run() {
local layout=$1 gcloudignore=$2
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
local l
while IFS= read -r l; do
if [[ $l == */* ]]; then
local p
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
touch go.mod
echo "$gcloudignore" > .gcloudignore
gcloud functions deploy a \
--project PROJECT \
--gen2 \
--runtime go121 \
--region REGION \
--trigger-http
unzip -l /tmp/out.zip \
| tail +4 | head -n -2 \
| awk '{print $4}' \
| grep -vE '/$' \
| grep -Fve .gcloudignore -e go.mod \
|| true
}
# setup() {
# load 'bats-support/load'
# load 'bats-assert/load'
# }
#
# teardown() {
# rm -rf /tmp/gcloudignore
# }
#
# _run() {
# local layout=$1 gcloudignore=$2
# mkdir /tmp/gcloudignore
# cd /tmp/gcloudignore
# local l
# while IFS= read -r l; do
# if [[ $l == */* ]]; then
# local p
# p=${l%/*}
# mkdir -p "$p"
# touch "$l"
# else
# touch "$l"
# fi
# done < <(echo "$layout")
# echo "$gcloudignore" > .gcloudignore
# local code
# code=`ml \
# 'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
# 'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
# 'fc = FileChooser.FromFile(".gcloudignore")' \
# 'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
# PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
# python -c "$code"
# unzip -l /tmp/out.zip \
# | tail +4 | head -n -2 \
# | awk '{print $4}' \
# | grep -vE '/$' \
# | grep -Fve .gcloudignore -e go.mod \
# || true
# }
@test 'b ~ a/b' {
layout=`ml \
a/b`
gcloudignore=`ml \
'b'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test 'a ~ a/b' {
layout=`ml \
a/b`
gcloudignore=`ml \
'a' \
'!/a'`
run _run "$layout" "$gcloudignore"
assert_output \
a/b
}
@test '* ~ aa' {
# if layout were aa, the path would be ./aa
layout=`ml \
aa/b`
gcloudignore=`ml \
'*'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test '* ~ aa/bb' {
layout=`ml \
aa/bb`
gcloudignore=`ml \
'*' \
'!/aa'`
run _run "$layout" "$gcloudignore"
assert_output \
aa/bb
}
@test '*/bb ~ aa/bb' {
layout=`ml \
aa/bb`
gcloudignore=`ml \
'*/bb'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test '*/cc ~ aa/bb/cc' {
layout=`ml \
aa/bb/cc`
gcloudignore=`ml \
'*/cc'`
run _run "$layout" "$gcloudignore"
assert_output \
aa/bb/cc
}
@test 'aa/* ~ aa/bb' {
layout=`ml \
aa/bb`
gcloudignore=`ml \
'aa/*'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test 'bb/* ~ aa/bb/cc' {
layout=`ml \
aa/bb/cc`
gcloudignore=`ml \
'bb/*'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test '*b/cc ~ aa/bb/cc' {
layout=`ml \
aa/bb/cc`
gcloudignore=`ml \
'*b/cc'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test '* ~ a/b' {
layout=`ml \
a/b`
gcloudignore=`ml \
'*' \
'!/a'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test '* ~ aa/b' {
layout=`ml \
aa/b`
gcloudignore=`ml \
'*' \
'!/aa'`
run _run "$layout" "$gcloudignore"
assert_output \
aa/b
}
@test '* ~ a/bb' {
layout=`ml \
a/bb`
gcloudignore=`ml \
'*' \
'!/a'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test '*/c ~ a/b/c' {
layout=`ml \
a/b/c`
gcloudignore=`ml \
'*/c'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test '*/c/d ~ a/b/c/d' {
layout=`ml \
a/b/c/d`
gcloudignore=`ml \
'*/c/d'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test '*/d ~ a/b/c/d' {
layout=`ml \
a/b/c/d`
gcloudignore=`ml \
'*/d'`
run _run "$layout" "$gcloudignore"
assert_output \
a/b/c/d
}
/app # bats-core/bin/bats a.bats
a.bats
✓ b ~ a/b
✓ a ~ a/b
✓ * ~ aa
✓ * ~ aa/bb
✓ */bb ~ aa/bb
✓ */cc ~ aa/bb/cc
✓ aa/* ~ aa/bb
✓ bb/* ~ aa/bb/cc
✓ *b/cc ~ aa/bb/cc
✓ * ~ a/b
✓ * ~ aa/b
✓ * ~ a/bb
✓ */c ~ a/b/c
✓ */c/d ~ a/b/c/d
✓ */d ~ a/b/c/d
15 tests, 0 failures
.
├── a
│ └── b
├── c
└── d
*
!/c
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
a
dir:a
matches*
a
is disallowed
c
was ignored because .
was disallowed.
.
├── a
│ └── b
├── c
└── d
*
!.
!/c
- It considers the
.
dir:.
matches*
,!.
.
is ALLOWED
- It considers the
./c
file:.
matches*
,!.
./c
matches*
,!/c
./c
is ADDED
- It considers the
./go.mod
file:.
matches*
,!.
./go.mod
matches*
./go.mod
is ignored
- It considers the
./.gcloudignore
file:.
matches*
,!.
./.gcloudignore
matches*
./.gcloudignore
is ignored
- It considers the
./d
file:.
matches*
,!.
./d
matches*
./d
is ignored
- It considers the
a
dir:a
matches*
a
is disallowed
It worked, but ./c
and ./d
matched *
.
b.bats
:
ml() { for l; do echo "$l"; done; }
setup() {
load 'bats-support/load'
load 'bats-assert/load'
(cd .. && patch -p0 < app/archive.py.patch)
}
teardown() {
(cd .. && patch -Rp0 < app/archive.py.patch)
rm -rf /tmp/gcloudignore
}
_run() {
local layout=$1 gcloudignore=$2
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
local l
while IFS= read -r l; do
if [[ $l == */* ]]; then
local p
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
touch go.mod
echo "$gcloudignore" > .gcloudignore
gcloud functions deploy a \
--project PROJECT \
--gen2 \
--runtime go121 \
--region REGION \
--trigger-http
unzip -l /tmp/out.zip \
| tail +4 | head -n -2 \
| awk '{print $4}' \
| grep -vE '/$' \
| grep -Fve .gcloudignore -e go.mod \
|| true
}
# setup() {
# load 'bats-support/load'
# load 'bats-assert/load'
# }
#
# teardown() {
# rm -rf /tmp/gcloudignore
# }
#
# _run() {
# local layout=$1 gcloudignore=$2
# mkdir /tmp/gcloudignore
# cd /tmp/gcloudignore
# local l
# while IFS= read -r l; do
# if [[ $l == */* ]]; then
# local p
# p=${l%/*}
# mkdir -p "$p"
# touch "$l"
# else
# touch "$l"
# fi
# done < <(echo "$layout")
# echo "$gcloudignore" > .gcloudignore
# local code
# code=`ml \
# 'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
# 'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
# 'fc = FileChooser.FromFile(".gcloudignore")' \
# 'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
# PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
# python -c "$code"
# unzip -l /tmp/out.zip \
# | tail +4 | head -n -2 \
# | awk '{print $4}' \
# | grep -vE '/$' \
# | grep -Fve .gcloudignore -e go.mod \
# || true
# }
@test 'unignore a file' {
layout=`ml \
a/b \
c \
d`
gcloudignore=`ml \
'*' \
'!/c'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test 'unignore a file (!.)' {
layout=`ml \
a/b \
c \
d`
gcloudignore=`ml \
'*' \
'!.' \
'!/c'`
run _run "$layout" "$gcloudignore"
assert_output \
c
}
/app # bats-core/bin/bats b.bats
b.bats
✓ unignore a file
✓ unignore a file (!.)
2 tests, 0 failures
.
├── a
│ └── b
├── c
│ └── d
└── e
*
!/a
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
c
dir:c
matches*
c
is disallowed
- It considers the
a
dir:a
matches*
,!/a
a
is ALLOWED
- It considers the
a/b
file:a
matches*
,!/a
a/b
matches*
a/b
is ignored
a/b
was ignored because it matched *
.
.
├── aa
│ └── b
├── c
│ └── d
└── e
*
!/aa
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
c
dir:c
matches*
c
is disallowed
- It considers the
aa
dir:aa
matches*
,!/aa
aa
is ALLOWED
- It considers the
aa/b
file:aa
matches*
,!/aa
aa/b
matches nothingaa/b
is ADDED
It worked.
.
├── a
│ └── b
├── c
│ └── d
└── e
*
!/a/**
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
c
dir:c
matches*
c
is disallowed
- It considers the
a
dir:a
matches*
,!/a/**
a
is ALLOWED
- It considers the
a/b
file:a
matches*
,!/a/**
a/b
matches*
,!/a/**
a/b
is ADDED
It worked.
c.bats
:
ml() { for l; do echo "$l"; done; }
setup() {
load 'bats-support/load'
load 'bats-assert/load'
(cd .. && patch -p0 < app/archive.py.patch)
}
teardown() {
(cd .. && patch -Rp0 < app/archive.py.patch)
rm -rf /tmp/gcloudignore
}
_run() {
local layout=$1 gcloudignore=$2
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
local l
while IFS= read -r l; do
if [[ $l == */* ]]; then
local p
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
touch go.mod
echo "$gcloudignore" > .gcloudignore
gcloud functions deploy a \
--project PROJECT \
--gen2 \
--runtime go121 \
--region REGION \
--trigger-http
unzip -l /tmp/out.zip \
| tail +4 | head -n -2 \
| awk '{print $4}' \
| grep -vE '/$' \
| grep -Fve .gcloudignore -e go.mod \
|| true
}
# setup() {
# load 'bats-support/load'
# load 'bats-assert/load'
# }
#
# teardown() {
# rm -rf /tmp/gcloudignore
# }
#
# _run() {
# local layout=$1 gcloudignore=$2
# mkdir /tmp/gcloudignore
# cd /tmp/gcloudignore
# local l
# while IFS= read -r l; do
# if [[ $l == */* ]]; then
# local p
# p=${l%/*}
# mkdir -p "$p"
# touch "$l"
# else
# touch "$l"
# fi
# done < <(echo "$layout")
# echo "$gcloudignore" > .gcloudignore
# local code
# code=`ml \
# 'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
# 'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
# 'fc = FileChooser.FromFile(".gcloudignore")' \
# 'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
# PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
# python -c "$code"
# unzip -l /tmp/out.zip \
# | tail +4 | head -n -2 \
# | awk '{print $4}' \
# | grep -vE '/$' \
# | grep -Fve .gcloudignore -e go.mod \
# || true
# }
@test 'unignore a dir' {
layout=`ml \
a/b \
c/d \
e`
gcloudignore=`ml \
'*' \
'!/a'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test 'unignore a dir (aa)' {
layout=`ml \
aa/b \
c/d \
e`
gcloudignore=`ml \
'*' \
'!/aa'`
run _run "$layout" "$gcloudignore"
assert_output \
aa/b
}
@test 'unignore a dir (!/a/**)' {
layout=`ml \
a/b \
c/d \
e`
gcloudignore=`ml \
'*' \
'!/a/**'`
run _run "$layout" "$gcloudignore"
assert_output \
a/b
}
/app # bats-core/bin/bats c.bats
c.bats
✓ unignore a dir
✓ unignore a dir (aa)
✓ unignore a dir (!a/**)
3 tests, 0 failures
.
├── a
│ ├── b
│ │ └── c
│ ├── d
│ └── e
├── f
│ └── g
└── h
*
!/a/d
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
f
dir:f
matches*
f
is disallowed
- It considers the
a
dir:a
matches*
a
is disallowed
- It considers the
a/b
dir:a
matches*
a/b
is disallowed
a/d
was ignored because a
was disallowed.
.
├── a
│ ├── b
│ │ └── c
│ ├── d
│ └── e
├── f
│ └── g
└── h
*
!/a
!/a/d
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
f
dir:f
matches*
f
is disallowed
- It considers the
a
dir:a
matches*
,!/a
a
is ALLOWED
- It considers the
a/e
file:a
matches*
,!/a
a/e
matches*
a/e
is ignored
- It considers the
a/d
file:a
matches*
,!/a
a/d
matches*
,!/a/d
a/d
is ADDED
- It considers the
a/b
dir:a
matches*
,!/a
a/b
matches*
a/b
is disallowed
It worked, but a/b
was disallowed and a/e
was ignored because they matched *
.
.
├── aa
│ ├── b
│ │ └── c
│ ├── d
│ └── e
├── f
│ └── g
└── h
*
!/aa
!/aa/d
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
f
dir:f
matches*
f
is disallowed
- It considers the
aa
dir:aa
matches*
,!/aa
aa
is ALLOWED
- It considers the
aa/e
file:aa
matches*
,!/aa
aa/e
matches nothingaa/e
is ADDED
- It considers the
aa/d
file:aa
matches*
,!/aa
aa/d
matches!/aa/d
aa/d
is ADDED
- It considers the
aa/b
dir:aa
matches*
,!/aa
aa/b
matches nothingaa/b
is ALLOWED
- It considers the
aa/b/c
file:aa
matches*
,!/aa
aa/b
matches nothingaa/b/c
matches nothingaa/b/c
is ADDED
aa/b/c
and aa/e
were added because dirs and files were allowed in aa
.
.
├── aa
│ ├── b
│ │ └── c
│ ├── d
│ └── e
├── f
│ └── g
└── h
*
!/aa
/aa/*
!/aa/d
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
f
dir:f
matches*
f
is disallowed
- It considers the
aa
dir:aa
matches*
,!/aa
aa
is ALLOWED
- It considers the
aa/e
file:aa
matches*
,!/aa
aa/e
matches/aa/*
aa/e
is ignored
- It considers the
aa/d
file:aa
matches*
,!/aa
aa/d
matches/aa/*
,!/aa/d
aa/d
is ADDED
- It considers the
aa/b
dir:aa
matches*
,!/aa
aa/b
matches/aa/*
aa/b
is disallowed
It worked.
d.bats
:
ml() { for l; do echo "$l"; done; }
setup() {
load 'bats-support/load'
load 'bats-assert/load'
(cd .. && patch -p0 < app/archive.py.patch)
}
teardown() {
(cd .. && patch -Rp0 < app/archive.py.patch)
rm -rf /tmp/gcloudignore
}
_run() {
local layout=$1 gcloudignore=$2
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
local l
while IFS= read -r l; do
if [[ $l == */* ]]; then
local p
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
touch go.mod
echo "$gcloudignore" > .gcloudignore
gcloud functions deploy a \
--project PROJECT \
--gen2 \
--runtime go121 \
--region REGION \
--trigger-http
unzip -l /tmp/out.zip \
| tail +4 | head -n -2 \
| awk '{print $4}' \
| grep -vE '/$' \
| grep -Fve .gcloudignore -e go.mod \
|| true
}
# setup() {
# load 'bats-support/load'
# load 'bats-assert/load'
# }
#
# teardown() {
# rm -rf /tmp/gcloudignore
# }
#
# _run() {
# local layout=$1 gcloudignore=$2
# mkdir /tmp/gcloudignore
# cd /tmp/gcloudignore
# local l
# while IFS= read -r l; do
# if [[ $l == */* ]]; then
# local p
# p=${l%/*}
# mkdir -p "$p"
# touch "$l"
# else
# touch "$l"
# fi
# done < <(echo "$layout")
# echo "$gcloudignore" > .gcloudignore
# local code
# code=`ml \
# 'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
# 'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
# 'fc = FileChooser.FromFile(".gcloudignore")' \
# 'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
# PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
# python -c "$code"
# unzip -l /tmp/out.zip \
# | tail +4 | head -n -2 \
# | awk '{print $4}' \
# | grep -vE '/$' \
# | grep -Fve .gcloudignore -e go.mod \
# || true
# }
@test 'unignore a file in a dir' {
layout=`ml \
a/b/c \
a/d \
a/e \
f/g \
h`
gcloudignore=`ml \
'*' \
'!/a/d'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test 'unignore a file in a dir (!/a)' {
layout=`ml \
a/b/c \
a/d \
a/e \
f/g \
h`
gcloudignore=`ml \
'*' \
'!/a' \
'!/a/d'`
run _run "$layout" "$gcloudignore"
assert_output \
a/d
}
@test 'unignore a file in a dir (aa)' {
layout=`ml \
aa/b/c \
aa/d \
aa/e \
f/g \
h`
gcloudignore=`ml \
'*' \
'!/aa' \
'!/aa/d'`
run _run "$layout" "$gcloudignore"
assert_output "`ml \
aa/e \
aa/d \
aa/b/c`"
}
@test 'unignore a file in a dir (/aa/*)' {
layout=`ml \
aa/b/c \
aa/d \
aa/e \
f/g \
h`
gcloudignore=`ml \
'*' \
'!/aa' \
'/aa/*' \
'!/aa/d'`
run _run "$layout" "$gcloudignore"
assert_output \
aa/d
}
/app # bats-core/bin/bats d.bats
d.bats
✓ unignore a file in a dir
✓ unignore a file in a dir (!a)
✓ unignore a file in a dir (aa)
✓ unignore a file in a dir (aa/*)
4 tests, 0 failures
.
├── a
│ ├── b
│ │ └── c
│ ├── d
│ │ └── e
│ └── f
├── g
│ └── h
└── i
*
!/a/b
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
g
dir:g
matches*
g
is disallowed
- It considers the
a
dir:a
matches*
a
is disallowed
- It considers the
a/b
dir:a
matches*
a/b
is disallowed
- It considers the
a/d
dir:a
matches*
a/d
is disallowed
a/b/c
was ignored because a/b
was disallowed because a
was disallowed.
.
├── a
│ ├── b
│ │ └── c
│ ├── d
│ │ └── e
│ └── f
├── g
│ └── h
└── i
*
!/a
!/a/b
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
g
dir:g
matches*
g
is disallowed
- It considers the
a
dir:a
matches*
,!/a
a
is ALLOWED
- It considers the
a/f
file:a
matches*
,!/a
a/f
matches*
a/f
is ignored
- It considers the
a/b
dir:a
matches*
,!/a
a/b
matches*
,!/a/b
a/b
is ALLOWED
- It considers the
a/b/c
file:a
matches*
,!/a
a/b
matches*
,!/a/b
a/b/c
matches nothinga/b/c
is ADDED
- It considers the
a/d
dir:a
matches*
,!/a
a/d
matches*
a/d
is disallowed
It worked, but a/d
was disallowed and a/f
was ignored because they matched *
.
.
├── aa
│ ├── b
│ │ └── c
│ ├── d
│ │ └── e
│ └── f
├── g
│ └── h
└── i
*
!/aa
!/aa/b
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
g
dir:g
matches*
g
is disallowed
- It considers the
aa
dir:aa
matches*
,!/aa
aa
is ALLOWED
- It considers the
aa/f
file:aa
matches*
,!/aa
aa/f
matches nothingaa/f
is ADDED
- It considers the
aa/b
dir:aa
matches*
,!/aa
aa/b
matches!/aa/b
aa/b
is ALLOWED
- It considers the
aa/b/c
file:aa
matches*
,!/aa
aa/b
matches!/aa/b
aa/b/c
matches nothingaa/b/c
is ADDED
- It considers the
aa/d
dir:aa
matches*
,!/aa
aa/d
matches nothingaa/d
is ALLOWED
- It considers the
aa/d/e
file:aa
matches*
,!/aa
aa/d
matches nothingaa/d/e
matches nothingaa/d/e
is ADDED
aa/d/e
and aa/f
were added because files and dirs were allowed in aa
.
.
├── aa
│ ├── b
│ │ └── c
│ ├── d
│ │ └── e
│ └── f
├── g
│ └── h
└── i
*
!aa
aa/*
!aa/b
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
g
dir:g
matches*
g
is disallowed
- It considers the
aa
dir:aa
matches*
,!/aa
aa
is ALLOWED
- It considers the
aa/f
file:aa
matches*
,!/aa
aa/f
matches/aa/*
aa/f
is ignored
- It considers the
aa/b
dir:aa
matches*
,!/aa
aa/b
matches/aa/*
,!/aa/b
aa/b
is ALLOWED
- It considers the
aa/b/c
file:aa
matches*
,!/aa
aa/b
matches/aa/*
,!/aa/b
aa/b/c
matches nothingaa/b/c
is ADDED
- It considers the
aa/d
dir:aa
matches*
,!/aa
aa/d
matches/aa/*
aa/d
is disallowed
It worked.
e.bats
:
ml() { for l; do echo "$l"; done; }
setup() {
load 'bats-support/load'
load 'bats-assert/load'
(cd .. && patch -p0 < app/archive.py.patch)
}
teardown() {
(cd .. && patch -Rp0 < app/archive.py.patch)
rm -rf /tmp/gcloudignore
}
_run() {
local layout=$1 gcloudignore=$2
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
local l
while IFS= read -r l; do
if [[ $l == */* ]]; then
local p
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
touch go.mod
echo "$gcloudignore" > .gcloudignore
gcloud functions deploy a \
--project PROJECT \
--gen2 \
--runtime go121 \
--region REGION \
--trigger-http
unzip -l /tmp/out.zip \
| tail +4 | head -n -2 \
| awk '{print $4}' \
| grep -vE '/$' \
| grep -Fve .gcloudignore -e go.mod \
|| true
}
# setup() {
# load 'bats-support/load'
# load 'bats-assert/load'
# }
#
# teardown() {
# rm -rf /tmp/gcloudignore
# }
#
# _run() {
# local layout=$1 gcloudignore=$2
# mkdir /tmp/gcloudignore
# cd /tmp/gcloudignore
# local l
# while IFS= read -r l; do
# if [[ $l == */* ]]; then
# local p
# p=${l%/*}
# mkdir -p "$p"
# touch "$l"
# else
# touch "$l"
# fi
# done < <(echo "$layout")
# echo "$gcloudignore" > .gcloudignore
# local code
# code=`ml \
# 'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
# 'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
# 'fc = FileChooser.FromFile(".gcloudignore")' \
# 'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
# PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
# python -c "$code"
# unzip -l /tmp/out.zip \
# | tail +4 | head -n -2 \
# | awk '{print $4}' \
# | grep -vE '/$' \
# | grep -Fve .gcloudignore -e go.mod \
# || true
# }
@test 'unignore a dir in a dir' {
layout=`ml \
a/b/c \
a/d/e \
a/f \
g/h \
i`
gcloudignore=`ml \
'*' \
'!/a/b'`
run _run "$layout" "$gcloudignore"
assert_output \
''
}
@test 'unignore a dir in a dir (!/a)' {
layout=`ml \
a/b/c \
a/d/e \
a/f \
g/h \
i`
gcloudignore=`ml \
'*' \
'!/a' \
'!/a/b'`
run _run "$layout" "$gcloudignore"
assert_output \
a/b/c
}
@test 'unignore a dir in a dir (aa)' {
layout=`ml \
aa/b/c \
aa/d/e \
aa/f \
g/h \
i`
gcloudignore=`ml \
'*' \
'!/aa' \
'!/aa/b'`
run _run "$layout" "$gcloudignore"
assert_output "`ml \
aa/f \
aa/b/c \
aa/d/e`"
}
@test 'unignore a dir in a dir (/aa/*)' {
layout=`ml \
aa/b/c \
aa/d/e \
aa/f \
g/h \
i`
gcloudignore=`ml \
'*' \
'!/aa' \
'/aa/*' \
'!/aa/b'`
run _run "$layout" "$gcloudignore"
assert_output \
aa/b/c
}
/app # bats-core/bin/bats e.bats
e.bats
✓ unignore a dir in a dir
✓ unignore a dir in a dir (!a)
✓ unignore a dir in a dir (aa)
✓ unignore a dir in a dir (aa/*)
4 tests, 0 failures
Althought it might be useful in theory, I don't see a use case for now.
.
└── aa
├── b
│ └── c
└── d
*
!/aa
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
aa
dir:aa
matches*
,!/aa
aa
is ALLOWED
- It considers the
aa/d
file:aa
matches*
,!/aa
aa/d
matches nothingaa/d
is ADDED
- It considers the
aa/b
dir:aa
matches*
,!/aa
aa/b
matches nothingaa/b
is ALLOWED
- It considers the
aa/b/c
file:aa
matches*
,!/aa
aa/b
matches nothingaa/b/c
matches nothingaa/b/c
is ADDED
aa/d
was added because files in aa
were allowed.
.
└── aa
├── b
│ └── c
└── d
*
!/aa/
- It considers the
.
dir:.
matches*
.
is disallowed
- It considers the
aa
dir:aa
matches*
aa
is disallowed
- It considers the
aa/b
dir:aa
matches*
,!/aa/
aa/b
matches nothingaa/b
is ALLOWED
- It considers the
aa/b/c
file:aa
matches*
,!/aa/
aa/b
matches nothingaa/b/c
matches nothingaa/b/c
is ADDED
Now files in aa
are not allowed and a/d
was ignored. Do note how aa
doesn't match !/aa/
when it's the last segment of a path, and matches !/aa/
when it isn't. That's because in the former case it's matched as a file.
f.bats
:
ml() { for l; do echo "$l"; done; }
setup() {
load 'bats-support/load'
load 'bats-assert/load'
(cd .. && patch -p0 < app/archive.py.patch)
}
teardown() {
(cd .. && patch -Rp0 < app/archive.py.patch)
rm -rf /tmp/gcloudignore
}
_run() {
local layout=$1 gcloudignore=$2
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
local l
while IFS= read -r l; do
if [[ $l == */* ]]; then
local p
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
touch go.mod
echo "$gcloudignore" > .gcloudignore
gcloud functions deploy a \
--project PROJECT \
--gen2 \
--runtime go121 \
--region REGION \
--trigger-http
unzip -l /tmp/out.zip \
| tail +4 | head -n -2 \
| awk '{print $4}' \
| grep -vE '/$' \
| grep -Fve .gcloudignore -e go.mod \
|| true
}
# setup() {
# load 'bats-support/load'
# load 'bats-assert/load'
# }
#
# teardown() {
# rm -rf /tmp/gcloudignore
# }
#
# _run() {
# local layout=$1 gcloudignore=$2
# mkdir /tmp/gcloudignore
# cd /tmp/gcloudignore
# local l
# while IFS= read -r l; do
# if [[ $l == */* ]]; then
# local p
# p=${l%/*}
# mkdir -p "$p"
# touch "$l"
# else
# touch "$l"
# fi
# done < <(echo "$layout")
# echo "$gcloudignore" > .gcloudignore
# local code
# code=`ml \
# 'from googlecloudsdk.core.util.archive import MakeZipFromDir' \
# 'from googlecloudsdk.command_lib.util.gcloudignore import FileChooser' \
# 'fc = FileChooser.FromFile(".gcloudignore")' \
# 'MakeZipFromDir("/tmp/out.zip", ".", fc.IsIncluded)'`
# PYTHONPATH=/google-cloud-sdk/lib/third_party:/google-cloud-sdk/lib:/venv/lib/python3.11/site-packages \
# python -c "$code"
# unzip -l /tmp/out.zip \
# | tail +4 | head -n -2 \
# | awk '{print $4}' \
# | grep -vE '/$' \
# | grep -Fve .gcloudignore -e go.mod \
# || true
# }
@test '!a/ trick' {
layout=`ml \
aa/b/c \
aa/d`
gcloudignore=`ml \
'*' \
'!/aa'`
run _run "$layout" "$gcloudignore"
assert_output "`ml \
aa/d \
aa/b/c`"
}
@test '!a/ trick (!/aa/)' {
layout=`ml \
aa/b/c \
aa/d`
gcloudignore=`ml \
'*' \
'!/aa/'`
run _run "$layout" "$gcloudignore"
assert_output \
aa/b/c
}
/app # bats-core/bin/bats f.bats
f.bats
✓ !a/ trick
✓ !a/ trick (!aa/)
2 tests, 0 failures
Running a script:
/app # bash ga.sh && cd /tmp/gcloudignore && (cd / && patch -p0 < app/display.patch >/dev/null) && gcloud functions deploy a --project PROJECT --gen2 --runtime go121 --region REGION --trigger-http; (cd / && patch -Rp0 < app/display.patch >/dev/null)
display.patch
:
--- /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
+++ /google-cloud-sdk/lib/googlecloudsdk/core/util/archive.py
@@ -73,17 +73,24 @@
try:
for root, _, filelist in os.walk(six.text_type(src_dir)):
dir_path = os.path.normpath(os.path.relpath(root, src_dir))
+ print('* It considers the `%s` dir:' % dir_path)
if not predicate(dir_path):
+ print(f' * `{dir_path}` is disallowed')
continue
+ print(f' * `{dir_path}` is ALLOWED')
if dir_path != os.curdir:
AddToArchive(zip_file, src_dir, dir_path, False)
for file_name in filelist:
file_path = os.path.join(dir_path, file_name)
+ print('* It considers the `%s` file:' % file_path)
if not predicate(file_path):
+ print(f' * `{file_path}` is ignored')
continue
+ print(f' * `{file_path}` is ADDED')
AddToArchive(zip_file, src_dir, file_path, True)
finally:
zip_file.close()
+ import sys; sys.exit()
def AddToArchive(zip_file, src_dir, rel_path, is_file):
--- /google-cloud-sdk/lib/googlecloudsdk/command_lib/util/gcloudignore.py
+++ /google-cloud-sdk/lib/googlecloudsdk/command_lib/util/gcloudignore.py
@@ -183,11 +183,18 @@
path_prefixes = glob.GetPathPrefixes(path)[1:] # root dir can't be matched
for path_prefix in path_prefixes:
prefix_match = Match.NO_MATCH
+ matches = []
for pattern in self.patterns:
is_prefix_dir = path_prefix != path or is_dir
match = pattern.Matches(path_prefix, is_dir=is_prefix_dir)
if match is not Match.NO_MATCH:
+ matches.append(pattern)
prefix_match = match
+ print(' * `%s` matches %s' % (path_prefix,
+ ', '.join(f"`{'!' if m.negated else ''}{m.pattern.pattern}{'/' if m.pattern.must_be_dir else ''}`" for m in matches)
+ if matches
+ else 'nothing'
+ ))
if prefix_match is Match.IGNORE:
log.debug('Skipping file [{}]'.format(path))
return False
ga.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
a/b \
c \
d`
gcloudignore=`ml \
'*' \
'!/c'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
gb.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
a/b \
c \
d`
gcloudignore=`ml \
'*' \
'!.' \
'!/c'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
ha.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
a/b \
c/d \
e`
gcloudignore=`ml \
'*' \
'!/a'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
hb.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
aa/b \
c/d \
e`
gcloudignore=`ml \
'*' \
'!/aa'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
hc.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
a/b \
c/d \
e`
gcloudignore=`ml \
'*' \
'!/a/**'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
ia.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
a/b/c \
a/d \
a/e \
f/g \
h`
gcloudignore=`ml \
'*' \
'!/a/d'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
ib.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
a/b/c \
a/d \
a/e \
f/g \
h`
gcloudignore=`ml \
'*' \
'!/a' \
'!/a/d'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
ic.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
aa/b/c \
aa/d \
aa/e \
f/g \
h`
gcloudignore=`ml \
'*' \
'!/aa' \
'!/aa/d'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
id.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
aa/b/c \
aa/d \
aa/e \
f/g \
h`
gcloudignore=`ml \
'*' \
'!/aa' \
'/aa/*' \
'!/aa/d'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
ja.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
a/b/c \
a/d/e \
a/f \
g/h \
i`
gcloudignore=`ml \
'*' \
'!/a/b'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
jb.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
a/b/c \
a/d/e \
a/f \
g/h \
i`
gcloudignore=`ml \
'*' \
'!/a' \
'!/a/b'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
jc.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
aa/b/c \
aa/d/e \
aa/f \
g/h \
i`
gcloudignore=`ml \
'*' \
'!/aa' \
'!/aa/b'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
jd.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
aa/b/c \
aa/d/e \
aa/f \
g/h \
i`
gcloudignore=`ml \
'*' \
'!/aa' \
'/aa/*' \
'!/aa/b'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
ka.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
aa/b/c \
aa/d`
gcloudignore=`ml \
'*' \
'!/aa'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo
kb.sh
:
rm -rf /tmp/gcloudignore
mkdir /tmp/gcloudignore
cd /tmp/gcloudignore
ml() { for l; do echo "$l"; done; }
layout=`ml \
aa/b/c \
aa/d`
gcloudignore=`ml \
'*' \
'!/aa/'`
while IFS= read -r l; do
if [[ $l == */* ]]; then
p=${l%/*}
mkdir -p "$p"
touch "$l"
else
touch "$l"
fi
done < <(echo "$layout")
echo "$gcloudignore" > .gcloudignore
touch go.mod
echo \`\`\`
tree | head -n -2
echo \`\`\`
echo
echo \`\`\`
cat .gcloudignore
echo \`\`\`
echo