Skip to content

Commit 7028c8c

Browse files
committed
Initial draft
1 parent 29c29a6 commit 7028c8c

File tree

2 files changed

+359
-27
lines changed

2 files changed

+359
-27
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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.transport;
11+
12+
import org.elasticsearch.gradle.Version;
13+
import org.elasticsearch.gradle.VersionProperties;
14+
import org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionDefinition;
15+
import org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionLatest;
16+
import org.gradle.api.DefaultTask;
17+
import org.gradle.api.Project;
18+
import org.gradle.api.file.DirectoryProperty;
19+
import org.gradle.api.provider.ListProperty;
20+
import org.gradle.api.provider.Property;
21+
import org.gradle.api.tasks.Input;
22+
import org.gradle.api.tasks.InputDirectory;
23+
import org.gradle.api.tasks.Optional;
24+
import org.gradle.api.tasks.PathSensitive;
25+
import org.gradle.api.tasks.PathSensitivity;
26+
import org.gradle.api.tasks.TaskAction;
27+
import org.gradle.api.tasks.options.Option;
28+
import org.gradle.process.ExecOperations;
29+
import org.gradle.process.ExecResult;
30+
31+
import javax.inject.Inject;
32+
import java.io.ByteArrayOutputStream;
33+
import java.io.IOException;
34+
import java.nio.charset.StandardCharsets;
35+
import java.nio.file.Path;
36+
import java.util.ArrayList;
37+
import java.util.Collections;
38+
import java.util.HashSet;
39+
import java.util.List;
40+
import java.util.Objects;
41+
import java.util.Set;
42+
import java.util.function.BiFunction;
43+
import java.util.function.Function;
44+
45+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.IdComponents.PATCH;
46+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.IdComponents.SERVER;
47+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.IdComponents.SUBSIDIARY;
48+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.LATEST_DIR;
49+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.SERVERLESS_BRANCH;
50+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionId;
51+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.latestFilePath;
52+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.readLatestFile;
53+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.writeDefinitionFile;
54+
import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.writeLatestFile;
55+
56+
/**
57+
* This task generates transport version definition files. These files
58+
* are runtime resources that TransportVersion loads statically.
59+
* They contain a comma separated list of integer ids. Each file is named the same
60+
* as the transport version name itself (with the .csv suffix).
61+
*/
62+
public abstract class GenerateTransportVersionDefinitionTask extends DefaultTask {
63+
64+
65+
/**
66+
* Specifies the directory in which contains all TransportVersionSet data files.
67+
*
68+
* @return
69+
*/
70+
@InputDirectory
71+
// @Optional
72+
@PathSensitive(PathSensitivity.RELATIVE)
73+
public abstract DirectoryProperty getResourcesDirectory();// The plugin should always set this, not optional
74+
75+
// assumption: this task is always run on main, so we can determine the name by diffing with main and looking for new files added in the
76+
// definition directory. (not true: once we generate the file, this will no longer hold true if we then need to update it)
77+
78+
/**
79+
* Used to set the name of the TransportVersionSet for which a data file will be generated.
80+
*/
81+
@Input
82+
// @Optional
83+
@Option(option = "name", description = "TBD")
84+
public abstract Property<String> getTransportVersionName(); // The plugin should always set this, not optional
85+
86+
/**
87+
* Used to set the `major.minor` release version for which the specific TransportVersion ID will be generated.
88+
* E.g.: "9.2", "8.18", etc.
89+
*/
90+
@Optional // This is optional as we will look for labels in an env var if this is not set.
91+
@Input
92+
@Option(option = "branches", description = "The branches for which to generate IDs, e.g. --branch=\"9.2,9.1,SERVERLESS\"")
93+
public abstract ListProperty<String> getBranches();
94+
95+
// @Optional
96+
// @Input
97+
// public abstract Property<Function<String, IdComponents>> getIdIncrementSupplier();
98+
99+
private final Path rootPath;
100+
private final ExecOperations execOperations;
101+
102+
@Inject
103+
public GenerateTransportVersionDefinitionTask(Project project, ExecOperations execOperations) {
104+
this.execOperations = execOperations;
105+
this.rootPath = getProject().getRootProject().getLayout().getProjectDirectory().getAsFile().toPath();
106+
}
107+
108+
@TaskAction
109+
public void generateTransportVersionData() throws IOException {
110+
getLogger().lifecycle("Name: " + getTransportVersionName().get());
111+
getLogger().lifecycle("Versions: " + getBranches().get());
112+
Path resourcesDir = Objects.requireNonNull(getResourcesDirectory().getAsFile().get()).toPath();
113+
String name = getTransportVersionName().isPresent() ? getTransportVersionName().get() : findLocalTransportVersionName();
114+
Set<String> targetMinorVersions = new HashSet<>(
115+
getBranches().isPresent()
116+
? new HashSet<>(getBranches().get())
117+
: findTargetMinorVersions()
118+
);
119+
120+
List<TransportVersionId> ids = new ArrayList<>();
121+
for (String branchName : getKnownBranchNames(resourcesDir)) {
122+
TransportVersionLatest latest = readLatestFile(resourcesDir, branchName);
123+
124+
if (name.equals(latest.name())) {
125+
if (targetMinorVersions.contains(branchName) == false) {
126+
// Regenerate to make this operation idempotent. Need to undo prior updates to the latest files if the list of minor
127+
// versions has changed.
128+
129+
// TODO should we just always regen?
130+
writeLatestFile(resourcesDir, readExistingLatest(branchName));
131+
}
132+
} else {
133+
if (targetMinorVersions.contains(branchName)) {
134+
// increment
135+
var incrementedId = incrementTVId(latest.id(), branchName);
136+
ids.add(incrementedId);
137+
writeLatestFile(resourcesDir, new TransportVersionLatest(branchName, name, incrementedId));
138+
}
139+
}
140+
}
141+
// (Re)write the definition file.
142+
writeDefinitionFile(resourcesDir, new TransportVersionDefinition(name, ids));
143+
}
144+
145+
private TransportVersionId incrementTVId(TransportVersionId id, String branchName) {
146+
// We can only run this task on main, so the ElasticsearchVersion will be for main.
147+
Version mainVersion = VersionProperties.getElasticsearchVersion();
148+
String latestFileNameForMain = mainVersion.getMajor() + "." + mainVersion.getMinor();
149+
150+
final var isMain = branchName.equals(latestFileNameForMain);
151+
if (isMain) {
152+
return id.bumpComponent(SERVER);
153+
} else if (branchName.equals(SERVERLESS_BRANCH)) {
154+
return id.bumpComponent(SUBSIDIARY);
155+
} else {
156+
return id.bumpComponent(PATCH);
157+
}
158+
}
159+
160+
private Set<String> getKnownBranchNames(Path resourcesDir) {
161+
// list files under latest
162+
// TODO add serverless? Otherwise just delete this function if it that doesn't make sense here.
163+
return recordExistingResources(resourcesDir.resolve(LATEST_DIR));
164+
}
165+
166+
private String findLocalTransportVersionName() {
167+
// check for missing
168+
// if none missing, look at git diff against main
169+
return "";
170+
}
171+
172+
private List<String> findTargetMinorVersions() {
173+
// look for env var indicating github PR link from CI
174+
// use github api to find current labels, filter down to version labels
175+
// map version labels to branches
176+
return List.of();
177+
}
178+
179+
// TODO duplicated
180+
private Set<String> recordExistingResources(Path resourcesPath) {
181+
String output = gitCommand("ls-tree", "--name-only", "-r", "main", resourcesPath.toString());
182+
return Set.of(output.split(System.lineSeparator()));
183+
}
184+
185+
// TODO duplicated
186+
private TransportVersionLatest readExistingLatest(String branch) {
187+
return readExistingFile(branch, this::latestRelativePath, TransportVersionLatest::fromString);
188+
}
189+
190+
// TODO duplicated
191+
private <T> T readExistingFile(String name, Function<String, String> pathFunction, BiFunction<String, String, T> parser) {
192+
String relativePath = pathFunction.apply(name);
193+
String content = gitCommand("show", "main:" + relativePath).strip();
194+
return parser.apply(relativePath, content);
195+
}
196+
197+
// TODO duplicated
198+
private String latestRelativePath(String branch) {
199+
return relativePath(latestFilePath(resourcesDirPath(), branch));
200+
}
201+
202+
// TODO duplicated
203+
private Path resourcesDirPath() {
204+
return getResourcesDirectory().get().getAsFile().toPath();
205+
}
206+
207+
// TODO duplicated
208+
private String relativePath(Path file) {
209+
return rootPath.relativize(file).toString();
210+
}
211+
212+
// TODO duplicated
213+
public String gitCommand(String... args) {
214+
final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
215+
216+
List<String> command = new ArrayList<>();
217+
Collections.addAll(command, "git", "-C", rootPath.toAbsolutePath().toString());
218+
Collections.addAll(command, args);
219+
220+
ExecResult result = execOperations.exec(spec -> {
221+
spec.setCommandLine(command);
222+
spec.setStandardOutput(stdout);
223+
spec.setErrorOutput(stdout);
224+
spec.setIgnoreExitValue(true);
225+
});
226+
227+
if (result.getExitValue() != 0) {
228+
throw new RuntimeException(
229+
"git command failed with exit code "
230+
+ result.getExitValue()
231+
+ System.lineSeparator()
232+
+ "command: "
233+
+ String.join(" ", command)
234+
+ System.lineSeparator()
235+
+ "output:"
236+
+ System.lineSeparator()
237+
+ stdout.toString(StandardCharsets.UTF_8)
238+
);
239+
}
240+
241+
return stdout.toString(StandardCharsets.UTF_8);
242+
}
243+
}

0 commit comments

Comments
 (0)