4
4
5
5
package io .airbyte .bootloader ;
6
6
7
- import static io .airbyte .config .persistence .split_secrets .SecretsHelpers .COORDINATE_FIELD ;
8
-
9
7
import com .fasterxml .jackson .databind .JsonNode ;
10
8
import com .google .common .annotations .VisibleForTesting ;
11
- import io .airbyte .commons .json .JsonPaths ;
12
9
import io .airbyte .commons .json .Jsons ;
13
- import io .airbyte .config .ConfigSchema ;
14
10
import io .airbyte .config .DestinationConnection ;
15
11
import io .airbyte .config .SourceConnection ;
16
12
import io .airbyte .config .StandardDestinationDefinition ;
17
13
import io .airbyte .config .StandardSourceDefinition ;
18
- import io .airbyte .config .persistence .ConfigPersistence ;
19
- import io .airbyte .config .persistence .split_secrets .SecretCoordinate ;
14
+ import io .airbyte .config .persistence .ConfigNotFoundException ;
15
+ import io .airbyte .config .persistence .ConfigRepository ;
16
+ import io .airbyte .config .persistence .SecretsRepositoryReader ;
17
+ import io .airbyte .config .persistence .SecretsRepositoryWriter ;
20
18
import io .airbyte .config .persistence .split_secrets .SecretPersistence ;
21
- import io .airbyte .config .persistence .split_secrets .SecretsHelpers ;
22
19
import io .airbyte .persistence .job .JobPersistence ;
20
+ import io .airbyte .protocol .models .ConnectorSpecification ;
23
21
import io .airbyte .validation .json .JsonValidationException ;
24
22
import java .io .IOException ;
23
+ import java .util .ArrayList ;
25
24
import java .util .List ;
26
25
import java .util .Map ;
27
26
import java .util .Optional ;
28
27
import java .util .UUID ;
29
- import java .util .concurrent .atomic .AtomicReference ;
30
- import java .util .function .Supplier ;
31
28
import java .util .stream .Collectors ;
32
29
import lombok .AllArgsConstructor ;
33
30
import lombok .Value ;
37
34
@ Slf4j
38
35
public class SecretMigrator {
39
36
40
- private final ConfigPersistence configPersistence ;
37
+ private final SecretsRepositoryReader secretsReader ;
38
+ private final SecretsRepositoryWriter secretsWriter ;
39
+ private final ConfigRepository configRepository ;
41
40
private final JobPersistence jobPersistence ;
42
41
private final Optional <SecretPersistence > secretPersistence ;
43
42
@@ -55,34 +54,39 @@ static class ConnectorConfiguration {
55
54
* Then for all the secret that are stored in a plain text format, it will save the plain text in
56
55
* the secret manager and store the coordinate in the config DB.
57
56
*/
58
- public void migrateSecrets () throws JsonValidationException , IOException {
57
+ public void migrateSecrets () throws JsonValidationException , IOException , ConfigNotFoundException {
59
58
if (secretPersistence .isEmpty ()) {
60
59
log .info ("No secret persistence is provided, the migration won't be run " );
61
60
62
61
return ;
63
62
}
64
- final List <StandardSourceDefinition > standardSourceDefinitions =
65
- configPersistence .listConfigs (ConfigSchema .STANDARD_SOURCE_DEFINITION , StandardSourceDefinition .class );
63
+ final List <StandardSourceDefinition > standardSourceDefinitions = configRepository .listStandardSourceDefinitions (true );
66
64
67
- final Map <UUID , JsonNode > definitionIdToSourceSpecs = standardSourceDefinitions
68
- .stream ().collect (Collectors .toMap (StandardSourceDefinition ::getSourceDefinitionId ,
69
- def -> def .getSpec ().getConnectionSpecification ()));
65
+ final Map <UUID , ConnectorSpecification > definitionIdToSourceSpecs = standardSourceDefinitions
66
+ .stream ().collect (Collectors .toMap (StandardSourceDefinition ::getSourceDefinitionId , StandardSourceDefinition ::getSpec ));
70
67
71
- final List <SourceConnection > sources = configPersistence .listConfigs (ConfigSchema .SOURCE_CONNECTION , SourceConnection .class );
68
+ final List <SourceConnection > sourcesWithoutSecrets = configRepository .listSourceConnection ();
69
+ final List <SourceConnection > sourcesWithSecrets = new ArrayList <>();
70
+ for (final SourceConnection source : sourcesWithoutSecrets ) {
71
+ final SourceConnection sourceWithSecrets = secretsReader .getSourceConnectionWithSecrets (source .getSourceId ());
72
+ sourcesWithSecrets .add (sourceWithSecrets );
73
+ }
72
74
73
- migrateSources (sources , definitionIdToSourceSpecs );
75
+ migrateSources (sourcesWithSecrets , definitionIdToSourceSpecs );
74
76
75
- final List <StandardDestinationDefinition > standardDestinationDefinitions =
76
- configPersistence .listConfigs (ConfigSchema .STANDARD_DESTINATION_DEFINITION ,
77
- StandardDestinationDefinition .class );
77
+ final List <StandardDestinationDefinition > standardDestinationDefinitions = configRepository .listStandardDestinationDefinitions (true );
78
78
79
- final Map <UUID , JsonNode > definitionIdToDestinationSpecs = standardDestinationDefinitions .stream ()
80
- .collect (Collectors .toMap (StandardDestinationDefinition ::getDestinationDefinitionId ,
81
- def -> def .getSpec ().getConnectionSpecification ()));
79
+ final Map <UUID , ConnectorSpecification > definitionIdToDestinationSpecs = standardDestinationDefinitions .stream ()
80
+ .collect (Collectors .toMap (StandardDestinationDefinition ::getDestinationDefinitionId , StandardDestinationDefinition ::getSpec ));
82
81
83
- final List <DestinationConnection > destinations = configPersistence .listConfigs (ConfigSchema .DESTINATION_CONNECTION , DestinationConnection .class );
82
+ final List <DestinationConnection > destinationsWithoutSecrets = configRepository .listDestinationConnection ();
83
+ final List <DestinationConnection > destinationsWithSecrets = new ArrayList <>();
84
+ for (final DestinationConnection destination : destinationsWithoutSecrets ) {
85
+ final DestinationConnection destinationWithoutSecrets = secretsReader .getDestinationConnectionWithSecrets (destination .getDestinationId ());
86
+ destinationsWithSecrets .add (destinationWithoutSecrets );
87
+ }
84
88
85
- migrateDestinations (destinations , definitionIdToDestinationSpecs );
89
+ migrateDestinations (destinationsWithSecrets , definitionIdToDestinationSpecs );
86
90
87
91
jobPersistence .setSecretMigrationDone ();
88
92
}
@@ -91,120 +95,46 @@ public void migrateSecrets() throws JsonValidationException, IOException {
91
95
* This is migrating the secrets for the source actors
92
96
*/
93
97
@ VisibleForTesting
94
- void migrateSources (final List <SourceConnection > sources , final Map <UUID , JsonNode > definitionIdToSourceSpecs )
98
+ void migrateSources (final List <SourceConnection > sources , final Map <UUID , ConnectorSpecification > definitionIdToSourceSpecs )
95
99
throws JsonValidationException , IOException {
96
100
log .info ("Migrating Sources" );
97
- final List <SourceConnection > sourceConnections = sources .stream ()
98
- .map (source -> {
99
- final JsonNode migratedConfig = migrateConfiguration (new ConnectorConfiguration (
100
- source .getWorkspaceId (),
101
- source .getConfiguration (),
102
- definitionIdToSourceSpecs .get (source .getSourceDefinitionId ())),
103
- () -> UUID .randomUUID ());
104
- source .setConfiguration (migratedConfig );
105
- return source ;
106
- })
107
- .toList ();
108
-
109
- for (final SourceConnection source : sourceConnections ) {
110
- configPersistence .writeConfig (ConfigSchema .SOURCE_CONNECTION , source .getSourceId ().toString (), source );
101
+ for (final SourceConnection source : sources ) {
102
+ final Optional <ConnectorSpecification > specOptional = Optional .ofNullable (definitionIdToSourceSpecs .get (source .getSourceDefinitionId ()));
103
+
104
+ if (specOptional .isPresent ()) {
105
+ secretsWriter .writeSourceConnection (source , specOptional .get ());
106
+ } else {
107
+ // if the spec can't be found, don't risk writing secrets to db. wipe out the configuration for the
108
+ // connector.
109
+ final SourceConnection sourceWithConfigRemoved = Jsons .clone (source );
110
+ sourceWithConfigRemoved .setConfiguration (Jsons .emptyObject ());
111
+ secretsWriter .writeSourceConnection (sourceWithConfigRemoved , new ConnectorSpecification ().withConnectionSpecification (Jsons .emptyObject ()));
112
+ }
111
113
}
112
114
}
113
115
114
116
/**
115
117
* This is migrating the secrets for the destination actors
116
118
*/
117
119
@ VisibleForTesting
118
- void migrateDestinations (final List <DestinationConnection > destinations , final Map <UUID , JsonNode > definitionIdToDestinationSpecs )
120
+ void migrateDestinations (final List <DestinationConnection > destinations , final Map <UUID , ConnectorSpecification > definitionIdToDestinationSpecs )
119
121
throws JsonValidationException , IOException {
120
122
log .info ("Migration Destinations" );
123
+ for (final DestinationConnection destination : destinations ) {
124
+ final Optional <ConnectorSpecification > specOptional =
125
+ Optional .ofNullable (definitionIdToDestinationSpecs .get (destination .getDestinationDefinitionId ()));
121
126
122
- final List <DestinationConnection > destinationConnections = destinations .stream ().map (destination -> {
123
- final JsonNode migratedConfig = migrateConfiguration (new ConnectorConfiguration (
124
- destination .getWorkspaceId (),
125
- destination .getConfiguration (),
126
- definitionIdToDestinationSpecs .get (destination .getDestinationDefinitionId ())),
127
- () -> UUID .randomUUID ());
128
- destination .setConfiguration (migratedConfig );
129
- return destination ;
130
- })
131
- .toList ();
132
- for (final DestinationConnection destination : destinationConnections ) {
133
- configPersistence .writeConfig (ConfigSchema .DESTINATION_CONNECTION , destination .getDestinationId ().toString (), destination );
134
- }
135
- }
136
-
137
- /**
138
- * This is a generic method to migrate an actor configuration It will extract the secret path form
139
- * the provided spec and then replace them by coordinates in the actor configuration
140
- */
141
- @ VisibleForTesting
142
- JsonNode migrateConfiguration (final ConnectorConfiguration connectorConfiguration , final Supplier <UUID > uuidProvider ) {
143
- if (connectorConfiguration .getSpec () == null ) {
144
- throw new IllegalStateException ("No connector definition to match the connector" );
145
- }
146
-
147
- final AtomicReference <JsonNode > connectorConfigurationJson = new AtomicReference <>(connectorConfiguration .getConfiguration ());
148
- final List <String > uniqSecretPaths = getSecretPath (connectorConfiguration .getSpec ())
149
- .stream ()
150
- .flatMap (secretPath -> getAllExplodedPath (connectorConfigurationJson .get (), secretPath ).stream ())
151
- .toList ();
152
-
153
- final UUID workspaceId = connectorConfiguration .getWorkspace ();
154
- uniqSecretPaths .forEach (secretPath -> {
155
- final Optional <JsonNode > secretValue = getValueForPath (connectorConfigurationJson .get (), secretPath );
156
- if (secretValue .isEmpty ()) {
157
- throw new IllegalStateException ("Missing secret for the path: " + secretPath );
158
- }
159
-
160
- // Only migrate plain text.
161
- if (secretValue .get ().isTextual ()) {
162
- final JsonNode stringSecretValue = secretValue .get ();
163
-
164
- final SecretCoordinate coordinate =
165
- new SecretCoordinate (SecretsHelpers .getCoordinatorBase ("airbyte_workspace_" , workspaceId , uuidProvider ), 1 );
166
- secretPersistence .get ().write (coordinate , stringSecretValue .textValue ());
167
- connectorConfigurationJson .set (replaceAtJsonNode (connectorConfigurationJson .get (), secretPath ,
168
- Jsons .jsonNode (Map .of (COORDINATE_FIELD , coordinate .getFullCoordinate ()))));
127
+ if (specOptional .isPresent ()) {
128
+ secretsWriter .writeDestinationConnection (destination , specOptional .get ());
169
129
} else {
170
- log .error ("Not migrating already migrated secrets" );
130
+ // if the spec can't be found, don't risk writing secrets to db. wipe out the configuration for the
131
+ // connector.
132
+ final DestinationConnection destinationWithConfigRemoved = Jsons .clone (destination );
133
+ destinationWithConfigRemoved .setConfiguration (Jsons .emptyObject ());
134
+ secretsWriter .writeDestinationConnection (destinationWithConfigRemoved ,
135
+ new ConnectorSpecification ().withConnectionSpecification (Jsons .emptyObject ()));
171
136
}
172
-
173
- });
174
-
175
- return connectorConfigurationJson .get ();
176
- }
177
-
178
- /**
179
- * Wrapper to help to mock static methods
180
- */
181
- @ VisibleForTesting
182
- JsonNode replaceAtJsonNode (final JsonNode connectorConfigurationJson , final String secretPath , final JsonNode replacement ) {
183
- return JsonPaths .replaceAtJsonNode (connectorConfigurationJson , secretPath , replacement );
184
- }
185
-
186
- /**
187
- * Wrapper to help to mock static methods
188
- */
189
- @ VisibleForTesting
190
- List <String > getSecretPath (final JsonNode specs ) {
191
- return SecretsHelpers .getSortedSecretPaths (specs );
192
- }
193
-
194
- /**
195
- * Wrapper to help to mock static methods
196
- */
197
- @ VisibleForTesting
198
- List <String > getAllExplodedPath (final JsonNode node , final String path ) {
199
- return JsonPaths .getPaths (node , path );
200
- }
201
-
202
- /**
203
- * Wrapper to help to mock static methods
204
- */
205
- @ VisibleForTesting
206
- Optional <JsonNode > getValueForPath (final JsonNode node , final String path ) {
207
- return JsonPaths .getSingleValue (node , path );
137
+ }
208
138
}
209
139
210
140
}
0 commit comments