Skip to content

Commit fd8cdf3

Browse files
committed
Merge remote-tracking branch 'origin/release' into chore/disable-autocommit
2 parents 2055a6b + 2945431 commit fd8cdf3

File tree

4 files changed

+177
-17
lines changed

4 files changed

+177
-17
lines changed

app/server/appsmith-git/src/main/resources/git.sh

Lines changed: 110 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ git_clone() {
3434
local private_key="$1"
3535
local remote_url="$2"
3636
local target_folder="$3"
37+
local git_root="$4"
3738

38-
local temp_private_key=$(mktemp /dev/shm/tmp.XXXXXX)
39+
local temp_private_key=$(mktemp ${git_root}tmp.XXXXXX)
3940
trap 'rm -rf "'"$temp_private_key"'"' EXIT ERR
4041

4142
echo "$private_key" > "$temp_private_key"
4243

43-
git -C "$target_folder" init "$target_folder" --initial-branch=none
44+
git -C "$target_folder" init --initial-branch=none
4445
git -C "$target_folder" remote add origin "$remote_url"
4546
GIT_SSH_COMMAND="ssh -i $temp_private_key -o StrictHostKeyChecking=no" git -C "$target_folder" fetch origin
4647
}
@@ -54,10 +55,10 @@ git_clean_up() {
5455
trap 'rm -rf "'"$target_folder"'"' EXIT ERR
5556

5657
## delete the repository from redis
57-
redis-cli -u "$redis_url" DEL "$redis_key"
58+
redis-exec "$redis_url" DEL "$redis_key"
5859

5960
## delete the repository branch_store
60-
redis-cli -u "$redis_url" DEL "$key_value_pair_key"
61+
redis-exec "$redis_url" DEL "$key_value_pair_key"
6162
}
6263

6364
# Uploads git repo to Redis as compressed archive
@@ -72,7 +73,7 @@ git_upload() {
7273
rm -f "$target_folder/.git/index.lock"
7374

7475
upload_branches_to_redis_hash "$target_folder" "$redis_url" "$key_value_pair_key"
75-
tar -cf - -C "$target_folder" . | zstd -q --threads=0 | base64 -w 0 | redis-cli -u "$redis_url" --raw -x SETEX "$redis_key" "$GIT_ARTIFACT_TTL"
76+
tar -cf - -C "$target_folder" . | zstd -q --threads=0 | base64 -w 0 | redis-exec "$redis_url" --raw -x SETEX "$redis_key" "$GIT_ARTIFACT_TTL"
7677
}
7778

7879
upload_branches_to_redis_hash() {
@@ -85,8 +86,8 @@ upload_branches_to_redis_hash() {
8586

8687
log_info "Preparing to upload branch store. Current branches: $branches"
8788

88-
redis-cli -u "$redis_url" DEL "$key_value_pair_key"
89-
redis-cli -u "$redis_url" HSET "$key_value_pair_key" $branches
89+
redis-exec "$redis_url" DEL "$key_value_pair_key"
90+
redis-exec "$redis_url" HSET "$key_value_pair_key" $branches
9091
}
9192

9293
# Downloads git repo from Redis or clones if not cached
@@ -105,8 +106,8 @@ git_download() {
105106
rm -rf "$target_folder"
106107
mkdir -p "$target_folder"
107108

108-
if [ "$(redis-cli -u "$redis_url" --raw EXISTS "$redis_key")" = "1" ]; then
109-
redis-cli -u "$redis_url" --raw GET "$redis_key" | base64 -d | zstd -d --threads=0 | tar -xf - -C "$target_folder"
109+
if [ "$(redis-exec "$redis_url" --raw EXISTS "$redis_key")" = "1" ]; then
110+
redis-exec "$redis_url" --raw GET "$redis_key" | base64 -d | zstd -d --threads=0 | tar -xf - -C "$target_folder"
110111
else
111112
log_warn "Cache miss. Repository: $target_folder with key: $redis_key does not exist in redis."
112113
return 1
@@ -123,11 +124,12 @@ git_clone_and_checkout() {
123124
local author_name="$2"
124125
local private_key="$3"
125126
local remote_url="$4"
126-
local target_folder="$5"
127-
local redis_url="$6"
128-
local key_value_pair_key="$7"
127+
local git_root="$5"
128+
local target_folder="$6"
129+
local redis_url="$7"
130+
local key_value_pair_key="$8"
129131

130-
## branches are after argument 7
132+
## branches are after argument 8
131133

132134
trap 'rm -rf "'"$target_folder"'"' ERR
133135

@@ -138,16 +140,16 @@ git_clone_and_checkout() {
138140
## create the same directory
139141
mkdir -p "$target_folder"
140142

141-
git_clone "$private_key" "$remote_url" "$target_folder"
143+
git_clone "$private_key" "$remote_url" "$target_folder" "$git_root"
142144
git -C "$target_folder" config user.name "$author_name"
143145
git -C "$target_folder" config user.email "$author_email"
144146
git -C "$target_folder" config fetch.parallel 4
145147
git -C "$target_folder" reflog expire --expire=now --all
146148
git -C "$target_folder" gc --prune=now --aggressive
147149

148-
# This provides all the arguments from arg 5 onwards to the function git_co_from_redis.
150+
# This provides all the arguments from arg 6 onwards to the function git_co_from_redis.
149151
# This includes the target folder, redis url, key value pair key, and all branch names from db.
150-
git_checkout_from_branch_store ${@:5}
152+
git_checkout_from_branch_store ${@:6}
151153
}
152154

153155
git_checkout_from_branch_store() {
@@ -159,7 +161,7 @@ git_checkout_from_branch_store() {
159161
# fetch raw value
160162
log_info "Searching Redis branch-store for key : $key_value_pair_key"
161163
local raw
162-
raw=$(redis-cli -u "$redis_url" --raw HGETALL "$key_value_pair_key" | sed 's/\"//g')
164+
raw=$(redis-exec "$redis_url" --raw HGETALL "$key_value_pair_key" | sed 's/\"//g')
163165

164166
# error handling: empty or missing key
165167
if [[ -z "$raw" ]]; then
@@ -253,3 +255,94 @@ git_merge_branch() {
253255
git -C "$target_folder" checkout "$destination_branch"
254256
git -C "$target_folder" merge "$source_branch" --strategy=recursive --allow-unrelated-histories --no-edit
255257
}
258+
259+
# Redis CLI wrapper that handles different URL schemes
260+
# Usage: redis-exec <redis-url> [redis-cli-args...]
261+
# Supports: redis://, rediss://, redis-cluster://
262+
redis-exec() {
263+
local url="$1"
264+
265+
if [[ -z "$url" ]]; then
266+
log_error "redis-exec: missing Redis URL"
267+
log_error "Usage: redis-exec <redis-url> [redis-cli-args...]"
268+
return 1
269+
fi
270+
271+
case "$url" in
272+
redis://*|rediss://*)
273+
# Standard Redis URL - pass directly to redis-cli -u
274+
redis-cli -u "$url" "${@:2}"
275+
;;
276+
redis-cluster://*)
277+
# Cluster URL - extract components and use cluster mode
278+
local stripped="${url#redis-cluster://}"
279+
280+
# Split into authority and path parts
281+
local authority="${stripped%%/*}"
282+
local path_part="${stripped#*/}"
283+
if [[ "$path_part" == "$stripped" ]]; then
284+
path_part="" # No path present
285+
fi
286+
287+
# Extract credentials from authority (user:pass@host:port)
288+
local credentials=""
289+
local host_port="$authority"
290+
if [[ "$authority" == *"@"* ]]; then
291+
credentials="${authority%%@*}"
292+
host_port="${authority##*@}"
293+
fi
294+
295+
# Parse credentials
296+
local username=""
297+
local password=""
298+
if [[ -n "$credentials" ]]; then
299+
if [[ "$credentials" == *":"* ]]; then
300+
username="${credentials%%:*}"
301+
password="${credentials##*:}"
302+
else
303+
password="$credentials" # Only password provided
304+
fi
305+
fi
306+
307+
# Extract host and port
308+
local host="${host_port%:*}"
309+
local port="${host_port##*:}"
310+
311+
# If no port specified, default to 6379
312+
if [[ "$port" == "$host" ]]; then
313+
port="6379"
314+
fi
315+
316+
# Extract database number from path
317+
local database=""
318+
if [[ -n "$path_part" && "$path_part" =~ ^[0-9]+(\?.*)?$ ]]; then
319+
database="${path_part%%\?*}" # Remove query params if present
320+
fi
321+
322+
# Build redis-cli command
323+
local cmd_args=("-c" "-h" "$host" "-p" "$port")
324+
325+
# Add authentication if present
326+
if [[ -n "$username" && -n "$password" ]]; then
327+
cmd_args+=("--user" "$username" "--pass" "$password")
328+
elif [[ -n "$password" ]]; then
329+
cmd_args+=("-a" "$password")
330+
fi
331+
332+
# Add database selection if specified
333+
if [[ -n "$database" ]]; then
334+
cmd_args+=("-n" "$database")
335+
fi
336+
337+
# Add remaining arguments
338+
cmd_args+=("${@:2}")
339+
340+
redis-cli "${cmd_args[@]}"
341+
;;
342+
*)
343+
log_error "redis-exec: unsupported URL scheme: $url"
344+
log_error "Supported schemes: redis://, rediss://, redis-cluster://"
345+
return 1
346+
;;
347+
esac
348+
}

app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
import java.io.IOException;
5959
import java.io.InputStream;
6060
import java.net.URL;
61+
import java.security.MessageDigest;
62+
import java.security.NoSuchAlgorithmException;
6163
import java.text.DateFormat;
6264
import java.text.SimpleDateFormat;
6365
import java.util.ArrayList;
@@ -1167,10 +1169,29 @@ void uploadFileInS3(
11671169
TransferManager transferManager =
11681170
TransferManagerBuilder.standard().withS3Client(connection).build();
11691171
final ObjectMetadata objectMetadata = new ObjectMetadata();
1172+
1173+
// Set content length
1174+
objectMetadata.setContentLength(payload.length);
1175+
11701176
// Only add content type if the user has mentioned it in the body
11711177
if (multipartFormDataDTO.getType() != null) {
11721178
objectMetadata.setContentType(multipartFormDataDTO.getType());
11731179
}
1180+
1181+
// Calculate and set Content-MD5 header for Object Lock compliance
1182+
try {
1183+
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
1184+
byte[] md5Hash = md5Digest.digest(payload);
1185+
String md5Base64 = Base64.getEncoder().encodeToString(md5Hash);
1186+
objectMetadata.setContentMD5(md5Base64);
1187+
log.debug("Set Content-MD5 header for S3 upload: {}", md5Base64);
1188+
} catch (NoSuchAlgorithmException e) {
1189+
log.warn(
1190+
"Failed to calculate MD5 checksum for S3 upload. Object Lock enabled buckets may reject this upload.",
1191+
e);
1192+
// Continue with upload without MD5 header - let AWS handle the error if Object Lock is enabled
1193+
}
1194+
11741195
transferManager
11751196
.upload(bucketName, path, inputStream, objectMetadata)
11761197
.waitForUploadResult();

app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import java.lang.reflect.Method;
4646
import java.net.MalformedURLException;
4747
import java.net.URL;
48+
import java.security.MessageDigest;
49+
import java.security.NoSuchAlgorithmException;
4850
import java.util.ArrayList;
4951
import java.util.Arrays;
5052
import java.util.HashMap;
@@ -1584,4 +1586,47 @@ public void verify_sanitizeGenerateCRUDPageTemplateInfo_addsInfoToReplaceTemplat
15841586
.block();
15851587
assertEquals(userSelectedBucketName, mappedColumnsAndTableName.get("templateBucket"));
15861588
}
1589+
1590+
@Test
1591+
public void testContentMD5CalculationForObjectLock() throws NoSuchAlgorithmException {
1592+
// Test that MD5 calculation works correctly for Object Lock compliance
1593+
String testContent = "Hello, World!";
1594+
byte[] payload = testContent.getBytes();
1595+
1596+
// Calculate MD5 hash using the same logic as in uploadFileInS3
1597+
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
1598+
byte[] md5Hash = md5Digest.digest(payload);
1599+
String md5Base64 = java.util.Base64.getEncoder().encodeToString(md5Hash);
1600+
1601+
// Verify MD5 calculation is correct
1602+
assertNotNull(md5Base64);
1603+
assertTrue(md5Base64.length() > 0);
1604+
1605+
// For "Hello, World!" the MD5 hash should be deterministic
1606+
// Expected MD5 for "Hello, World!" is 65a8e27d8879283831b664bd8b7f0ad4
1607+
String expectedMd5Hex = "65a8e27d8879283831b664bd8b7f0ad4";
1608+
String actualMd5Hex = bytesToHex(md5Hash);
1609+
assertEquals(expectedMd5Hex, actualMd5Hex);
1610+
1611+
// Verify base64 encoding
1612+
String expectedBase64 = java.util.Base64.getEncoder().encodeToString(hexToBytes(expectedMd5Hex));
1613+
assertEquals(expectedBase64, md5Base64);
1614+
}
1615+
1616+
private String bytesToHex(byte[] bytes) {
1617+
StringBuilder result = new StringBuilder();
1618+
for (byte b : bytes) {
1619+
result.append(String.format("%02x", b));
1620+
}
1621+
return result.toString();
1622+
}
1623+
1624+
private byte[] hexToBytes(String hex) {
1625+
int len = hex.length();
1626+
byte[] data = new byte[len / 2];
1627+
for (int i = 0; i < len; i += 2) {
1628+
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16));
1629+
}
1630+
return data;
1631+
}
15871632
}

app/server/appsmith-server/src/main/java/com/appsmith/server/aspect/GitRouteAspect.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ private Mono<?> clone(Context ctx) {
572572
ctx.getGitProfile().getAuthorName(),
573573
ctx.getGitKey(),
574574
ctx.getGitMeta().getRemoteUrl(),
575+
gitServiceConfig.getGitRootPath(),
575576
ctx.getRepoPath(),
576577
redisUrl,
577578
ctx.getBranchStoreKey());

0 commit comments

Comments
 (0)