-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
S-109550 Implement config filtering for Deploy status scripts (#64)
* 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
1 parent
a77c120
commit e875a45
Showing
3 changed files
with
333 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!") | ||
} | ||
}) | ||
} | ||
} |