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

fix: correct apiBasePath & server URL for primary and additional gateways #3922

Merged
merged 39 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
777246a
server drop down fix
Shobhajayanna Nov 27, 2024
7a36dab
wip - fix
taban03 Nov 27, 2024
6900bb2
distinguish between gw and other services
taban03 Nov 27, 2024
bc6ab4f
wip - update basePathn for additional GW
taban03 Nov 29, 2024
6875f5d
adding apimlid to the URL
Shobhajayanna Dec 2, 2024
52693e6
removing the URLs changes
Shobhajayanna Dec 2, 2024
20d8516
add header
taban03 Dec 2, 2024
2cea8f5
fetching and updating apiml id of additional gateway
Shobhajayanna Dec 2, 2024
43ded7f
fetching and updating apiml id of additional gateway
Shobhajayanna Dec 2, 2024
d897088
refactor
Shobhajayanna Dec 2, 2024
3aa9340
check for catalog and null value
taban03 Dec 3, 2024
c8321a8
revert back the serverurl construction
taban03 Dec 3, 2024
87a4270
fix tests
Shobhajayanna Dec 3, 2024
27d8aa7
fix dependency to align to v3
taban03 Dec 4, 2024
5d71211
fix UI
taban03 Dec 4, 2024
e274379
refactoring
taban03 Dec 4, 2024
fe8a38c
fix issue with ReferenceError: process is not defined
taban03 Dec 5, 2024
d7f1e01
fix issue with ReferenceError: process is not defined
taban03 Dec 5, 2024
d391229
adding tests
Shobhajayanna Dec 5, 2024
6feabe8
Merge branch 'v3.x.x' into reboot/gh3906/servers-drop-down
Shobhajayanna Dec 5, 2024
eed0267
Update ApiCatalogController.java
Shobhajayanna Dec 5, 2024
ebb9401
Update ApiCatalogControllerTests.java
Shobhajayanna Dec 5, 2024
0647089
Update ApiCatalogControllerTests.java
Shobhajayanna Dec 5, 2024
e70ec71
Update CachedProductFamilyService.java
Shobhajayanna Dec 5, 2024
92c1686
add integration test
taban03 Dec 5, 2024
0e9a8a6
revert back unnecessary code
taban03 Dec 5, 2024
9277059
Merge branch 'v3.x.x' into reboot/gh3906/servers-drop-down
Shobhajayanna Dec 5, 2024
466eaed
add api-catalog service container
taban03 Dec 6, 2024
58d574a
fix e2e tests
taban03 Dec 6, 2024
81aaa98
increase coverage and fix code smells
taban03 Dec 6, 2024
53d7e20
cleanup workflow
taban03 Dec 6, 2024
584f734
fix test
taban03 Dec 6, 2024
696adb2
fix config
taban03 Dec 6, 2024
6363ecc
add missing mock service and multi-tenancy setup config for local env
taban03 Dec 7, 2024
baa271e
minor eslint fix
taban03 Dec 7, 2024
3b9b012
minor eslint fix
taban03 Dec 7, 2024
4a5b347
add debug messages
taban03 Dec 7, 2024
e6ba6c8
fix test
taban03 Dec 7, 2024
2babacb
address comment
taban03 Dec 9, 2024
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
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
Loading