Skip to content

Commit

Permalink
add active-directory-from-concourse.v2 format based on deployevent
Browse files Browse the repository at this point in the history
  • Loading branch information
majewsky committed Aug 14, 2023
1 parent b8c398f commit 2f3ca5c
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 46 deletions.
149 changes: 131 additions & 18 deletions internal/handlers/active-directory-deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,28 @@ import (
"github.com/sapcc/tenso/internal/tenso"
)

//nolint:dupl
func init() {
tenso.ValidationHandlerRegistry.Add(func() tenso.ValidationHandler { return &activeDirectoryDeploymentValidator{} })
tenso.TranslationHandlerRegistry.Add(func() tenso.TranslationHandler { return &activeDirectoryDeploymentToSNowTranslator{} })
tenso.DeliveryHandlerRegistry.Add(func() tenso.DeliveryHandler { return &activeDirectoryDeploymentToSNowDeliverer{} })
tenso.ValidationHandlerRegistry.Add(func() tenso.ValidationHandler { return &activeDirectoryDeploymentV1Validator{} })
tenso.TranslationHandlerRegistry.Add(func() tenso.TranslationHandler { return &activeDirectoryDeploymentV1ToSNowTranslator{} })

tenso.ValidationHandlerRegistry.Add(func() tenso.ValidationHandler { return &activeDirectoryDeploymentV2Validator{} })
tenso.TranslationHandlerRegistry.Add(func() tenso.TranslationHandler { return &activeDirectoryDeploymentV2ToSNowTranslator{} })
tenso.DeliveryHandlerRegistry.Add(func() tenso.DeliveryHandler { return &activeDirectoryDeploymentV1ToSNowDeliverer{} })
}

////////////////////////////////////////////////////////////////////////////////
// BEGIN v1 implementation
// TODO: Remove once pipeline has been migrated to v2 event format.
////////////////////////////////////////////////////////////////////////////////

// NOTE: This event is quite similar to the standard `deployevent.Event`, but
// since it is not generated by the standard process with our
// concourse-release-resource, it ends up looking sufficiently different to
// require different types throughout.
//
// TODO: Consider changing the AD deployment pipeline to use concourse-release-resource.
type activeDirectoryDeploymentEvent struct {
type activeDirectoryDeploymentV1Event struct {
Region string `json:"region"`
RecordedAt activeDirectoryEventTime `json:"recorded_at"`
Landscape string `json:"landscape"` //e.g. "dev" or "prod"
Expand Down Expand Up @@ -91,19 +100,19 @@ func (a *activeDirectoryEventTime) UnmarshalJSON(buf []byte) error {
////////////////////////////////////////////////////////////////////////////////
// ValidationHandler

type activeDirectoryDeploymentValidator struct {
type activeDirectoryDeploymentV1Validator struct {
}

func (v *activeDirectoryDeploymentValidator) Init(*gophercloud.ProviderClient, gophercloud.EndpointOpts) error {
func (v *activeDirectoryDeploymentV1Validator) Init(*gophercloud.ProviderClient, gophercloud.EndpointOpts) error {
return nil
}

func (v *activeDirectoryDeploymentValidator) PluginTypeID() string {
func (v *activeDirectoryDeploymentV1Validator) PluginTypeID() string {
return "active-directory-deployment-from-concourse.v1"
}

func (v *activeDirectoryDeploymentValidator) ValidatePayload(payload []byte) (*tenso.PayloadInfo, error) {
event, err := jsonUnmarshalStrict[activeDirectoryDeploymentEvent](payload)
func (v *activeDirectoryDeploymentV1Validator) ValidatePayload(payload []byte) (*tenso.PayloadInfo, error) {
event, err := jsonUnmarshalStrict[activeDirectoryDeploymentV1Event](payload)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -156,21 +165,21 @@ func (v *activeDirectoryDeploymentValidator) ValidatePayload(payload []byte) (*t
////////////////////////////////////////////////////////////////////////////////
// TranslationHandler for SNow

type activeDirectoryDeploymentToSNowTranslator struct {
type activeDirectoryDeploymentV1ToSNowTranslator struct {
Mapping servicenow.MappingConfiguration
}

func (t *activeDirectoryDeploymentToSNowTranslator) Init(*gophercloud.ProviderClient, gophercloud.EndpointOpts) (err error) {
func (t *activeDirectoryDeploymentV1ToSNowTranslator) Init(*gophercloud.ProviderClient, gophercloud.EndpointOpts) (err error) {
t.Mapping, err = servicenow.LoadMappingConfiguration()
return err
}

func (t *activeDirectoryDeploymentToSNowTranslator) PluginTypeID() string {
func (t *activeDirectoryDeploymentV1ToSNowTranslator) PluginTypeID() string {
return "active-directory-deployment-from-concourse.v1->active-directory-deployment-to-servicenow.v1"
}

func (t *activeDirectoryDeploymentToSNowTranslator) TranslatePayload(payload []byte) ([]byte, error) {
event, err := jsonUnmarshalStrict[activeDirectoryDeploymentEvent](payload)
func (t *activeDirectoryDeploymentV1ToSNowTranslator) TranslatePayload(payload []byte) ([]byte, error) {
event, err := jsonUnmarshalStrict[activeDirectoryDeploymentV1Event](payload)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -222,22 +231,126 @@ func (t *activeDirectoryDeploymentToSNowTranslator) TranslatePayload(payload []b
return chg.Serialize(t.Mapping, t.Mapping.ActiveDirectoryDeployment)
}

////////////////////////////////////////////////////////////////////////////////
// END v1 implementation (when removing v1 support, remove until here)
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// ValidationHandler

type activeDirectoryDeploymentV2Validator struct {
}

func (v *activeDirectoryDeploymentV2Validator) Init(*gophercloud.ProviderClient, gophercloud.EndpointOpts) error {
return nil
}

func (v *activeDirectoryDeploymentV2Validator) PluginTypeID() string {
return "active-directory-deployment-from-concourse.v2"
}

func (v *activeDirectoryDeploymentV2Validator) ValidatePayload(payload []byte) (*tenso.PayloadInfo, error) {
event, err := parseAndValidateDeployEvent(payload)
if err != nil {
return nil, err
}

if len(event.HelmReleases) != 0 {
return nil, errors.New("helm-release[] may not be set for Active Directory deployment events")
}
if len(event.TerraformRuns) != 0 {
return nil, errors.New("terraform-runs[] may not be set for Active Directory deployment events")
}
if event.ADDeployment == nil {
return nil, errors.New("active-directory-deployment may not be empty")
}

ad := *event.ADDeployment
missingField := func(field string) error {
return fmt.Errorf("active-directory-deployment.%s may not be empty", field)
}
if ad.Landscape == "" {
return nil, missingField("landscape")
}
if ad.Hostname == "" {
return nil, missingField("hostname")
}
if !strings.HasSuffix(ad.Hostname, ".sap") {
return nil, fmt.Errorf(`value for field active-directory-deployment.host is invalid: %q`, ad.Hostname)
}
if !ad.Outcome.IsKnownInputValue() {
return nil, fmt.Errorf(`invalid value for field active-directory-deployment.outcome: %q`, ad.Outcome)
}
if ad.StartedAt == nil {
return nil, missingField("started-at")
}
if ad.FinishedAt == nil && (ad.Outcome != deployevent.OutcomeNotDeployed && ad.Outcome != deployevent.OutcomeADDeploymentFailed) {
return nil, fmt.Errorf(`field active-directory-deployment.finished-at must be set for outcome %q`, ad.Outcome)
}
if ad.FinishedAt != nil && (ad.Outcome == deployevent.OutcomeNotDeployed || ad.Outcome == deployevent.OutcomeADDeploymentFailed) {
return nil, fmt.Errorf(`field active-directory-deployment.finished-at may not be set for outcome %q`, ad.Outcome)
}

return &tenso.PayloadInfo{
Description: fmt.Sprintf("core/active-directory: deploy AD to %s", ad.Hostname),
}, nil
}

////////////////////////////////////////////////////////////////////////////////
// TranslationHandler for SNow

type activeDirectoryDeploymentV2ToSNowTranslator struct {
Mapping servicenow.MappingConfiguration
}

func (t *activeDirectoryDeploymentV2ToSNowTranslator) Init(*gophercloud.ProviderClient, gophercloud.EndpointOpts) (err error) {
t.Mapping, err = servicenow.LoadMappingConfiguration()
return err
}

func (t *activeDirectoryDeploymentV2ToSNowTranslator) PluginTypeID() string {
return "active-directory-deployment-from-concourse.v2->active-directory-deployment-to-servicenow.v1"
}

func (t *activeDirectoryDeploymentV2ToSNowTranslator) TranslatePayload(payload []byte) ([]byte, error) {
event, err := jsonUnmarshalStrict[deployevent.Event](payload)
if err != nil {
return nil, err
}

chg := servicenow.Change{
StartedAt: event.CombinedStartDate(),
EndedAt: event.RecordedAt,
Outcome: event.CombinedOutcome(),
Summary: fmt.Sprintf("Deploy AD to %s", event.ADDeployment.Hostname),
Description: fmt.Sprintf("Deployed active-directory in landscape %s with versions: %s\n\nOutcome: %s",
event.ADDeployment.Landscape,
strings.Join(inputDescriptorsOf(event), ", "),
event.ADDeployment.Outcome,
),
Executee: event.Pipeline.CreatedBy, //NOTE: can be empty
Region: event.Region,
}

return chg.Serialize(t.Mapping, t.Mapping.ActiveDirectoryDeployment)
}

////////////////////////////////////////////////////////////////////////////////
// DeliveryHandler for SNow

type activeDirectoryDeploymentToSNowDeliverer struct {
type activeDirectoryDeploymentV1ToSNowDeliverer struct {
Client *servicenow.Client
}

func (d *activeDirectoryDeploymentToSNowDeliverer) Init(*gophercloud.ProviderClient, gophercloud.EndpointOpts) (err error) {
func (d *activeDirectoryDeploymentV1ToSNowDeliverer) Init(*gophercloud.ProviderClient, gophercloud.EndpointOpts) (err error) {
d.Client, err = servicenow.NewClientFromEnv("TENSO_SERVICENOW")
return err
}

func (d *activeDirectoryDeploymentToSNowDeliverer) PluginTypeID() string {
func (d *activeDirectoryDeploymentV1ToSNowDeliverer) PluginTypeID() string {
return "active-directory-deployment-to-servicenow.v1"
}

func (d *activeDirectoryDeploymentToSNowDeliverer) DeliverPayload(ctx context.Context, payload []byte) (*tenso.DeliveryLog, error) {
func (d *activeDirectoryDeploymentV1ToSNowDeliverer) DeliverPayload(ctx context.Context, payload []byte) (*tenso.DeliveryLog, error) {
return d.Client.DeliverChangePayload(ctx, payload)
}
69 changes: 42 additions & 27 deletions internal/handlers/active-directory-deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package handlers_test

import (
"fmt"
"os"
"testing"

Expand All @@ -34,25 +35,32 @@ func TestActiveDirectoryDeploymentValidationSuccess(t *testing.T) {
//this one we actually need
t.Setenv("TENSO_SERVICENOW_MAPPING_CONFIG_PATH", "fixtures/servicenow-mapping-config.yaml")

s := test.NewSetup(t,
test.WithRoute("active-directory-deployment-from-concourse.v1 -> active-directory-deployment-to-servicenow.v1"),
)
vh := s.Config.EnabledRoutes[0].ValidationHandler
for _, eventFormat := range []string{"v1", "v2"} {
t.Logf("-- testing event format %s", eventFormat)

testCases := []string{
"fixtures/active-directory-deployment-from-concourse.v1.dev.json",
"fixtures/active-directory-deployment-from-concourse.v1.failed.json",
}

for _, tc := range testCases {
sourcePayloadBytes, err := os.ReadFile(tc)
test.Must(t, err)
payloadInfo, err := vh.ValidatePayload(sourcePayloadBytes)
test.Must(t, err)
assert.DeepEqual(t, "event description",
payloadInfo.Description,
"core/active-directory: deploy AD to ad-dev.example.sap",
s := test.NewSetup(t,
test.WithRoute(fmt.Sprintf(
"active-directory-deployment-from-concourse.%[1]s -> active-directory-deployment-to-servicenow.v1",
eventFormat,
)),
)
vh := s.Config.EnabledRoutes[0].ValidationHandler

testCases := []string{
fmt.Sprintf("fixtures/active-directory-deployment-from-concourse.%s.dev.json", eventFormat),
fmt.Sprintf("fixtures/active-directory-deployment-from-concourse.%s.failed.json", eventFormat),
}

for _, tc := range testCases {
sourcePayloadBytes, err := os.ReadFile(tc)
test.Must(t, err)
payloadInfo, err := vh.ValidatePayload(sourcePayloadBytes)
test.Must(t, err)
assert.DeepEqual(t, "event description",
payloadInfo.Description,
"core/active-directory: deploy AD to ad-dev.example.sap",
)
}
}
}

Expand All @@ -64,15 +72,22 @@ func TestActiveDirectoryDeploymentConversionToSNow(t *testing.T) {
//this one we actually need
t.Setenv("TENSO_SERVICENOW_MAPPING_CONFIG_PATH", "fixtures/servicenow-mapping-config.yaml")

s := test.NewSetup(t,
test.WithRoute("active-directory-deployment-from-concourse.v1 -> active-directory-deployment-to-servicenow.v1"),
)
th := s.Config.EnabledRoutes[0].TranslationHandler
for _, eventFormat := range []string{"v1", "v2"} {
t.Logf("-- testing event format %s", eventFormat)

sourcePayloadBytes, err := os.ReadFile("fixtures/active-directory-deployment-from-concourse.v1.dev.json")
test.Must(t, err)
targetPayloadBytes, err := th.TranslatePayload(sourcePayloadBytes)
test.Must(t, err)
assert.JSONFixtureFile("fixtures/active-directory-deployment-to-servicenow.v1.dev.json").
AssertResponseBody(t, "translated payload", targetPayloadBytes)
s := test.NewSetup(t,
test.WithRoute(fmt.Sprintf(
"active-directory-deployment-from-concourse.%s -> active-directory-deployment-to-servicenow.v1",
eventFormat,
)),
)
th := s.Config.EnabledRoutes[0].TranslationHandler

sourcePayloadBytes, err := os.ReadFile(fmt.Sprintf("fixtures/active-directory-deployment-from-concourse.%s.dev.json", eventFormat))
test.Must(t, err)
targetPayloadBytes, err := th.TranslatePayload(sourcePayloadBytes)
test.Must(t, err)
assert.JSONFixtureFile("fixtures/active-directory-deployment-to-servicenow.v1.dev.json").
AssertResponseBody(t, "translated payload", targetPayloadBytes)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"region": "qa-de-1",
"recorded_at": "2023-06-22T16:24:33Z",
"git": {
"convergedcloud-ad-config_dev": {
"authored-at": "2023-06-14T11:43:57+02:00",
"branch": "main",
"commit-id": "09d2af8dd22201dd8d48e5dcfcaed281ff9422c7",
"committed-at": "2023-06-14T11:43:57+02:00",
"remote-url": "https://git.example.org/ad-config.git"
},
"convergedcloud-ad": {
"authored-at": "2023-06-21T20:06:21+02:00",
"branch": "main",
"commit-id": "e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e",
"committed-at": "2023-06-21T20:06:21+02:00",
"remote-url": "https://git.example.org/ad.git"
}
},
"pipeline": {
"build-number": "58",
"build-url": "https://concourse.example.org/teams/core/pipelines/active-directory/jobs/dev-deploy/builds/58",
"job": "dev-deploy",
"name": "active-directory",
"team": "core"
},
"active-directory-deployment": {
"landscape": "dev",
"host": "ad-dev.example.sap",
"outcome": "succeeded",
"finished-at": "2023-06-22T16:24:25Z",
"started-at": "2023-06-22T16:21:02Z",
"duration": 203
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"region": "qa-de-1",
"recorded_at": "2023-06-22T16:24:33Z",
"git": {
"convergedcloud-ad-config_dev": {
"authored-at": "2023-06-14T11:43:57+02:00",
"branch": "main",
"commit-id": "09d2af8dd22201dd8d48e5dcfcaed281ff9422c7",
"committed-at": "2023-06-14T11:43:57+02:00",
"remote-url": "https://git.example.org/ad-config.git"
},
"convergedcloud-ad": {
"authored-at": "2023-06-21T20:06:21+02:00",
"branch": "main",
"commit-id": "e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e",
"committed-at": "2023-06-21T20:06:21+02:00",
"remote-url": "https://git.example.org/ad.git"
}
},
"pipeline": {
"build-number": "58",
"build-url": "https://concourse.example.org/teams/core/pipelines/active-directory/jobs/dev-deploy/builds/58",
"job": "dev-deploy",
"name": "active-directory",
"team": "core"
},
"active-directory-deployment": {
"landscape": "dev",
"host": "ad-dev.example.sap",
"outcome": "active-directory-deployment-failed",
"started-at": "2023-06-22T16:21:02Z"
}
}

4 changes: 4 additions & 0 deletions internal/handlers/helm-deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/sapcc/tenso/internal/tenso"
)

//nolint:dupl
func init() {
tenso.ValidationHandlerRegistry.Add(func() tenso.ValidationHandler { return &helmDeploymentValidator{} })
tenso.DeliveryHandlerRegistry.Add(func() tenso.DeliveryHandler { return &helmDeploymentToElkDeliverer{} })
Expand Down Expand Up @@ -73,6 +74,9 @@ func (h *helmDeploymentValidator) ValidatePayload(payload []byte) (*tenso.Payloa
return nil, err
}

if event.ADDeployment != nil {
return nil, errors.New("active-directory-deployment may not be set for Helm deployment events")
}
if len(event.TerraformRuns) != 0 {
return nil, errors.New("terraform-runs[] may not be set for Helm deployment events")
}
Expand Down
Loading

0 comments on commit 2f3ca5c

Please sign in to comment.