Skip to content
103 changes: 36 additions & 67 deletions src/openapi/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -203,72 +203,45 @@ IngressPublic:

AplServiceSpec:
type: object
allOf:
- type: object
properties:
namespace:
$ref: 'definitions.yaml#/idName'
description: A Kubernetes namespace.
port:
type: integer
minimum: 1
maximum: 65535
default: 80
x-formtype: SelectWidget
ksvc:
type: object
properties:
namespace:
$ref: 'definitions.yaml#/idName'
description: A Kubernetes namespace.
port:
predeployed:
description: Set this flag it the service is managed by Knative service.
type: boolean
default: false
trafficControl:
title: Traffic Control
description: Split traffic between multiple versions (blue-green, canary).
properties:
enabled:
type: boolean
default: false
weightV1:
type: integer
minimum: 1
maximum: 65535
default: 80
x-formtype: SelectWidget
ksvc:
type: object
properties:
predeployed:
description: Set this flag it the service is managed by Knative service.
type: boolean
default: false
trafficControl:
title: Traffic Control
description: Split traffic between multiple versions (blue-green, canary).
properties:
enabled:
type: boolean
default: false
weightV1:
type: integer
default: 90
title: Weight v1
description: 'Percentage of traffic to version 1.'
weightV2:
type: integer
default: 10
title: Weight v2
description: 'Percentage of traffic to version 2.'
- oneOf:
- $ref: '#/AplIngressCluster'
- $ref: '#/AplIngressPublic'

AplIngressCluster:
additionalProperties: false
title: No Exposure
type: object
properties:
type:
type: string
enum:
- cluster
default: cluster
required:
- type

AplIngressPublic:
type: object
properties:
type:
type: string
enum:
- public
default: public
default: 90
title: Weight v1
description: 'Percentage of traffic to version 1.'
weightV2:
type: integer
default: 10
title: Weight v2
description: 'Percentage of traffic to version 2.'
ingressClassName:
description: Assign service to a paricular Load Balancer by selecting ingress class name.
title: Ingress Class Name
$ref: 'definitions.yaml#/idName'
default: 'platform'
description: Assign service to a paricular Load Balancer by selecting ingress class name.
title: Ingress Class Name
$ref: 'definitions.yaml#/idName'
default: 'platform'
tlsPass:
description: Pass through the request as is to the backing service. Only available for pre-deployed regular (non-Knative) services.
title: TLS passthrough
Expand Down Expand Up @@ -341,7 +314,3 @@ AplIngressPublic:
required:
- name
- value
required:
- type
description: Will only accept traffic coming from an external loadbalancer.
title: External
12 changes: 5 additions & 7 deletions src/otomi-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ describe('Data validation', () => {
},
},
spec: {
type: 'public',
domain: 'b.a.com',
},
status: {},
Expand All @@ -79,7 +78,6 @@ describe('Data validation', () => {
},
},
spec: {
type: 'public',
domain: 'b.a.com',
paths: ['/test/'],
},
Expand All @@ -98,7 +96,7 @@ describe('Data validation', () => {
const svc: AplServiceRequest = {
kind: 'AplTeamService',
metadata: { name: 'svc' },
spec: { type: 'public', domain: 'b.a.com' },
spec: { domain: 'b.a.com' },
}
expect(() => otomiStack.checkPublicUrlInUse(teamId, svc)).toThrow(new PublicUrlExists())
})
Expand All @@ -107,7 +105,7 @@ describe('Data validation', () => {
const svc: AplServiceRequest = {
kind: 'AplTeamService',
metadata: { name: 'svc' },
spec: { type: 'public', domain: 'b.a.com', paths: ['/test/'] },
spec: { domain: 'b.a.com', paths: ['/test/'] },
}
expect(() => otomiStack.checkPublicUrlInUse(teamId, svc)).toThrow(new PublicUrlExists())
})
Expand All @@ -116,7 +114,7 @@ describe('Data validation', () => {
const svc: AplServiceRequest = {
kind: 'AplTeamService',
metadata: { name: 'svc' },
spec: { type: 'public', domain: 'b.a.com', paths: ['/bla'] },
spec: { domain: 'b.a.com', paths: ['/bla'] },
}
expect(() => otomiStack.checkPublicUrlInUse(teamId, svc)).not.toThrow()
})
Expand All @@ -125,7 +123,7 @@ describe('Data validation', () => {
const svc: AplServiceRequest = {
kind: 'AplTeamService',
metadata: { name: 'svc' },
spec: { type: 'cluster' },
spec: {},
}
expect(() => otomiStack.checkPublicUrlInUse(teamId, svc)).not.toThrow()
})
Expand All @@ -134,7 +132,7 @@ describe('Data validation', () => {
const svc: AplServiceRequest = {
kind: 'AplTeamService',
metadata: { name: 'svc' },
spec: { type: 'public', domain: 'c.a.com' },
spec: { domain: 'c.a.com' },
}

expect(() => otomiStack.checkPublicUrlInUse(teamId, svc)).not.toThrow()
Expand Down
96 changes: 41 additions & 55 deletions src/otomi-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,7 @@ export default class OtomiStack {
await this.createAplService(teamId, {
kind: 'AplTeamService',
metadata: { name: projectName },
spec: { type: 'cluster', ...data.spec.service },
spec: { ...data.spec.service },
})
}
await this.saveTeamConfigItem(project)
Expand Down Expand Up @@ -1936,28 +1936,23 @@ export default class OtomiStack {
checkPublicUrlInUse(teamId: string, service: AplServiceRequest): void {
// skip when editing or when svc is of type "cluster" as it has no url
const newSvc = service.spec
if (newSvc?.type === 'public') {
const services = this.repoService.getTeamConfigService(teamId).getServices()

const servicesFiltered = filter(services, (svc) => {
if (svc.spec.type === 'public') {
const { domain, paths } = svc.spec

// no paths for existing or new service? then just check base url
if (!newSvc.paths?.length && !paths?.length) return domain === newSvc.domain
// one has paths but other doesn't? no problem
if ((newSvc.paths?.length && !paths?.length) || (!newSvc.paths?.length && paths?.length)) return false
// both have paths, so check full
return paths?.some((p) => {
const existingUrl = `${domain}${p}`
const newUrls: string[] = newSvc.paths?.map((_p: string) => `${domain}${_p}`) || []
return newUrls.includes(existingUrl)
})
}
return false
const services = this.repoService.getTeamConfigService(teamId).getServices()

const servicesFiltered = filter(services, (svc) => {
const { domain, paths } = svc.spec

// no paths for existing or new service? then just check base url
if (!newSvc.paths?.length && !paths?.length) return domain === newSvc.domain
// one has paths but other doesn't? no problem
if ((newSvc.paths?.length && !paths?.length) || (!newSvc.paths?.length && paths?.length)) return false
// both have paths, so check full
return paths?.some((p) => {
const existingUrl = `${domain}${p}`
const newUrls: string[] = newSvc.paths?.map((_p: string) => `${domain}${_p}`) || []
return newUrls.includes(existingUrl)
})
if (servicesFiltered.length > 0) throw new PublicUrlExists()
}
})
if (servicesFiltered.length > 0) throw new PublicUrlExists()
}

emitPipelineStatus(sha: string): void {
Expand Down Expand Up @@ -2434,44 +2429,37 @@ export default class OtomiStack {
'cname',
]
const inService = omit(serviceSpec, publicIngressFields)
if (serviceSpec.type === 'public') {
const { cluster, dns } = this.getSettings(['cluster', 'dns'])
const url = getServiceUrl({
domain: serviceSpec.domain,
name: service.metadata.name,
teamId: service.metadata.labels['apl.io/teamId'],
cluster,
dns,
})
return removeBlankAttributes({
...inService,
...serviceMeta,
ingress: {
...pick(serviceSpec, publicIngressFields),
domain: url.domain,
subdomain: url.subdomain,
useDefaultHost: !serviceSpec.domain && serviceSpec.ownHost,
},
})
} else {
return removeBlankAttributes({
...serviceMeta,
...inService,
ingress: { type: 'cluster' },
})
}

const { cluster, dns } = this.getSettings(['cluster', 'dns'])
const url = getServiceUrl({
domain: serviceSpec.domain,
name: service.metadata.name,
teamId: service.metadata.labels['apl.io/teamId'],
cluster,
dns,
})
return removeBlankAttributes({
...serviceMeta,
...inService,
ingress: {
...pick(serviceSpec, publicIngressFields),
domain: url.domain,
subdomain: url.subdomain,
useDefaultHost: !serviceSpec.domain && serviceSpec.ownHost,
},
})
}

convertDbServiceToValues(svc: Service): ServiceSpec {
const { name } = svc
const svcCommon = omit(svc, ['name', 'ingress', 'path'])
if (svc.ingress?.type === 'public') {
const ing = svc.ingress
const domain = ing.subdomain ? `${ing.subdomain}.${ing.domain}` : ing.domain
const { ingress } = svc
const domain = ingress.subdomain ? `${ingress.subdomain}.${ingress.domain}` : ingress.domain
return {
name,
...svcCommon,
...pick(ing, [
...pick(ingress, [
'hasCert',
'certName',
'paths',
Expand All @@ -2482,15 +2470,13 @@ export default class OtomiStack {
'useCname',
'cname',
]),
type: 'public',
ownHost: ing.useDefaultHost,
domain: ing.useDefaultHost ? undefined : domain,
ownHost: ingress.useDefaultHost,
domain: ingress.useDefaultHost ? undefined : domain,
}
} else {
return {
name,
...svcCommon,
type: svc.ingress?.type || 'cluster',
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/services/TeamConfigService.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Mock UUID to generate predictable values
import { AlreadyExists, NotExistError } from '../error'
import {
AplBackupRequest,
AplBuildRequest,
Expand All @@ -11,7 +12,6 @@ import {
TeamConfig,
} from '../otomi-models'
import { TeamConfigService } from './TeamConfigService'
import { AlreadyExists, NotExistError } from '../error'

jest.mock('uuid', () => ({
v4: jest.fn(() => 'mocked-uuid'),
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('TeamConfigService', () => {
const serviceData: AplServiceRequest = {
kind: 'AplTeamService',
metadata: { name: 'TestService' },
spec: { type: 'public' },
spec: {},
}
test('should create a service', () => {
const createdService = service.createService(serviceData)
Expand All @@ -167,7 +167,7 @@ describe('TeamConfigService', () => {
'apl.io/teamId': 'team1',
},
},
spec: { type: 'public' },
spec: {},
status: {},
})
expect(service.getServices()).toHaveLength(1)
Expand Down Expand Up @@ -459,7 +459,7 @@ describe('TeamConfigService', () => {
service.createService({
kind: 'AplTeamService',
metadata: { name: 'ExistingService' },
spec: { type: 'public' },
spec: {},
})
expect(service.doesProjectNameExist('ExistingService')).toBe(true)
})
Expand All @@ -478,7 +478,7 @@ describe('TeamConfigService', () => {
service.createService({
kind: 'AplTeamService',
metadata: { name: 'SomeService' },
spec: { type: 'public' },
spec: {},
})
expect(service.doesProjectNameExist('NonExistentProject')).toBe(false)
})
Expand Down
Loading