Skip to content

Commit

Permalink
S-109550 Implement config filtering for Deploy status scripts (#64)
Browse files Browse the repository at this point in the history
* S-107984 Add support for CI serialization

* S-107984 Add nil for JSON CI.id serialiation

* S-108775 Add configId to DeploymentProviderEvent (#57)

* Add documentation comments

---------

Co-authored-by: Goran Pugar <goranpg@gmail.com>
Co-authored-by: gpugar <45165905+gpugar@users.noreply.github.com>
  • Loading branch information
3 people committed Jun 4, 2024
1 parent a77c120 commit e875a45
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 0 deletions.
117 changes: 117 additions & 0 deletions task/ci/deser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package ci

import (
"encoding/json"
"reflect"
"time"
)

type TypeMap map[reflect.Type]string

var typeMapRegistry = make(TypeMap)

// RegisterTypeMappings register a struct type maps to synthetic type.
func RegisterTypeMappings(types TypeMap) {
for reflection, typeName := range types {
typeMapRegistry[reflection] = typeName
}
}

// RegisterTypeMapping a struct type to synthetic type mapping registration.
func RegisterTypeMapping(typeReflection reflect.Type, typeName string) {
typeMapRegistry[typeReflection] = typeName
}

// ToSyntheticType map a struct type to synthetic type.
func ToSyntheticType(searchedType reflect.Type) string {
if typeName, ok := typeMapRegistry[searchedType]; ok {
return typeName
}
return searchedType.Name()
}

// SerializeCi serializes any struct into the desired JSON format.
func SerializeCi(data interface{}) ([]byte, error) {
jsonData, err := serializeData(data)
if err != nil {
return nil, err
}
return json.Marshal(jsonData)
}

func serializeData(data interface{}) (map[string]interface{}, error) {
jsonData := make(map[string]interface{})
dataType := reflect.TypeOf(data)
dataValue := reflect.ValueOf(data)

jsonData["id"] = nil
jsonData["type"] = ToSyntheticType(dataType)
for i := 0; i < dataType.NumField(); i++ {
field := dataType.Field(i)
tag := field.Tag.Get("synthetic")
fieldValue := dataValue.Field(i)

if len(tag) > 0 {
// Handle synthetic fields
switch fieldValue.Kind() {
case reflect.Interface:
nestedJSON, err := serializeData(fieldValue.Interface())
if err != nil {
return nil, err
}
jsonData[tag] = nestedJSON
case reflect.Struct:
if field.Type == reflect.TypeOf(time.Time{}) {
// Convert time.Time to ISO format string
jsonData[tag] = fieldValue.Interface().(time.Time).Format(time.RFC3339)
} else {
nestedJSON, err := serializeData(fieldValue.Interface())
if err != nil {
return nil, err
}
jsonData[tag] = nestedJSON
}
default:
if !isEmptyValue(fieldValue) {
value := fieldValue.Interface()
jsonData[tag] = value
}
}
} else if field.Anonymous {
// Handle embedded fields
embedded, err := serializeData(fieldValue.Interface())
if err != nil {
return nil, err
}
for k, v := range embedded {
jsonData[k] = v
}
}
}

return jsonData, nil
}

func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Struct:
if v.Type() == reflect.TypeOf(time.Time{}) {
return v.Interface().(time.Time).IsZero()
}
default:
return v.IsNil()
}
return false
}
74 changes: 74 additions & 0 deletions task/deployment-provider/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package deployment_provider

import (
"github.com/digital-ai/release-integration-sdk-go/task/ci"
"reflect"
"time"
)

func init() {
ci.RegisterTypeMappings(ci.TypeMap{
reflect.TypeOf(Environment{}): "xlrelease.Environment",
reflect.TypeOf(Application{}): "xlrelease.Application",
reflect.TypeOf(DeploymentState{}): "xlrelease.DeploymentState",
reflect.TypeOf(DeploymentProviderEvent{}): "events.DeploymentProviderEvent",
})
}

type DeploymentProviderEvent struct {
Operation string `synthetic:"operation"`
Environment Environment `synthetic:"environment"`
Application Application `synthetic:"application"`
DeploymentState DeploymentState `synthetic:"deploymentState"`
ConfigId string `synthetic:"configId"`
}

type Environment struct {
Title string `synthetic:"title"`
CorrelationUid string `synthetic:"correlationUid"`
DeploymentTarget DeploymentTarget `synthetic:"deploymentTarget"`
}

type Application struct {
Title string `synthetic:"title"`
CorrelationUid string `synthetic:"correlationUid"`
ApplicationSource ApplicationSource `synthetic:"applicationSource"`
}

type DeploymentState struct {
StatusGroup string `synthetic:"statusGroup"`
Status string `synthetic:"status"`
VersionTag string `synthetic:"versionTag"`
VersionState string `synthetic:"versionState"`
DeploymentType string `synthetic:"deploymentType"`
User string `synthetic:"user"`
LastChangeTime time.Time `synthetic:"lastChangeTime"`
}

type ApplicationSourceType struct {
ServerUrl string `synthetic:"serverUrl"`
}

type DeploymentTargetType struct {
TargetUrl string `synthetic:"targetUrl"`
}

type ComposedType interface {
Type() reflect.Type
}

type ApplicationSource interface {
ComposedType
}

type DeploymentTarget interface {
ComposedType
}

func RegisterApplicationSourceType(typeName string) {
ci.RegisterTypeMapping(reflect.TypeOf(ApplicationSourceType{}), typeName)
}

func RegisterDeploymentTargetType(typeName string) {
ci.RegisterTypeMapping(reflect.TypeOf(DeploymentTargetType{}), typeName)
}
142 changes: 142 additions & 0 deletions task/deployment-provider/types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package deployment_provider

import (
"encoding/json"
"github.com/digital-ai/release-integration-sdk-go/task/ci"
"reflect"
"testing"
"time"
)

type PluginDeploymentTarget struct {
DeploymentTargetType
EnvironmentPath string `synthetic:"environmentPath"`
}

func (das PluginDeploymentTarget) Type() reflect.Type {
return reflect.ValueOf(PluginDeploymentTarget{}).Type()
}

type PluginApplicationSource struct {
ApplicationSourceType
ApplicationPath string `synthetic:"applicationPath"`
ApplicationType string `synthetic:"applicationType"`
}

func (das PluginApplicationSource) Type() reflect.Type {
return reflect.TypeOf(PluginApplicationSource{})
}

func TestSerializeCi(t *testing.T) {
type Tests struct {
name string
event DeploymentProviderEvent
appSourceTypeName string
deployTargetTypeName string
returnValue map[string]interface{}
expectedErr error
}

timeDate, _ := time.Parse(time.RFC3339, "2024-04-20T05:35:00+01:00")
tests := []Tests{
{
name: "status-filter",
event: DeploymentProviderEvent{
Operation: "create",
Application: Application{
Title: "important-app",
CorrelationUid: "UUID-APP",
ApplicationSource: PluginApplicationSource{
ApplicationSourceType: ApplicationSourceType{
ServerUrl: "test-server-url",
},
ApplicationType: "Ear",
ApplicationPath: "Applications/important-app",
},
},
DeploymentState: DeploymentState{
Status: "crashed",
StatusGroup: "failed",
DeploymentType: "rollback",
LastChangeTime: timeDate,
User: "rollback-user",
VersionTag: "1.2",
},
Environment: Environment{
Title: "production-important",
CorrelationUid: "UUID-ENV",
DeploymentTarget: PluginDeploymentTarget{
DeploymentTargetType: DeploymentTargetType{
TargetUrl: "target-url",
},
EnvironmentPath: "Environments/production",
},
},
},
appSourceTypeName: "plugin.ApplicationSource",
deployTargetTypeName: "plugin.DeploymentTarget",
returnValue: map[string]interface{}{
"operation": "create",
"id": nil,
"type": "events.DeploymentProviderEvent",
"application": map[string]interface{}{
"id": nil,
"title": "important-app",
"correlationUid": "UUID-APP",
"type": "xlrelease.Application",
"applicationSource": map[string]interface{}{
"id": nil,
"type": "plugin.ApplicationSource",
"applicationPath": "Applications/important-app",
"applicationType": "Ear",
"serverUrl": "test-server-url",
},
},
"deploymentState": map[string]interface{}{
"id": nil,
"type": "xlrelease.DeploymentState",
"status": "crashed",
"statusGroup": "failed",
"deploymentType": "rollback",
"lastChangeTime": "2024-04-20T05:35:00+01:00",
"user": "rollback-user",
"versionTag": "1.2",
},
"environment": map[string]interface{}{
"id": nil,
"title": "production-important",
"correlationUid": "UUID-ENV",
"type": "xlrelease.Environment",
"deploymentTarget": map[string]interface{}{
"id": nil,
"type": "plugin.DeploymentTarget",
"targetUrl": "target-url",
"environmentPath": "Environments/production",
},
},
},
expectedErr: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
RegisterApplicationSourceType(tt.appSourceTypeName)
RegisterDeploymentTargetType(tt.deployTargetTypeName)
got, err := ci.SerializeCi(tt.event)
if !reflect.DeepEqual(err, tt.expectedErr) {
t.Fatalf("Actual: `%v`; Expected: `%v`", err, tt.expectedErr)
}
var data map[string]interface{}
err = json.Unmarshal(got, &data)
if err != nil {
t.Fatalf("Actual: `%v` - couln't unmarshal to map", string(got))
}
if !reflect.DeepEqual(data, tt.returnValue) {
t.Fatalf("Actual: `%v`; Expected: `%v`", data, tt.returnValue)
} else {
t.Logf("Success!")
}
})
}
}

0 comments on commit e875a45

Please sign in to comment.