Skip to content

Commit

Permalink
Introduce bucket website endpoint tests
Browse files Browse the repository at this point in the history
Issue: ZENKO-4837
  • Loading branch information
williamlardier committed Oct 25, 2024
1 parent 4276731 commit 7fce279
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/end2end/configure-e2e-ctst.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export NOTIF_KAFKA_HOST=${KAFKA_HOST_PORT%:*}
export NOTIF_KAFKA_PORT=${KAFKA_HOST_PORT#*:}

echo "127.0.0.1 iam.zenko.local ui.zenko.local s3-local-file.zenko.local keycloak.zenko.local \
sts.zenko.local management.zenko.local s3.zenko.local" | sudo tee -a /etc/hosts
sts.zenko.local management.zenko.local s3.zenko.local website.mywebsite.com" | sudo tee -a /etc/hosts

# Add bucket notification target
envsubst < ./configs/notification_destinations.yaml | kubectl apply -f -
Expand Down
1 change: 1 addition & 0 deletions .github/scripts/end2end/patch-coredns.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ corefile="
rewrite name exact sts.dr.zenko.local ingress-nginx-controller.ingress-nginx.svc.cluster.local
rewrite name exact iam.dr.zenko.local ingress-nginx-controller.ingress-nginx.svc.cluster.local
rewrite name exact shell-ui.dr.zenko.local ingress-nginx-controller.ingress-nginx.svc.cluster.local
rewrite name exact website.mywebsite.com ingress-nginx-controller.ingress-nginx.svc.cluster.local
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
Expand Down
20 changes: 20 additions & 0 deletions tests/ctst/features/bucketWebsite.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Feature: Bucket Websites

@2.6.0
@PreMerge
@BucketWebsite
Scenario Outline: Bucket Website CRUD
# The scenario should test that we can put a bucket website configuration on a bucket
# send an index.html
# And also use a pensieve API to add the new endpoint to the list
# Then using the local etc hosts, we should be able to load the html page
Given an existing bucket "website" "" versioning, "without" ObjectLock "without" retention mode
And an index html file
When the user puts the bucket website configuration
And the user creates an S3 Bucket policy granting public read access
And the "<domain>" endpoint is added to the overlay
Then the user should be able to load the index.html file from the "<domain>" endpoint

Examples:
| domain |
| mywebsite.com |
10 changes: 5 additions & 5 deletions tests/ctst/steps/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ export async function deleteFile(path: string) {
return fsp.unlink(path);
}

async function uploadSetup(world: Zenko, action: string) {
async function uploadSetup(world: Zenko, action: string, body?: string) {
if (action !== 'PutObject' && action !== 'UploadPart') {
return;
}
const objectSize = world.getSaved<number>('objectSize') || 0;
if (objectSize > 0) {
if (body || objectSize > 0) {
const tempFileName = `${Utils.randomString()}_${world.getSaved<string>('objectName')}`;
world.addToSaved('tempFileName', `/tmp/${tempFileName}`);
const objectBody = 'a'.repeat(objectSize);
const objectBody = body || 'a'.repeat(objectSize);
await saveAsFile(tempFileName, objectBody);
world.addCommandParameter({ body: world.getSaved<string>('tempFileName') });
}
Expand Down Expand Up @@ -199,15 +199,15 @@ async function createBucketWithConfiguration(
}
}

async function putObject(world: Zenko, objectName?: string) {
async function putObject(world: Zenko, objectName?: string, content?: string) {
world.resetCommand();
let finalObjectName = objectName;
if (!finalObjectName) {
finalObjectName = `${Utils.randomString()}`;
}
world.addToSaved('objectName', finalObjectName);
world.logger.debug('Adding object', { objectName: finalObjectName });
await uploadSetup(world, 'PutObject');
await uploadSetup(world, 'PutObject', content);
world.addCommandParameter({ key: finalObjectName });
world.addCommandParameter({ bucket: world.getSaved<string>('bucketName') });
const userMetadata = world.getSaved<string>('userMetadata');
Expand Down
86 changes: 86 additions & 0 deletions tests/ctst/steps/website/website.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import assert from 'assert';
import { Given, When, Then } from '@cucumber/cucumber';
import Zenko from '../../world/Zenko';
import { putObject } from '../utils/utils';
import { S3, Utils } from 'cli-testing';

const pageMessage = Utils.randomString();

Given('an index html file', async function (this: Zenko) {
// push a file with a basic html content named index.html in the bucket
const content = `<html><head><title>Index</title></head><body><h1>${pageMessage}</h1></body></html>`;
this.addToSaved('objectSize', content.length);
await putObject(this, 'index.html', content);
});

When('the user puts the bucket website configuration', async function (this: Zenko) {
const bucketWebSiteConfiguration = JSON.stringify({
IndexDocument: {
Suffix: 'index.html',
},
ErrorDocument: {
Key: 'error.html',
},
});

await S3.putBucketWebsite({
bucket: this.getSaved<string>('bucketName'),
websiteConfiguration: bucketWebSiteConfiguration,
});
});

When('the {string} endpoint is added to the overlay', async function (this: Zenko, endpoint: string) {
await this.addWebsiteEndpoint(endpoint);
});

When('the user creates an S3 Bucket policy granting public read access', async function (this: Zenko) {
const policy = {
Version: '2012-10-17',
Statement: [
{
Sid: 'PublicReadGetObject',
Effect: 'Allow',
Principal: '*',
Action: [
's3:GetObject',
],
Resource: [
`arn:aws:s3:::${this.getSaved<string>('bucketName')}/*`,
],
},
],
};
await S3.putBucketPolicy({
bucket: this.getSaved<string>('bucketName'),
policy: JSON.stringify(policy),
});
});

Then('the user should be able to load the index.html file from the {string} endpoint',
async function (this: Zenko, endpoint: string) {
const baseUrl = this.parameters.ssl === false ? 'http://' : 'https://';
// The ingress may take some time to be ready (<60s)
const uri = `${baseUrl}${this.getSaved<string>('bucketName')}.${endpoint}`;
let response;
let content;
let tries = 60;

while (tries > 0) {
tries--;
try {
response = await fetch(uri);
content = await response.text();
assert.strictEqual(content.includes(pageMessage), true);
return;
} catch (err) {
this.logger.debug('Error when fetching the bucket website', {
err,
uri,
response,
content,
});
await Utils.sleep(1000);
}
}
assert.fail('Failed to fetch the bucket website after 20 tries');
});
28 changes: 27 additions & 1 deletion tests/ctst/world/Zenko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ export default class Zenko extends World<ZenkoWorldParameters> {
method: Method,
path: string,
headers: object = {},
payload: object = {}
payload: object | string = {},
): Promise<{ statusCode: number; data: object } | { statusCode: number; err: unknown }> {
const token = await this.getWebIdentityToken(
this.parameters.KeycloakUsername || 'zenko-end2end',
Expand All @@ -937,9 +937,25 @@ export default class Zenko extends World<ZenkoWorldParameters> {
};
try {
const response: AxiosResponse = await axiosInstance(axiosConfig);
this.logger.debug('Management API request', {
method,
path,
headers,
payload,
response: response.data,
statusCode: response.status,
});
return { statusCode: response.status, data: response.data as object };
/* eslint-disable */
} catch (err: any) {
this.logger.debug('Error when making management API request', {
method,
path,
headers,
payload,
err: err.response.data,
status: err.response.status,
});
return {
statusCode: err.response.status,
err: err.response.data,
Expand All @@ -948,6 +964,16 @@ export default class Zenko extends World<ZenkoWorldParameters> {
}
}

async addWebsiteEndpoint(this: Zenko, endpoint: string) :
Promise<{ statusCode: number; data: object } | { statusCode: number; err: unknown }> {
return await this.managementAPIRequest('POST',
`/config/${this.parameters.InstanceID}/website/endpoint`,
{
'Content-Type': 'application/json',
},
`"${endpoint}"`);
}

async deleteLocation(this: Zenko, locationName: string) :
Promise<{ statusCode: number; data: object } | { statusCode: number; err: unknown }> {
return await this.managementAPIRequest('DELETE',
Expand Down

0 comments on commit 7fce279

Please sign in to comment.