Skip to content

Commit

Permalink
fix: correct apiBasePath & server URL for primary and additional gate…
Browse files Browse the repository at this point in the history
…ways (#3922)

* server drop down fix

Signed-off-by: sj895092 <[email protected]>

* wip - fix

Signed-off-by: Andrea Tabone <[email protected]>

* distinguish between gw and other services

Signed-off-by: Andrea Tabone <[email protected]>

* wip - update basePathn for additional GW

Signed-off-by: Andrea Tabone <[email protected]>

* adding apimlid to the URL

Signed-off-by: sj895092 <[email protected]>

* removing the URLs changes

Signed-off-by: sj895092 <[email protected]>

* add header

Signed-off-by: Andrea Tabone <[email protected]>

* fetching and updating apiml id of additional gateway

Signed-off-by: sj895092 <[email protected]>

* fetching and updating apiml id of additional gateway

Signed-off-by: sj895092 <[email protected]>

* refactor

Signed-off-by: sj895092 <[email protected]>

* check for catalog and null value

Signed-off-by: Andrea Tabone <[email protected]>

* revert back the serverurl construction

Signed-off-by: Andrea Tabone <[email protected]>

* fix tests

Signed-off-by: sj895092 <[email protected]>

* fix dependency to align to v3

Signed-off-by: Andrea Tabone <[email protected]>

* fix UI

Signed-off-by: Andrea Tabone <[email protected]>

* refactoring

Signed-off-by: Andrea Tabone <[email protected]>

* fix issue with ReferenceError: process is not defined

Signed-off-by: Andrea Tabone <[email protected]>

* fix issue with ReferenceError: process is not defined

Signed-off-by: Andrea Tabone <[email protected]>

* adding tests

Signed-off-by: sj895092 <[email protected]>

* Update ApiCatalogController.java

Signed-off-by: ShobhaJayanna <[email protected]>

* Update ApiCatalogControllerTests.java

Signed-off-by: ShobhaJayanna <[email protected]>

* Update ApiCatalogControllerTests.java

Signed-off-by: ShobhaJayanna <[email protected]>

* Update CachedProductFamilyService.java

Signed-off-by: ShobhaJayanna <[email protected]>

* add integration test

Signed-off-by: Andrea Tabone <[email protected]>

* revert back unnecessary code

Signed-off-by: Andrea Tabone <[email protected]>

* add api-catalog service container

Signed-off-by: Andrea Tabone <[email protected]>

* fix e2e tests

Signed-off-by: Andrea Tabone <[email protected]>

* increase coverage and fix code smells

Signed-off-by: Andrea Tabone <[email protected]>

* cleanup workflow

Signed-off-by: Andrea Tabone <[email protected]>

* fix test

Signed-off-by: Andrea Tabone <[email protected]>

* fix config

Signed-off-by: Andrea Tabone <[email protected]>

* add missing mock service and multi-tenancy setup config for local env

Signed-off-by: Andrea Tabone <[email protected]>

* minor eslint fix

Signed-off-by: Andrea Tabone <[email protected]>

* minor eslint fix

Signed-off-by: Andrea Tabone <[email protected]>

* add debug messages

Signed-off-by: Andrea Tabone <[email protected]>

* fix test

Signed-off-by: Andrea Tabone <[email protected]>

* address comment

Signed-off-by: Andrea Tabone <[email protected]>

---------

Signed-off-by: sj895092 <[email protected]>
Signed-off-by: Andrea Tabone <[email protected]>
Signed-off-by: ShobhaJayanna <[email protected]>
Co-authored-by: Andrea Tabone <[email protected]>
  • Loading branch information
Shobhajayanna and taban03 authored Dec 9, 2024
1 parent 6c6306a commit aa50350
Show file tree
Hide file tree
Showing 20 changed files with 213 additions and 61 deletions.
30 changes: 10 additions & 20 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -270,26 +270,27 @@ jobs:

services:
# First group of services represents central apiml instance with central gateway registry
api-catalog-services:
image: ghcr.io/balhar-jakub/api-catalog-services:${{ github.run_id }}-${{ github.run_number }}
volumes:
- /api-defs:/api-defs
discovery-service:
image: ghcr.io/balhar-jakub/discovery-service:${{ github.run_id }}-${{ github.run_number }}
gateway-service:
image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }}
env:
APIML_SERVICE_APIMLID: central-apiml
APIML_SERVICE_HOSTNAME: gateway-service
zaas-service:
image: ghcr.io/balhar-jakub/zaas-service:${{ github.run_id }}-${{ github.run_number }}
env:
APIML_SECURITY_X509_ENABLED: true
APIML_SECURITY_X509_ACCEPTFORWARDEDCERT: true
APIML_SECURITY_X509_CERTIFICATESURL: https://gateway-service:10010/gateway/certificates
central-gateway-service:
gateway-service:
image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }}
env:
APIML_SERVICE_APIMLID: central-apiml
APIML_SERVICE_HOSTNAME: central-gateway-service
APIML_SERVICE_HOSTNAME: gateway-service
APIML_GATEWAY_REGISTRY_ENABLED: true
APIML_SECURITY_X509_REGISTRY_ALLOWEDUSERS: USER,UNKNOWNUSER
mock-services:
image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }}

# Second group of services represents domain apiml instance which registers it's gateway in central's discovery service
discovery-service-2:
Expand All @@ -299,28 +300,18 @@ jobs:
env:
APIML_SERVICE_HOSTNAME: discovery-service-2
APIML_SERVICE_PORT: 10031
gateway-service-2:
image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }}
env:
APIML_SERVICE_APIMLID: domain-apiml
APIML_SERVICE_HOSTNAME: gateway-service-2
APIML_SERVICE_PORT: 10037
APIML_SERVICE_DISCOVERYSERVICEURLS: https://discovery-service-2:10031/eureka/
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_DISCOVERYSERVICEURLS: https://discovery-service:10011/eureka
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_0_GATEWAYURL: /
ZWE_CONFIGS_APIML_SERVICE_ADDITIONALREGISTRATION_0_ROUTES_0_SERVICEURL: /
zaas-service-2:
image: ghcr.io/balhar-jakub/zaas-service:${{ github.run_id }}-${{ github.run_number }}
env:
APIML_SECURITY_X509_ENABLED: true
APIML_SECURITY_X509_ACCEPTFORWARDEDCERT: true
APIML_SECURITY_X509_CERTIFICATESURL: https://gateway-service:10010/gateway/certificates
APIML_SERVICE_DISCOVERYSERVICEURLS: https://discovery-service-2:10031/eureka/
central-gateway-service-2:
gateway-service-2:
image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }}
env:
APIML_SERVICE_APIMLID: domain-apiml
APIML_SERVICE_HOSTNAME: central-gateway-service-2
APIML_SERVICE_HOSTNAME: gateway-service-2
APIML_GATEWAY_REGISTRY_ENABLED: false
APIML_SECURITY_X509_REGISTRY_ALLOWEDUSERS: USER,UNKNOWNUSER
APIML_SERVICE_DISCOVERYSERVICEURLS: https://discovery-service-2:10031/eureka/
Expand Down Expand Up @@ -447,7 +438,6 @@ jobs:
gateway-service:
image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }}
env:
APIML_SECURITY_X509_ENABLED: true
APIML_SECURITY_X509_ACCEPTFORWARDEDCERT: true
APIML_SECURITY_X509_CERTIFICATESURL: https://gateway-service:10010/gateway/certificates
mock-services:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,17 @@ APIService createAPIServiceFromInstance(InstanceInfo instanceInfo) {
String title = instanceInfo.getMetadata().get(SERVICE_TITLE);
if (StringUtils.equalsIgnoreCase(GATEWAY.getServiceId(), serviceId)) {
if (RegistrationType.of(instanceInfo.getMetadata()).isAdditional()) {
// additional registration for GW means domain one, update serviceId with the ApimlId
// additional registration for GW means domain one, update serviceId and basePath with the ApimlId
String apimlId = instanceInfo.getMetadata().get(APIML_ID);
if (apimlId != null) {
serviceId = apimlId;
apiBasePath = String.join("/", "", serviceId.toLowerCase());
title += " (" + apimlId + ")";
}
}
else {
apiBasePath = "/";
}
}

return new APIService.Builder(StringUtils.lowerCase(serviceId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,13 +546,15 @@ void givenPrimaryInstance_whenCreateDto_thenDoNotUpdateTitle() {
var dto = createDto(RegistrationType.ADDITIONAL);
assertEquals("title (apimlId)", dto.getTitle());
assertEquals("apimlid", dto.getServiceId());
assertEquals("/apimlid", dto.getBasePath());
}

@Test
void givenPrimaryInstance_whenCreateDto_thenAddApimlIdIntoTitle() {
var dto = createDto(RegistrationType.PRIMARY);
assertEquals("title", dto.getTitle());
assertEquals("gateway", dto.getServiceId());
assertEquals("/", dto.getBasePath());
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('>>> Multi-tenancy deployment test', () => {
'#swaggerContainer > div > div:nth-child(2) > div.scheme-container > section > div:nth-child(1) > div > div > label > select > option'
)
.should('exist')
.should('contain', `${baseUrl.match(/^https?:\/\/([^/?#]+)(?:[/?#]|$)/i)[1]}/gateway/api/v1`);
.should('contain', `${baseUrl.match(/^https?:\/\/([^/?#]+)(?:[/?#]|$)/i)[1]}/apiml2/gateway/api/v1`);

cy.get('.tabs-container').should('not.exist');
cy.get('.serviceTab').should('exist').and('contain', 'API Gateway');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ describe("Swagger rendering", () => {
.get('label')
.should('contain', "API Base Path:");

const regex = new RegExp(`^\/${service.serviceId}\/api(\/v1)?$`);
let regexContent = `^\/${service.serviceId}\/api(\/v1)?$`;
if (service.serviceId === 'gateway') {
regexContent = '/';
}
const regex = new RegExp(regexContent);

cy.get('@basePath')
.get('#apiBasePath').invoke("text").should(text => {
expect(text).to.match(regex);
Expand Down
1 change: 1 addition & 0 deletions api-catalog-ui/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions api-catalog-ui/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"lodash": "4.17.21",
"loglevel": "1.9.2",
"openapi-snippet": "0.14.0",
"process": "0.11.10",
"react": "18.3.1",
"react-app-polyfill": "3.0.0",
"react-dom": "18.3.1",
Expand Down Expand Up @@ -136,8 +137,8 @@
"source-map-explorer": "2.5.3",
"start-server-and-test": "2.0.8",
"tmpl": "1.0.5",
"yaml": "2.6.0",
"undici": "6.19.8"
"undici": "6.19.8",
"yaml": "2.6.0"
},
"overrides": {
"nth-check": "2.1.1",
Expand Down
20 changes: 13 additions & 7 deletions api-catalog-ui/frontend/src/components/ServiceTab/ServiceTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,19 @@ export default class ServiceTab extends Component {
const { selectedVersion } = this.state;

let basePath = '';
if (selectedService.basePath) {
const version = selectedVersion || selectedService.defaultApiVersion;
let gatewayUrl = '';
if (selectedService.apis && selectedService.apis[version] && selectedService.apis[version].gatewayUrl) {
gatewayUrl = selectedService.apis[version].gatewayUrl;
if (selectedService?.basePath) {
if (selectedService?.instances?.[0]?.includes('gateway')) {
// Return the basePath right away, since it's a GW instance (either primary or additional)
basePath = selectedService.basePath;
} else {
const version = selectedVersion || selectedService.defaultApiVersion;
let gatewayUrl = '';
if (selectedService.apis && selectedService.apis[version] && selectedService.apis[version].gatewayUrl) {
gatewayUrl = selectedService.apis[version].gatewayUrl;
}
// Take the first part of the basePath and then add the gatewayUrl
basePath = `/${selectedService.serviceId}/${gatewayUrl}`;
}
// Take the first part of the basePath and then add the gatewayUrl
basePath = `/${selectedService.serviceId}/${gatewayUrl}`;
}
return basePath;
}
Expand Down Expand Up @@ -321,6 +326,7 @@ ServiceTab.propTypes = {
gatewayUrl: PropTypes.string,
})
),
instances: PropTypes.arrayOf(PropTypes.string),
apiVersions: PropTypes.arrayOf(PropTypes.string),
serviceId: PropTypes.string,
status: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const selectedService = {
defaultApiVersion: ['org.zowe v1'],
ssoAllInstances: true,
apis: { 'org.zowe v1': { gatewayUrl: 'api/v1' } },
instances: ["localhost:gateway:10010"]
};

const selectedServiceDown = {
Expand Down
34 changes: 23 additions & 11 deletions api-catalog-ui/frontend/src/components/Swagger/SwaggerUIApiml.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,30 @@ import InstanceInfo from '../ServiceTab/InstanceInfo';
import getBaseUrl from '../../helpers/urls';
import { CustomizedSnippedGenerator } from '../../utils/generateSnippets';
import { AdvancedFilterPlugin } from '../../utils/filterApis';
import PropTypes from "prop-types";

function transformSwaggerToCurrentHost(swagger) {
function transformSwaggerToCurrentHost(swagger, selectedService) {
swagger.host = window.location.host;

if (swagger.servers !== null && swagger.servers !== undefined) {
if (swagger.servers?.length) {
swagger.servers.forEach((server) => {
const location = `${window.location.protocol}//${window.location.host}`;
try {
const swaggerUrl = new URL(server.url);
server.url = location + swaggerUrl.pathname;
if (swaggerUrl?.pathname?.includes('gateway')) {
const basePath = selectedService?.basePath === '/' ? '' : selectedService?.basePath || '';

server.url = location + basePath + swaggerUrl.pathname;
}
else {
server.url = location + swaggerUrl.pathname;
}
} catch (e) {
// not a proper url, assume it is an endpoint
server.url = location + server;
}
});
}

return swagger;
}

Expand Down Expand Up @@ -130,11 +137,9 @@ export default class SwaggerUIApiml extends Component {
// If no version selected use the default apiDoc
if (
(selectedVersion === null || selectedVersion === undefined) &&
selectedService.apiDoc !== null &&
selectedService.apiDoc !== undefined &&
selectedService.apiDoc.length !== 0
selectedService?.apiDoc?.length
) {
const swagger = transformSwaggerToCurrentHost(JSON.parse(selectedService.apiDoc));
const swagger = transformSwaggerToCurrentHost(JSON.parse(selectedService.apiDoc), selectedService);

this.setState({
swaggerReady: true,
Expand All @@ -148,9 +153,9 @@ export default class SwaggerUIApiml extends Component {
},
});
}
if (selectedVersion !== null && selectedVersion !== undefined) {
if (selectedVersion && selectedService) {
const basePath = `${selectedService.serviceId}/${selectedVersion}`;
const url = `${getBaseUrl()}${process.env.REACT_APP_APIDOC_UPDATE}/${basePath}`;
const url = `${getBaseUrl()}${process?.env.REACT_APP_APIDOC_UPDATE}/${basePath}`;
this.setState({
swaggerReady: true,
swaggerProps: {
Expand All @@ -161,7 +166,7 @@ export default class SwaggerUIApiml extends Component {
plugins: [this.customPlugins, AdvancedFilterPlugin, CustomizedSnippedGenerator(codeSnippets)],
responseInterceptor: (res) => {
// response.text field is used to render the swagger
const swagger = transformSwaggerToCurrentHost(JSON.parse(res.text));
const swagger = transformSwaggerToCurrentHost(JSON.parse(res.text), selectedService);
res.text = JSON.stringify(swagger);
return res;
},
Expand Down Expand Up @@ -204,6 +209,13 @@ export default class SwaggerUIApiml extends Component {
}
}

SwaggerUIApiml.propTypes = {
selectedService: PropTypes.shape({
apiDoc: PropTypes.string,
}).isRequired,
url: PropTypes.string,
};

SwaggerUIApiml.defaultProps = {
url: `${getBaseUrl()}/apidoc`,
};
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ describe('>>> Swagger component tests', () => {

const container = document.createElement('div');
document.body.appendChild(container);
await act(async () => createRoot(container).render(<SwaggerUI selectedService={service} />, container));
const root = createRoot(container);
await act(async () => root.render(<SwaggerUI selectedService={service} />));
expect(container.textContent).toContain(`API documentation could not be retrieved`);
});

Expand Down Expand Up @@ -294,6 +295,44 @@ describe('>>> Swagger component tests', () => {
expect(swaggerDiv).toBeDefined();
});

it('should set correct Gateway server URL based on its basePath', async () => {
const endpoint = '/gateway/api/v1';
const service = {
serviceId: 'gateway',
title: 'Gateway service',
description: 'Gateway service',
status: 'UP',
secured: true,
homePageUrl: 'http://localhost:10010/',
basePath: '/',
apiDoc: JSON.stringify({
openapi: '3.0.0',
servers: [{ url: `https://bad.com${endpoint}` }],
}),
apis: {
default: {
apiId: 'gateway',
},
},
defaultApiVersion: 0,
};
const wrapper = shallow(
<div>
<SwaggerUI selectedService={service}/>
</div>
);


const tiles = [{}];
const container = document.createElement('div');

await act(async () =>
createRoot(container).render(<SwaggerUI selectedService={service} tiles={tiles}/>, container)
);
expect(container.textContent).toContain(`Servershttp://localhost${endpoint}`);

});

it('should not create element api portal disabled and span already exists', () => {
const service = {
serviceId: 'testservice',
Expand Down
20 changes: 11 additions & 9 deletions api-catalog-ui/frontend/src/epics/fetch-tiles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import * as log from 'loglevel';
import { of, throwError, timer } from 'rxjs';
import { ofType } from 'redux-observable';
import process from 'process';
window.process = process; // Polyfill process for the browser
import { catchError, debounceTime, exhaustMap, map, mergeMap, retryWhen, takeUntil } from 'rxjs/operators';
import { FETCH_TILES_REQUEST, FETCH_NEW_TILES_REQUEST, FETCH_TILES_STOP } from '../constants/catalog-tile-constants';
import {
Expand All @@ -22,9 +24,9 @@ import {
import { userActions } from '../actions/user-actions';
import getBaseUrl from '../helpers/urls';

const updatePeriod = Number(process.env.REACT_APP_STATUS_UPDATE_PERIOD);
const debounce = Number(process.env.REACT_APP_STATUS_UPDATE_DEBOUNCE);
const scalingDuration = process.env.REACT_APP_STATUS_UPDATE_SCALING_DURATION;
const updatePeriod = Number(process?.env.REACT_APP_STATUS_UPDATE_PERIOD);
const debounce = Number(process?.env.REACT_APP_STATUS_UPDATE_DEBOUNCE);
const scalingDuration = process?.env.REACT_APP_STATUS_UPDATE_SCALING_DURATION;

// terminate the epic if you get any of the following Ajax error codes
const terminatingStatusCodes = [500, 401, 403];
Expand All @@ -33,11 +35,11 @@ const excludedMessageCodes = ['ZWEAM104'];

function checkOrigin() {
// only allow the gateway url to authenticate the user
let allowOrigin = process.env.REACT_APP_GATEWAY_URL;
let allowOrigin = process?.env.REACT_APP_GATEWAY_URL;
if (
process.env.REACT_APP_GATEWAY_URL === null ||
process.env.REACT_APP_GATEWAY_URL === undefined ||
process.env.REACT_APP_GATEWAY_URL === ''
process?.env.REACT_APP_GATEWAY_URL === null ||
process?.env.REACT_APP_GATEWAY_URL === undefined ||
process?.env.REACT_APP_GATEWAY_URL === ''
) {
allowOrigin = window.location.origin;
}
Expand All @@ -53,7 +55,7 @@ function checkOrigin() {
* @returns the URL to call
*/
function getUrl(action) {
let url = `${getBaseUrl()}${process.env.REACT_APP_CATALOG_UPDATE}`;
let url = `${getBaseUrl()}${process?.env.REACT_APP_CATALOG_UPDATE}`;
if (action.payload !== undefined) {
url += `/${action.payload}`;
}
Expand Down Expand Up @@ -83,7 +85,7 @@ function shouldTerminate(error) {

export const retryMechanism =
(scheduler) =>
({ maxRetries = Number(process.env.REACT_APP_STATUS_UPDATE_MAX_RETRIES) } = {}) =>
({ maxRetries = Number(process?.env.REACT_APP_STATUS_UPDATE_MAX_RETRIES) } = {}) =>
(attempts) =>
attempts.pipe(
mergeMap((error, i) => {
Expand Down
Loading

0 comments on commit aa50350

Please sign in to comment.