Skip to content

Commit

Permalink
Merge pull request #224 from Interhyp/222-support-backstage-relations…
Browse files Browse the repository at this point in the history
…-dependson-providesapis-consumesapis

#222 add new service attribute spec to present backstage compatible r…
  • Loading branch information
KRaffael authored Nov 2, 2023
2 parents c913368 + 72e63c7 commit 6165a17
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.20'
go-version: '1.21'

- name: Build
run: go build
Expand Down
24 changes: 21 additions & 3 deletions api/generated_apimodel.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 89 additions & 0 deletions api/openapi-v3-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -2194,6 +2194,26 @@
"type": "boolean",
"description": "The value defines if the service is available from the internet and the time period in which security holes must be processed."
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"example": ["some-tag", "other-tag"]
},
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"some-key": "some-value",
"other-key": "other-value"
}
},
"spec": {
"$ref": "#/components/schemas/ServiceSpecDto"
},
"timeStamp": {
"type": "string",
"description": "ISO-8601 UTC date time at which this information was originally committed. When sending an update, include the original timestamp you got so we can detect concurrent updates.",
Expand Down Expand Up @@ -2286,6 +2306,26 @@
"type": "boolean",
"description": "The value defines if the service is available from the internet and the time period in which security holes must be processed."
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"example": ["some-tag", "other-tag"]
},
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"some-key": "some-value",
"other-key": "other-value"
}
},
"spec": {
"$ref": "#/components/schemas/ServiceSpecDto"
},
"jiraIssue": {
"type": "string",
"description": "The jira issue to use for committing a change, or the last jira issue used.",
Expand Down Expand Up @@ -2356,6 +2396,26 @@
"type": "boolean",
"description": "The value defines if the service is available from the internet and the time period in which security holes must be processed."
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"example": ["some-tag", "other-tag"]
},
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"some-key": "some-value",
"other-key": "other-value"
}
},
"spec": {
"$ref": "#/components/schemas/ServiceSpecDto"
},
"timeStamp": {
"type": "string",
"description": "ISO-8601 UTC date time at which this information was originally committed. When sending an update, include the original timestamp you got so we can detect concurrent updates.",
Expand Down Expand Up @@ -2431,6 +2491,35 @@
}
}
},
"ServiceSpecDto": {
"type": "object",
"properties": {
"dependsOn": {
"description": "A relation denoting a dependency on another entity",
"type": "array",
"items": {
"type": "string"
},
"example": ["some-service", "other-service"]
},
"providesApis":{
"description": "A relation with an API, provided by this entity",
"type": "array",
"items": {
"type": "string"
},
"example": ["some-api"]
},
"consumesApis": {
"description": "A relation with an API, consumed by this entity",
"type": "array",
"items": {
"type": "string"
},
"example": ["some-api", "other-api"]
}
}
},
"Notification": {
"type": "object",
"description": "Schema of the Dto sent to all configured downstreams upon a change of service.",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/Interhyp/metadata-service

go 1.20
go 1.21

// exclude actually unused dependencies (mostly of pact-go, which is testing only anyway)
// because our scanner fails to understand they are not in use
Expand Down
35 changes: 35 additions & 0 deletions internal/service/services/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ func (s *Impl) mapServiceCreateDtoToServiceDto(serviceCreateDto openapi.ServiceC
Description: serviceCreateDto.Description,
Lifecycle: &initialServiceLifecycle,
InternetExposed: serviceCreateDto.InternetExposed,
Spec: serviceCreateDto.Spec,
Tags: serviceCreateDto.Tags,
Labels: serviceCreateDto.Labels,
}
}

Expand Down Expand Up @@ -343,6 +346,23 @@ func patchService(current openapi.ServiceDto, patch openapi.ServicePatchDto) ope
Description: patchStringPtr(patch.Description, current.Description),
Lifecycle: patchStringPtr(patch.Lifecycle, current.Lifecycle),
InternetExposed: patchPtr[bool](patch.InternetExposed, current.InternetExposed),
Spec: patchServiceSpec(patch.Spec, current.Spec),
Tags: patchStringSlice(patch.Tags, current.Tags),
Labels: patchMapSlice(patch.Labels, current.Labels),
}
}

func patchServiceSpec(patch *openapi.ServiceSpecDto, current *openapi.ServiceSpecDto) *openapi.ServiceSpecDto {
if patch != nil && current != nil {
return &openapi.ServiceSpecDto{
DependsOn: patchStringSlice(patch.DependsOn, current.DependsOn),
ProvidesApis: patchStringSlice(patch.ProvidesApis, current.ProvidesApis),
ConsumesApis: patchStringSlice(patch.ConsumesApis, current.ConsumesApis),
}
} else if patch != nil {
return patch
} else {
return current
}
}

Expand All @@ -364,6 +384,18 @@ func patchStringSlice(patch []string, original []string) []string {
}
}

func patchMapSlice(patch *map[string]string, original *map[string]string) *map[string]string {
if patch != nil {
if len(*patch) == 0 {
// remove
return nil
}
return patch
} else {
return original
}
}

func patchQuicklinkSlice(patch []openapi.Quicklink, original []openapi.Quicklink) []openapi.Quicklink {
if patch != nil {
if len(patch) == 0 {
Expand Down Expand Up @@ -525,6 +557,9 @@ func (s *Impl) validRepoKey(ctx context.Context, candidate string, serviceName s
if candidate == serviceName+".helm-deployment" {
return nil
}
if candidate == serviceName+".none" {
return nil
}
return errors.New("repository key must have acceptable name and type combination (allowed types: api implementation helm-deployment), and for helm-deployment the name must match the service name")
}

Expand Down
76 changes: 76 additions & 0 deletions internal/service/services/services_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,41 @@ func tstCurrent() openapi.ServiceDto {
}
}

func tstCurrentSpec() openapi.ServiceDto {
return openapi.ServiceDto{
Owner: "owner",
Quicklinks: []openapi.Quicklink{
{
Url: p("url"),
Title: p("title"),
Description: p("desc"),
},
},
Repositories: []string{"repo1", "repo2"},
AlertTarget: "target",
DevelopmentOnly: b(true),
OperationType: p("PLATFORM"),
TimeStamp: "ts",
CommitHash: "hash",
Lifecycle: p("experimental"),
Spec: &openapi.ServiceSpecDto{
DependsOn: []string{"other-domain"},
ProvidesApis: []string{"some-other-api"},
ConsumesApis: []string{"other-api"},
},
}
}

func tstPatchService(t *testing.T, patch openapi.ServicePatchDto, expected openapi.ServiceDto) {
actual := patchService(tstCurrent(), patch)
require.Equal(t, expected, actual)
}

func tstPatchServiceSpec(t *testing.T, patch openapi.ServicePatchDto, expected openapi.ServiceDto) {
actual := patchService(tstCurrentSpec(), patch)
require.Equal(t, expected, actual)
}

func TestPatchService_EmptyPatch(t *testing.T) {
docs.Description("patching of services works with an empty patch")
expected := tstCurrent()
Expand Down Expand Up @@ -120,6 +150,52 @@ func TestPatchService_ClearFields(t *testing.T) {
})
}

func TestPatchServiceSpec_EmptyPatch(t *testing.T) {
docs.Description("patching of service spec with an empty patch")
expected := tstCurrent()
tstPatchService(t, openapi.ServicePatchDto{
TimeStamp: "ts",
CommitHash: "hash",
}, expected)
}

func TestPatchServiceSpec_ReplaceAll(t *testing.T) {
docs.Description("patching of service spec with an empty patch")
expected := tstCurrent()
expected.Spec = &openapi.ServiceSpecDto{
DependsOn: []string{"some-domain"},
ProvidesApis: []string{"some-other-api"},
ConsumesApis: []string{"some-api"},
}
tstPatchService(t, openapi.ServicePatchDto{
TimeStamp: "ts",
CommitHash: "hash",
Spec: &openapi.ServiceSpecDto{
DependsOn: []string{"some-domain"},
ProvidesApis: []string{"some-other-api"},
ConsumesApis: []string{"some-api"},
},
}, expected)
}

func TestPatchServiceSpec_ReplaceSpecific(t *testing.T) {
docs.Description("patching of service spec with an empty patch")
expected := tstCurrent()
expected.Spec = &openapi.ServiceSpecDto{
DependsOn: []string{"some-domain"},
ConsumesApis: []string{"some-api"},
}
tstPatchServiceSpec(t, openapi.ServicePatchDto{
TimeStamp: "ts",
CommitHash: "hash",
Spec: &openapi.ServiceSpecDto{
DependsOn: []string{"some-domain"},
ProvidesApis: []string{},
ConsumesApis: []string{"some-api"},
},
}, expected)
}

func tstValid() openapi.ServiceDto {
description := "short service description"
return openapi.ServiceDto{
Expand Down
23 changes: 23 additions & 0 deletions test/acceptance/servicectl_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,29 @@ func TestPATCHService_ChangeOwner(t *testing.T) {
require.Equal(t, tstServiceMovedExpectedKafka("some-service-backend"), string(actual))
}

func TestPATCHService_ChangeSpec(t *testing.T) {
tstReset()

docs.Given("Given an authenticated admin user")
token := tstValidAdminToken()

docs.When("When they perform a valid patch of an existing service that changes its spec")
body := tstServicePatch()
body.Spec = &openapi.ServiceSpecDto{
DependsOn: []string{"some-service", "other-service"},
ProvidesApis: []string{},
ConsumesApis: []string{"some-api"},
}
response, err := tstPerformPatch("/rest/api/v1/services/some-service-backend", token, &body)

docs.Then("Then the request is successful and the response is as expected")
tstAssert(t, response, err, http.StatusOK, "service-patch-spec.json")

docs.Then("And the service has been cached and can be read again, returning the correct spec")
readAgain, err := tstPerformGet("/rest/api/v1/services/some-service-backend", tstUnauthenticated())
tstAssert(t, readAgain, err, http.StatusOK, "service-patch-spec.json")
}

func TestPATCHService_ImplementationCrossrefAllowed(t *testing.T) {
tstReset()

Expand Down
Loading

0 comments on commit 6165a17

Please sign in to comment.