Skip to content

Commit 335f5ed

Browse files
cgardenssubodh1810
andauthored
copy seed configurations from config/init to server (#4417)
Co-authored-by: subodh <[email protected]>
1 parent 8b01d98 commit 335f5ed

File tree

133 files changed

+301
-472
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+301
-472
lines changed

airbyte-config/init/build.gradle

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,42 @@ dependencies {
77

88
implementation project(':airbyte-config:models')
99
}
10+
11+
// generate seed for each yaml file.
12+
task generateSeed {
13+
def seeds = [
14+
[
15+
"sourceDefinitionId",
16+
new File(project.projectDir, '/src/main/resources/seed/source_definitions.yaml'),
17+
new File(project.projectDir, '/src/main/resources/config/STANDARD_SOURCE_DEFINITION')
18+
],
19+
[
20+
"destinationDefinitionId",
21+
new File(project.projectDir, '/src/main/resources/seed/destination_definitions.yaml'),
22+
new File(project.projectDir, '/src/main/resources/config/STANDARD_DESTINATION_DEFINITION')
23+
],
24+
]
25+
seeds.each{val ->
26+
def name = val[0]
27+
def taskName = "generateSeed$name"
28+
dependsOn taskName
29+
task "$taskName"(type: JavaExec) {
30+
classpath = sourceSets.main.runtimeClasspath
31+
32+
main = 'io.airbyte.config.init.SeedRepository'
33+
34+
// arguments to pass to the application
35+
args '--id-name'
36+
args val[0]
37+
args '--input-path'
38+
args val[1]
39+
args '--output-path'
40+
args val[2]
41+
}
42+
}
43+
}
44+
45+
// we only want to attempt generateSeed if tests have passed.
46+
generateSeed.dependsOn(check)
47+
generateSeed.dependsOn(assemble)
48+
build.dependsOn(generateSeed)
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2020 Airbyte
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package io.airbyte.config.init;
26+
27+
import com.fasterxml.jackson.databind.JsonNode;
28+
import io.airbyte.commons.io.IOs;
29+
import io.airbyte.commons.json.Jsons;
30+
import io.airbyte.config.helpers.YamlListToStandardDefinitions;
31+
import java.io.IOException;
32+
import java.nio.file.Files;
33+
import java.nio.file.Path;
34+
import java.util.stream.Collectors;
35+
import org.apache.commons.cli.CommandLine;
36+
import org.apache.commons.cli.CommandLineParser;
37+
import org.apache.commons.cli.DefaultParser;
38+
import org.apache.commons.cli.HelpFormatter;
39+
import org.apache.commons.cli.Option;
40+
import org.apache.commons.cli.Options;
41+
import org.apache.commons.cli.ParseException;
42+
43+
/**
44+
* This class takes in a yaml file with a list of objects. It then then assigns each object a uuid
45+
* based on its name attribute. The uuid is written as a field in the object with the key specified
46+
* as the id-name. It then writes each object to its own file in the specified output directory.
47+
* Each file's name is the generated uuid. The goal is that a user should be able to add objects to
48+
* the database seed without having to generate uuids themselves. The output files should be
49+
* compatible with our file system database (config persistence).
50+
*/
51+
public class SeedRepository {
52+
53+
private static final Options OPTIONS = new Options();
54+
private static final Option ID_NAME_OPTION = new Option("id", "id-name", true, "field name of the id");
55+
private static final Option INPUT_PATH_OPTION = new Option("i", "input-path", true, "path to input file");
56+
private static final Option OUTPUT_PATH_OPTION = new Option("o", "output-path", true, "path to where files will be output");
57+
58+
static {
59+
ID_NAME_OPTION.setRequired(true);
60+
INPUT_PATH_OPTION.setRequired(true);
61+
OUTPUT_PATH_OPTION.setRequired(true);
62+
OPTIONS.addOption(ID_NAME_OPTION);
63+
OPTIONS.addOption(INPUT_PATH_OPTION);
64+
OPTIONS.addOption(OUTPUT_PATH_OPTION);
65+
}
66+
67+
private static CommandLine parse(final String[] args) {
68+
final CommandLineParser parser = new DefaultParser();
69+
final HelpFormatter helpFormatter = new HelpFormatter();
70+
71+
try {
72+
return parser.parse(OPTIONS, args);
73+
} catch (final ParseException e) {
74+
helpFormatter.printHelp("", OPTIONS);
75+
throw new IllegalArgumentException(e);
76+
}
77+
}
78+
79+
public static void main(final String[] args) throws IOException {
80+
final CommandLine parsed = parse(args);
81+
final String idName = parsed.getOptionValue(ID_NAME_OPTION.getOpt());
82+
final Path inputPath = Path.of(parsed.getOptionValue(INPUT_PATH_OPTION.getOpt()));
83+
final Path outputPath = Path.of(parsed.getOptionValue(OUTPUT_PATH_OPTION.getOpt()));
84+
85+
new SeedRepository().run(idName, inputPath, outputPath);
86+
}
87+
88+
public void run(final String idName, final Path input, final Path output) throws IOException {
89+
final var jsonNode = YamlListToStandardDefinitions.verifyAndConvertToJsonNode(idName, IOs.readFile(input));
90+
final var elementsIter = jsonNode.elements();
91+
92+
// clean output directory.
93+
for (final Path file : Files.list(output).collect(Collectors.toList())) {
94+
Files.delete(file);
95+
}
96+
97+
// write to output directory.
98+
while (elementsIter.hasNext()) {
99+
final JsonNode element = Jsons.clone(elementsIter.next());
100+
IOs.writeFile(
101+
output,
102+
element.get(idName).asText() + ".json",
103+
Jsons.toPrettyString(element));
104+
}
105+
}
106+
107+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2020 Airbyte
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package io.airbyte.config.init;
26+
27+
import static org.junit.jupiter.api.Assertions.*;
28+
29+
import com.fasterxml.jackson.databind.JsonNode;
30+
import com.fasterxml.jackson.databind.node.ArrayNode;
31+
import com.fasterxml.jackson.databind.node.ObjectNode;
32+
import com.google.common.collect.ImmutableMap;
33+
import io.airbyte.commons.io.IOs;
34+
import io.airbyte.commons.json.Jsons;
35+
import io.airbyte.commons.yaml.Yamls;
36+
import java.io.IOException;
37+
import java.nio.file.Files;
38+
import java.nio.file.Path;
39+
import java.util.ArrayList;
40+
import java.util.UUID;
41+
import java.util.stream.Collectors;
42+
import org.junit.jupiter.api.BeforeEach;
43+
import org.junit.jupiter.api.Test;
44+
45+
class SeedRepositoryTest {
46+
47+
private static final String CONFIG_ID = "configId";
48+
private static final JsonNode OBJECT = Jsons.jsonNode(ImmutableMap.builder()
49+
.put(CONFIG_ID, UUID.randomUUID())
50+
.put("name", "barker")
51+
.put("description", "playwright")
52+
.build());
53+
54+
private Path input;
55+
private Path output;
56+
57+
@BeforeEach
58+
void setup() throws IOException {
59+
input = Files.createTempDirectory("test_input").resolve("input.yaml");
60+
output = Files.createTempDirectory("test_output");
61+
62+
writeSeedList(OBJECT);
63+
}
64+
65+
@Test
66+
void testWrite() throws IOException {
67+
new SeedRepository().run(CONFIG_ID, input, output);
68+
final JsonNode actual = Jsons.deserialize(IOs.readFile(output, OBJECT.get(CONFIG_ID).asText() + ".json"));
69+
assertEquals(OBJECT, actual);
70+
}
71+
72+
@Test
73+
void testOverwrites() throws IOException {
74+
new SeedRepository().run(CONFIG_ID, input, output);
75+
final JsonNode actual = Jsons.deserialize(IOs.readFile(output, OBJECT.get(CONFIG_ID).asText() + ".json"));
76+
assertEquals(OBJECT, actual);
77+
78+
final JsonNode clone = Jsons.clone(OBJECT);
79+
((ObjectNode) clone).put("description", "revolutionary");
80+
writeSeedList(clone);
81+
82+
new SeedRepository().run(CONFIG_ID, input, output);
83+
final JsonNode actualAfterOverwrite = Jsons.deserialize(IOs.readFile(output, OBJECT.get(CONFIG_ID).asText() + ".json"));
84+
assertEquals(clone, actualAfterOverwrite);
85+
}
86+
87+
@Test
88+
void testFailsOnDuplicateId() {
89+
final JsonNode object = Jsons.clone(OBJECT);
90+
((ObjectNode) object).put("name", "howard");
91+
92+
writeSeedList(OBJECT, object);
93+
final SeedRepository seedRepository = new SeedRepository();
94+
assertThrows(IllegalArgumentException.class, () -> seedRepository.run(CONFIG_ID, input, output));
95+
}
96+
97+
@Test
98+
void testFailsOnDuplicateName() {
99+
final JsonNode object = Jsons.clone(OBJECT);
100+
((ObjectNode) object).put(CONFIG_ID, UUID.randomUUID().toString());
101+
102+
writeSeedList(OBJECT, object);
103+
final SeedRepository seedRepository = new SeedRepository();
104+
assertThrows(IllegalArgumentException.class, () -> seedRepository.run(CONFIG_ID, input, output));
105+
}
106+
107+
@Test
108+
void testPristineOutputDir() throws IOException {
109+
IOs.writeFile(output, "blah.json", "{}");
110+
assertEquals(1, Files.list(output).count());
111+
112+
new SeedRepository().run(CONFIG_ID, input, output);
113+
114+
// verify the file that the file that was already in the directory is gone.
115+
assertEquals(1, Files.list(output).count());
116+
assertEquals(OBJECT.get(CONFIG_ID).asText() + ".json", Files.list(output).collect(Collectors.toList()).get(0).getFileName().toString());
117+
}
118+
119+
private void writeSeedList(JsonNode... seeds) {
120+
final JsonNode seedList = Jsons.jsonNode(new ArrayList<>());
121+
for (JsonNode seed : seeds) {
122+
((ArrayNode) seedList).add(seed);
123+
}
124+
IOs.writeFile(input, Yamls.serialize(seedList));
125+
}
126+
127+
}

airbyte-integrations/connector-templates/generator/plopfile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ${additionalMessage || ""}
2424

2525
module.exports = function (plop) {
2626
const docRoot = '../../../docs/integrations';
27-
const definitionRoot = '../../../airbyte-server/src/main/resources';
27+
const definitionRoot = '../../../airbyte-config/init/src/main/resources';
2828

2929
const pythonSourceInputRoot = '../source-python';
3030
const singerSourceInputRoot = '../source-singer';

airbyte-server/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ RUN chmod +x wait
1414
COPY build/distributions/${APPLICATION}*.tar ${APPLICATION}.tar
1515

1616
RUN mkdir latest_seeds
17-
COPY build/resources/main/config latest_seeds
17+
COPY build/config_init/resources/main/config latest_seeds
1818

1919
RUN tar xf ${APPLICATION}.tar --strip-components=1
2020

airbyte-server/build.gradle

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ dependencies {
4141
testImplementation "org.testcontainers:postgresql:1.15.1"
4242
}
4343

44+
// we want to be able to access the generated db files from config/init when we build the server docker image.
45+
task copySeed(type: Copy, dependsOn: [project(':airbyte-config:init').processResources]) {
46+
from "${project(':airbyte-config:init').buildDir}/resources/main/config"
47+
into "${buildDir}/config_init/resources/main/config"
48+
}
49+
50+
// need to make sure that the files are in the resource directory before copying.
51+
//project.tasks.copySeed.mustRunAfter(project(':airbyte-config:init').tasks.processResources)
52+
assemble.dependsOn(project.tasks.copySeed)
53+
4454
application {
4555
mainClass = 'io.airbyte.server.ServerApp'
4656
}
@@ -60,44 +70,4 @@ run {
6070
environment "AIRBYTE_VERSION", env.VERSION
6171
environment "AIRBYTE_ROLE", System.getenv('AIRBYTE_ROLE')
6272
environment "TEMPORAL_HOST", "localhost:7233"
63-
6473
}
65-
66-
// generate seed for each yaml file.
67-
task generateSeed {
68-
def seeds = [
69-
[
70-
"sourceDefinitionId",
71-
new File(project.projectDir, '/src/main/resources/seed/source_definitions.yaml'),
72-
new File(project.projectDir, '/src/main/resources/config/STANDARD_SOURCE_DEFINITION')
73-
],
74-
[
75-
"destinationDefinitionId",
76-
new File(project.projectDir, '/src/main/resources/seed/destination_definitions.yaml'),
77-
new File(project.projectDir, '/src/main/resources/config/STANDARD_DESTINATION_DEFINITION')
78-
],
79-
]
80-
seeds.each{val ->
81-
def name = val[0]
82-
def taskName = "generateSeed$name"
83-
dependsOn taskName
84-
task "$taskName"(type: JavaExec) {
85-
classpath = sourceSets.main.runtimeClasspath
86-
87-
main = 'io.airbyte.server.SeedRepository'
88-
89-
// arguments to pass to the application
90-
args '--id-name'
91-
args val[0]
92-
args '--input-path'
93-
args val[1]
94-
args '--output-path'
95-
args val[2]
96-
}
97-
}
98-
}
99-
100-
// we only want to attempt generateSeed if tests have passed.
101-
generateSeed.dependsOn(check)
102-
generateSeed.dependsOn(assemble)
103-
build.dependsOn(generateSeed)

airbyte-server/seed.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ WORKDIR /app
44

55
# the sole purpose of this image is to seed the data volume with the default data
66
# that the app should have when it is first installed.
7-
COPY build/resources/main/config seed/config
7+
COPY build/config_init/resources/main/config seed/config

airbyte-server/src/main/java/io/airbyte/server/services/AirbyteGithubStore.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ public class AirbyteGithubStore {
4343

4444
private static final String GITHUB_BASE_URL = "https://raw.githubusercontent.com";
4545
private static final String SOURCE_DEFINITION_LIST_LOCATION_PATH =
46-
"/airbytehq/airbyte/master/airbyte-server/src/main/resources/seed/source_definitions.yaml";
46+
"/airbytehq/airbyte/master/airbyte-config/init/src/main/resources/seed/source_definitions.yaml";
4747
private static final String DESTINATION_DEFINITION_LIST_LOCATION_PATH =
48-
"/airbytehq/airbyte/master/airbyte-server/src/main/resources/seed/destination_definitions.yaml";
48+
"/airbytehq/airbyte/master/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml";
4949

5050
private static final HttpClient httpClient = HttpClient.newHttpClient();
5151

0 commit comments

Comments
 (0)