Skip to content

Commit

Permalink
Merge 7e61235 into 41f5df9
Browse files Browse the repository at this point in the history
  • Loading branch information
nertim committed Sep 21, 2018
2 parents 41f5df9 + 7e61235 commit 05f40de
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { ContainerACRService } from './services/container-acr.service';
import { ArmSiteDescriptor } from '../../shared/resourceDescriptors';
import { ArmObj } from '../../shared/models/arm/arm-obj';
import { HttpResult } from '../../shared/models/http-result';
import { ContainerValidationService } from './services/container-validation.service';

@Injectable()
export class ContainerSettingsManager {
Expand All @@ -54,7 +55,8 @@ export class ContainerSettingsManager {
private _fb: FormBuilder,
private _siteService: SiteService,
private _acrService: ContainerACRService,
private _logService: LogService) {
private _logService: LogService,
private _containerValidationService: ContainerValidationService) {
this.requiredValidator = new RequiredValidator(this._ts);
this.urlValidator = new URLValidator(this._ts);
}
Expand Down Expand Up @@ -162,30 +164,78 @@ export class ContainerSettingsManager {
}
}

public applyContainerConfig(resourceId: string, os: ContainerOS, formData: ContainerFormData): Observable<boolean> {
return this._validateContainerImage(resourceId, os, formData);
}

public saveContainerConfig(resourceId: string, os: ContainerOS, formData: ContainerFormData): Observable<boolean> {
return Observable
.zip(
this._saveContainerAppSettings(resourceId, os, formData),
this._saveContainerSiteConfig(resourceId, os, formData))
.switchMap(responses => {
const [appSettingsUpdateResponse, siteConfigUpdateResponse] = responses;

if (appSettingsUpdateResponse.isSuccessful && siteConfigUpdateResponse.isSuccessful) {
if (formData.imageSource === 'azureContainerRegistry') {
return this._manageAcrWebhook(resourceId, os, formData);
} else {
return Observable.of(true);
}
} else {
return Observable.throw({
errorId: errorIds.failedToUpdateContainerConfigData,
resourceId: resourceId,
message: this._ts.instant(PortalResources.failedToUpdateContainerConfigData),
return this
._validateContainerImage(resourceId, os, formData)
.switchMap(r => {
return Observable
.zip(
this._saveContainerAppSettings(resourceId, os, formData),
this._saveContainerSiteConfig(resourceId, os, formData))
.switchMap(responses => {
const [appSettingsUpdateResponse, siteConfigUpdateResponse] = responses;

if (appSettingsUpdateResponse.isSuccessful && siteConfigUpdateResponse.isSuccessful) {
if (formData.imageSource === 'azureContainerRegistry') {
return this._manageAcrWebhook(resourceId, os, formData);
} else {
return Observable.of(true);
}
} else {
return Observable.throw({
errorId: errorIds.failedToUpdateContainerConfigData,
resourceId: resourceId,
message: this._ts.instant(PortalResources.failedToUpdateContainerConfigData),
});
}
});
}
});
}

private _validateContainerImage(resourceId: string, os: ContainerOS, formData: ContainerFormData): Observable<boolean> {
const containerType = this._getFormContainerType(formData.siteConfig.fxVersion);
const serverUrl = new URL(formData.appSettings[ContainerConstants.serverUrlSetting]);

if (os === 'windows'
&& containerType === 'single'
&& !serverUrl.host.startsWith('mcr.microsoft.com')) {
const fxVersionParts = formData.siteConfig.fxVersion.split('|');
const imageAndTagParts = fxVersionParts[1].split(':');
const image = imageAndTagParts[0];
const tag = imageAndTagParts[1];

return this._containerValidationService
.validateContainerImage(
resourceId,
formData.appSettings[ContainerConstants.serverUrlSetting],
'windows',
image,
tag,
formData.appSettings[ContainerConstants.usernameSetting],
formData.appSettings[ContainerConstants.passwordSetting],
)
.switchMap(r => {
if (r.isSuccessful) {
return Observable.of(true);
} else {
if (r.error.result && r.error.result._body) {
return Observable.throw({
message: r.error.result._body,
});
} else {
return Observable.throw({ ...r.error, resourceId });
}
}
});
} else {
return Observable.of(true);
}
}

private _manageAcrWebhook(resourceId: string, os: ContainerOS, formData: ContainerFormData): Observable<boolean> {
const siteDescriptor: ArmSiteDescriptor = new ArmSiteDescriptor(resourceId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,30 @@ export class ContainerSettingsComponent extends FeatureComponent<TreeViewInfo<Co
this.statusMessage = null;
this._markFormGroupDirtyAndValidate(this.form);
if (this.form.valid) {
this.isUpdating = true;
const data = this.containerSettingsManager.containerFormData;
this._portalService.returnPcv3Results<string>(JSON.stringify(data));
this.containerSettingsManager
.applyContainerConfig(this.containerConfigureInfo.resourceId, this.containerConfigureInfo.os, data)
.catch(error => {
this.isUpdating = false;
this.statusMessage = {
level: 'error',
message: error.message,
};

return Observable.of(false);
})
.subscribe(updateSuccess => {
this.isUpdating = false;

if (updateSuccess) {
this.statusMessage = {
level: 'success',
message: this._ts.instant(PortalResources.containerSettingsUpdateSuccess),
};
this._portalService.returnPcv3Results<string>(JSON.stringify(data));
}
});
} else {
this.statusMessage = {
level: 'error',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ContainerContinuousDeliveryComponent } from './container-image-source/c
import { ContainerACRService } from './services/container-acr.service';
import { ContainerLogsService } from './services/container-logs.service';
import { ContainerMultiConfigService } from './services/container-multiconfig.service';
import { ContainerValidationService } from './services/container-validation.service';

@NgModule({
imports: [
Expand Down Expand Up @@ -43,6 +44,7 @@ import { ContainerMultiConfigService } from './services/container-multiconfig.se
ContainerACRService,
ContainerLogsService,
ContainerMultiConfigService,
ContainerValidationService,
],
exports: [
ContainerSettingsComponent,
Expand Down
16 changes: 16 additions & 0 deletions client/src/app/site/container-settings/container-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,19 @@ export interface ACRWebhookPayload {
status: 'enabled' | 'disabled';
scope: string;
}

export interface ProxyRequest<T> {
body: T;
headers: { [name: string]: string };
method: string;
url: string;
}

export interface GetRepositoryTagRequest {
baseUrl: string;
platform: string;
repository: string;
tag: string;
username: string;
password: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Injectable, Injector } from '@angular/core';
import { CacheService } from '../../../shared/services/cache.service';
import { UserService } from '../../../shared/services/user.service';
import { ConditionalHttpClient, Result } from '../../../shared/conditional-http-client';
import { ProxyRequest, GetRepositoryTagRequest } from '../container-settings';
import { Constants } from '../../../shared/models/constants';

@Injectable()
export class ContainerValidationService {
private readonly _client: ConditionalHttpClient;

constructor(
private _cacheService: CacheService,
private _userService: UserService,
injector: Injector) {
this._client = new ConditionalHttpClient(injector, _ => this._userService.getStartupInfo().map(i => i.token));
}

public validateContainerImage(resourceId: string, baseUrl: string, platform: string, repository: string, tag: string, username: string, password: string): Result<any> {
const proxyRequestBody: GetRepositoryTagRequest = {
baseUrl,
platform,
repository,
tag,
username,
password,
};

const proxyRequest: ProxyRequest<GetRepositoryTagRequest> = {
method: 'POST',
url: `${Constants.webAppsHostName}/api/Websites/GetRepositoryTagAsync`,
body: proxyRequestBody,
headers: {},
};

const validateImage = this._userService
.getStartupInfo()
.first()
.switchMap(i => {
proxyRequest.headers['Authorization'] = `Bearer ${i.token}`;
return this._cacheService
.post('/api/validateContainerImage', true, null, proxyRequest)
.map(r => r.json());
});

return this._client.execute({ resourceId: resourceId }, t => validateImage);
}
}
52 changes: 52 additions & 0 deletions server/src/actions/containerValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Request, Response } from 'express';
import Axios from 'axios';

export interface ProxyRequest<T> {
body: T;
headers: { [name: string]: string };
method: string;
url: string;
}

export interface GetRepositoryTagRequest {
baseUrl: string;
platform: string;
repository: string;
tag: string;
username: string;
password: string;
}

export async function validateContainerImage(req: Request, res: Response) {
const proxyPayload = req.body as ProxyRequest<GetRepositoryTagRequest>;

try {
await Axios.post(proxyPayload.url, proxyPayload.body, {
headers: proxyPayload.headers
});

res.status(200).send({});
} catch (e) {
if (e.response && e.response.status) {
let message = e.message;
if (e.response.data && e.response.data.content) {
const error = JSON.parse(e.response.data.content);
if (error.errors && error.errors[0]) {
res.status(e.response.status).send(error.errors[0].message);
}
} else {
res.status(e.response.status).send(message);
}
} else if (e.request) {
res.status(400).send({
reason: 'ContainerValidationError',
error: 'request error'
});
} else {
res.status(e.code).send({
reason: 'ContainerValidationError',
error: e.code
});
}
}
}
2 changes: 2 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { setupAzureStorage } from './actions/storage';
import * as appInsights from 'applicationinsights';
import { trackAppServicePerformance } from './telemetry-helper';
import { getAcrRepositories, getAcrTags } from './actions/acr';
import { validateContainerImage } from './actions/containerValidation';

const cookieSession = require('cookie-session');
if (process.env.aiInstrumentationKey) {
Expand Down Expand Up @@ -118,6 +119,7 @@ app.post('/api/triggerFunctionAPIM', triggerFunctionAPIM);
app.get('/api/runtimetoken/*', getLinuxRuntimeToken);
app.post('/api/getAcrRepositories', getAcrRepositories);
app.post('/api/getAcrTags', getAcrTags);
app.post('/api/validateContainerImage', validateContainerImage);
setupDeploymentCenter(app);
setupAzureStorage(app);

Expand Down

0 comments on commit 05f40de

Please sign in to comment.