diff --git a/.travis.yml b/.travis.yml index ee1714c787..751a9102e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ cache: - node_modules stages: - Lint + - Build - Test jobs: include: @@ -44,6 +45,12 @@ jobs: script: - golint src/jetstream/... - ./deploy/ci/travis/update-go-report-card.sh + - stage: Build + name: Build Frontend and Backend + env: + - CI_ENV=true + script: + - "./deploy/ci/travis/e2e-build-script.sh build" - stage: Test name: Frontend Unit Tests env: diff --git a/deploy/ci/automation/e2e-clean-remnants.sh b/deploy/ci/automation/e2e-clean-remnants.sh index 4f87d94a0b..c9eac080a6 100755 --- a/deploy/ci/automation/e2e-clean-remnants.sh +++ b/deploy/ci/automation/e2e-clean-remnants.sh @@ -105,4 +105,14 @@ clean "$USERS" "-" "delete-user" "^(acceptancee2etravis)(invite[0-9])(20[0-9]*)[ USERS=$(cf org-users -a e2e | grep "accept" | sed -e 's/^[[:space:]]*//') clean "$USERS" "-" "delete-user" "^(acceptancee2etravis)(invite[0-9])(20[0-9]*)[Tt]([0-9]*)[zZ].*" +# Users without roles +echo "Cleaning users without roles" +USERS=$(cf curl "/v2/users?results-per-page=100" | jq -r .resources[].entity.username) +clean "$USERS" "-" "delete-user" "^(acceptance\.e2e\.travisci)(-remove-users)\.(20[0-9]*)[Tt]([0-9]*)[zZ].*" +clean "$USERS" "-" "delete-user" "^(acceptance\.e2e\.travis)(-remove-users)\.(20[0-9]*)[Tt]([0-9]*)[zZ].*" + + echo "Done" + +# Get users without usernames +#cf curl "/v2/users?results-per-page=10" | jq '.resources[] | { "username": .entity.username, "guid": .metadata.guid, "created": .metadata.created_at }' | jq 'select(.username==null)' | jq '. | select(.guid|match("^[0-9a-z]*-[0-9a-z]*-[0-9a-z]*[0-9a-z]*"))' diff --git a/deploy/ci/travis/e2e-build-script.sh b/deploy/ci/travis/e2e-build-script.sh new file mode 100755 index 0000000000..fee8595eed --- /dev/null +++ b/deploy/ci/travis/e2e-build-script.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +echo "Stratos e2e build" +echo "=================" + +MC_HOST="s3" + +LOCAL_BUILD="true" + +# Use Travis env vars: +# TRAVIS_PULL_REQUEST +# TRAVIS_REPO_SLUG +# TRAVIS_COMMIT + +if [ -z "$TRAVIS_REPO_SLUG" ]; then + echo "Need to be running in Trvis" + exit 1 +fi + +if [ -z "$TRAVIS_COMMIT" ]; then + echo "Need to be running in Trvis" + exit 1 +fi + +GIT_ID="${TRAVIS_REPO_SLUG}_${TRAVIS_COMMIT}_${TRAVIS_PULL_REQUEST}" +GIT_ID="${GIT_ID//\//_}" +echo $GIT_ID + +TAR_NAME="${GIT_ID}.tar" +GZIP_NAME="${GIT_ID}.tgz" + +# Ensure we have the mc command +DIRNAME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${DIRNAME}/e2e-mc-helper.sh" + +function tryGetExistingBuild() { + echo "Looking for existing build: ${GIT_ID}" + + mc cp -q --insecure ${MC_HOST}/${S3_BUILDS_BUCKET}/${GZIP_NAME} ./ + if [ $? -eq 0 ]; then + # We found an existing build, so download and unpack it + echo "Downloading build package" + tar -xvf ${GZIP_NAME} + if [ $? -eq 0 ]; then + LOCAL_BUILD="false" + fi + rm -rf ${GZIP_NAME} + fi +} + +if [ -n "${AWS_ENDPOINT}" ]; then + tryGetExistingBuild +fi + +if [ "${LOCAL_BUILD}" == "false" ]; then + echo "Downloaded and unpacked an existing build - no need to build locally" +else + + set -e + +# Get go +curl -sL -o ~/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme +chmod +x ~/bin/gimme +eval "$(gimme 1.12.4)" +go version + +npm run build +npm run build-backend + + set +e + tar cvfz ${GZIP_NAME} dist/* src/jetstream/jetstream + +# Upload + mc cp -q --insecure ${GZIP_NAME} ${MC_HOST}/${S3_BUILDS_BUCKET} + +fi diff --git a/deploy/ci/travis/e2e-mc-helper.sh b/deploy/ci/travis/e2e-mc-helper.sh new file mode 100644 index 0000000000..4dbb3109b6 --- /dev/null +++ b/deploy/ci/travis/e2e-mc-helper.sh @@ -0,0 +1,19 @@ +# Helper for mc command + +mc version > /dev/null +if [ $? -eq 0 ]; then + echo "mc command already installed and confgiured" +else + echo "Installing and configuring mc command ..." + + wget https://dl.minio.io/client/mc/release/linux-amd64/mc + chmod +x mc + cp mc ~/bin + + mc -install -y >/dev/null 2>&1 + + echo "Configuring mc client" + mc config host add s3 ${AWS_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} --insecure + + echo "mc command ready" +fi diff --git a/deploy/ci/travis/run-e2e-tests.sh b/deploy/ci/travis/run-e2e-tests.sh index 0b2ab023a9..3159e8f75e 100755 --- a/deploy/ci/travis/run-e2e-tests.sh +++ b/deploy/ci/travis/run-e2e-tests.sh @@ -43,14 +43,12 @@ echo "Using local deployment for e2e tests" # Start a local UAA - this will take a few seconds to come up in the background docker run -d -p 8080:8080 splatform/stratos-uaa -# Get go -curl -sL -o ~/bin/gimme https://raw.githubusercontent.com/travis-ci/gimme/master/gimme -chmod +x ~/bin/gimme -eval "$(gimme 1.12.4)" -go version - -npm run build -npm run build-backend +# Build if needed or use existing build for this commit +DIRNAME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +set +e +source "${DIRNAME}/e2e-build-script.sh" +set -e + # Copy travis config.properties file cp deploy/ci/travis/config.properties src/jetstream/ pushd src/jetstream diff --git a/deploy/ci/travis/upload-e2e-test-report.sh b/deploy/ci/travis/upload-e2e-test-report.sh index 8a54092604..cc20deb2e1 100755 --- a/deploy/ci/travis/upload-e2e-test-report.sh +++ b/deploy/ci/travis/upload-e2e-test-report.sh @@ -8,19 +8,13 @@ if [ -z "${AWS_ENDPOINT}" ]; then exit 0 fi -wget https://dl.minio.io/client/mc/release/linux-amd64/mc -chmod +x mc +DIRNAME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${DIRNAME}/e2e-mc-helper.sh" -echo "Uploading test report...." +echo "Uploading test report ..." -./mc -install -y - -echo "Configuring upload client" -./mc config host add s3 ${AWS_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} --insecure - -echo "Uploading ..." # Sync the E2E reports -./mc cp -q --insecure -r e2e-reports s3/${S3_BUCKET} +mc cp -q --insecure -r e2e-reports s3/${S3_BUCKET} if [[ $? != 0 ]]; then echo 'Error uploading test reports: $?' diff --git a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts index fbe7825d20..d5d1be5d1d 100644 --- a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts +++ b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'; import { HttpModule } from '@angular/http'; import { EffectsModule } from '@ngrx/effects'; +import { generateASEntities } from '../../cf-autoscaler/src/store/autoscaler-entity-generator'; import { generateStratosEntities } from '../../core/src/base-entity-types'; import { CATALOGUE_ENTITIES, EntityCatalogueFeatureModule } from '../../core/src/core/entity-catalogue.module'; import { entityCatalogue, TestEntityCatalogue } from '../../core/src/core/entity-catalogue/entity-catalogue.service'; @@ -23,7 +24,8 @@ import { CloudFoundryStoreModule } from './store/cloud-foundry.store.module'; testEntityCatalogue.clear(); return [ ...generateCFEntities(), - ...generateStratosEntities() + ...generateStratosEntities(), + ...generateASEntities(), // FIXME: CF should not depend on autoscaler. See #3916 ]; } } diff --git a/src/frontend/packages/cloud-foundry/src/entity-action-builders/git-action-builder.ts b/src/frontend/packages/cloud-foundry/src/entity-action-builders/git-action-builder.ts index 533cd5bcce..5622cac9b4 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-action-builders/git-action-builder.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-action-builders/git-action-builder.ts @@ -13,8 +13,6 @@ import { export const gitRepoActionBuilders = { getRepoInfo: ( - repoEntityID: string, - endpointGuid: string, projectEnvVars: EnvVarStratosProject ) => new FetchGitHubRepoInfo(projectEnvVars) } as OrchestratedActionBuilders; @@ -46,7 +44,7 @@ export const gitCommitActionBuilders: GitCommitActionBuildersConfig = { commitSha: string, endpointGuid: string, commitMeta: GitMeta - ) => new FetchCommits(commitMeta.scm, commitSha, commitMeta.projectName) + ) => new FetchCommits(commitMeta.scm, commitMeta.projectName, commitSha) }; export interface GitBranchActionBuilders extends OrchestratedActionBuilders { diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/create-application/create-application-step3/create-application-step3.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/create-application/create-application-step3/create-application-step3.component.ts index 499903caee..86f395338c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/create-application/create-application-step3/create-application-step3.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/create-application/create-application-step3/create-application-step3.component.ts @@ -5,10 +5,7 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { catchError, filter, first, map, mergeMap, switchMap, tap } from 'rxjs/operators'; -import { AssignRouteToApplication } from '../../../../../../cloud-foundry/src/actions/application-service-routes.actions'; import { CreateNewApplication } from '../../../../../../cloud-foundry/src/actions/application.actions'; -import { GetOrganization } from '../../../../../../cloud-foundry/src/actions/organization.actions'; -import { CreateRoute } from '../../../../../../cloud-foundry/src/actions/route.actions'; import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, @@ -20,14 +17,14 @@ import { selectNewAppState } from '../../../../../../cloud-foundry/src/store/eff import { selectCfRequestInfo } from '../../../../../../cloud-foundry/src/store/selectors/api.selectors'; import { CreateNewApplicationState } from '../../../../../../cloud-foundry/src/store/types/create-application.types'; import { IDomain } from '../../../../../../core/src/core/cf-api.types'; +import { entityCatalogue } from '../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityServiceFactory } from '../../../../../../core/src/core/entity-service-factory.service'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { RouterNav } from '../../../../../../store/src/actions/router.actions'; import { getDefaultRequestState, RequestInfoState } from '../../../../../../store/src/reducers/api-request-reducer/types'; import { APIResource } from '../../../../../../store/src/types/api.types'; -import { createEntityRelationKey } from '../../../../entity-relations/entity-relations.types'; -import { entityCatalogue } from '../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; +import { createEntityRelationKey } from '../../../../entity-relations/entity-relations.types'; import { createGetApplicationAction } from '../../application.service'; @@ -168,8 +165,12 @@ export class CreateApplicationStep3Component implements OnInit { this.newAppData = state; const orgEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, organizationEntityType); const getOrgActionBuilder = orgEntity.actionOrchestrator.getActionBuilder('get'); - const getOrgAction = getOrgActionBuilder(state.cloudFoundryDetails.org, state.cloudFoundryDetails.cloudFoundry, [ - createEntityRelationKey(organizationEntityType, domainEntityType)]); + const getOrgAction = getOrgActionBuilder(state.cloudFoundryDetails.org, state.cloudFoundryDetails.cloudFoundry, { + includeRelations: [ + createEntityRelationKey(organizationEntityType, domainEntityType) + ], + populateMissing: true + }); const orgEntService = this.entityServiceFactory.create>( state.cloudFoundryDetails.org, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts index 804fabd843..6acf2e6725 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts @@ -230,8 +230,12 @@ export class DeployApplicationStep2Component const commitSha = commit || branch.commit.sha; const entityID = projectInfo.full_name + '-' + commitSha; const gitCommitEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, gitCommitEntityType); - const fetchCommitActionBuilder = gitCommitEntity.actionOrchestrator.getActionBuilder('fetchCommit'); - const fetchCommitAction = fetchCommitActionBuilder(this.scm, commitSha, projectInfo.full_name) as FetchCommit; + const fetchCommitActionBuilder = gitCommitEntity.actionOrchestrator.getActionBuilder('get'); + const fetchCommitAction = fetchCommitActionBuilder(null, null, { + scm: this.scm, + projectName: projectInfo.full_name, + commitId: commitSha + }) as FetchCommit; const commitEntityService = this.entityServiceFactory.create( entityID, fetchCommitAction diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.spec.ts index 418aa5b77f..25dec485d3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.spec.ts @@ -20,9 +20,17 @@ describe('CloudFoundrySummaryTabComponent', () => { beforeEach( async(() => { TestBed.configureTestingModule({ - declarations: [CloudFoundrySummaryTabComponent, CardCfInfoComponent, CardCfRecentAppsComponent, CompactAppCardComponent], + declarations: [ + CloudFoundrySummaryTabComponent, + CardCfInfoComponent, + CardCfRecentAppsComponent, + CompactAppCardComponent + ], imports: generateCfBaseTestModules(), - providers: [...generateTestCfEndpointServiceProvider(), TabNavService] + providers: [ + ...generateTestCfEndpointServiceProvider(), + TabNavService + ] }).compileComponents(); }) ); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/csi-mode.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/csi-mode.service.ts index 2f3a60c9a7..317a0fbfd9 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/csi-mode.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/csi-mode.service.ts @@ -130,8 +130,8 @@ export class CsiModeService { const servceBindingEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, serviceBindingEntityType); const actionBuilder = servceBindingEntity.actionOrchestrator.getActionBuilder('create'); const createServiceBindingAction = actionBuilder( - cfGuid, guid, + cfGuid, { applicationGuid: appGuid, serviceInstanceGuid, params } ); this.store.dispatch(createServiceBindingAction); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.spec.ts index 0f02ebaf4d..eb08f04ef7 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.spec.ts @@ -18,9 +18,17 @@ describe('CardCfInfoComponent', () => { beforeEach( async(() => { TestBed.configureTestingModule({ - declarations: [CardCfInfoComponent, MetadataItemComponent, BooleanIndicatorComponent], + declarations: [ + CardCfInfoComponent, + MetadataItemComponent, + BooleanIndicatorComponent + ], imports: generateCfBaseTestModulesNoShared(), - providers: [generateTestCfEndpointService(), UserInviteService, ConfirmationDialogService] + providers: [ + generateTestCfEndpointService(), + UserInviteService, + ConfirmationDialogService + ] }).compileComponents(); }) ); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts index a78c004789..657b2a422a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts @@ -45,6 +45,7 @@ export class CardCfInfoComponent implements OnInit, OnDestroy { map(entity => this.getDescription(entity)) ); + // FIXME: CF should not depend on autoscaler. See #3916 this.autoscalerVersion$ = fetchAutoscalerInfo(this.cfEndpointService.cfGuid, this.esf).pipe( map(e => e.entityRequestInfo.error ? null : diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-data-source.ts index fc5a389ab8..6741e0fa61 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-data-source.ts @@ -31,12 +31,15 @@ export class AppServiceBindingDataSource extends ListDataSource(entityKeys: string[], defaultState: T) { +export function getDefaultStateFromEntityCatalogue(entityKeys: string[], defaultState: T, initialState: IRequestTypeState) { return entityKeys.reduce((currentState, entityKey) => { if (currentState[entityKey]) { return currentState; @@ -23,5 +24,5 @@ export function getDefaultStateFromEntityCatalogue(entityKeys: string[] ...currentState, [entityKey]: defaultState }; - }, {}) as T; + }, initialState) as T; } diff --git a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.html b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.html index f83ef80ba6..8377145582 100644 --- a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.html +++ b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.html @@ -19,13 +19,16 @@

Edit User Profile

-

Current password is required when changing email address

-

Current password is required when changing email address or password

- +

Current password is required when changing email address

+

Current password is required when changing email address or password

+

Change Password (Leave blank to keep current password)

+ + + diff --git a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts index c05f60543f..d76fa89193 100644 --- a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts +++ b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts @@ -23,6 +23,8 @@ export class EditProfileInfoComponent implements OnInit, OnDestroy { editProfileForm: FormGroup; + needsPasswordForEmailChange: boolean; + constructor( private userProfileService: UserProfileService, private fb: FormBuilder, @@ -36,6 +38,8 @@ export class EditProfileInfoComponent implements OnInit, OnDestroy { newPassword: '', confirmPassword: '', }); + + this.needsPasswordForEmailChange = false; } private sub: Subscription; @@ -56,6 +60,9 @@ export class EditProfileInfoComponent implements OnInit, OnDestroy { ngOnInit() { this.userProfileService.fetchUserProfile(); this.userProfileService.userProfile$.pipe(first()).subscribe(profile => { + // UAA needs the user's password for email changes. Local user does not + // Both need it for password change + this.needsPasswordForEmailChange = (profile.origin === 'uaa'); this.profile = profile; this.emailAddress = this.userProfileService.getPrimaryEmailAddress(profile); this.editProfileForm.setValue({ @@ -76,7 +83,10 @@ export class EditProfileInfoComponent implements OnInit, OnDestroy { onChanges() { this.sub = this.editProfileForm.valueChanges.subscribe(values => { - const required = values.emailAddress !== this.emailAddress || values.newPassword.length; + // Old password is required if either email or new pw is specified (uaa) + // or only if new pw is specified (local account) + const required = this.needsPasswordForEmailChange ? + values.emailAddress !== this.emailAddress || values.newPassword.length : values.newPassword.length; this.passwordRequired = !!required; if (required !== this.lastRequired) { this.lastRequired = required; diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html index 354c82ff5f..93feabd5f5 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html @@ -7,12 +7,13 @@

User Profile