Skip to content

Commit

Permalink
Added target in stage spec and solution create in providers.stage.cre…
Browse files Browse the repository at this point in the history
…ate (#612)

* wnew create

* resovle comments
  • Loading branch information
iwangjintian authored Jan 27, 2025
1 parent 55f5e2d commit 65a2a9e
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 28 deletions.
3 changes: 0 additions & 3 deletions api/constants/constants.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//go:build !azure
// +build !azure

/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
Expand Down
33 changes: 33 additions & 0 deletions api/pkg/apis/v1alpha1/helper/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build !azure

/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
* SPDX-License-Identifier: MIT
*/

package helper

import (
"context"

"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model"
api_utils "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func GetInstanceTargetName(name string) string {
return name
}

func GetInstanceRootResource(name string) string {
return ""
}

func GetInstanceOwnerReferences(apiClient api_utils.ApiClient, ctx context.Context, objectName string, objectNamespace string, instanceState model.InstanceState, user string, pwd string) ([]metav1.OwnerReference, error) {
return nil, nil
}

func GetSolutionContainerOwnerReferences(apiClient api_utils.ApiClient, ctx context.Context, objectName string, objectNamespace string, user string, pwd string) ([]metav1.OwnerReference, error) {
return nil, nil
}
74 changes: 74 additions & 0 deletions api/pkg/apis/v1alpha1/helper/helper_azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//go:build azure

/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
* SPDX-License-Identifier: MIT
*/

package helper

import (
"context"
"fmt"
"strings"

"github.com/eclipse-symphony/symphony/api/constants"
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model"
api_utils "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/utils"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func GetInstanceTargetName(name string) string {
parts := strings.Split(name, "/")
if len(parts) < 3 {
return ""
}
version := parts[len(parts)-1]
solution := parts[len(parts)-3]
return fmt.Sprintf("%s:%s", solution, version)
}

func GetInstanceRootResource(name string) string {
parts := strings.Split(name, "/")
if len(parts) < 3 {
return ""
}
return parts[len(parts)-3]
}

func GetInstanceOwnerReferences(apiClient api_utils.ApiClient, ctx context.Context, objectName string, objectNamespace string, instanceState model.InstanceState, user string, pwd string) ([]metav1.OwnerReference, error) {
parts := strings.Split(instanceState.Spec.Solution, constants.ReferenceSeparator)
if len(parts) != 2 {
return nil, v1alpha2.NewCOAError(nil, fmt.Sprintf("Invalid solution name: instance - %s", objectName), v1alpha2.BadRequest)
}
sc, err := apiClient.GetSolutionContainer(ctx, parts[0], objectNamespace, user, pwd)
if err != nil {
return nil, err
}
return []metav1.OwnerReference{
{
APIVersion: fmt.Sprintf("%s/%s", model.SolutionGroup, "v1"),
Kind: "SolutionContainer",
Name: sc.ObjectMeta.Name,
UID: sc.ObjectMeta.UID,
},
}, nil
}

func GetSolutionContainerOwnerReferences(apiClient api_utils.ApiClient, ctx context.Context, objectName string, objectNamespace string, user string, pwd string) ([]metav1.OwnerReference, error) {
target, err := apiClient.GetTarget(ctx, objectName, objectNamespace, user, pwd)
if err != nil {
return nil, err
}

return []metav1.OwnerReference{
{
APIVersion: fmt.Sprintf("%s/%s", model.FabricGroup, "v1"),
Kind: "Target",
Name: target.ObjectMeta.Name,
UID: target.ObjectMeta.UID,
},
}, nil
}
1 change: 1 addition & 0 deletions api/pkg/apis/v1alpha1/managers/stage/stage-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ func (s *StageManager) HandleTriggerEvent(ctx context.Context, campaign model.Ca
if triggerData.Schedule != "" {
inputs["__schedule"] = triggerData.Schedule
}
inputs["__target"] = currentStage.Target

for k, v := range inputs {
var val interface{}
Expand Down
1 change: 1 addition & 0 deletions api/pkg/apis/v1alpha1/model/campaign.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type StageSpec struct {
Inputs map[string]interface{} `json:"inputs,omitempty"`
HandleErrors bool `json:"handleErrors,omitempty"`
Schedule string `json:"schedule,omitempty"`
Target string `json:"target,omitempty"`
}

// UnmarshalJSON customizes the JSON unmarshalling for StageSpec
Expand Down
6 changes: 6 additions & 0 deletions api/pkg/apis/v1alpha1/model/objectmeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (

"github.com/eclipse-symphony/symphony/api/constants"
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

type ObjectMeta struct {
Expand All @@ -30,6 +32,10 @@ type ObjectMeta struct {

Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`

UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`

OwnerReferences []metav1.OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"`
}

// UnmarshalJSON custom unmarshaller to handle Generation field(accept both of number and string)
Expand Down
2 changes: 0 additions & 2 deletions api/pkg/apis/v1alpha1/model/package.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build !azure

/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
Expand Down
189 changes: 181 additions & 8 deletions api/pkg/apis/v1alpha1/providers/stage/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"sync"
"time"

"github.com/eclipse-symphony/symphony/api/constants"
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/helper"
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model"
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/providers/metrics"
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/providers/stage"
Expand All @@ -40,6 +42,9 @@ var (
mLog = logger.NewLogger(loggerName)
once sync.Once
providerOperationMetrics *metrics.Metrics
label_key = os.Getenv("LABEL_KEY")
label_value = os.Getenv("LABEL_VALUE")
annotation_name = os.Getenv("ANNOTATION_KEY")
)

type CreateStageProviderConfig struct {
Expand Down Expand Up @@ -178,13 +183,159 @@ func (i *CreateStageProvider) Process(ctx context.Context, mgrContext contexts.M
}
objectName = api_utils.ConvertReferenceToObjectName(objectName)
lastSummaryMessage := ""
objectNamespace := stage.GetNamespace(inputs)
if objectNamespace == "" {
objectNamespace = "default"
}
switch objectType {
case "instance":
objectNamespace := stage.GetNamespace(inputs)
if objectNamespace == "" {
objectNamespace = "default"
}
case "solution":
if strings.EqualFold(action, RemoveAction) {
solutionName := api_utils.ConvertReferenceToObjectName(objectName)
observ_utils.EmitUserAuditsLogs(ctx, " P (Create Stage): Start to delete solution name %s namespace %s", solutionName, objectNamespace)
err = i.ApiClient.DeleteSolution(ctx, solutionName, objectNamespace, i.Config.User, i.Config.Password)
if err != nil {
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.DeleteSolutionFailed.String(),
)
mLog.ErrorfCtx(ctx, " P (Create Stage) process failed, failed to delete solution: %+v", err)
return nil, false, err
}
outputs["objectType"] = objectType
outputs["objectName"] = objectName
return outputs, false, nil
} else if strings.EqualFold(action, CreateAction) {
objectName := stage.ReadInputString(inputs, "objectName")
solutionName := api_utils.ConvertReferenceToObjectName(objectName)
var solutionState model.SolutionState
err = json.Unmarshal(objectData, &solutionState)
if err != nil {
mLog.ErrorfCtx(ctx, "Failed to unmarshal solution state for input %s: %s", objectName, err.Error())
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.InvalidSolutionCatalog.String(),
)
return outputs, false, v1alpha2.NewCOAError(nil, fmt.Sprintf("invalid embeded solution in inputs %s", objectName), v1alpha2.BadRequest)
}

solutionState.ObjectMeta.Namespace = objectNamespace
solutionState.ObjectMeta.Name = solutionName
parts := strings.Split(objectName, constants.ReferenceSeparator)
if len(parts) == 2 {
solutionState.Spec.RootResource = parts[0]
solutionState.Spec.Version = parts[1]
} else {
mLog.ErrorfCtx(ctx, "Solution name is invalid: solution - %s.", objectName)
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.InvalidSolutionCatalog.String(),
)
return outputs, false, v1alpha2.NewCOAError(nil, fmt.Sprintf("Invalid solution name: %s", objectName), v1alpha2.BadRequest)
}

if label_key != "" && label_value != "" {
// Check if labels exists within metadata, if not create it
labels := solutionState.ObjectMeta.Labels
if labels == nil {
labels = make(map[string]string)
solutionState.ObjectMeta.Labels = labels
}
// Add the label
labels[label_key] = label_value
}
if annotation_name != "" {
solutionState.ObjectMeta.UpdateAnnotation(annotation_name, parts[1])
}
mLog.DebugfCtx(ctx, " P (Create Processor): check solution contains %v, namespace %s", solutionState.Spec.RootResource, objectNamespace)
_, err := i.ApiClient.GetSolutionContainer(ctx, solutionState.Spec.RootResource, objectNamespace, i.Config.User, i.Config.Password)
if err != nil && api_utils.IsNotFound(err) {
mLog.DebugfCtx(ctx, "Solution container %s doesn't exist: %s", solutionState.Spec.RootResource, err.Error())
solutionContainerState := model.SolutionContainerState{ObjectMeta: model.ObjectMeta{Name: solutionState.Spec.RootResource, Namespace: objectNamespace, Labels: solutionState.ObjectMeta.Labels}}

// Set the owner reference
target := stage.ReadInputString(inputs, "__target")
target = helper.GetInstanceTargetName(target)
ownerReference, err := helper.GetSolutionContainerOwnerReferences(i.ApiClient, ctx, target, objectNamespace, i.Config.User, i.Config.Password)
if err != nil {
mLog.ErrorfCtx(ctx, "Failed to get owner reference for solution %s: %s", objectName, err.Error())
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.CreateSolutionFailed.String(),
)
return outputs, false, err
}
if ownerReference != nil {
solutionContainerState.ObjectMeta.OwnerReferences = ownerReference
}
containerObjectData, _ := json.Marshal(solutionContainerState)
observ_utils.EmitUserAuditsLogs(ctx, " P (Materialize Processor): Start to create solution container %v in namespace %s", solutionState.Spec.RootResource, objectNamespace)
err = i.ApiClient.CreateSolutionContainer(ctx, solutionState.Spec.RootResource, containerObjectData, objectNamespace, i.Config.User, i.Config.Password)
if err != nil {
mLog.ErrorfCtx(ctx, "Failed to create solution container %s: %s", solutionState.Spec.RootResource, err.Error())
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.ParentObjectCreateFailed.String(),
)
return outputs, false, err
}
} else if err != nil {
mLog.ErrorfCtx(ctx, "Failed to get solution container %s: %s", solutionState.Spec.RootResource, err.Error())
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.ParentObjectMissing.String(),
)
return outputs, false, err
}

objectData, _ := json.Marshal(solutionState)
mLog.DebugfCtx(ctx, " P (Materialize Processor): materialize solution %v to namespace %s", solutionState.ObjectMeta.Name, solutionState.ObjectMeta.Namespace)
observ_utils.EmitUserAuditsLogs(ctx, " P (Materialize Processor): Start to materialize solution %v to namespace %s", solutionState.ObjectMeta.Name, solutionState.ObjectMeta.Namespace)
err = i.ApiClient.UpsertSolution(ctx, solutionState.ObjectMeta.Name, objectData, solutionState.ObjectMeta.Namespace, i.Config.User, i.Config.Password)
if err != nil {
mLog.ErrorfCtx(ctx, "Failed to create solution %s: %s", solutionState.ObjectMeta.Name, err.Error())
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.CreateSolutionFromCatalogFailed.String(),
)
return outputs, false, err
}
outputs["objectType"] = objectType
outputs["objectName"] = solutionName
return outputs, false, nil
} else {
err = v1alpha2.NewCOAError(nil, fmt.Sprintf("Unsupported action: %s", action), v1alpha2.BadRequest)
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.UnsupportedAction.String(),
)
mLog.ErrorfCtx(ctx, " P (Create Stage) process failed, error: %+v", err)
return nil, false, err
}
case "instance":
if strings.EqualFold(action, RemoveAction) {
observ_utils.EmitUserAuditsLogs(ctx, " P (Create Stage): Start to delete instance name %s namespace %s", objectName, objectNamespace)
err = i.ApiClient.DeleteInstance(ctx, objectName, objectNamespace, i.Config.User, i.Config.Password)
Expand Down Expand Up @@ -232,9 +383,31 @@ func (i *CreateStageProvider) Process(ctx context.Context, mgrContext contexts.M
return outputs, false, err
}

label_key := os.Getenv("LABEL_KEY")
label_value := os.Getenv("LABEL_VALUE")
annotation_name := os.Getenv("ANNOTATION_KEY")
target := stage.ReadInputString(inputs, "__target")
if target != "" {
instanceState.Spec.Target = model.TargetSelector{
Name: helper.GetInstanceTargetName(target),
}
}

// Set the owner reference
ownerReference, err := helper.GetInstanceOwnerReferences(i.ApiClient, ctx, objectName, objectNamespace, instanceState, i.Config.User, i.Config.Password)
if err != nil {
mLog.ErrorfCtx(ctx, "Failed to get owner reference for instance %s: %s", objectName, err.Error())
providerOperationMetrics.ProviderOperationErrors(
create,
functionName,
metrics.ProcessOperation,
metrics.RunOperationType,
v1alpha2.CreateInstanceFailed.String(),
)
return outputs, false, err
}
if ownerReference != nil {
instanceState.ObjectMeta.OwnerReferences = ownerReference
}

// Set the labels
if label_key != "" && label_value != "" {
// Check if labels exists within metadata, if not create it
labels := instanceState.ObjectMeta.Labels
Expand Down
Loading

0 comments on commit 65a2a9e

Please sign in to comment.