docman 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +17 -4
- data/bin/functions.sh +265 -0
- data/config/config.yaml +36 -39
- data/config/cucumber.yml +2 -0
- data/docman.gemspec +3 -0
- data/features/acquia/acquia.feature +65 -0
- data/features/init/init.feature +27 -0
- data/features/local/local.feature +88 -0
- data/features/local/local_deploy.feature +6 -0
- data/features/support/env.rb +1 -1
- data/lib/application.rb +48 -11
- data/lib/docman/builders/builder.rb +34 -34
- data/lib/docman/builders/dir_builder.rb +21 -0
- data/lib/docman/builders/drupal_drush_builder.rb +22 -0
- data/lib/docman/builders/git_direct_builder.rb +20 -0
- data/lib/docman/builders/git_strip_builder.rb +27 -0
- data/lib/docman/cli.rb +3 -0
- data/lib/docman/commands/clean_changed_cmd.rb +23 -0
- data/lib/docman/commands/command.rb +137 -0
- data/lib/docman/commands/composite_command.rb +28 -0
- data/lib/docman/commands/create_symlink_cmd.rb +24 -0
- data/lib/docman/commands/execute_script_cmd.rb +40 -0
- data/lib/docman/commands/git_commit_cmd.rb +25 -0
- data/lib/docman/commands/ssh_target_checker.rb +28 -0
- data/lib/docman/commands/target_checker.rb +22 -0
- data/lib/docman/config.rb +31 -0
- data/lib/docman/context.rb +11 -0
- data/lib/docman/deployers/common_deployer.rb +2 -1
- data/lib/docman/deployers/deployer.rb +121 -17
- data/lib/docman/deployers/git_deployer.rb +6 -3
- data/lib/docman/docroot_config.rb +15 -17
- data/lib/docman/docroot_controller.rb +23 -35
- data/lib/docman/exceptions/command_validation_error.rb +5 -0
- data/lib/docman/exceptions/no_changes_error.rb +5 -0
- data/lib/docman/exec.rb +1 -1
- data/lib/docman/git_util.rb +43 -15
- data/lib/docman/info.rb +63 -9
- data/lib/docman/logging.rb +41 -0
- data/lib/docman/version.rb +1 -1
- metadata +72 -8
- data/features/local.feature +0 -60
- data/features/local_deploy.feature +0 -37
- data/lib/docman/builders/common_builder.rb +0 -16
- data/lib/docman/builders/git_builder.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4bab1348bae18d45a114e6b4d6b09062941dea2b
|
4
|
+
data.tar.gz: aea9b94d3dcc51c3c8434742656f8d088a31e5fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f92686f1ef0f5da95066dfbf18ef3b6d784d00df281347126855cbca9ee9a919596ce1d0d6b67634d5f11e91d4ff06b817b7b5d95d82eb9eae96f9d6d467a309
|
7
|
+
data.tar.gz: fe889237d4ca329320f242e9e86655f9e589b7186deb41ac39f2a091defc898298b2c374ea14a91dd7183c549d70db3b16db47d0571082412bda84f88ec22d56
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Docman
|
2
2
|
|
3
|
-
Docman made for DOCroot MANagement for Drupal projects
|
3
|
+
Docman made for DOCroot MANagement for Drupal projects. Useful to manage multiple websites in one Drupal multisite installation. We are assuming that there is a git repository with Drupal core and multiple git repositories for each website in multisite environment (think about each repository containing /modules /themes /libraries, etc). This becomes useful, if you can setup a middleware like jenkins which will effectively "build" your multisite environment using this tool.
|
4
|
+
|
5
|
+
Notes: we are speaking about the code only, media files should be managed separately and for now are out of scope of this tool.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -16,13 +18,24 @@ Or install it yourself as:
|
|
16
18
|
|
17
19
|
$ gem install docman
|
18
20
|
|
19
|
-
## Usage
|
21
|
+
## Usage (in process of documentation)
|
22
|
+
|
23
|
+
Build local environment:
|
24
|
+
|
25
|
+
$ docman build local environment
|
26
|
+
|
27
|
+
Build the destination docroot using your settings:
|
28
|
+
|
29
|
+
$ docman build <docroot> stable
|
30
|
+
|
31
|
+
Deploy built docroot (Drupal core with multiple websites in multisite) to your environment:
|
20
32
|
|
33
|
+
$ docman deploy <docroot>
|
21
34
|
|
22
35
|
## Contributing
|
23
36
|
|
24
|
-
1. Fork it ( https://github.com/
|
37
|
+
1. Fork it ( https://github.com/Adyax/docman/fork )
|
25
38
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
26
39
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
27
40
|
4. Push to the branch (`git push origin my-new-feature`)
|
28
|
-
5. Create a new Pull Request
|
41
|
+
5. Create a new Pull Request
|
data/bin/functions.sh
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
#
|
4
|
+
# Check if git directory has no changes.
|
5
|
+
#
|
6
|
+
dm_is_clean () {
|
7
|
+
${git} rev-parse --verify HEAD >/dev/null || echo 1
|
8
|
+
${git} update-index -q --ignore-submodules --refresh
|
9
|
+
err=0
|
10
|
+
|
11
|
+
if ! ${git} diff-files --quiet --ignore-submodules
|
12
|
+
then
|
13
|
+
err=1
|
14
|
+
fi
|
15
|
+
|
16
|
+
if ! ${git} diff-index --cached --quiet --ignore-submodules HEAD --
|
17
|
+
then
|
18
|
+
err=1
|
19
|
+
fi
|
20
|
+
|
21
|
+
test -z "$(git status --porcelain)" || err=1
|
22
|
+
|
23
|
+
if [ $err = 1 ]
|
24
|
+
then
|
25
|
+
echo $err
|
26
|
+
fi
|
27
|
+
}
|
28
|
+
|
29
|
+
#
|
30
|
+
# Git subtree add/pull depending on dir existance.
|
31
|
+
#
|
32
|
+
dm_git_subtree_pull () {
|
33
|
+
if [ ! -d "$1" ]; then
|
34
|
+
dm_debug "Subtree add $1 $2 $3"
|
35
|
+
${git} subtree add --squash --prefix=$1 $2 $3
|
36
|
+
else
|
37
|
+
dm_debug "Subtree pull $1 $2 $3"
|
38
|
+
${git} subtree pull --squash --prefix=$1 $2 $3 -m "Subtree pull $1 $2 $3"
|
39
|
+
fi
|
40
|
+
}
|
41
|
+
|
42
|
+
#
|
43
|
+
# Git subtree add/pull depending on dir existance.
|
44
|
+
#
|
45
|
+
dm_git_get () {
|
46
|
+
_version=${3:-master}
|
47
|
+
_target=${4:-branch}
|
48
|
+
if [ ! -d "$2" ]; then
|
49
|
+
dm_debug "Local repository not exists: $1 $2"
|
50
|
+
${git} clone $1 $2
|
51
|
+
cd $2
|
52
|
+
${git} checkout $3
|
53
|
+
|
54
|
+
else
|
55
|
+
cd $2
|
56
|
+
dm_debug "Local repository exists: $1 $2"
|
57
|
+
if [[ "$_target" == 'branch' ]]; then
|
58
|
+
${git} pull origin $3
|
59
|
+
elif [[ "$_target" == 'tag' ]]; then
|
60
|
+
echo $4
|
61
|
+
${git} checkout $3
|
62
|
+
fi
|
63
|
+
fi
|
64
|
+
}
|
65
|
+
|
66
|
+
#
|
67
|
+
# Git get version (branch or tag).
|
68
|
+
#
|
69
|
+
dm_git_checkout_and_copy () {
|
70
|
+
_DIR=${WORKSPACE}/attic/$1
|
71
|
+
mkdir -p $_DIR
|
72
|
+
if [[ ! -d "$_DIR/$3/$4" ]]; then
|
73
|
+
${git} clone $2 $_DIR/$3/$4
|
74
|
+
cd $_DIR/$3/$4
|
75
|
+
${git} checkout $4
|
76
|
+
else
|
77
|
+
cd $_DIR/$3/$4
|
78
|
+
if [[ "$3" == 'branch' ]]; then
|
79
|
+
${git} pull origin $4
|
80
|
+
elif [[ "$3" == 'tag' ]]; then
|
81
|
+
echo $4
|
82
|
+
${git} checkout $4
|
83
|
+
fi
|
84
|
+
fi
|
85
|
+
|
86
|
+
rm -fR $5
|
87
|
+
mkdir -p $5
|
88
|
+
# We don't need .git here.
|
89
|
+
rm -fR $_DIR/$3/$4/.git
|
90
|
+
cp -fR $_DIR/$3/$4/. $5/
|
91
|
+
rm -fR $_DIR/$3/$4/
|
92
|
+
}
|
93
|
+
|
94
|
+
dm_find_repo_dir_by_name () {
|
95
|
+
basename="$(basename "${1}")"
|
96
|
+
needle="$(basename "${2}")"
|
97
|
+
if [[ $basename == "$needle" ]]; then
|
98
|
+
echo $1
|
99
|
+
return
|
100
|
+
fi
|
101
|
+
|
102
|
+
for dirname in $1/*; do
|
103
|
+
[ -d "$dirname" ] || continue # if not a directory, skip
|
104
|
+
dm_find_repo_dir_by_name $dirname $2
|
105
|
+
done
|
106
|
+
}
|
107
|
+
|
108
|
+
dm_get_repo_state_config () {
|
109
|
+
repo_dir=$(dm_find_repo_dir_by_name $1 $2)
|
110
|
+
if [ -d "$repo_dir/_states" ]; then
|
111
|
+
dm_read_properties_file $repo_dir/_states/${3}.properties $4
|
112
|
+
else
|
113
|
+
echo "No state dir"
|
114
|
+
exit 1
|
115
|
+
fi
|
116
|
+
}
|
117
|
+
|
118
|
+
dm_write_properties_file_value () {
|
119
|
+
sed -i '' "s/^\($2\s*=\s*\).*\$/\1$3/" $1
|
120
|
+
}
|
121
|
+
|
122
|
+
dm_set_repo_state_config () {
|
123
|
+
repo_dir=$(dm_find_repo_dir_by_name $1 $2)
|
124
|
+
if [ -d "$repo_dir/_states" ]; then
|
125
|
+
dm_write_properties_file_value $repo_dir/_states/${3}.properties VERSION_TYPE $4
|
126
|
+
dm_write_properties_file_value $repo_dir/_states/${3}.properties VERSION $5
|
127
|
+
else
|
128
|
+
echo "No state dir"
|
129
|
+
exit 1
|
130
|
+
fi
|
131
|
+
|
132
|
+
cd "$repo_dir"
|
133
|
+
${git} add -A
|
134
|
+
${git} commit -m "Updated $2 $3 to $4-$5"
|
135
|
+
${git} push origin master
|
136
|
+
}
|
137
|
+
|
138
|
+
dm_build_recursive () {
|
139
|
+
# Skip if no info.properties file.
|
140
|
+
[ -f "${1}/info.properties" ] || return
|
141
|
+
|
142
|
+
dm_read_properties_file ${1}/info.properties
|
143
|
+
|
144
|
+
BUILD_DIR_NAME="$(basename "${2}")"
|
145
|
+
BUILD_DIR="$2"
|
146
|
+
ITEM_TYPE_PARAM="target_${_type}_deploy_as"
|
147
|
+
ITEM_TYPE=${!ITEM_TYPE_PARAM}
|
148
|
+
|
149
|
+
dm_read_properties_file $1/_states/${_DM_BUILD_STATE}.properties
|
150
|
+
echo "Install into $BUILD_DIR, Type: $ITEM_TYPE, Version: $VERSION_TYPE-$VERSION"
|
151
|
+
|
152
|
+
if [[ $ITEM_TYPE == "repo" ]]; then
|
153
|
+
dm_git_get ${repo} $BUILD_DIR $VERSION $VERSION_TYPE
|
154
|
+
ROOT_REPO_DIR="$BUILD_DIR"
|
155
|
+
fi
|
156
|
+
|
157
|
+
if [[ $ITEM_TYPE == "dir" ]]; then
|
158
|
+
mkdir -p $BUILD_DIR
|
159
|
+
fi
|
160
|
+
|
161
|
+
if [[ $ITEM_TYPE == "copy_strip_git" ]]; then
|
162
|
+
cd $ROOT_REPO_DIR
|
163
|
+
#dm_git_subtree_pull $_subtree_prefix $repo $VERSION
|
164
|
+
dm_git_checkout_and_copy $_subtree_prefix $repo $VERSION_TYPE $VERSION $BUILD_DIR
|
165
|
+
fi
|
166
|
+
|
167
|
+
if [[ $ITEM_TYPE == "drupal" ]]; then
|
168
|
+
dm_debug "Install Drupal"
|
169
|
+
# Build docroot dir.
|
170
|
+
if [ -f "$BUILD_DIR/modules/system/system.info" ]; then
|
171
|
+
INSTALLED_DRUPAL_VERSION=$(grep "version = \"" $BUILD_DIR/modules/system/system.info | awk -F\" '{print $2}')
|
172
|
+
fi
|
173
|
+
|
174
|
+
# Install Drupal.
|
175
|
+
mkdir -p $BUILD_DIR
|
176
|
+
if [[ ! "$INSTALLED_DRUPAL_VERSION" == "$VERSION" ]] || [[ "$_FORCE" == "1" ]]
|
177
|
+
then
|
178
|
+
TEMP_DRUPAL_DIR=`mktemp -d -t 'drupal'`
|
179
|
+
mkdir -p $TEMP_DRUPAL_DIR
|
180
|
+
if [ ! -d "$TEMP_DRUPAL_DIR/drupal-${VERSION}" ] || [[ "$_FORCE" == "1" ]]
|
181
|
+
then
|
182
|
+
cd $TEMP_DRUPAL_DIR
|
183
|
+
drush dl drupal-${VERSION} --yes
|
184
|
+
rm -fR drupal-${VERSION}/sites
|
185
|
+
fi
|
186
|
+
|
187
|
+
rm -fR $BUILD_DIR
|
188
|
+
mkdir -p $BUILD_DIR
|
189
|
+
cp -fR $TEMP_DRUPAL_DIR/drupal-${VERSION}/. $BUILD_DIR
|
190
|
+
#rm -fR ${WORKSPACE}/drupal/drupal-${VERSION}
|
191
|
+
else
|
192
|
+
echo "Drupal already installed."
|
193
|
+
fi
|
194
|
+
fi
|
195
|
+
|
196
|
+
cd $BUILD_DIR
|
197
|
+
for dirname in $1/*; do
|
198
|
+
[ -d "$dirname" ] || continue # if not a directory, skip
|
199
|
+
basename="$(basename "${dirname}")"
|
200
|
+
dm_build_recursive $dirname $2/$basename
|
201
|
+
done
|
202
|
+
}
|
203
|
+
|
204
|
+
#
|
205
|
+
# Git commit if there are changes in dir.
|
206
|
+
#
|
207
|
+
dm_git_commit_if_changes () {
|
208
|
+
repo_clean=$(dm_is_clean)
|
209
|
+
if [ "$repo_clean" == "1" ]
|
210
|
+
then
|
211
|
+
#${git} merge --ff-only
|
212
|
+
${git} add --all $1
|
213
|
+
${git} commit -m "$2"
|
214
|
+
changed=1
|
215
|
+
fi
|
216
|
+
}
|
217
|
+
|
218
|
+
#
|
219
|
+
# Git subtree add/pull depending on dir existance.
|
220
|
+
#
|
221
|
+
dm_debug () {
|
222
|
+
if [ "$_DEBUG" == "1" ]; then
|
223
|
+
echo 1>&2 "$@"
|
224
|
+
fi
|
225
|
+
}
|
226
|
+
|
227
|
+
dm_read_properties_file () {
|
228
|
+
dm_debug "Read properties: $1 $2"
|
229
|
+
TEMPFILE=`mktemp 2>/dev/null || mktemp -t 'tmp'`
|
230
|
+
cat $1 |
|
231
|
+
if [ "$(uname)" == "Darwin" ]
|
232
|
+
then
|
233
|
+
sed -Ee 's/"/"/'g|sed -Ee "s/(.*)=(.*)/$2\1=\"\2\"/g">$TEMPFILE
|
234
|
+
else
|
235
|
+
sed -re 's/"/"/'g|sed -re "s/(.*)=(.*)/$2\1=\"\2\"/g">$TEMPFILE
|
236
|
+
fi
|
237
|
+
source $TEMPFILE
|
238
|
+
rm $TEMPFILE
|
239
|
+
}
|
240
|
+
|
241
|
+
#
|
242
|
+
# Send message to slack.
|
243
|
+
#
|
244
|
+
dm_slack_send () {
|
245
|
+
slack_message_fallback="$1"
|
246
|
+
slack_message_text="$1"
|
247
|
+
slack_message_pretext=""
|
248
|
+
|
249
|
+
if [ "$enable_slack_notification" == "1" ]
|
250
|
+
then
|
251
|
+
if [ -n "$2" ]
|
252
|
+
then
|
253
|
+
slack_message_color="$2"
|
254
|
+
attachments="\"attachments\": [{\"fallback\": \"${slack_message_fallback}\", \"text\": \"${slack_message_text}\", \"pretext\": \"${slack_message_pretext}\", \"color\": \"${slack_message_color}\", \"fields\": [
|
255
|
+
{ \"title\": \"Project\", \"value\": \"${project}\", \"short\": true },
|
256
|
+
{ \"title\": \"Environment\", \"value\": \"prod\", \"short\": true }
|
257
|
+
]}],"
|
258
|
+
else
|
259
|
+
attachments=""
|
260
|
+
fi
|
261
|
+
|
262
|
+
curl -X POST --data-urlencode "payload={\"channel\": \"#${slack_channel}\", $attachments \"username\": \"${slack_username}\", \"text\": \"$1\", \"icon_emoji\": \":ghost:\"}" ${slack_address}?token=${slack_token}
|
263
|
+
fi
|
264
|
+
}
|
265
|
+
|
data/config/config.yaml
CHANGED
@@ -4,52 +4,49 @@ deploy_targets:
|
|
4
4
|
handler: :common_deployer
|
5
5
|
builders:
|
6
6
|
root:
|
7
|
-
handler: :
|
8
|
-
type: dir
|
7
|
+
handler: :dir_builder
|
9
8
|
drupal:
|
10
|
-
handler: :
|
11
|
-
type: drupal
|
9
|
+
handler: :drupal_drush_builder
|
12
10
|
repo:
|
13
|
-
handler: :
|
14
|
-
type: direct
|
11
|
+
handler: :git_direct_builder
|
15
12
|
dir:
|
16
|
-
handler: :
|
17
|
-
|
13
|
+
handler: :dir_builder
|
14
|
+
environments:
|
15
|
+
development: local
|
16
|
+
staging: local
|
17
|
+
stable: local
|
18
18
|
acquia:
|
19
19
|
handler: :git_deployer
|
20
|
-
deploy_action: git_push
|
21
20
|
builders:
|
22
21
|
root:
|
23
|
-
handler: :
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
handler: :git_direct_builder
|
23
|
+
hooks:
|
24
|
+
builder:
|
25
|
+
after_execute:
|
26
|
+
- type: :git_commit
|
27
|
+
execution_dir: $PROJECT$
|
27
28
|
drupal:
|
28
|
-
handler: :
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
handler: :drupal_drush_builder
|
30
|
+
hooks:
|
31
|
+
builder:
|
32
|
+
after_execute:
|
33
|
+
- type: :git_commit
|
34
|
+
execution_dir: $PROJECT$
|
32
35
|
repo:
|
33
|
-
handler: :
|
34
|
-
|
35
|
-
|
36
|
-
|
36
|
+
handler: :git_strip_builder
|
37
|
+
hooks:
|
38
|
+
builder:
|
39
|
+
after_execute:
|
40
|
+
- type: :git_commit
|
41
|
+
execution_dir: $PROJECT$
|
37
42
|
dir:
|
38
|
-
handler: :
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
deploy_target: acquia
|
49
|
-
state: development
|
50
|
-
test:
|
51
|
-
deploy_target: acquia
|
52
|
-
state: staging
|
53
|
-
prod:
|
54
|
-
deploy_target: acquia
|
55
|
-
state: stable
|
43
|
+
handler: :dir_builder
|
44
|
+
hooks:
|
45
|
+
builder:
|
46
|
+
after_execute:
|
47
|
+
- type: :git_commit
|
48
|
+
execution_dir: $PROJECT$
|
49
|
+
environments:
|
50
|
+
development: dev
|
51
|
+
staging: test
|
52
|
+
stable: test
|
data/config/cucumber.yml
ADDED
data/docman.gemspec
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
Feature: Docroot management - Acquia
|
2
|
+
|
3
|
+
In order to manage docroot
|
4
|
+
As a developer using Cucumber
|
5
|
+
I want to use the deploy steps to deploy to local
|
6
|
+
|
7
|
+
@announce
|
8
|
+
@no-clobber
|
9
|
+
@build
|
10
|
+
@acquia
|
11
|
+
Scenario: Acquia build development
|
12
|
+
Given I cd to "sample-docroot"
|
13
|
+
Then I run `docman build acquia development`
|
14
|
+
Then the exit status should be 0
|
15
|
+
Then the following directories should exist:
|
16
|
+
| master |
|
17
|
+
| master/docroot |
|
18
|
+
| master/docroot/sites |
|
19
|
+
| master/hooks |
|
20
|
+
| master/profiles |
|
21
|
+
| master/profiles/sample_profile |
|
22
|
+
| master/projects/sample_project1 |
|
23
|
+
| master/projects/sample_project2 |
|
24
|
+
| master/docroot/profiles/sample_profile |
|
25
|
+
Then the following files should exist:
|
26
|
+
| master/docroot/CHANGELOG.txt |
|
27
|
+
|
28
|
+
@announce
|
29
|
+
@no-clobber
|
30
|
+
@acquia
|
31
|
+
@develop
|
32
|
+
@deploy
|
33
|
+
Scenario: Acquia deploy sample project 1 develop
|
34
|
+
Given I cd to "sample-docroot"
|
35
|
+
Then I run `docman deploy acquia sample_project1 branch develop`
|
36
|
+
Then the exit status should be 0
|
37
|
+
Then the following directories should exist:
|
38
|
+
| master |
|
39
|
+
| master/docroot |
|
40
|
+
| master/docroot/sites |
|
41
|
+
| master/hooks |
|
42
|
+
| master/profiles |
|
43
|
+
| master/profiles/sample_profile |
|
44
|
+
| master/projects/sample_project1 |
|
45
|
+
| master/projects/sample_project2 |
|
46
|
+
|
47
|
+
@announce
|
48
|
+
@no-clobber
|
49
|
+
@acquia
|
50
|
+
@deploy
|
51
|
+
@master
|
52
|
+
@sample_project2
|
53
|
+
Scenario: Acquia deploy sample project 2 master
|
54
|
+
Given I cd to "sample-docroot"
|
55
|
+
Then I run `docman deploy acquia sample_project2 branch master`
|
56
|
+
Then the exit status should be 0
|
57
|
+
Then the following directories should exist:
|
58
|
+
| master |
|
59
|
+
| master/docroot |
|
60
|
+
| master/docroot/sites |
|
61
|
+
| master/hooks |
|
62
|
+
| master/profiles |
|
63
|
+
| master/profiles/sample_profile |
|
64
|
+
| master/projects/sample_project1 |
|
65
|
+
| master/projects/sample_project2 |
|