Skip to content

Commit e784f43

Browse files
committed
Add support for a custom transport version check predicate
1 parent e52288d commit e784f43

File tree

6 files changed

+255
-58
lines changed

6 files changed

+255
-58
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.gradle.internal.dependencies.patches;
11+
12+
import org.objectweb.asm.ClassReader;
13+
import org.objectweb.asm.ClassVisitor;
14+
import org.objectweb.asm.ClassWriter;
15+
16+
import java.io.File;
17+
import java.io.FileOutputStream;
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.util.Enumeration;
21+
import java.util.HashMap;
22+
import java.util.Locale;
23+
import java.util.Map;
24+
import java.util.function.Function;
25+
import java.util.jar.JarEntry;
26+
import java.util.jar.JarFile;
27+
import java.util.jar.JarOutputStream;
28+
29+
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
30+
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
31+
32+
public class Utils {
33+
public static void patchJar(File inputFile, File outputFile, Map<String, Function<ClassWriter, ClassVisitor>> patchers) {
34+
var classPatchers = new HashMap<>(patchers);
35+
try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) {
36+
Enumeration<JarEntry> entries = jarFile.entries();
37+
while (entries.hasMoreElements()) {
38+
JarEntry entry = entries.nextElement();
39+
String entryName = entry.getName();
40+
// Add the entry to the new JAR file
41+
jos.putNextEntry(new JarEntry(entryName));
42+
43+
Function<ClassWriter, ClassVisitor> classPatcher = classPatchers.remove(entryName);
44+
if (classPatcher != null) {
45+
byte[] classToPatch = jarFile.getInputStream(entry).readAllBytes();
46+
47+
ClassReader classReader = new ClassReader(classToPatch);
48+
ClassWriter classWriter = new ClassWriter(classReader, COMPUTE_MAXS | COMPUTE_FRAMES);
49+
classReader.accept(classPatcher.apply(classWriter), 0);
50+
jos.write(classWriter.toByteArray());
51+
} else {
52+
// Read the entry's data and write it to the new JAR
53+
try (InputStream is = jarFile.getInputStream(entry)) {
54+
is.transferTo(jos);
55+
}
56+
}
57+
jos.closeEntry();
58+
}
59+
} catch (IOException ex) {
60+
throw new RuntimeException(ex);
61+
}
62+
63+
if (classPatchers.isEmpty() == false) {
64+
throw new IllegalArgumentException(
65+
String.format(
66+
Locale.ROOT,
67+
"error patching [%s]: the jar does not contain [%s]",
68+
inputFile.getName(),
69+
String.join(", ", patchers.keySet())
70+
)
71+
);
72+
}
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.gradle.internal.dependencies.patches.awsv2sdk;
11+
12+
import org.elasticsearch.gradle.internal.dependencies.patches.Utils;
13+
import org.gradle.api.artifacts.transform.CacheableTransform;
14+
import org.gradle.api.artifacts.transform.InputArtifact;
15+
import org.gradle.api.artifacts.transform.TransformAction;
16+
import org.gradle.api.artifacts.transform.TransformOutputs;
17+
import org.gradle.api.artifacts.transform.TransformParameters;
18+
import org.gradle.api.file.FileSystemLocation;
19+
import org.gradle.api.provider.Provider;
20+
import org.gradle.api.tasks.Classpath;
21+
import org.jetbrains.annotations.NotNull;
22+
import org.objectweb.asm.ClassVisitor;
23+
import org.objectweb.asm.ClassWriter;
24+
25+
import java.io.File;
26+
import java.util.Map;
27+
import java.util.function.Function;
28+
29+
import static java.util.Map.entry;
30+
31+
@CacheableTransform
32+
public abstract class Awsv2ClassPatcher implements TransformAction<TransformParameters.None> {
33+
34+
private static final String JAR_FILE_TO_PATCH = "aws-query-protocol";
35+
36+
private static final Map<String, Function<ClassWriter, ClassVisitor>> CLASS_PATCHERS = Map.ofEntries(
37+
entry("software/amazon/awssdk/protocols/query/internal/marshall/ListQueryMarshaller.class", StringFormatInPathResolverPatcher::new)
38+
);
39+
40+
@Classpath
41+
@InputArtifact
42+
public abstract Provider<FileSystemLocation> getInputArtifact();
43+
44+
@Override
45+
public void transform(@NotNull TransformOutputs outputs) {
46+
File inputFile = getInputArtifact().get().getAsFile();
47+
48+
if (inputFile.getName().startsWith(JAR_FILE_TO_PATCH) == false) {
49+
System.out.println("Skipping " + inputFile.getName());
50+
outputs.file(getInputArtifact());
51+
} else {
52+
System.out.println("Patching " + inputFile.getName());
53+
File outputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar"));
54+
Utils.patchJar(inputFile, outputFile, CLASS_PATCHERS);
55+
}
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.gradle.internal.dependencies.patches.awsv2sdk;
11+
12+
import org.objectweb.asm.ClassVisitor;
13+
import org.objectweb.asm.ClassWriter;
14+
import org.objectweb.asm.MethodVisitor;
15+
import org.objectweb.asm.Type;
16+
17+
import java.util.Locale;
18+
19+
import static org.objectweb.asm.Opcodes.ASM9;
20+
import static org.objectweb.asm.Opcodes.GETSTATIC;
21+
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
22+
23+
class StringFormatInPathResolverPatcher extends ClassVisitor {
24+
25+
StringFormatInPathResolverPatcher(ClassWriter classWriter) {
26+
super(ASM9, classWriter);
27+
}
28+
29+
@Override
30+
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
31+
return new ReplaceCallMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions));
32+
}
33+
34+
/**
35+
* Replaces calls to String.format(format, args); with calls to String.format(Locale.ROOT, format, args);
36+
*/
37+
private static class ReplaceCallMethodVisitor extends MethodVisitor {
38+
private static final String CLASS_INTERNAL_NAME = Type.getInternalName(String.class);
39+
private static final String METHOD_NAME = "format";
40+
private static final String OLD_METHOD_DESCRIPTOR = Type.getMethodDescriptor(
41+
Type.getType(String.class),
42+
Type.getType(String.class),
43+
Type.getType(Object[].class)
44+
);
45+
private static final String NEW_METHOD_DESCRIPTOR = Type.getMethodDescriptor(
46+
Type.getType(String.class),
47+
Type.getType(Locale.class),
48+
Type.getType(String.class),
49+
Type.getType(Object[].class)
50+
);
51+
52+
private boolean foundFormatPattern = false;
53+
54+
ReplaceCallMethodVisitor(MethodVisitor methodVisitor) {
55+
super(ASM9, methodVisitor);
56+
}
57+
58+
@Override
59+
public void visitLdcInsn(Object value) {
60+
if (value instanceof String s && s.startsWith("%s")) {
61+
// Push the extra arg on the stack
62+
mv.visitFieldInsn(GETSTATIC, Type.getInternalName(Locale.class), "ROOT", Type.getDescriptor(Locale.class));
63+
foundFormatPattern = true;
64+
}
65+
super.visitLdcInsn(value);
66+
}
67+
68+
@Override
69+
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
70+
if (opcode == INVOKESTATIC
71+
&& foundFormatPattern
72+
&& CLASS_INTERNAL_NAME.equals(owner)
73+
&& METHOD_NAME.equals(name)
74+
&& OLD_METHOD_DESCRIPTOR.equals(descriptor)) {
75+
// Replace the call with String.format(Locale.ROOT, format, args)
76+
mv.visitMethodInsn(INVOKESTATIC, CLASS_INTERNAL_NAME, METHOD_NAME, NEW_METHOD_DESCRIPTOR, false);
77+
foundFormatPattern = false;
78+
} else {
79+
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
80+
}
81+
}
82+
}
83+
}

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/HdfsClassPatcher.java

Lines changed: 2 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;
1111

12+
import org.elasticsearch.gradle.internal.dependencies.patches.Utils;
1213
import org.gradle.api.artifacts.transform.CacheableTransform;
1314
import org.gradle.api.artifacts.transform.InputArtifact;
1415
import org.gradle.api.artifacts.transform.TransformAction;
@@ -20,28 +21,17 @@
2021
import org.gradle.api.tasks.Input;
2122
import org.gradle.api.tasks.Optional;
2223
import org.jetbrains.annotations.NotNull;
23-
import org.objectweb.asm.ClassReader;
2424
import org.objectweb.asm.ClassVisitor;
2525
import org.objectweb.asm.ClassWriter;
2626

2727
import java.io.File;
28-
import java.io.FileOutputStream;
29-
import java.io.IOException;
30-
import java.io.InputStream;
31-
import java.util.Enumeration;
3228
import java.util.HashMap;
3329
import java.util.List;
34-
import java.util.Locale;
3530
import java.util.Map;
3631
import java.util.function.Function;
37-
import java.util.jar.JarEntry;
38-
import java.util.jar.JarFile;
39-
import java.util.jar.JarOutputStream;
4032
import java.util.regex.Pattern;
4133

4234
import static java.util.Map.entry;
43-
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
44-
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
4535

4636
@CacheableTransform
4737
public abstract class HdfsClassPatcher implements TransformAction<HdfsClassPatcher.Parameters> {
@@ -99,51 +89,8 @@ public void transform(@NotNull TransformOutputs outputs) {
9989
Map<String, Function<ClassWriter, ClassVisitor>> jarPatchers = new HashMap<>(patchers.jarPatchers());
10090
File outputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar"));
10191

102-
patchJar(inputFile, outputFile, jarPatchers);
103-
104-
if (jarPatchers.isEmpty() == false) {
105-
throw new IllegalArgumentException(
106-
String.format(
107-
Locale.ROOT,
108-
"error patching [%s] with [%s]: the jar does not contain [%s]",
109-
inputFile.getName(),
110-
patchers.artifactPattern().toString(),
111-
String.join(", ", jarPatchers.keySet())
112-
)
113-
);
114-
}
92+
Utils.patchJar(inputFile, outputFile, jarPatchers);
11593
});
11694
}
11795
}
118-
119-
private static void patchJar(File inputFile, File outputFile, Map<String, Function<ClassWriter, ClassVisitor>> jarPatchers) {
120-
try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) {
121-
Enumeration<JarEntry> entries = jarFile.entries();
122-
while (entries.hasMoreElements()) {
123-
JarEntry entry = entries.nextElement();
124-
String entryName = entry.getName();
125-
// Add the entry to the new JAR file
126-
jos.putNextEntry(new JarEntry(entryName));
127-
128-
Function<ClassWriter, ClassVisitor> classPatcher = jarPatchers.remove(entryName);
129-
if (classPatcher != null) {
130-
byte[] classToPatch = jarFile.getInputStream(entry).readAllBytes();
131-
132-
ClassReader classReader = new ClassReader(classToPatch);
133-
ClassWriter classWriter = new ClassWriter(classReader, COMPUTE_FRAMES | COMPUTE_MAXS);
134-
classReader.accept(classPatcher.apply(classWriter), 0);
135-
136-
jos.write(classWriter.toByteArray());
137-
} else {
138-
// Read the entry's data and write it to the new JAR
139-
try (InputStream is = jarFile.getInputStream(entry)) {
140-
is.transferTo(jos);
141-
}
142-
}
143-
jos.closeEntry();
144-
}
145-
} catch (IOException ex) {
146-
throw new RuntimeException(ex);
147-
}
148-
}
14996
}

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/SubjectGetSubjectPatcher.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class SubjectGetSubjectPatcher extends ClassVisitor {
2525

2626
@Override
2727
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
28-
return new ReplaceCallMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions), name, access, descriptor);
28+
return new ReplaceCallMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions));
2929
}
3030

3131
/**
@@ -35,7 +35,7 @@ private static class ReplaceCallMethodVisitor extends MethodVisitor {
3535
private static final String SUBJECT_CLASS_INTERNAL_NAME = "javax/security/auth/Subject";
3636
private static final String METHOD_NAME = "getSubject";
3737

38-
ReplaceCallMethodVisitor(MethodVisitor methodVisitor, String name, int access, String descriptor) {
38+
ReplaceCallMethodVisitor(MethodVisitor methodVisitor) {
3939
super(ASM9, methodVisitor);
4040
}
4141

plugins/discovery-ec2/build.gradle

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,31 @@ esplugin {
1515
classname ='org.elasticsearch.discovery.ec2.Ec2DiscoveryPlugin'
1616
}
1717

18+
def patched = Attribute.of('patched', Boolean)
19+
20+
configurations {
21+
compileClasspath {
22+
attributes {
23+
attribute(patched, true)
24+
}
25+
}
26+
runtimeClasspath {
27+
attributes {
28+
attribute(patched, true)
29+
}
30+
}
31+
testCompileClasspath {
32+
attributes {
33+
attribute(patched, true)
34+
}
35+
}
36+
testRuntimeClasspath {
37+
attributes {
38+
attribute(patched, true)
39+
}
40+
}
41+
}
42+
1843
dependencies {
1944

2045
implementation "software.amazon.awssdk:annotations:${versions.awsv2sdk}"
@@ -30,7 +55,7 @@ dependencies {
3055
implementation "software.amazon.awssdk:sdk-core:${versions.awsv2sdk}"
3156
implementation "software.amazon.awssdk:utils:${versions.awsv2sdk}"
3257

33-
runtimeOnly "software.amazon.awssdk:aws-query-protocol:${versions.awsv2sdk}"
58+
implementation "software.amazon.awssdk:aws-query-protocol:${versions.awsv2sdk}"
3459
runtimeOnly "software.amazon.awssdk:checksums-spi:${versions.awsv2sdk}"
3560
runtimeOnly "software.amazon.awssdk:checksums:${versions.awsv2sdk}"
3661
runtimeOnly "software.amazon.awssdk:http-auth-aws:${versions.awsv2sdk}"
@@ -65,6 +90,17 @@ dependencies {
6590
testImplementation project(':test:fixtures:ec2-imds-fixture')
6691

6792
internalClusterTestImplementation project(':test:fixtures:ec2-imds-fixture')
93+
94+
attributesSchema {
95+
attribute(patched)
96+
}
97+
artifactTypes.getByName("jar") {
98+
attributes.attribute(patched, false)
99+
}
100+
registerTransform(org.elasticsearch.gradle.internal.dependencies.patches.awsv2sdk.Awsv2ClassPatcher) {
101+
from.attribute(patched, false)
102+
to.attribute(patched, true)
103+
}
68104
}
69105

70106
tasks.named("dependencyLicenses").configure {

0 commit comments

Comments
 (0)