mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-03 23:26:54 +00:00
parent
8cec6a6d08
commit
667c837ae0
@ -211,7 +211,7 @@ public class CatalogApplication extends Application<CatalogApplicationConfig> {
|
|||||||
"There are pending migrations to be run on the database."
|
"There are pending migrations to be run on the database."
|
||||||
+ " Please backup your data and run `./bootstrap/bootstrap_storage.sh migrate-all`."
|
+ " Please backup your data and run `./bootstrap/bootstrap_storage.sh migrate-all`."
|
||||||
+ " You can find more information on upgrading OpenMetadata at"
|
+ " You can find more information on upgrading OpenMetadata at"
|
||||||
+ " https://docs.open-metadata.org/install/upgrade-openmetadata");
|
+ " https://docs.open-metadata.org/deployment/upgrade");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,6 +230,21 @@ public class AirflowRESTClient extends PipelineServiceClient {
|
|||||||
String.format("Failed to kill running workflows due to %s", response.body()));
|
String.format("Failed to kill running workflows due to %s", response.body()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getHostIp() {
|
||||||
|
HttpResponse<String> response;
|
||||||
|
try {
|
||||||
|
response = getRequestAuthenticatedForJsonContent("%s/%s/ip", serviceURL, API_ENDPOINT);
|
||||||
|
if (response.statusCode() == 200) {
|
||||||
|
return JsonUtils.readValue(response.body(), new TypeReference<>() {});
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw PipelineServiceClientException.byMessage("Failed to get Pipeline Service host IP.", e.getMessage());
|
||||||
|
}
|
||||||
|
throw new PipelineServiceClientException(
|
||||||
|
String.format("Failed to get Pipeline Service host IP due to %s", response.body()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> getLastIngestionLogs(IngestionPipeline ingestionPipeline) {
|
public Map<String, String> getLastIngestionLogs(IngestionPipeline ingestionPipeline) {
|
||||||
HttpResponse<String> response;
|
HttpResponse<String> response;
|
||||||
|
@ -520,6 +520,24 @@ public class IngestionPipelineResource extends EntityResource<IngestionPipeline,
|
|||||||
return pipelineServiceClient.getServiceStatus();
|
return pipelineServiceClient.getServiceStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/ip")
|
||||||
|
@Operation(
|
||||||
|
operationId = "checkAirflowHostIp",
|
||||||
|
summary = "Check the Airflow REST host IP",
|
||||||
|
tags = "IngestionPipelines",
|
||||||
|
description = "Check the Airflow REST host IP",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Pipeline Service host IP",
|
||||||
|
content = @Content(mediaType = "application/json"))
|
||||||
|
})
|
||||||
|
public Response getHostIp(@Context UriInfo uriInfo, @Context SecurityContext securityContext) {
|
||||||
|
Map<String, String> hostIp = pipelineServiceClient.getHostIp();
|
||||||
|
return Response.ok(hostIp, MediaType.APPLICATION_JSON_TYPE).build();
|
||||||
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("/{id}")
|
@Path("/{id}")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -141,4 +141,7 @@ public abstract class PipelineServiceClient {
|
|||||||
|
|
||||||
/* Get the all last run logs of a deployed pipeline */
|
/* Get the all last run logs of a deployed pipeline */
|
||||||
public abstract HttpResponse<String> killIngestion(IngestionPipeline ingestionPipeline);
|
public abstract HttpResponse<String> killIngestion(IngestionPipeline ingestionPipeline);
|
||||||
|
|
||||||
|
/* Get the Pipeline Service host IP to whitelist in source systems */
|
||||||
|
public abstract Map<String, String> getHostIp();
|
||||||
}
|
}
|
||||||
|
@ -57,4 +57,9 @@ public class MockPipelineServiceClient extends PipelineServiceClient {
|
|||||||
public HttpResponse<String> killIngestion(IngestionPipeline ingestionPipeline) {
|
public HttpResponse<String> killIngestion(IngestionPipeline ingestionPipeline) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getHostIp() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
# Copyright 2021 Collate
|
||||||
|
# Licensed 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.
|
||||||
|
"""
|
||||||
|
IP endpoint
|
||||||
|
"""
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
try:
|
||||||
|
from importlib.metadata import version
|
||||||
|
except ImportError:
|
||||||
|
from importlib_metadata import version
|
||||||
|
|
||||||
|
from airflow.api_connexion import security
|
||||||
|
from airflow.security import permissions
|
||||||
|
from airflow.www.app import csrf
|
||||||
|
from openmetadata_managed_apis.api.app import blueprint
|
||||||
|
from openmetadata_managed_apis.api.response import ApiResponse
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/ip", methods=["GET"])
|
||||||
|
@csrf.exempt
|
||||||
|
@security.requires_access([(permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG)])
|
||||||
|
def get_host_ip():
|
||||||
|
"""
|
||||||
|
/ip endpoint to check Airflow host IP. Users will need to whitelist
|
||||||
|
this IP to access their source systems.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return ApiResponse.success({"ip": requests.get("https://api.ipify.org").text})
|
||||||
|
except Exception as err:
|
||||||
|
return ApiResponse.error(
|
||||||
|
status=ApiResponse.STATUS_SERVER_ERROR,
|
||||||
|
error=f"Internal error obtaining host IP - {err} - {traceback.format_exc()}",
|
||||||
|
)
|
@ -100,6 +100,14 @@ export const checkAirflowStatus = (): Promise<AxiosResponse> => {
|
|||||||
return APIClient.get('/services/ingestionPipelines/status');
|
return APIClient.get('/services/ingestionPipelines/status');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getPipelineServiceHostIp = async () => {
|
||||||
|
const response = await APIClient.get<{ ip?: string }>(
|
||||||
|
'/services/ingestionPipelines/ip'
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
export const getIngestionPipelineLogById = (
|
export const getIngestionPipelineLogById = (
|
||||||
id: string
|
id: string
|
||||||
): Promise<AxiosResponse> => {
|
): Promise<AxiosResponse> => {
|
||||||
|
@ -13,13 +13,16 @@
|
|||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import Form, { FormProps } from '@rjsf/core';
|
import Form, { FormProps } from '@rjsf/core';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { LoadingState } from 'Models';
|
import { LoadingState } from 'Models';
|
||||||
import React, { FunctionComponent, useState } from 'react';
|
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||||
|
import { getPipelineServiceHostIp } from '../../../axiosAPIs/ingestionPipelineAPI';
|
||||||
import { ConfigData } from '../../../interface/service.interface';
|
import { ConfigData } from '../../../interface/service.interface';
|
||||||
import { formatFormDataForRender } from '../../../utils/JSONSchemaFormUtils';
|
import { formatFormDataForRender } from '../../../utils/JSONSchemaFormUtils';
|
||||||
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||||
|
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||||
import { Button } from '../../buttons/Button/Button';
|
import { Button } from '../../buttons/Button/Button';
|
||||||
import { ArrayFieldTemplate } from '../../JSONSchemaTemplate/ArrayFieldTemplate';
|
import { ArrayFieldTemplate } from '../../JSONSchemaTemplate/ArrayFieldTemplate';
|
||||||
import { ObjectFieldTemplate } from '../../JSONSchemaTemplate/ObjectFieldTemplate';
|
import { ObjectFieldTemplate } from '../../JSONSchemaTemplate/ObjectFieldTemplate';
|
||||||
@ -55,6 +58,22 @@ const FormBuilder: FunctionComponent<Props> = ({
|
|||||||
const [connectionTestingState, setConnectionTestingState] =
|
const [connectionTestingState, setConnectionTestingState] =
|
||||||
useState<LoadingState>('initial');
|
useState<LoadingState>('initial');
|
||||||
|
|
||||||
|
const [hostIp, setHostIp] = useState<string>('[fetching]');
|
||||||
|
|
||||||
|
const fetchHostIp = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getPipelineServiceHostIp();
|
||||||
|
setHostIp(data?.ip || '[unknown]');
|
||||||
|
} catch (error) {
|
||||||
|
setHostIp('[error - unknown]');
|
||||||
|
showErrorToast(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchHostIp();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setLocalFormData(formatFormDataForRender(formData));
|
setLocalFormData(formatFormDataForRender(formData));
|
||||||
if (onCancel) {
|
if (onCancel) {
|
||||||
@ -108,10 +127,22 @@ const FormBuilder: FunctionComponent<Props> = ({
|
|||||||
|
|
||||||
case 'initial':
|
case 'initial':
|
||||||
default:
|
default:
|
||||||
return 'Test your connections before creating service';
|
return 'Test your connections before creating the service';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPipelineServiceHostIpForm = () => {
|
||||||
|
return (
|
||||||
|
<div className="tw-flex">
|
||||||
|
<SVGIcons alt="info-badge" icon={Icons.ARROW_RIGHT_PRIMARY} />
|
||||||
|
<span className="tw-ml-2">
|
||||||
|
OpenMetadata will connect to your resource from the IP {hostIp}. Make
|
||||||
|
sure to allow inbound traffic in your network security settings.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
ArrayFieldTemplate={ArrayFieldTemplate}
|
ArrayFieldTemplate={ArrayFieldTemplate}
|
||||||
@ -136,6 +167,11 @@ const FormBuilder: FunctionComponent<Props> = ({
|
|||||||
No Connection Configs available.
|
No Connection Configs available.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!isEmpty(schema) && onTestConnection && (
|
||||||
|
<div className="tw-flex tw-justify-between tw-bg-white tw-border tw-border-main tw-shadow tw-rounded tw-p-3 tw-mt-4">
|
||||||
|
<div className="tw-self-center">{getPipelineServiceHostIpForm()}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!isEmpty(schema) && onTestConnection && (
|
{!isEmpty(schema) && onTestConnection && (
|
||||||
<div className="tw-flex tw-justify-between tw-bg-white tw-border tw-border-main tw-shadow tw-rounded tw-p-3 tw-mt-4">
|
<div className="tw-flex tw-justify-between tw-bg-white tw-border tw-border-main tw-shadow tw-rounded tw-p-3 tw-mt-4">
|
||||||
<div className="tw-self-center">{getConnectionTestingMessage()}</div>
|
<div className="tw-self-center">{getConnectionTestingMessage()}</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user