Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NIFI-13798: Update Airtable docs with PATs due to API Keys deprecation #9308

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.migration.PropertyConfiguration;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
Expand Down Expand Up @@ -108,10 +109,11 @@ public class QueryAirtableTable extends AbstractProcessor {
.required(true)
.build();

static final PropertyDescriptor API_KEY = new PropertyDescriptor.Builder()
.name("api-key")
.displayName("API Key")
.description("The REST API key to use in queries. Should be generated on Airtable's account page.")
// API Keys are deprecated, Airtable now provides Personal Access Tokens instead.
static final PropertyDescriptor PAT = new PropertyDescriptor.Builder()
.name("pat")
.displayName("Personal Access Token")
.description("The Personal Access Token (PAT) to use in queries. Should be generated on Airtable's account page.")
.required(true)
.sensitive(true)
.expressionLanguageSupported(ExpressionLanguageScope.NONE)
Expand Down Expand Up @@ -195,7 +197,7 @@ public class QueryAirtableTable extends AbstractProcessor {

private static final List<PropertyDescriptor> PROPERTIES = Collections.unmodifiableList(Arrays.asList(
API_URL,
API_KEY,
PAT,
BASE_ID,
TABLE_ID,
FIELDS,
Expand Down Expand Up @@ -225,11 +227,11 @@ public Set<Relationship> getRelationships() {
@OnScheduled
public void onScheduled(final ProcessContext context) {
final String apiUrl = context.getProperty(API_URL).evaluateAttributeExpressions().getValue();
final String apiKey = context.getProperty(API_KEY).getValue();
final String pat = context.getProperty(PAT).getValue();
final String baseId = context.getProperty(BASE_ID).evaluateAttributeExpressions().getValue();
final String tableId = context.getProperty(TABLE_ID).evaluateAttributeExpressions().getValue();
final WebClientServiceProvider webClientServiceProvider = context.getProperty(WEB_CLIENT_SERVICE_PROVIDER).asControllerService(WebClientServiceProvider.class);
airtableRestService = new AirtableRestService(webClientServiceProvider, apiUrl, apiKey, baseId, tableId);
airtableRestService = new AirtableRestService(webClientServiceProvider, apiUrl, pat, baseId, tableId);
}

@Override
Expand Down Expand Up @@ -282,6 +284,11 @@ public void onTrigger(final ProcessContext context, final ProcessSession session
transferFlowFiles(session, flowFiles, retrieveTableResult.getTotalRecordCount());
}

@Override
public void migrateProperties(final PropertyConfiguration config) {
config.renameProperty("api-key", PAT.getName());
}

private AirtableGetRecordsParameters buildGetRecordsParameters(final ProcessContext context,
final String lastRecordFetchTime,
final String nowDateTimeString) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,19 @@ public class AirtableRestService {

private final WebClientServiceProvider webClientServiceProvider;
private final String apiUrl;
private final String apiKey;
private final String pat;
private final String baseId;
private final String tableId;

public AirtableRestService(final WebClientServiceProvider webClientServiceProvider,
final String apiUrl,
final String apiKey,
final String pat,
final String baseId,
final String tableId) {
this.webClientServiceProvider = webClientServiceProvider;
// Ensure the apiUrl ends with "/"
this.apiUrl = apiUrl;
this.apiKey = apiKey;
this.pat = pat;
this.baseId = baseId;
this.tableId = tableId;
}
Expand All @@ -61,7 +62,7 @@ public <R> R getRecords(final AirtableGetRecordsParameters getRecordsParameters,
try (final HttpResponseEntity response = webClientServiceProvider.getWebClientService()
.get()
.uri(uri)
.header("Authorization", "Bearer " + apiKey)
.header("Authorization", "Bearer " + pat)
.retrieve()) {

final InputStream bodyInputStream = response.body();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ processor can query records from a single base and table via Airtable's REST API
able to handle a large number of records. It can also split large record sets to multiple FlowFiles just like a database
processor.

### API Key
### Personal Access Token

Airtable REST API calls requires an API Key that needs to be passed in a request. An Airtable account is required to
generate an API Key.
Please note that API Keys were deprecated, Airtable now provides Personal Access Tokens (PATs) instead.
Airtable REST API calls requires a PAT (Personal Access Token) that needs to be passed in a request. An Airtable account
is required to generate the PAT.

### API rate limit

Expand All @@ -37,6 +38,6 @@ is recommended to start off with the default settings and to increase both param

### Metadata API

Currently the Metadata API of Airtable is unstable, and we don't provide a way to use it. Until it becomes stable you
Currently, the Metadata API of Airtable is unstable, and we don't provide a way to use it. Until it becomes stable you
can set up a ConvertRecord or MergeRecord processor with a JsonTreeReader to read the content and convert it into a
Record with schema.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.processors.airtable;

import org.apache.nifi.processors.airtable.service.AirtableRestService;
import org.apache.nifi.web.client.provider.api.WebClientServiceProvider;
import org.apache.nifi.web.client.provider.service.StandardWebClientServiceProvider;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class TestAirtableRestService {

private static final String API_URL_WITHOUT_SLASH = "https://api.airtable.com/v0";
private static final String API_URL_WITH_SLASH = "https://api.airtable.com/v0/";
private static final String PAT = "pat";
private static final String BASE_ID = "base-id";
private static final String TABLE_ID = "table-id";
private static final String EXPECTED_URL = String.format("%s/%s/%s", API_URL_WITHOUT_SLASH, BASE_ID, TABLE_ID);

private final WebClientServiceProvider webClientServiceProvider = new StandardWebClientServiceProvider();

@Test
void testApiUrlEndsWithoutSlash() {
AirtableRestService serviceWithoutSlash = new AirtableRestService(webClientServiceProvider, API_URL_WITHOUT_SLASH, PAT, BASE_ID, TABLE_ID);
String apiUrlWithSlash = serviceWithoutSlash.createUriBuilder().build().toString();
assertEquals(EXPECTED_URL, apiUrlWithSlash);
}

@Test
void testApiUrlEndsWithSlash() {
AirtableRestService serviceWithSlash = new AirtableRestService(webClientServiceProvider, API_URL_WITH_SLASH, PAT, BASE_ID, TABLE_ID);
String apiUrlWithSlash = serviceWithSlash.createUriBuilder().build().toString();
assertEquals(EXPECTED_URL, apiUrlWithSlash);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void setUp() throws Exception {
runner.addControllerService("webClientService", webClientServiceProvider);
runner.enableControllerService(webClientServiceProvider);

runner.setProperty(QueryAirtableTable.API_KEY, "???");
runner.setProperty(QueryAirtableTable.PAT, "???");
runner.setProperty(QueryAirtableTable.BASE_ID, "baseid");
runner.setProperty(QueryAirtableTable.TABLE_ID, "tableid");
runner.setProperty(QueryAirtableTable.WEB_CLIENT_SERVICE_PROVIDER, webClientServiceProvider.getIdentifier());
Expand Down