Skip to content
Merged
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
18 changes: 16 additions & 2 deletions deploy/lib/buildAndPushContainers.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,26 @@ function findErrorInBuildOutput(buildOutput) {
}

module.exports = {
buildAndPushContainers() {
async buildAndPushContainers() {
// used for pushing
const auth = {
username: 'any',
password: this.provider.scwToken,
};

// used for building: see https://docs.docker.com/engine/api/v1.37/#tag/Image/operation/ImageBuild
const registryAuth = {};
registryAuth['rg.' + this.provider.scwRegion + '.scw.cloud'] = {
username: 'any',
password: this.provider.scwToken,
};

try {
await docker.checkAuth(registryAuth);
} catch (err) {
throw new Error(`Authentication to registry failed`);
}

const containerNames = Object.keys(this.containers);
const promises = containerNames.map((containerName) => {
const container = this.containers[containerName];
Expand All @@ -60,7 +74,7 @@ module.exports = {
this.serverless.cli.log(`Building and pushing container ${container.name} to: ${imageName} ...`);

return new Promise(async (resolve, reject) => {
const buildStream = await docker.buildImage(tarStream, { t: imageName })
const buildStream = await docker.buildImage(tarStream, { t: imageName, registryconfig: registryAuth })
const buildStreamEvents = await extractStreamContents(buildStream, this.provider.options.verbose);

const buildError = findErrorInBuildOutput(buildStreamEvents);
Expand Down
6 changes: 6 additions & 0 deletions shared/api/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ class RegistryApi {
return this.apiManager.delete(`namespaces/${namespaceId}`)
.catch(manageError);
}

createRegistryNamespace(params) {
return this.apiManager.post("namespaces", params)
.then(response => response.data)
.catch(manageError);
}
}

module.exports = RegistryApi;
111 changes: 111 additions & 0 deletions tests/containers/containers_private_registry.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'use strict';

const crypto = require('crypto');
const Docker = require('dockerode');

const docker = new Docker();

const path = require('path');
const fs = require('fs');
const { expect } = require('chai');

const { getTmpDirPath, replaceTextInFile } = require('../utils/fs');
const { getServiceName, sleep, serverlessDeploy, serverlessRemove} = require('../utils/misc');
const { ContainerApi, RegistryApi } = require('../../shared/api');
const { CONTAINERS_API_URL, REGISTRY_API_URL } = require('../../shared/constants');
const { execSync, execCaptureOutput } = require('../../shared/child-process');

const serverlessExec = path.join('serverless');

describe('Build and deploy on container with a base image private', () => {
const templateName = path.resolve(__dirname, '..', '..', 'examples', 'container');
const tmpDir = getTmpDirPath();
let oldCwd;
let serviceName;
const scwRegion = process.env.SCW_REGION;
const scwProject = process.env.SCW_DEFAULT_PROJECT_ID || process.env.SCW_PROJECT;
const scwToken = process.env.SCW_SECRET_KEY || process.env.SCW_TOKEN;
const apiUrl = `${CONTAINERS_API_URL}/${scwRegion}`;
const registryApiUrl = `${REGISTRY_API_URL}/${scwRegion}/`;
let api;
let registryApi;
let namespace;
let containerName;

const originalImageRepo = 'python';
const imageTag = '3-alpine';
let privateRegistryImageRepo;
let privateRegistryNamespaceId;

beforeAll(async () => {
oldCwd = process.cwd();
serviceName = getServiceName();
api = new ContainerApi(apiUrl, scwToken);
registryApi = new RegistryApi(registryApiUrl, scwToken);

// pull the base image, create a private registry, push it into that registry, and remove the image locally
// to check that the image is pulled at build time
const registryName = `private-registry-${crypto.randomBytes(16).toString('hex')}`;
const privateRegistryNamespace = await registryApi.createRegistryNamespace({name: registryName, project_id: scwProject});
privateRegistryNamespaceId = privateRegistryNamespace.id;

privateRegistryImageRepo = `rg.${scwRegion}.scw.cloud/${registryName}/python`;

await docker.pull(`${originalImageRepo}:${imageTag}`);
const originalImage = docker.getImage(`${originalImageRepo}:${imageTag}`);
await originalImage.tag({repo: privateRegistryImageRepo, tag: imageTag});
const privateRegistryImage = docker.getImage(`${privateRegistryImageRepo}:${imageTag}`);
await privateRegistryImage.push({
stream: false,
username: 'nologin',
password: scwToken
});
await privateRegistryImage.remove();
});

afterAll(async () => {
await registryApi.deleteRegistryNamespace(privateRegistryNamespaceId);
process.chdir(oldCwd);
});

it('should create service in tmp directory', () => {
execSync(`${serverlessExec} create --template-path ${templateName} --path ${tmpDir}`);
process.chdir(tmpDir);
execSync(`npm link ${oldCwd}`);
replaceTextInFile('serverless.yml', 'scaleway-container', serviceName);
replaceTextInFile('serverless.yml', '<scw-token>', scwToken);
replaceTextInFile('serverless.yml', '<scw-project-id>', scwProject);
replaceTextInFile(path.join('my-container', 'Dockerfile'), 'FROM python:3-alpine', `FROM ${privateRegistryImageRepo}:${imageTag}`);
expect(fs.existsSync(path.join(tmpDir, 'serverless.yml'))).to.be.equal(true);
expect(fs.existsSync(path.join(tmpDir, 'my-container'))).to.be.equal(true);
});

it('should deploy service/container to scaleway', async () => {
serverlessDeploy();
namespace = await api.getNamespaceFromList(serviceName);
namespace.containers = await api.listContainers(namespace.id);
containerName = namespace.containers[0].name;
});

it('should invoke container from scaleway', async () => {
// TODO query function status instead of having an arbitrary sleep
await sleep(30000);

let output = execCaptureOutput(serverlessExec, ['invoke', '--function', containerName]);
expect(output).to.be.equal('{"message":"Hello, World from Scaleway Container !"}');
});

it('should remove service from scaleway', async () => {
serverlessRemove();
try {
await api.getNamespace(namespace.id);
} catch (err) {
expect(err.response.status).to.be.equal(404);
}
});

it('should remove registry namespace properly', async () => {
const response = await registryApi.deleteRegistryNamespace(namespace.registry_namespace_id);
expect(response.status).to.be.equal(200);
});
});