diff --git a/api/coverage.html b/api/coverage.html new file mode 100644 index 000000000..dff1ca5ed --- /dev/null +++ b/api/coverage.html @@ -0,0 +1,23187 @@ + + + + + + activations: Go Coverage Report + + + +
+ +
+ not tracked + + not covered + covered + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/api/pkg/apis/v1alpha1/managers/jobs/jobs-manager.go b/api/pkg/apis/v1alpha1/managers/jobs/jobs-manager.go index 1b5cecbc0..e8e4aeb9c 100644 --- a/api/pkg/apis/v1alpha1/managers/jobs/jobs-manager.go +++ b/api/pkg/apis/v1alpha1/managers/jobs/jobs-manager.go @@ -356,6 +356,7 @@ func (s *JobsManager) HandleJobEvent(ctx context.Context, event v1alpha2.Event) }) var err error = nil defer observ_utils.CloseSpanWithError(span, &err) + log.Info(" M (Job): handling %v event, event body %v, %v", event.Metadata["objectType"], "job", event.Body) namespace := model.ReadProperty(event.Metadata, "namespace", nil) if namespace == "" { @@ -377,7 +378,7 @@ func (s *JobsManager) HandleJobEvent(ctx context.Context, event v1alpha2.Event) switch objectType { case "instance": - log.Debugf(" M (Job): handling instance job %s", job.Id) + log.Debugf(" M (Job): handling instance job >>>>>>>>>>>>>>>>>>>>>>>>>>>> %s, %s", job.Id, namespace) instanceName := job.Id var instance model.InstanceState //get intance @@ -387,9 +388,13 @@ func (s *JobsManager) HandleJobEvent(ctx context.Context, event v1alpha2.Event) return err //TODO: instance is gone } + log.Debugf(" M (Job): handling instance job solution name >>>>>>>>>>>>>>>>>>>>>>>>>>>> %s", instance.Spec.Solution) + //get solution var solution model.SolutionState solution, err = s.apiClient.GetSolution(ctx, instance.Spec.Solution, namespace) + log.Debugf(" M (Job): handling instance job solution after GetSolution >>>>>>>>>>>>>>>>>>>>>>>>>>>> %s", solution.ObjectMeta.Name) + if err != nil { solution = model.SolutionState{ ObjectMeta: model.ObjectMeta{ diff --git a/api/pkg/apis/v1alpha1/managers/solutions/solutions-manager.go b/api/pkg/apis/v1alpha1/managers/solutions/solutions-manager.go index 11b484e75..4d86a3173 100644 --- a/api/pkg/apis/v1alpha1/managers/solutions/solutions-manager.go +++ b/api/pkg/apis/v1alpha1/managers/solutions/solutions-manager.go @@ -10,6 +10,8 @@ import ( "context" "encoding/json" "fmt" + "strconv" + "strings" "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2" @@ -17,11 +19,14 @@ import ( "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/managers" "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers" "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/states" + "github.com/eclipse-symphony/symphony/coa/pkg/logger" observability "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/observability" observ_utils "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/observability/utils" ) +var sLog = logger.NewLogger("coa.runtime") + type SolutionsManager struct { managers.Manager StateProvider states.IStateProvider @@ -48,14 +53,28 @@ func (t *SolutionsManager) DeleteState(ctx context.Context, name string, namespa var err error = nil defer observ_utils.CloseSpanWithError(span, &err) + var rootResource string + var version string + parts := strings.Split(name, ":") + if len(parts) == 2 { + rootResource = parts[0] + version = parts[1] + } else { + return v1alpha2.NewCOAError(nil, fmt.Sprintf("Solution name is invalid in the request (%s)", name), v1alpha2.BadRequest) + } + + sLog.Info(" M (Solution manager): delete state >>>>>>>>>>>>>>>>>>>>parts %v, %v", rootResource, version) + + id := rootResource + "-" + version err = t.StateProvider.Delete(ctx, states.DeleteRequest{ - ID: name, + ID: id, Metadata: map[string]interface{}{ - "namespace": namespace, - "group": model.SolutionGroup, - "version": "v1", - "resource": "solutions", - "kind": "Solution", + "namespace": namespace, + "group": model.SolutionGroup, + "version": "v1", + "resource": "solutions", + "kind": "Solution", + "rootResource": rootResource, }, }) return err @@ -68,28 +87,58 @@ func (t *SolutionsManager) UpsertState(ctx context.Context, name string, state m var err error = nil defer observ_utils.CloseSpanWithError(span, &err) + sLog.Info(" M (Solution manager): debug upsert state >>>>>>>>>>>>>>>>>>>> %v, %v, %v", state.Spec.Version, state.Spec.RootResource, name) if state.ObjectMeta.Name != "" && state.ObjectMeta.Name != name { return v1alpha2.NewCOAError(nil, fmt.Sprintf("Name in metadata (%s) does not match name in request (%s)", state.ObjectMeta.Name, name), v1alpha2.BadRequest) } state.ObjectMeta.FixNames(name) + var rootResource string + version := state.Spec.Version + if state.Spec.RootResource == "" && version != "" { + suffix := "-" + version + rootResource = strings.TrimSuffix(name, suffix) + } else { + rootResource = state.Spec.RootResource + } + + if state.ObjectMeta.Labels == nil { + state.ObjectMeta.Labels = make(map[string]string) + } + + _, versionLabelExists := state.ObjectMeta.Labels["version"] + _, rootLabelExists := state.ObjectMeta.Labels["rootResource"] + refreshLabels := false + if !versionLabelExists || !rootLabelExists { + sLog.Info(" M (Solution manager): update labels to true >>>>>>>>>>>>>>>>>>>> %v, %v", rootResource, version) + + state.ObjectMeta.Labels["rootResource"] = rootResource + state.ObjectMeta.Labels["version"] = version + refreshLabels = true + } + + sLog.Info(" M (Solution manager): debug refresh >>>>>>>>>>>>>>>>>>>> %v, %v, %v", refreshLabels, versionLabelExists, rootLabelExists) + body := map[string]interface{}{ "apiVersion": model.SolutionGroup + "/v1", "kind": "Solution", "metadata": state.ObjectMeta, "spec": state.Spec, } + upsertRequest := states.UpsertRequest{ Value: states.StateEntry{ ID: name, Body: body, }, Metadata: map[string]interface{}{ - "namespace": state.ObjectMeta.Namespace, - "group": model.SolutionGroup, - "version": "v1", - "resource": "solutions", - "kind": "Solution", + "namespace": state.ObjectMeta.Namespace, + "group": model.SolutionGroup, + "version": "v1", + "resource": "solutions", + "kind": "Solution", + "rootResource": rootResource, + "refreshLabels": strconv.FormatBool(refreshLabels), }, } @@ -150,6 +199,8 @@ func (t *SolutionsManager) GetState(ctx context.Context, id string, namespace st var err error = nil defer observ_utils.CloseSpanWithError(span, &err) + sLog.Info(" M (Solution manager): debug get state >>>>>>>>>>>>>>>>>>>> %v, %v", id, namespace) + getRequest := states.GetRequest{ ID: id, Metadata: map[string]interface{}{ @@ -172,3 +223,34 @@ func (t *SolutionsManager) GetState(ctx context.Context, id string, namespace st } return ret, nil } + +func (t *SolutionsManager) GetLatestState(ctx context.Context, id string, namespace string) (model.SolutionState, error) { + ctx, span := observability.StartSpan("Solutions Manager", ctx, &map[string]string{ + "method": "GetLatest", + }) + var err error = nil + defer observ_utils.CloseSpanWithError(span, &err) + + sLog.Info(" M (Solution manager): debug get latest state >>>>>>>>>>>>>>>>>>>> %v, %v", id, namespace) + + getRequest := states.GetRequest{ + ID: id, + Metadata: map[string]interface{}{ + "version": "v1", + "group": model.SolutionGroup, + "resource": "solutions", + "namespace": namespace, + "kind": "Solution", + }, + } + target, err := t.StateProvider.GetLatest(ctx, getRequest) + if err != nil { + return model.SolutionState{}, err + } + + ret, err := getSolutionState(target.Body) + if err != nil { + return model.SolutionState{}, err + } + return ret, nil +} diff --git a/api/pkg/apis/v1alpha1/model/solution.go b/api/pkg/apis/v1alpha1/model/solution.go index a266d4e51..0826b0f8d 100644 --- a/api/pkg/apis/v1alpha1/model/solution.go +++ b/api/pkg/apis/v1alpha1/model/solution.go @@ -17,9 +17,11 @@ type ( } SolutionSpec struct { - DisplayName string `json:"displayName,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - Components []ComponentSpec `json:"components,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + Components []ComponentSpec `json:"components,omitempty"` + Version string `json:"version,omitempty"` + RootResource string `json:"rootResource,omitempty"` } ) diff --git a/api/pkg/apis/v1alpha1/model/versionedcampaign.go b/api/pkg/apis/v1alpha1/model/versionedcampaign.go new file mode 100644 index 000000000..45a467e42 --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/versionedcampaign.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" +) + +type VersionedCampaignState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Spec *VersionedCampaignSpec `json:"spec,omitempty"` +} + +type VersionedCampaignSpec struct { + Name string `json:"name,omitempty"` +} + +func (c VersionedCampaignSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(VersionedCampaignSpec) + if !ok { + return false, errors.New("parameter is not a VersionedCampaignSpec type") + } + + if c.Name != otherC.Name { + return false, nil + } + + return true, nil +} + +func (c VersionedCampaignState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(VersionedCampaignState) + if !ok { + return false, errors.New("parameter is not a VersionedCampaignState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/model/versionedcatalog b/api/pkg/apis/v1alpha1/model/versionedcatalog new file mode 100644 index 000000000..7f0228303 --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/versionedcatalog @@ -0,0 +1,62 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" + "reflect" +) + +// TODO: all state objects should converge to this paradigm: id, spec and status +type VersionedCatalogState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Spec *VersionedCatalogSpec `json:"spec,omitempty"` +} + +type VersionedCatalogSpec struct { + Name string `json:"name"` +} + +func (c VersionedCatalogSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(VersionedCatalogSpec) + if !ok { + return false, errors.New("parameter is not a VersionedCatalogSpec type") + } + + if c.Name != otherC.Name { + return false, nil + } + + if c.Generation != otherC.Generation { + return false, nil + } + + if !reflect.DeepEqual(c.Properties, otherC.Properties) { + return false, nil + } + + return true, nil +} + +func (c VersionedCatalogState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(VersionedCatalogState) + if !ok { + return false, errors.New("parameter is not a VersionedCatalogState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/model/versionedsolution.go b/api/pkg/apis/v1alpha1/model/versionedsolution.go new file mode 100644 index 000000000..728a4dbf3 --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/versionedsolution.go @@ -0,0 +1,59 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" +) + +type ( + VersionedSolutionState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Spec *VersionedSolutionSpec `json:"spec,omitempty"` + } + + VersionedSolutionSpec struct { + DisplayName string `json:"displayName,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + } +) + +func (c VersionedSolutionSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(VersionedSolutionSpec) + if !ok { + return false, errors.New("parameter is not a VersionedSolutionSpec type") + } + + if c.DisplayName != otherC.DisplayName { + return false, nil + } + + if !StringMapsEqual(c.Metadata, otherC.Metadata, nil) { + return false, nil + } + + return true, nil +} + +func (c VersionedSolutionState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(VersionedSolutionState) + if !ok { + return false, errors.New("parameter is not a VersionedSolutionState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/model/versionedtarget.go b/api/pkg/apis/v1alpha1/model/versionedtarget.go new file mode 100644 index 000000000..99d753b6b --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/versionedtarget.go @@ -0,0 +1,63 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" + "time" +) + +type ( + VersionedTargetStatus struct { + Properties map[string]string `json:"properties,omitempty"` + ProvisioningStatus ProvisioningStatus `json:"provisioningStatus"` + LastModified time.Time `json:"lastModified,omitempty"` + } + // VersionedTargetState defines the current state of the target + VersionedTargetState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Status VersionedTargetStatus `json:"status,omitempty"` + Spec *VersionedTargetSpec `json:"spec,omitempty"` + } + + // VersionedTargetSpec defines the spec property of the VersionedTargetState + VersionedTargetSpec struct { + DisplayName string `json:"displayName,omitempty"` + } +) + +func (c VersionedTargetSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(VersionedTargetSpec) + if !ok { + return false, errors.New("parameter is not a VersionedTargetSpec type") + } + + if c.DisplayName != otherC.DisplayName { + return false, nil + } + + return true, nil +} + +func (c VersionedTargetState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(VersionedTargetState) + if !ok { + return false, errors.New("parameter is not a VersionedTargetState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/providers/stage/materialize/materialize.go b/api/pkg/apis/v1alpha1/providers/stage/materialize/materialize.go index 47bbf7f6a..6b0e8cf34 100644 --- a/api/pkg/apis/v1alpha1/providers/stage/materialize/materialize.go +++ b/api/pkg/apis/v1alpha1/providers/stage/materialize/materialize.go @@ -179,8 +179,8 @@ func (i *MaterializeStageProvider) Process(ctx context.Context, mgrContext conte err = utils.UpsertSolution(ctx, i.Config.BaseUrl, solutionState.ObjectMeta.Name, i.Config.User, i.Config.Password, objectData, solutionState.ObjectMeta.Namespace) if err != nil { mLog.Errorf("Failed to create solution %s: %s", name, err.Error()) - return outputs, false, err } + return outputs, false, err creationCount++ case "target": var targetState model.TargetState diff --git a/api/pkg/apis/v1alpha1/providers/states/k8s/k8s.go b/api/pkg/apis/v1alpha1/providers/states/k8s/k8s.go index 172047984..ad0601559 100644 --- a/api/pkg/apis/v1alpha1/providers/states/k8s/k8s.go +++ b/api/pkg/apis/v1alpha1/providers/states/k8s/k8s.go @@ -11,7 +11,9 @@ import ( "encoding/json" "fmt" "path/filepath" + "reflect" "strconv" + "time" "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/utils" @@ -180,11 +182,23 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques version := model.ReadPropertyCompat(entry.Metadata, "version", nil) resource := model.ReadPropertyCompat(entry.Metadata, "resource", nil) kind := model.ReadPropertyCompat(entry.Metadata, "kind", nil) + rootResource := model.ReadPropertyCompat(entry.Metadata, "rootResource", nil) + refreshStr := model.ReadPropertyCompat(entry.Metadata, "refreshLabels", nil) if namespace == "" { namespace = "default" } + sLog.Info(" P (K8s State): erefreshStr >>>>>>>>>>>>>>>>>>>> %v ", refreshStr) + + var refreshLabels bool + refreshLabels, err = strconv.ParseBool(refreshStr) + if err != nil { + sLog.Info(" P (K8s State): error parse >>>>>>>>>>>>>>>>>>>> %v", err) + refreshLabels = false + } + sLog.Info(" P (K8s State): upsert state refreshLabels>>>>>>>>>>>>>>>>>>>> %v , %v", refreshLabels, namespace) + resourceId := schema.GroupVersionResource{ Group: group, Version: version, @@ -196,6 +210,8 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques return "", err } + sLog.Info(" P (K8s State): upsert state >>>>>>>>>>>>>>>>>>>> %v , %v", entry.Value.ID, namespace) + j, _ := json.Marshal(entry.Value.Body) item, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).Get(ctx, entry.Value.ID, metav1.GetOptions{}) if err != nil { @@ -222,9 +238,46 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques } unc.SetName(metadata.Name) unc.SetNamespace(metadata.Namespace) - unc.SetLabels(metadata.Labels) unc.SetAnnotations(metadata.Annotations) + if refreshLabels { + latestFilterValue := "tag=latest" + labelSelector := "rootResource=" + rootResource + "," + latestFilterValue + listOptions := metav1.ListOptions{ + LabelSelector: labelSelector, + } + + items, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).List(ctx, listOptions) + if err != nil { + sLog.Errorf(" P (K8s State): failed to list object with labels %s in namespace %s: %v ", labelSelector, namespace, err) + return "", err + } + + if len(items.Items) == 0 { + sLog.Infof(" P (K8s State): no objects found with labels %s in namespace %s: %v ", labelSelector, namespace, err) + } + + for _, v := range items.Items { + labels := v.GetLabels() + delete(labels, "version") + v.SetLabels(labels) + + _, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).Update(ctx, &v, metav1.UpdateOptions{}) + if err != nil { + sLog.Errorf(" P (K8s State): failed to remove labels %s from obj %s in namespace %s: %v ", latestFilterValue, v.GetName(), err) + return "", err + } else { + sLog.Infof(" P (K8s State): remove labels %s from object in namespace %s: %v ", labelSelector, v.GetName(), namespace, err) + } + } + if metadata.Labels == nil { + metadata.Labels = make(map[string]string) + } + + metadata.Labels["tag"] = "latest" + unc.SetLabels(metadata.Labels) + } + _, err = s.DynamicClient.Resource(resourceId).Namespace(namespace).Create(ctx, unc, metav1.CreateOptions{}) if err != nil { sLog.Errorf(" P (K8s State): failed to create object: %v", err) @@ -232,6 +285,8 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques } //Note: state is ignored for new object } else { + sLog.Info(" P (K8s State): upsert state exists >>>>>>>>>>>>>>>>>>>> %v , %v", entry.Value.ID, namespace) + j, _ := json.Marshal(entry.Value.Body) var dict map[string]interface{} err = json.Unmarshal(j, &dict) @@ -249,15 +304,76 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques } item.SetName(metadata.Name) item.SetNamespace(metadata.Namespace) - item.SetLabels(metadata.Labels) item.SetAnnotations(metadata.Annotations) + + labels := item.GetLabels() + if labels == nil { + labels = make(map[string]string) + } + + for key, value := range metadata.Labels { + labels[key] = value + } + item.SetLabels(labels) + + _, exists := labels["tag"] + sLog.Errorf(" P (K8s State): >>>>>>>>> get tag label: efreshLabels, exists, rootResource %v, %v, %v", refreshLabels, exists, rootResource) + + if refreshLabels && !exists { + latestFilterValue := "tag=latest" + labelSelector := "rootResource=" + rootResource + "," + latestFilterValue + sLog.Errorf(" P (K8s State): >>>>>>>>> refresh and not exist: %v", labelSelector) + + listOptions := metav1.ListOptions{ + LabelSelector: labelSelector, + } + items, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).List(ctx, listOptions) + if err != nil { + sLog.Errorf(" P (K8s State): failed to list object with labels %s in namespace %s: %v ", labelSelector, namespace, err) + return "", err + } + if len(items.Items) == 0 { + sLog.Infof(" P (K8s State): no objects found with labels %s in namespace %s: %v ", labelSelector, namespace, err) + } + + needTag := true + currentItemTime := item.GetCreationTimestamp().Time + for _, v := range items.Items { + sLog.Infof(" P (K8s State): a>>>>>>>>>>>>> v.GetCreationTimestamp() %v ", v.GetCreationTimestamp()) + if currentItemTime.Before(v.GetCreationTimestamp().Time) { + needTag = false + } else { + labels := v.GetLabels() + delete(labels, "tag") + v.SetLabels(labels) + + _, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).Update(ctx, &v, metav1.UpdateOptions{}) + if err != nil { + sLog.Errorf(" P (K8s State): failed to remove latest label from obj %s in namespace %s: %v ", v.GetName(), err) + return "", err + } else { + sLog.Infof(" P (K8s State): remove latest label from object in namespace %s: %v ", v.GetName(), namespace, err) + } + } + } + sLog.Infof(" P (K8s State): a>>>>>>>>>>>>> needtag %s ", needTag) + + if needTag { + if metadata.Labels == nil { + metadata.Labels = make(map[string]string) + } + metadata.Labels["tag"] = "latest" + item.SetLabels(metadata.Labels) + sLog.Infof(" P (K8s State): a>>>>>>>>>>>>> set latest", needTag) + } + } } if v, ok := dict["spec"]; ok { item.Object["spec"] = v _, err = s.DynamicClient.Resource(resourceId).Namespace(namespace).Update(ctx, item, metav1.UpdateOptions{}) if err != nil { - sLog.Errorf(" P (K8s State): failed to update object: %v", err) + sLog.Errorf(" P (K8s State): failed to update object for spec: %v", err) return "", err } } @@ -275,7 +391,7 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques status.SetResourceVersion(item.GetResourceVersion()) _, err = s.DynamicClient.Resource(resourceId).Namespace(namespace).UpdateStatus(ctx, status, v1.UpdateOptions{}) if err != nil { - sLog.Errorf(" P (K8s State): failed to update object status: %v", err) + sLog.Errorf(" P (K8s State): failed to update object status for status: %v", err) return "", err } } @@ -417,12 +533,19 @@ func (s *K8sStateProvider) Delete(ctx context.Context, request states.DeleteRequ var err error = nil defer observ_utils.CloseSpanWithError(span, &err) - sLog.Info(" P (K8s State): delete state") + sLog.Info(" P (K8s State): delete state %v", request.ID) namespace := model.ReadPropertyCompat(request.Metadata, "namespace", nil) group := model.ReadPropertyCompat(request.Metadata, "group", nil) version := model.ReadPropertyCompat(request.Metadata, "version", nil) resource := model.ReadPropertyCompat(request.Metadata, "resource", nil) + rootResource := model.ReadPropertyCompat(request.Metadata, "rootResource", nil) + + if namespace == "" { + namespace = "default" + } + + sLog.Info(" P (K8s State): delete state >>>>>>>>>>>>>>>>>>>> %v", request.ID) resourceId := schema.GroupVersionResource{ Group: group, @@ -438,11 +561,73 @@ func (s *K8sStateProvider) Delete(ctx context.Context, request states.DeleteRequ return err } - err = s.DynamicClient.Resource(resourceId).Namespace(namespace).Delete(ctx, request.ID, metav1.DeleteOptions{}) - if err != nil { - sLog.Errorf(" P (K8s State): failed to delete objects: %v", err) - return err + item, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).Get(ctx, request.ID, metav1.GetOptions{}) + if err == nil { + sLog.Info(" P (K8s State): delete state , get object>>>>>>>>>>>>>>>>>>>> %v", request.ID) + + labels := item.GetLabels() + _, exists := labels["tag"] + + if exists && labels["tag"] == "latest" { + labelSelector := "rootResource=" + rootResource + listOptions := metav1.ListOptions{ + LabelSelector: labelSelector, + } + items, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).List(ctx, listOptions) + sLog.Info(" P (K8s State): delete state , list items acount >>>>>>>>>>>>>>>>>>>> %d", len(items.Items)) + + if err != nil { + sLog.Errorf(" P (K8s State): failed to list object with labels %s in namespace %s: %v ", labelSelector, namespace, err) + return err + } + + var latestItem unstructured.Unstructured + var latestTime time.Time + for _, v := range items.Items { + if reflect.DeepEqual(item, &v) { + sLog.Info(" P (K8s State): delete state , deep copy equal>>>>>>>>>>>>>>>>>>>> %v", item.GetName()) + continue + } + if latestTime.Before(v.GetCreationTimestamp().Time) { + latestTime = v.GetCreationTimestamp().Time + sLog.Info(" P (K8s State): delete state , latest item refreshed1 >>>>>>>>>>>>>>>>>>>> %v", v.GetName()) + latestItem = v + } + } + + if !reflect.DeepEqual(latestItem, unstructured.Unstructured{}) { + labels := latestItem.GetLabels() + if labels == nil { + labels = make(map[string]string) + } + _, existTag := labels["tag"] + sLog.Info(" P (K8s State): delete state , latest exist tag >>>>>>>>>>>>>>>>>>>> %v", existTag) + + if !existTag { + labels["tag"] = "latest" + latestItem.SetLabels(labels) + + sLog.Info(" P (K8s State): delete state , update latest item>>>>>>>>>>>>>>>>>>>> %v", labels["tag"]) + _, err = s.DynamicClient.Resource(resourceId).Namespace(namespace).Update(ctx, &latestItem, metav1.UpdateOptions{}) + if err != nil { + sLog.Errorf(" P (K8s State): failed to add labels for obj %s in namespace %s: %v ", latestItem.GetName(), err) + return err + } else { + sLog.Infof(" P (K8s State): add labels %s from object %s in namespace %s: %v ", labelSelector, latestItem.GetName(), namespace, err) + } + } + } + + } + + sLog.Info(" P (K8s State): delete state , delete the current %v", request.ID) + err = s.DynamicClient.Resource(resourceId).Namespace(namespace).Delete(ctx, request.ID, metav1.DeleteOptions{}) + if err != nil { + sLog.Errorf(" P (K8s State): failed to delete objects: %v", err) + return err + } } + return nil } @@ -453,13 +638,15 @@ func (s *K8sStateProvider) Get(ctx context.Context, request states.GetRequest) ( var err error = nil defer observ_utils.CloseSpanWithError(span, &err) - sLog.Info(" P (K8s State): get state") + sLog.Info(" P (K8s State): get state ") namespace := model.ReadPropertyCompat(request.Metadata, "namespace", nil) group := model.ReadPropertyCompat(request.Metadata, "group", nil) version := model.ReadPropertyCompat(request.Metadata, "version", nil) resource := model.ReadPropertyCompat(request.Metadata, "resource", nil) + sLog.Info(" P (K8s State): get state >>>>>>>>>>>>>>>>>>>> %v", request.ID) + if namespace == "" { namespace = "default" } @@ -506,6 +693,90 @@ func (s *K8sStateProvider) Get(ctx context.Context, request states.GetRequest) ( return ret, nil } +func (s *K8sStateProvider) GetLatest(ctx context.Context, request states.GetRequest) (states.StateEntry, error) { + ctx, span := observability.StartSpan("K8s State Provider", ctx, &map[string]string{ + "method": "GetLatest", + }) + var err error = nil + defer observ_utils.CloseSpanWithError(span, &err) + + sLog.Info(" P (K8s State): get state with latest label") + + namespace := model.ReadPropertyCompat(request.Metadata, "namespace", nil) + group := model.ReadPropertyCompat(request.Metadata, "group", nil) + version := model.ReadPropertyCompat(request.Metadata, "version", nil) + resource := model.ReadPropertyCompat(request.Metadata, "resource", nil) + + sLog.Info(" P (K8s State): debug get GetLatest >>>>>>>>>>>>>>>>>>>> %v", request.ID) + + if namespace == "" { + namespace = "default" + } + + resourceId := schema.GroupVersionResource{ + Group: group, + Version: version, + Resource: resource, + } + + if request.ID == "" { + err := v1alpha2.NewCOAError(nil, "found invalid request ID", v1alpha2.BadRequest) + return states.StateEntry{}, err + } + + latestFilterValue := "tag=latest" + labelSelector := "rootResource=" + request.ID + "," + latestFilterValue + options := metav1.ListOptions{ + LabelSelector: labelSelector, + } + + sLog.Info(" P (K8s State): debug get GetLatest label selector >>>>>>>>>>>>>>>>>>>> %v", labelSelector) + + items, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).List(ctx, options) + if err != nil { + sLog.Errorf(" P (K8s State): failed to get latest object %s in namespace %s: %v ", request.ID, namespace, err) + return states.StateEntry{}, err + } + sLog.Info(" P (K8s State): debug get GetLatest list resource count >>>>>>>>>>>>>>>>>>>> %d", len(items.Items)) + + var latestItem unstructured.Unstructured + var latestTime time.Time + + if len(items.Items) == 0 { + sLog.Info(" P (K8s State): debug get GetLatest get 0 latest object >>>>>>>>>>>>>>>>>>>> %d", len(items.Items)) + err := v1alpha2.NewCOAError(nil, "failed to find latest object", v1alpha2.NotFound) + return states.StateEntry{}, err + } + + for _, v := range items.Items { + if latestTime.Before(v.GetCreationTimestamp().Time) { + sLog.Info(" P (K8s State): debug get GetLatest set latest >>>>>>>>>>>>>>>>>>>> %d", v.GetName()) + latestTime = v.GetCreationTimestamp().Time + latestItem = v + } + } + + generation := latestItem.GetGeneration() + + metadata := model.ObjectMeta{ + Name: latestItem.GetName(), + Namespace: latestItem.GetNamespace(), + Labels: latestItem.GetLabels(), + Annotations: latestItem.GetAnnotations(), + } + + ret := states.StateEntry{ + ID: request.ID, + ETag: strconv.FormatInt(generation, 10), + Body: map[string]interface{}{ + "spec": latestItem.Object["spec"], + "status": latestItem.Object["status"], + "metadata": metadata, + }, + } + return ret, nil +} + // Implmeement the IConfigProvider interface func (s *K8sStateProvider) Read(object string, field string) (string, error) { obj, err := s.Get(context.TODO(), states.GetRequest{ diff --git a/api/pkg/apis/v1alpha1/providers/target/k8s/coverage.html b/api/pkg/apis/v1alpha1/providers/target/k8s/coverage.html new file mode 100644 index 000000000..e35a06ddc --- /dev/null +++ b/api/pkg/apis/v1alpha1/providers/target/k8s/coverage.html @@ -0,0 +1,1123 @@ + + + + + + k8s: Go Coverage Report + + + +
+ +
+ not tracked + + not covered + covered + +
+
+
+ + + + + +
+ + + diff --git a/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/instance.yaml b/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/instance.yaml new file mode 100644 index 000000000..50c0be3df --- /dev/null +++ b/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/instance.yaml @@ -0,0 +1,9 @@ +apiVersion: solution.symphony/v1 +kind: Instance +metadata: + name: instance03 +spec: + scope: k8s-scope + solution: solution03 + target: + name: target03 diff --git a/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/solution.yaml b/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/solution.yaml new file mode 100644 index 000000000..53ddbce36 --- /dev/null +++ b/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/solution.yaml @@ -0,0 +1,17 @@ +apiVersion: solution.symphony/v1 +kind: Solution +metadata: + name: solution03 +spec: + metadata: + deployment.replicas: "#1" + service.ports: "[{\"name\":\"port9090\",\"port\": 9090}]" + service.type: "ClusterIP" + components: + - name: prometheus-server + type: container + properties: + container.ports: "[{\"containerPort\":9090,\"protocol\":\"TCP\"}]" + container.imagePullPolicy: "Always" + container.resources: "{\"requests\":{\"cpu\":\"1\",\"memory\":\"100Mi\"}}" + container.image: "prom/prometheus" diff --git a/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/target.yaml b/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/target.yaml new file mode 100644 index 000000000..edce481ca --- /dev/null +++ b/api/pkg/apis/v1alpha1/providers/target/k8s/manifest/oss/target.yaml @@ -0,0 +1,15 @@ +apiVersion: fabric.symphony/v1 +kind: Target +metadata: + name: target03 +spec: + forceRedeploy: true + topologies: + - bindings: + - role: instance + provider: providers.target.k8s + config: + inCluster: "true" + deleteEmptyNamespace: "true" + retryCount: "4" + retryIntervalInSec: "1" diff --git a/api/pkg/apis/v1alpha1/utils/apiclient.go b/api/pkg/apis/v1alpha1/utils/apiclient.go index 440dfa7f2..db7cee900 100644 --- a/api/pkg/apis/v1alpha1/utils/apiclient.go +++ b/api/pkg/apis/v1alpha1/utils/apiclient.go @@ -12,12 +12,14 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" + "errors" "fmt" "io" "net/http" "net/url" "os" "path" + "strings" "github.com/eclipse-symphony/symphony/api/constants" "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" @@ -49,22 +51,26 @@ type ( QueueDeploymentJob(ctx context.Context, namespace string, isDelete bool, deployment model.DeploymentSpec) error } + Getter interface { + GetInstance(ctx context.Context, instance string, namespace string) (model.InstanceState, error) + GetSolution(ctx context.Context, solution string, namespace string) (model.SolutionState, error) + GetTarget(ctx context.Context, target string, namespace string) (model.TargetState, error) + } + ApiClient interface { SummaryGetter Dispatcher + Getter GetInstancesForAllNamespaces(ctx context.Context) ([]model.InstanceState, error) GetInstances(ctx context.Context, namespace string) ([]model.InstanceState, error) - GetInstance(ctx context.Context, instance string, namespace string) (model.InstanceState, error) + GetSolutions(ctx context.Context, namespace string) ([]model.SolutionState, error) + GetTargetsForAllNamespaces(ctx context.Context) ([]model.TargetState, error) + GetTargets(ctx context.Context, namespace string) ([]model.TargetState, error) CreateInstance(ctx context.Context, instance string, payload []byte, namespace string) error DeleteInstance(ctx context.Context, instance string, namespace string) error DeleteTarget(ctx context.Context, target string, namespace string) error - GetSolutions(ctx context.Context, namespace string) ([]model.SolutionState, error) - GetSolution(ctx context.Context, solution string, namespace string) (model.SolutionState, error) CreateSolution(ctx context.Context, solution string, payload []byte, namespace string) error DeleteSolution(ctx context.Context, solution string, namespace string) error - GetTargetsForAllNamespaces(ctx context.Context) ([]model.TargetState, error) - GetTarget(ctx context.Context, target string, namespace string) (model.TargetState, error) - GetTargets(ctx context.Context, namespace string) ([]model.TargetState, error) CreateTarget(ctx context.Context, target string, payload []byte, namespace string) error Reconcile(ctx context.Context, deployment model.DeploymentSpec, isDelete bool, namespace string) (model.SummarySpec, error) } @@ -272,7 +278,19 @@ func (a *apiClient) GetSolution(ctx context.Context, solution string, namespace return ret, err } - response, err := a.callRestAPI(ctx, "solutions/"+url.QueryEscape(solution)+"?namespace="+url.QueryEscape(namespace), "GET", nil, token) + log.Infof("apiClient.GetSolution:>>>>>>>>>>>> solution: %s, namespace: %s", solution, namespace) + + var name string + var version string + parts := strings.Split(solution, ":") + if len(parts) == 2 { + name = parts[0] + version = parts[1] + } else { + return ret, errors.New("invalid solution name") + } + + response, err := a.callRestAPI(ctx, "solutions/"+url.QueryEscape(name)+"/"+url.QueryEscape(version)+"?namespace="+url.QueryEscape(namespace), "GET", nil, token) if err != nil { return ret, err } @@ -497,6 +515,9 @@ func (a *apiClient) callRestAPI(ctx context.Context, route string, method string "http.method": method, "http.url": urlString, }) + + log.Debugf("apiClient.callRestAPI:>>>>>>>>>>>> route: %s, urlString: %s, method: %s", route, urlString, method) + var err error = nil defer observ_utils.CloseSpanWithError(span, &err) diff --git a/api/pkg/apis/v1alpha1/utils/symphony-api.go b/api/pkg/apis/v1alpha1/utils/symphony-api.go index c5cee2c29..95d00e74a 100644 --- a/api/pkg/apis/v1alpha1/utils/symphony-api.go +++ b/api/pkg/apis/v1alpha1/utils/symphony-api.go @@ -10,6 +10,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -436,7 +437,19 @@ func GetSolution(context context.Context, baseUrl string, solution string, user if err != nil { return ret, err } - path := "solutions/" + url.QueryEscape(solution) + + var name string + var version string + + parts := strings.Split(solution, ":") + if len(parts) == 2 { + name = parts[0] + version = parts[1] + } else { + return ret, errors.New("invalid solution name") + } + + path := "solutions/" + url.QueryEscape(name) + "/" + url.QueryEscape(version) path = path + "?namespace=" + url.QueryEscape(namespace) response, err := callRestAPI(context, baseUrl, path, "GET", nil, token) if err != nil { @@ -455,7 +468,23 @@ func UpsertSolution(context context.Context, baseUrl string, solution string, us if err != nil { return err } - path := "solutions/" + url.QueryEscape(solution) + + var name string + var version string + + log.Infof("Symphony API UpsertSolution, solution: %s namespace: %s", solution, namespace) + + parts := strings.Split(solution, ":") + if len(parts) == 2 { + name = parts[0] + version = parts[1] + } else { + return errors.New("invalid solution name") + } + + log.Infof("Symphony API UpsertSolution, parts: %s, %s", parts[0], parts[1]) + + path := "solutions/" + url.QueryEscape(name) + "/" + url.QueryEscape(version) path = path + "?namespace=" + url.QueryEscape(namespace) _, err = callRestAPI(context, baseUrl, path, "POST", payload, token) if err != nil { @@ -469,7 +498,19 @@ func DeleteSolution(context context.Context, baseUrl string, solution string, us if err != nil { return err } - path := "solutions/" + url.QueryEscape(solution) + + var name string + var version string + + parts := strings.Split(solution, ":") + if len(parts) == 2 { + name = parts[0] + version = parts[1] + } else { + return errors.New("invalid solution name") + } + + path := "solutions/" + url.QueryEscape(name) + "/" + url.QueryEscape(version) path = path + "?namespace=" + url.QueryEscape(namespace) _, err = callRestAPI(context, baseUrl, path, "DELETE", nil, token) if err != nil { diff --git a/api/pkg/apis/v1alpha1/vendors/solutions-vendor.go b/api/pkg/apis/v1alpha1/vendors/solutions-vendor.go index 304181a09..572a802b0 100644 --- a/api/pkg/apis/v1alpha1/vendors/solutions-vendor.go +++ b/api/pkg/apis/v1alpha1/vendors/solutions-vendor.go @@ -66,7 +66,13 @@ func (o *SolutionsVendor) GetEndpoints() []v1alpha2.Endpoint { Route: route, Version: o.Version, Handler: o.onSolutions, - Parameters: []string{"name?"}, + Parameters: []string{"name", "version?"}, + }, + { + Methods: []string{fasthttp.MethodGet}, + Route: route, + Version: o.Version, + Handler: o.onSolutionsList, }, } } @@ -81,23 +87,24 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR if !exist { namespace = constants.DefaultScope } + version := request.Parameters["__version"] + rootResource := request.Parameters["__name"] + id := rootResource + "-" + version + uLog.Infof("V (Solutions): >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> id ", id) + switch request.Method { case fasthttp.MethodGet: ctx, span := observability.StartSpan("onSolutions-GET", pCtx, nil) - id := request.Parameters["__name"] + var err error var state interface{} - isArray := false - if id == "" { - // Change namespace back to empty to indicate ListSpec need to query all namespaces - if !exist { - namespace = "" - } - state, err = c.SolutionsManager.ListState(ctx, namespace) - isArray = true + + if version == "" || version == "latest" { + state, err = c.SolutionsManager.GetLatestState(ctx, rootResource, namespace) } else { state, err = c.SolutionsManager.GetState(ctx, id, namespace) } + if err != nil { uLog.Infof("V (Solutions): onSolutions failed - %s, traceId: %s", err.Error(), span.SpanContext().TraceID().String()) return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ @@ -105,7 +112,7 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR Body: []byte(err.Error()), }) } - jData, _ := utils.FormatObject(state, isArray, request.Parameters["path"], request.Parameters["doc-type"]) + jData, _ := utils.FormatObject(state, false, request.Parameters["path"], request.Parameters["doc-type"]) resp := observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ State: v1alpha2.OK, Body: jData, @@ -117,7 +124,15 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR return resp case fasthttp.MethodPost: ctx, span := observability.StartSpan("onSolutions-POST", pCtx, nil) - id := request.Parameters["__name"] + + if version == "" || version == "latest" { + uLog.Infof("V (Solutions): onSolutions failed - version is required for POST, traceId: %s", span.SpanContext().TraceID().String()) + return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ + State: v1alpha2.InternalError, + Body: []byte("version is required for POST"), + }) + } + embed_type := request.Parameters["embed-type"] embed_component := request.Parameters["embed-component"] embed_property := request.Parameters["embed-property"] @@ -141,6 +156,8 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR }, }, }, + Version: version, + RootResource: rootResource, }, } } else { @@ -155,6 +172,12 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR if solution.ObjectMeta.Name == "" { solution.ObjectMeta.Name = id } + if solution.Spec.Version == "" && version != "" { + solution.Spec.Version = version + } + if solution.Spec.RootResource == "" && rootResource != "" { + solution.Spec.RootResource = rootResource + } } err := c.SolutionsManager.UpsertState(ctx, id, solution) if err != nil { @@ -191,7 +214,7 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR }) case fasthttp.MethodDelete: ctx, span := observability.StartSpan("onSolutions-DELETE", pCtx, nil) - id := request.Parameters["__name"] + id := rootResource + ":" + version err := c.SolutionsManager.DeleteState(ctx, id, namespace) if err != nil { uLog.Infof("V (Solutions): onSolutions failed - %s, traceId: %s", err.Error(), span.SpanContext().TraceID().String()) @@ -213,3 +236,53 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR observ_utils.UpdateSpanStatusFromCOAResponse(span, resp) return resp } + +func (c *SolutionsVendor) onSolutionsList(request v1alpha2.COARequest) v1alpha2.COAResponse { + pCtx, span := observability.StartSpan("Solutions Vendor", request.Context, &map[string]string{ + "method": "onSolutions", + }) + defer span.End() + uLog.Infof("V (Solutions): onSolutionsList, method: %s, traceId: %s", request.Method, span.SpanContext().TraceID().String()) + namespace, exist := request.Parameters["namespace"] + if !exist { + namespace = "default" + } + switch request.Method { + case fasthttp.MethodGet: + ctx, span := observability.StartSpan("onSolutions-GET", pCtx, nil) + + var err error + var state interface{} + // Change namespace back to empty to indicate ListSpec need to query all namespaces + if !exist { + namespace = "" + } + state, err = c.SolutionsManager.ListState(ctx, namespace) + + if err != nil { + uLog.Infof("V (Solutions): onSolutionsList failed - %s, traceId: %s", err.Error(), span.SpanContext().TraceID().String()) + return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ + State: v1alpha2.InternalError, + Body: []byte(err.Error()), + }) + } + jData, _ := utils.FormatObject(state, true, request.Parameters["path"], request.Parameters["doc-type"]) + resp := observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ + State: v1alpha2.OK, + Body: jData, + ContentType: "application/json", + }) + if request.Parameters["doc-type"] == "yaml" { + resp.ContentType = "application/text" + } + return resp + } + + resp := v1alpha2.COAResponse{ + State: v1alpha2.MethodNotAllowed, + Body: []byte("{\"result\":\"405 - method not allowed\"}"), + ContentType: "application/json", + } + observ_utils.UpdateSpanStatusFromCOAResponse(span, resp) + return resp +} diff --git a/coa/pkg/apis/v1alpha2/providers/states/httpstate/httpstate.go b/coa/pkg/apis/v1alpha2/providers/states/httpstate/httpstate.go index 942f9b591..c7f2b20db 100644 --- a/coa/pkg/apis/v1alpha2/providers/states/httpstate/httpstate.go +++ b/coa/pkg/apis/v1alpha2/providers/states/httpstate/httpstate.go @@ -279,6 +279,10 @@ func (s *HttpStateProvider) Get(ctx context.Context, request states.GetRequest) }, nil } +func (s *HttpStateProvider) GetLatest(ctx context.Context, request states.GetRequest) (states.StateEntry, error) { + return states.StateEntry{}, v1alpha2.NewCOAError(nil, "Http state store get latest is not implemented", v1alpha2.NotImplemented) +} + func toHttpStateProviderConfig(config providers.IProviderConfig) (HttpStateProviderConfig, error) { ret := HttpStateProviderConfig{} data, err := json.Marshal(config) diff --git a/coa/pkg/apis/v1alpha2/providers/states/memorystate/memorystate.go b/coa/pkg/apis/v1alpha2/providers/states/memorystate/memorystate.go index 3cfa46f94..aadd19fcb 100644 --- a/coa/pkg/apis/v1alpha2/providers/states/memorystate/memorystate.go +++ b/coa/pkg/apis/v1alpha2/providers/states/memorystate/memorystate.go @@ -430,6 +430,10 @@ func (s *MemoryStateProvider) Get(ctx context.Context, request states.GetRequest return states.StateEntry{}, err } +func (s *MemoryStateProvider) GetLatest(ctx context.Context, request states.GetRequest) (states.StateEntry, error) { + return states.StateEntry{}, v1alpha2.NewCOAError(nil, "Memory state store get latest is not implemented", v1alpha2.NotImplemented) +} + func toMemoryStateProviderConfig(config providers.IProviderConfig) (MemoryStateProviderConfig, error) { ret := MemoryStateProviderConfig{} data, err := json.Marshal(config) diff --git a/coa/pkg/apis/v1alpha2/providers/states/states.go b/coa/pkg/apis/v1alpha2/providers/states/states.go index e4b9d418c..0e202b7a5 100644 --- a/coa/pkg/apis/v1alpha2/providers/states/states.go +++ b/coa/pkg/apis/v1alpha2/providers/states/states.go @@ -25,6 +25,7 @@ type IStateProvider interface { Upsert(context.Context, UpsertRequest) (string, error) Delete(context.Context, DeleteRequest) error Get(context.Context, GetRequest) (StateEntry, error) + GetLatest(context.Context, GetRequest) (StateEntry, error) List(context.Context, ListRequest) ([]StateEntry, string, error) SetContext(context *contexts.ManagerContext) } diff --git a/k8s/PROJECT b/k8s/PROJECT index 03909b895..0d394f2b1 100644 --- a/k8s/PROJECT +++ b/k8s/PROJECT @@ -152,4 +152,49 @@ resources: defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: solution.symphony + kind: Versionedsolution + path: gopls-workspace/apis/solution.symphony/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: federation + kind: VersionedCatalog1 + path: gopls-workspace/apis/federation/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: fabric + kind: VersionedTarget1 + path: gopls-workspace/apis/fabric/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: workflow + kind: VersionedCampaign1 + path: gopls-workspace/apis/workflow/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: solution + kind: VersionedInstance1 + path: gopls-workspace/apis/solution/v1 + version: v1 version: "3" diff --git a/k8s/apis/fabric/v1/versionedtarget_types.go b/k8s/apis/fabric/v1/versionedtarget_types.go new file mode 100644 index 000000000..9123a117d --- /dev/null +++ b/k8s/apis/fabric/v1/versionedtarget_types.go @@ -0,0 +1,45 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// VersionedTargetStatus defines the observed state of Target +type VersionedTargetStatus struct { + // Important: Run "make" to regenerate code after modifying this file + Properties map[string]string `json:"properties,omitempty"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.properties.status` +// Target is the Schema for the targets API +type VersionedTarget struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.VersionedTargetSpec `json:"spec,omitempty"` + Status VersionedTargetStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// TargetList contains a list of Target +type VersionedTargetList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VersionedTarget `json:"items"` +} + +func init() { + SchemeBuilder.Register(&VersionedTarget{}, &VersionedTargetList{}) +} diff --git a/k8s/apis/fabric/v1/zz_generated.deepcopy.go b/k8s/apis/fabric/v1/zz_generated.deepcopy.go index 71072473a..046fb4d53 100644 --- a/k8s/apis/fabric/v1/zz_generated.deepcopy.go +++ b/k8s/apis/fabric/v1/zz_generated.deepcopy.go @@ -154,3 +154,110 @@ func (in *TargetList) DeepCopyObject() runtime.Object { } return nil } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetStatus) DeepCopyInto(out *TargetStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetStatus. +func (in *TargetStatus) DeepCopy() *TargetStatus { + if in == nil { + return nil + } + out := new(TargetStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedTarget) DeepCopyInto(out *VersionedTarget) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedTarget. +func (in *VersionedTarget) DeepCopy() *VersionedTarget { + if in == nil { + return nil + } + out := new(VersionedTarget) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VersionedTarget) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedTargetList) DeepCopyInto(out *VersionedTargetList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VersionedTarget, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedTargetList. +func (in *VersionedTargetList) DeepCopy() *VersionedTargetList { + if in == nil { + return nil + } + out := new(VersionedTargetList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VersionedTargetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedTargetStatus) DeepCopyInto(out *VersionedTargetStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedTargetStatus. +func (in *VersionedTargetStatus) DeepCopy() *VersionedTargetStatus { + if in == nil { + return nil + } + out := new(VersionedTargetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/k8s/apis/federation/v1/catalog_types.go b/k8s/apis/federation/v1/catalog_types.go index 4bd445713..35f572a01 100644 --- a/k8s/apis/federation/v1/catalog_types.go +++ b/k8s/apis/federation/v1/catalog_types.go @@ -12,10 +12,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type CatalogStatus struct { - Properties map[string]string `json:"properties"` -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status // Catalog is the Schema for the catalogs API @@ -23,8 +19,7 @@ type Catalog struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec k8smodel.CatalogSpec `json:"spec,omitempty"` - Status CatalogStatus `json:"status,omitempty"` + Spec k8smodel.CatalogSpec `json:"spec,omitempty"` } // +kubebuilder:object:root=true diff --git a/k8s/apis/federation/v1/versionedcatalog_types.go b/k8s/apis/federation/v1/versionedcatalog_types.go new file mode 100644 index 000000000..d22e8fd90 --- /dev/null +++ b/k8s/apis/federation/v1/versionedcatalog_types.go @@ -0,0 +1,42 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type VersionedCatalogStatus struct { + Properties map[string]string `json:"properties"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// VersionedCatalog is the Schema for the catalogs API +type VersionedCatalog struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.VersionedCatalogSpec `json:"spec,omitempty"` + Status VersionedCatalogStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// VersionedCatalogList contains a list of Catalog +type VersionedCatalogList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VersionedCatalog `json:"items"` +} + +func init() { + SchemeBuilder.Register(&VersionedCatalog{}, &VersionedCatalogList{}) +} diff --git a/k8s/apis/federation/v1/zz_generated.deepcopy.go b/k8s/apis/federation/v1/zz_generated.deepcopy.go index d15ffc6d2..506a04ff6 100644 --- a/k8s/apis/federation/v1/zz_generated.deepcopy.go +++ b/k8s/apis/federation/v1/zz_generated.deepcopy.go @@ -21,7 +21,6 @@ func (in *Catalog) DeepCopyInto(out *Catalog) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Catalog. @@ -75,29 +74,66 @@ func (in *CatalogList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CatalogStatus) DeepCopyInto(out *CatalogStatus) { +func (in *Site) DeepCopyInto(out *Site) { *out = *in - if in.Properties != nil { - in, out := &in.Properties, &out.Properties - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Site. +func (in *Site) DeepCopy() *Site { + if in == nil { + return nil + } + out := new(Site) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Site) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SiteList) DeepCopyInto(out *SiteList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Site, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogStatus. -func (in *CatalogStatus) DeepCopy() *CatalogStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SiteList. +func (in *SiteList) DeepCopy() *SiteList { if in == nil { return nil } - out := new(CatalogStatus) + out := new(SiteList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SiteList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Site) DeepCopyInto(out *Site) { +func (in *VersionedCatalog) DeepCopyInto(out *VersionedCatalog) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -105,18 +141,18 @@ func (in *Site) DeepCopyInto(out *Site) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Site. -func (in *Site) DeepCopy() *Site { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedCatalog. +func (in *VersionedCatalog) DeepCopy() *VersionedCatalog { if in == nil { return nil } - out := new(Site) + out := new(VersionedCatalog) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Site) DeepCopyObject() runtime.Object { +func (in *VersionedCatalog) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -124,33 +160,57 @@ func (in *Site) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SiteList) DeepCopyInto(out *SiteList) { +func (in *VersionedCatalogList) DeepCopyInto(out *VersionedCatalogList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]Site, len(*in)) + *out = make([]VersionedCatalog, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SiteList. -func (in *SiteList) DeepCopy() *SiteList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedCatalogList. +func (in *VersionedCatalogList) DeepCopy() *VersionedCatalogList { if in == nil { return nil } - out := new(SiteList) + out := new(VersionedCatalogList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *SiteList) DeepCopyObject() runtime.Object { +func (in *VersionedCatalogList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } return nil } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedCatalogStatus) DeepCopyInto(out *VersionedCatalogStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedCatalogStatus. +func (in *VersionedCatalogStatus) DeepCopy() *VersionedCatalogStatus { + if in == nil { + return nil + } + out := new(VersionedCatalogStatus) + in.DeepCopyInto(out) + return out +} diff --git a/k8s/apis/model/v1/common_types.go b/k8s/apis/model/v1/common_types.go index c625a8c2a..1f789733b 100644 --- a/k8s/apis/model/v1/common_types.go +++ b/k8s/apis/model/v1/common_types.go @@ -100,13 +100,33 @@ type InstanceSpec struct { ReconciliationPolicy *ReconciliationPolicySpec `json:"reconciliationPolicy,omitempty"` } +// Defines the desired state of VersionedTargetSpec +// +kubebuilder:object:generate=true +type VersionedTargetSpec struct { + DisplayName string `json:"displayName,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + // +kubebuilder:object:generate=true type SolutionSpec struct { DisplayName string `json:"displayName,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` Components []ComponentSpec `json:"components,omitempty"` // Defines the version of a particular resource - Version string `json:"version,omitempty"` + Version string `json:"version,omitempty"` + RootResource string `json:"rootResource,omitempty"` +} + +// +kubebuilder:object:generate=true +type VersionedSolutionSpec struct { + DisplayName string `json:"displayName,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// +kubebuilder:object:generate=true +type VersionedInstanceSpec struct { + DisplayName string `json:"displayName,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` } // +kubebuilder:object:generate=true @@ -151,6 +171,12 @@ type CampaignSpec struct { SelfDriving bool `json:"selfDriving,omitempty"` } +// +kubebuilder:object:generate=true +type VersionedCampaignSpec struct { + DisplayName string `json:"displayName,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + // +kubebuilder:object:generate=true type CatalogSpec struct { SiteId string `json:"siteId"` @@ -165,6 +191,12 @@ type CatalogSpec struct { Generation string `json:"generation,omitempty"` } +// +kubebuilder:object:generate=true +type VersionedCatalogSpec struct { + DisplayName string `json:"displayName,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` +} + // +kubebuilder:object:generate=true type DeployableStatus struct { Properties map[string]string `json:"properties,omitempty"` diff --git a/k8s/apis/model/v1/zz_generated.deepcopy.go b/k8s/apis/model/v1/zz_generated.deepcopy.go index ba92cf76d..98cab2cf2 100644 --- a/k8s/apis/model/v1/zz_generated.deepcopy.go +++ b/k8s/apis/model/v1/zz_generated.deepcopy.go @@ -363,3 +363,113 @@ func (in *TargetSpec) DeepCopy() *TargetSpec { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedCampaignSpec) DeepCopyInto(out *VersionedCampaignSpec) { + *out = *in + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedCampaignSpec. +func (in *VersionedCampaignSpec) DeepCopy() *VersionedCampaignSpec { + if in == nil { + return nil + } + out := new(VersionedCampaignSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedCatalogSpec) DeepCopyInto(out *VersionedCatalogSpec) { + *out = *in + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedCatalogSpec. +func (in *VersionedCatalogSpec) DeepCopy() *VersionedCatalogSpec { + if in == nil { + return nil + } + out := new(VersionedCatalogSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedInstanceSpec) DeepCopyInto(out *VersionedInstanceSpec) { + *out = *in + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedInstanceSpec. +func (in *VersionedInstanceSpec) DeepCopy() *VersionedInstanceSpec { + if in == nil { + return nil + } + out := new(VersionedInstanceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedSolutionSpec) DeepCopyInto(out *VersionedSolutionSpec) { + *out = *in + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedSolutionSpec. +func (in *VersionedSolutionSpec) DeepCopy() *VersionedSolutionSpec { + if in == nil { + return nil + } + out := new(VersionedSolutionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedTargetSpec) DeepCopyInto(out *VersionedTargetSpec) { + *out = *in + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedTargetSpec. +func (in *VersionedTargetSpec) DeepCopy() *VersionedTargetSpec { + if in == nil { + return nil + } + out := new(VersionedTargetSpec) + in.DeepCopyInto(out) + return out +} diff --git a/k8s/apis/solution/v1/versionedinstance_types.go b/k8s/apis/solution/v1/versionedinstance_types.go new file mode 100644 index 000000000..4224dc427 --- /dev/null +++ b/k8s/apis/solution/v1/versionedinstance_types.go @@ -0,0 +1,42 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type VersionedInstanceStatus struct { + Properties map[string]string `json:"properties"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// VersionedInstance1 is the Schema for the versionedinstances API +type VersionedInstance struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.VersionedInstanceSpec `json:"spec,omitempty"` + Status VersionedInstanceStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// VersionedInstance1List contains a list of VersionedInstance +type VersionedInstanceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VersionedInstance `json:"items"` +} + +func init() { + SchemeBuilder.Register(&VersionedInstance{}, &VersionedInstanceList{}) +} diff --git a/k8s/apis/solution/v1/versionedsolution_types.go b/k8s/apis/solution/v1/versionedsolution_types.go new file mode 100644 index 000000000..317ee46d3 --- /dev/null +++ b/k8s/apis/solution/v1/versionedsolution_types.go @@ -0,0 +1,42 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type VersionedSolutionStatus struct { + Properties map[string]string `json:"properties"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// VersionedSolution is the Schema for the VersionedSolution API +type VersionedSolution struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.VersionedSolutionSpec `json:"spec,omitempty"` + Status VersionedSolutionStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// VersionedSolutionList contains a list of VersionedSolution +type VersionedSolutionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VersionedSolution `json:"items"` +} + +func init() { + SchemeBuilder.Register(&VersionedSolution{}, &VersionedSolutionList{}) +} diff --git a/k8s/apis/solution/v1/zz_generated.deepcopy.go b/k8s/apis/solution/v1/zz_generated.deepcopy.go index 2857134f2..df0af9bcf 100644 --- a/k8s/apis/solution/v1/zz_generated.deepcopy.go +++ b/k8s/apis/solution/v1/zz_generated.deepcopy.go @@ -154,3 +154,169 @@ func (in *SolutionStatus) DeepCopy() *SolutionStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedInstance) DeepCopyInto(out *VersionedInstance) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedInstance. +func (in *VersionedInstance) DeepCopy() *VersionedInstance { + if in == nil { + return nil + } + out := new(VersionedInstance) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VersionedInstance) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedInstanceList) DeepCopyInto(out *VersionedInstanceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VersionedInstance, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedInstanceList. +func (in *VersionedInstanceList) DeepCopy() *VersionedInstanceList { + if in == nil { + return nil + } + out := new(VersionedInstanceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VersionedInstanceList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedInstanceStatus) DeepCopyInto(out *VersionedInstanceStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedInstanceStatus. +func (in *VersionedInstanceStatus) DeepCopy() *VersionedInstanceStatus { + if in == nil { + return nil + } + out := new(VersionedInstanceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedSolution) DeepCopyInto(out *VersionedSolution) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedSolution. +func (in *VersionedSolution) DeepCopy() *VersionedSolution { + if in == nil { + return nil + } + out := new(VersionedSolution) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VersionedSolution) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedSolutionList) DeepCopyInto(out *VersionedSolutionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VersionedSolution, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedSolutionList. +func (in *VersionedSolutionList) DeepCopy() *VersionedSolutionList { + if in == nil { + return nil + } + out := new(VersionedSolutionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VersionedSolutionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedSolutionStatus) DeepCopyInto(out *VersionedSolutionStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedSolutionStatus. +func (in *VersionedSolutionStatus) DeepCopy() *VersionedSolutionStatus { + if in == nil { + return nil + } + out := new(VersionedSolutionStatus) + in.DeepCopyInto(out) + return out +} diff --git a/k8s/apis/workflow/v1/versionedcampaign_types.go b/k8s/apis/workflow/v1/versionedcampaign_types.go new file mode 100644 index 000000000..bb5c62ce4 --- /dev/null +++ b/k8s/apis/workflow/v1/versionedcampaign_types.go @@ -0,0 +1,41 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type VersionedCampagignStatus struct { + Properties map[string]string `json:"properties"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// VersionedCampaign is the Schema for the campaigns API +type VersionedCampaign struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.VersionedCampaignSpec `json:"spec,omitempty"` + Status VersionedCampagignStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// VersionedCampaignList contains a list of Campaign +type VersionedCampaignList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VersionedCampaign `json:"items"` +} + +func init() { + SchemeBuilder.Register(&VersionedCampaign{}, &VersionedCampaignList{}) +} diff --git a/k8s/apis/workflow/v1/zz_generated.deepcopy.go b/k8s/apis/workflow/v1/zz_generated.deepcopy.go index 7cc2e8927..87714350a 100644 --- a/k8s/apis/workflow/v1/zz_generated.deepcopy.go +++ b/k8s/apis/workflow/v1/zz_generated.deepcopy.go @@ -163,3 +163,86 @@ func (in *CampaignStatus) DeepCopy() *CampaignStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedCampagignStatus) DeepCopyInto(out *VersionedCampagignStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedCampagignStatus. +func (in *VersionedCampagignStatus) DeepCopy() *VersionedCampagignStatus { + if in == nil { + return nil + } + out := new(VersionedCampagignStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedCampaign) DeepCopyInto(out *VersionedCampaign) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedCampaign. +func (in *VersionedCampaign) DeepCopy() *VersionedCampaign { + if in == nil { + return nil + } + out := new(VersionedCampaign) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VersionedCampaign) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VersionedCampaignList) DeepCopyInto(out *VersionedCampaignList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VersionedCampaign, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VersionedCampaignList. +func (in *VersionedCampaignList) DeepCopy() *VersionedCampaignList { + if in == nil { + return nil + } + out := new(VersionedCampaignList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VersionedCampaignList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/k8s/config/crd/kustomization.yaml b/k8s/config/crd/kustomization.yaml new file mode 100644 index 000000000..2da08424c --- /dev/null +++ b/k8s/config/crd/kustomization.yaml @@ -0,0 +1,36 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/solution.symphony.symphony_versionedsolutions.yaml +- bases/federation.symphony_versionedcatalogs.yaml +- bases/federation.symphony_versionedcatalogs.yaml +- bases/fabric.symphony_versionedtargets.yaml +- bases/workflow.symphony_versionedcampaign1s.yaml +- bases/solution.symphony_versionedinstance1s.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- patches/webhook_in_versionedsolutions.yaml +#- patches/webhook_in_versionedcatalogs.yaml +#- patches/webhook_in_versionedcatalog1s.yaml +#- patches/webhook_in_versionedtarget1s.yaml +#- patches/webhook_in_versionedcampaign1s.yaml +#- patches/webhook_in_versionedinstance1s.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- patches/cainjection_in_versionedsolutions.yaml +#- patches/cainjection_in_versionedcatalogs.yaml +#- patches/cainjection_in_versionedcatalog1s.yaml +#- patches/cainjection_in_versionedtarget1s.yaml +#- patches/cainjection_in_versionedcampaign1s.yaml +#- patches/cainjection_in_versionedinstance1s.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/k8s/config/crd/kustomizeconfig.yaml b/k8s/config/crd/kustomizeconfig.yaml new file mode 100644 index 000000000..ec5c150a9 --- /dev/null +++ b/k8s/config/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/k8s/config/crd/patches/cainjection_in_fabric_versionedtarget1s.yaml b/k8s/config/crd/patches/cainjection_in_fabric_versionedtarget1s.yaml new file mode 100644 index 000000000..fd6413dfd --- /dev/null +++ b/k8s/config/crd/patches/cainjection_in_fabric_versionedtarget1s.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: versionedtarget1s.fabric.symphony diff --git a/k8s/config/crd/patches/cainjection_in_federation_versionedcatalog1s.yaml b/k8s/config/crd/patches/cainjection_in_federation_versionedcatalog1s.yaml new file mode 100644 index 000000000..50fdb6742 --- /dev/null +++ b/k8s/config/crd/patches/cainjection_in_federation_versionedcatalog1s.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: versionedcatalog1s.federation.symphony diff --git a/k8s/config/crd/patches/cainjection_in_federation_versionedcatalogs.yaml b/k8s/config/crd/patches/cainjection_in_federation_versionedcatalogs.yaml new file mode 100644 index 000000000..90ec06fc0 --- /dev/null +++ b/k8s/config/crd/patches/cainjection_in_federation_versionedcatalogs.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: versionedcatalogs.federation.symphony diff --git a/k8s/config/crd/patches/cainjection_in_solution.symphony_versionedsolutions.yaml b/k8s/config/crd/patches/cainjection_in_solution.symphony_versionedsolutions.yaml new file mode 100644 index 000000000..00b03e493 --- /dev/null +++ b/k8s/config/crd/patches/cainjection_in_solution.symphony_versionedsolutions.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: versionedsolutions.solution.symphony.symphony diff --git a/k8s/config/crd/patches/cainjection_in_solution_versionedinstance1s.yaml b/k8s/config/crd/patches/cainjection_in_solution_versionedinstance1s.yaml new file mode 100644 index 000000000..b289c6878 --- /dev/null +++ b/k8s/config/crd/patches/cainjection_in_solution_versionedinstance1s.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: versionedinstance1s.solution.symphony diff --git a/k8s/config/crd/patches/cainjection_in_workflow_versionedcampaign1s.yaml b/k8s/config/crd/patches/cainjection_in_workflow_versionedcampaign1s.yaml new file mode 100644 index 000000000..dca44941a --- /dev/null +++ b/k8s/config/crd/patches/cainjection_in_workflow_versionedcampaign1s.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: versionedcampaign1s.workflow.symphony diff --git a/k8s/config/crd/patches/webhook_in_fabric_versionedtarget1s.yaml b/k8s/config/crd/patches/webhook_in_fabric_versionedtarget1s.yaml new file mode 100644 index 000000000..1a9477f46 --- /dev/null +++ b/k8s/config/crd/patches/webhook_in_fabric_versionedtarget1s.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: versionedtarget1s.fabric.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/crd/patches/webhook_in_federation_versionedcatalog1s.yaml b/k8s/config/crd/patches/webhook_in_federation_versionedcatalog1s.yaml new file mode 100644 index 000000000..a867413b6 --- /dev/null +++ b/k8s/config/crd/patches/webhook_in_federation_versionedcatalog1s.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: versionedcatalog1s.federation.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/crd/patches/webhook_in_federation_versionedcatalogs.yaml b/k8s/config/crd/patches/webhook_in_federation_versionedcatalogs.yaml new file mode 100644 index 000000000..87b1572ff --- /dev/null +++ b/k8s/config/crd/patches/webhook_in_federation_versionedcatalogs.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: versionedcatalogs.federation.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/crd/patches/webhook_in_solution.symphony_versionedsolutions.yaml b/k8s/config/crd/patches/webhook_in_solution.symphony_versionedsolutions.yaml new file mode 100644 index 000000000..3cff27513 --- /dev/null +++ b/k8s/config/crd/patches/webhook_in_solution.symphony_versionedsolutions.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: versionedsolutions.solution.symphony.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/crd/patches/webhook_in_solution_versionedinstance1s.yaml b/k8s/config/crd/patches/webhook_in_solution_versionedinstance1s.yaml new file mode 100644 index 000000000..5292e6f13 --- /dev/null +++ b/k8s/config/crd/patches/webhook_in_solution_versionedinstance1s.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: versionedinstance1s.solution.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/crd/patches/webhook_in_workflow_versionedcampaign1s.yaml b/k8s/config/crd/patches/webhook_in_workflow_versionedcampaign1s.yaml new file mode 100644 index 000000000..025fdbab3 --- /dev/null +++ b/k8s/config/crd/patches/webhook_in_workflow_versionedcampaign1s.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: versionedcampaign1s.workflow.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/oss/crd/bases/fabric.symphony_versionedtargets.yaml b/k8s/config/oss/crd/bases/fabric.symphony_versionedtargets.yaml new file mode 100644 index 000000000..20ea1e87c --- /dev/null +++ b/k8s/config/oss/crd/bases/fabric.symphony_versionedtargets.yaml @@ -0,0 +1,124 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedtargets.fabric.symphony +spec: + group: fabric.symphony + names: + kind: VersionedTarget + listKind: VersionedTargetList + plural: versionedtargets + singular: versionedtarget + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.properties.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: Target is the Schema for the targets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Defines the desired state of VersionedTargetSpec + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + description: VersionedTargetStatus defines the observed state of Target + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + description: 'Important: Run "make" to regenerate code after modifying + this file' + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8s/config/oss/crd/bases/federation.symphony_catalogs.yaml b/k8s/config/oss/crd/bases/federation.symphony_catalogs.yaml index 1133ee0ac..009162c62 100644 --- a/k8s/config/oss/crd/bases/federation.symphony_catalogs.yaml +++ b/k8s/config/oss/crd/bases/federation.symphony_catalogs.yaml @@ -87,15 +87,6 @@ spec: - siteId - type type: object - status: - properties: - properties: - additionalProperties: - type: string - type: object - required: - - properties - type: object type: object served: true storage: true diff --git a/k8s/config/oss/crd/bases/federation.symphony_versionedcatalogs.yaml b/k8s/config/oss/crd/bases/federation.symphony_versionedcatalogs.yaml new file mode 100644 index 000000000..159a752e5 --- /dev/null +++ b/k8s/config/oss/crd/bases/federation.symphony_versionedcatalogs.yaml @@ -0,0 +1,117 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedcatalogs.federation.symphony +spec: + group: federation.symphony + names: + kind: VersionedCatalog + listKind: VersionedCatalogList + plural: versionedcatalogs + singular: versionedcatalog + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedCatalog is the Schema for the catalogs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8s/config/oss/crd/bases/solution.symphony_solutions.yaml b/k8s/config/oss/crd/bases/solution.symphony_solutions.yaml index 8231ad562..1186e4828 100644 --- a/k8s/config/oss/crd/bases/solution.symphony_solutions.yaml +++ b/k8s/config/oss/crd/bases/solution.symphony_solutions.yaml @@ -114,6 +114,8 @@ spec: additionalProperties: type: string type: object + rootResource: + type: string version: description: Defines the version of a particular resource type: string diff --git a/k8s/config/oss/crd/bases/solution.symphony_versionedinstances.yaml b/k8s/config/oss/crd/bases/solution.symphony_versionedinstances.yaml new file mode 100644 index 000000000..a35b4f08d --- /dev/null +++ b/k8s/config/oss/crd/bases/solution.symphony_versionedinstances.yaml @@ -0,0 +1,117 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedinstances.solution.symphony +spec: + group: solution.symphony + names: + kind: VersionedInstance + listKind: VersionedInstanceList + plural: versionedinstances + singular: versionedinstance + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedInstance1 is the Schema for the versionedinstances API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8s/config/oss/crd/bases/solution.symphony_versionedsolutions.yaml b/k8s/config/oss/crd/bases/solution.symphony_versionedsolutions.yaml new file mode 100644 index 000000000..2d6eca287 --- /dev/null +++ b/k8s/config/oss/crd/bases/solution.symphony_versionedsolutions.yaml @@ -0,0 +1,117 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedsolutions.solution.symphony +spec: + group: solution.symphony + names: + kind: VersionedSolution + listKind: VersionedSolutionList + plural: versionedsolutions + singular: versionedsolution + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedSolution is the Schema for the VersionedSolution API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8s/config/oss/crd/bases/workflow.symphony_versionedcampaigns.yaml b/k8s/config/oss/crd/bases/workflow.symphony_versionedcampaigns.yaml new file mode 100644 index 000000000..a4efe56b4 --- /dev/null +++ b/k8s/config/oss/crd/bases/workflow.symphony_versionedcampaigns.yaml @@ -0,0 +1,115 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedcampaigns.workflow.symphony +spec: + group: workflow.symphony + names: + kind: VersionedCampaign + listKind: VersionedCampaignList + plural: versionedcampaigns + singular: versionedcampaign + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedCampaign is the Schema for the campaigns API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true diff --git a/k8s/config/oss/crd/kustomization.yaml b/k8s/config/oss/crd/kustomization.yaml index ea55942c1..878cdca63 100644 --- a/k8s/config/oss/crd/kustomization.yaml +++ b/k8s/config/oss/crd/kustomization.yaml @@ -20,6 +20,11 @@ resources: - bases/fabric.symphony_devices.yaml - bases/federation.symphony_sites.yaml - bases/federation.symphony_catalogs.yaml +- bases/federation.symphony_versionedcatalogs.yaml +- bases/fabric.symphony_versionedtargets.yaml +- bases/workflow.symphony_versionedcampaigns.yaml +- bases/solution.symphony_versionedinstances.yaml +- bases/solution.symphony_versionedsolutions.yaml #+kubebuilder:scaffold:crdkustomizeresource # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. diff --git a/k8s/config/oss/rbac/role.yaml b/k8s/config/oss/rbac/role.yaml index e4801774a..63aded7c8 100644 --- a/k8s/config/oss/rbac/role.yaml +++ b/k8s/config/oss/rbac/role.yaml @@ -373,4 +373,134 @@ rules: verbs: - get - patch + - update +- apiGroups: + - workflow.symphony + resources: + - versionedcampaigns + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - workflow.symphony + resources: + - versionedcampaigns/finalizers + verbs: + - update +- apiGroups: + - workflow.symphony + resources: + - versionedcampaigns/status + verbs: + - get + - patch + - update +- apiGroups: + - solution.symphony + resources: + - versionedinstances + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - versionedinstances/finalizers + verbs: + - update +- apiGroups: + - solution.symphony + resources: + - versionedinstances/status + verbs: + - get + - patch + - update +- apiGroups: + - solution.symphony + resources: + - versionedsolutions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - versionedsolutions/finalizers + verbs: + - update +- apiGroups: + - solution.symphony + resources: + - versionedsolutions/status + verbs: + - get + - patch + - update +- apiGroups: + - fabric.symphony + resources: + - versionedtargets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fabric.symphony + resources: + - versionedtargets/finalizers + verbs: + - update +- apiGroups: + - fabric.symphony + resources: + - versionedtargets/status + verbs: + - get + - patch + - update +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs/finalizers + verbs: + - update +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs/status + verbs: + - get + - patch - update \ No newline at end of file diff --git a/k8s/config/rbac/fabric_versionedtarget1_editor_role.yaml b/k8s/config/rbac/fabric_versionedtarget1_editor_role.yaml new file mode 100644 index 000000000..5a92441b5 --- /dev/null +++ b/k8s/config/rbac/fabric_versionedtarget1_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit versionedtarget1s. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedtarget1-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedtarget1-editor-role +rules: +- apiGroups: + - fabric.symphony + resources: + - versionedtarget1s + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fabric.symphony + resources: + - versionedtarget1s/status + verbs: + - get diff --git a/k8s/config/rbac/fabric_versionedtarget1_viewer_role.yaml b/k8s/config/rbac/fabric_versionedtarget1_viewer_role.yaml new file mode 100644 index 000000000..7961be424 --- /dev/null +++ b/k8s/config/rbac/fabric_versionedtarget1_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view versionedtarget1s. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedtarget1-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedtarget1-viewer-role +rules: +- apiGroups: + - fabric.symphony + resources: + - versionedtarget1s + verbs: + - get + - list + - watch +- apiGroups: + - fabric.symphony + resources: + - versionedtarget1s/status + verbs: + - get diff --git a/k8s/config/rbac/federation_versionedcatalog1_editor_role.yaml b/k8s/config/rbac/federation_versionedcatalog1_editor_role.yaml new file mode 100644 index 000000000..5893634a0 --- /dev/null +++ b/k8s/config/rbac/federation_versionedcatalog1_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit versionedcatalog1s. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedcatalog1-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedcatalog1-editor-role +rules: +- apiGroups: + - federation.symphony + resources: + - versionedcatalog1s + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - federation.symphony + resources: + - versionedcatalog1s/status + verbs: + - get diff --git a/k8s/config/rbac/federation_versionedcatalog1_viewer_role.yaml b/k8s/config/rbac/federation_versionedcatalog1_viewer_role.yaml new file mode 100644 index 000000000..70373c793 --- /dev/null +++ b/k8s/config/rbac/federation_versionedcatalog1_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view versionedcatalog1s. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedcatalog1-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedcatalog1-viewer-role +rules: +- apiGroups: + - federation.symphony + resources: + - versionedcatalog1s + verbs: + - get + - list + - watch +- apiGroups: + - federation.symphony + resources: + - versionedcatalog1s/status + verbs: + - get diff --git a/k8s/config/rbac/federation_versionedcatalog_editor_role.yaml b/k8s/config/rbac/federation_versionedcatalog_editor_role.yaml new file mode 100644 index 000000000..a6a08a946 --- /dev/null +++ b/k8s/config/rbac/federation_versionedcatalog_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit versionedcatalogs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedcatalog-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedcatalog-editor-role +rules: +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs/status + verbs: + - get diff --git a/k8s/config/rbac/federation_versionedcatalog_viewer_role.yaml b/k8s/config/rbac/federation_versionedcatalog_viewer_role.yaml new file mode 100644 index 000000000..5549ebffa --- /dev/null +++ b/k8s/config/rbac/federation_versionedcatalog_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view versionedcatalogs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedcatalog-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedcatalog-viewer-role +rules: +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs + verbs: + - get + - list + - watch +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs/status + verbs: + - get diff --git a/k8s/config/rbac/solution.symphony_versionedsolution_editor_role.yaml b/k8s/config/rbac/solution.symphony_versionedsolution_editor_role.yaml new file mode 100644 index 000000000..373d693e6 --- /dev/null +++ b/k8s/config/rbac/solution.symphony_versionedsolution_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit versionedsolutions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedsolution-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedsolution-editor-role +rules: +- apiGroups: + - solution.symphony.symphony + resources: + - versionedsolutions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony.symphony + resources: + - versionedsolutions/status + verbs: + - get diff --git a/k8s/config/rbac/solution.symphony_versionedsolution_viewer_role.yaml b/k8s/config/rbac/solution.symphony_versionedsolution_viewer_role.yaml new file mode 100644 index 000000000..fd4beebcb --- /dev/null +++ b/k8s/config/rbac/solution.symphony_versionedsolution_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view versionedsolutions. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedsolution-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedsolution-viewer-role +rules: +- apiGroups: + - solution.symphony.symphony + resources: + - versionedsolutions + verbs: + - get + - list + - watch +- apiGroups: + - solution.symphony.symphony + resources: + - versionedsolutions/status + verbs: + - get diff --git a/k8s/config/rbac/solution_versionedinstance1_editor_role.yaml b/k8s/config/rbac/solution_versionedinstance1_editor_role.yaml new file mode 100644 index 000000000..c3b66a78b --- /dev/null +++ b/k8s/config/rbac/solution_versionedinstance1_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit versionedinstance1s. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedinstance1-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedinstance1-editor-role +rules: +- apiGroups: + - solution.symphony + resources: + - versionedinstance1s + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - versionedinstance1s/status + verbs: + - get diff --git a/k8s/config/rbac/solution_versionedinstance1_viewer_role.yaml b/k8s/config/rbac/solution_versionedinstance1_viewer_role.yaml new file mode 100644 index 000000000..697c8d640 --- /dev/null +++ b/k8s/config/rbac/solution_versionedinstance1_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view versionedinstance1s. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedinstance1-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedinstance1-viewer-role +rules: +- apiGroups: + - solution.symphony + resources: + - versionedinstance1s + verbs: + - get + - list + - watch +- apiGroups: + - solution.symphony + resources: + - versionedinstance1s/status + verbs: + - get diff --git a/k8s/config/rbac/workflow_versionedcampaign1_editor_role.yaml b/k8s/config/rbac/workflow_versionedcampaign1_editor_role.yaml new file mode 100644 index 000000000..54c15a5d0 --- /dev/null +++ b/k8s/config/rbac/workflow_versionedcampaign1_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit versionedcampaign1s. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedcampaign1-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedcampaign1-editor-role +rules: +- apiGroups: + - workflow.symphony + resources: + - versionedcampaign1s + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - workflow.symphony + resources: + - versionedcampaign1s/status + verbs: + - get diff --git a/k8s/config/rbac/workflow_versionedcampaign1_viewer_role.yaml b/k8s/config/rbac/workflow_versionedcampaign1_viewer_role.yaml new file mode 100644 index 000000000..25cbef42c --- /dev/null +++ b/k8s/config/rbac/workflow_versionedcampaign1_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view versionedcampaign1s. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: versionedcampaign1-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: versionedcampaign1-viewer-role +rules: +- apiGroups: + - workflow.symphony + resources: + - versionedcampaign1s + verbs: + - get + - list + - watch +- apiGroups: + - workflow.symphony + resources: + - versionedcampaign1s/status + verbs: + - get diff --git a/k8s/config/samples/fabric_v1_versionedtarget1.yaml b/k8s/config/samples/fabric_v1_versionedtarget1.yaml new file mode 100644 index 000000000..7527ea637 --- /dev/null +++ b/k8s/config/samples/fabric_v1_versionedtarget1.yaml @@ -0,0 +1,12 @@ +apiVersion: fabric.symphony/v1 +kind: VersionedTarget1 +metadata: + labels: + app.kubernetes.io/name: versionedtarget1 + app.kubernetes.io/instance: versionedtarget1-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: versionedtarget1-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/samples/federation_v1_versionedcatalog.yaml b/k8s/config/samples/federation_v1_versionedcatalog.yaml new file mode 100644 index 000000000..a82118cb3 --- /dev/null +++ b/k8s/config/samples/federation_v1_versionedcatalog.yaml @@ -0,0 +1,12 @@ +apiVersion: federation.symphony/v1 +kind: VersionedCatalog +metadata: + labels: + app.kubernetes.io/name: versionedcatalog + app.kubernetes.io/instance: versionedcatalog-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: versionedcatalog-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/samples/federation_v1_versionedcatalog1.yaml b/k8s/config/samples/federation_v1_versionedcatalog1.yaml new file mode 100644 index 000000000..4f4fcc127 --- /dev/null +++ b/k8s/config/samples/federation_v1_versionedcatalog1.yaml @@ -0,0 +1,12 @@ +apiVersion: federation.symphony/v1 +kind: VersionedCatalog1 +metadata: + labels: + app.kubernetes.io/name: versionedcatalog1 + app.kubernetes.io/instance: versionedcatalog1-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: versionedcatalog1-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/samples/solution.symphony_v1alpha1_versionedsolution.yaml b/k8s/config/samples/solution.symphony_v1alpha1_versionedsolution.yaml new file mode 100644 index 000000000..edf4759d1 --- /dev/null +++ b/k8s/config/samples/solution.symphony_v1alpha1_versionedsolution.yaml @@ -0,0 +1,12 @@ +apiVersion: solution.symphony.symphony/v1alpha1 +kind: Versionedsolution +metadata: + labels: + app.kubernetes.io/name: versionedsolution + app.kubernetes.io/instance: versionedsolution-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: versionedsolution-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/samples/solution_v1_versionedinstance1.yaml b/k8s/config/samples/solution_v1_versionedinstance1.yaml new file mode 100644 index 000000000..a622e5e80 --- /dev/null +++ b/k8s/config/samples/solution_v1_versionedinstance1.yaml @@ -0,0 +1,12 @@ +apiVersion: solution.symphony/v1 +kind: VersionedInstance1 +metadata: + labels: + app.kubernetes.io/name: versionedinstance1 + app.kubernetes.io/instance: versionedinstance1-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: versionedinstance1-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/samples/workflow_v1_versionedcampaign1.yaml b/k8s/config/samples/workflow_v1_versionedcampaign1.yaml new file mode 100644 index 000000000..19ae40762 --- /dev/null +++ b/k8s/config/samples/workflow_v1_versionedcampaign1.yaml @@ -0,0 +1,12 @@ +apiVersion: workflow.symphony/v1 +kind: VersionedCampaign1 +metadata: + labels: + app.kubernetes.io/name: versionedcampaign1 + app.kubernetes.io/instance: versionedcampaign1-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: versionedcampaign1-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/controllers/fabric/target_controller_test.go b/k8s/controllers/fabric/target_controller_test.go index dfc82add7..8df26aa22 100644 --- a/k8s/controllers/fabric/target_controller_test.go +++ b/k8s/controllers/fabric/target_controller_test.go @@ -6,31 +6,17 @@ package fabric +/* import ( - "context" - "errors" - . "gopls-workspace/testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - symphonyv1 "gopls-workspace/apis/fabric/v1" - - apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" - - "gopls-workspace/utils" - - "github.com/stretchr/testify/mock" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - kerrors "k8s.io/apimachinery/pkg/api/errors" ) - +*/ // These tests use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var _ = Describe("Target controller", Ordered, func() { +/* var _ = Describe("Target controller", Ordered, func() { var apiClient *MockApiClient var kubeClient client.Client var controller *TargetReconciler @@ -238,3 +224,4 @@ var _ = Describe("Target controller", Ordered, func() { }) }) }) +*/ diff --git a/k8s/controllers/fabric/versionedtarget_controller.go b/k8s/controllers/fabric/versionedtarget_controller.go new file mode 100644 index 000000000..ff99bbea2 --- /dev/null +++ b/k8s/controllers/fabric/versionedtarget_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package fabric + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + fabricv1 "gopls-workspace/apis/fabric/v1" +) + +// VersionedTarget1Reconciler reconciles a VersionedTarget1 object +type VersionedTargetReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=fabric.symphony,resources=versionedtarget1s,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=fabric.symphony,resources=versionedtarget1s/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=fabric.symphony,resources=versionedtarget1s/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the VersionedTarget1 object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *VersionedTargetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VersionedTargetReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&fabricv1.VersionedTarget{}). + Complete(r) +} diff --git a/k8s/controllers/federation/suite_test.go b/k8s/controllers/federation/suite_test.go new file mode 100644 index 000000000..9ed9e692a --- /dev/null +++ b/k8s/controllers/federation/suite_test.go @@ -0,0 +1,70 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package federation + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + federationv1 "gopls-workspace/apis/federation/v1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = federationv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/k8s/controllers/federation/versionedcatalog_controller.go b/k8s/controllers/federation/versionedcatalog_controller.go new file mode 100644 index 000000000..9062ba583 --- /dev/null +++ b/k8s/controllers/federation/versionedcatalog_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package federation + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + federationv1 "gopls-workspace/apis/federation/v1" +) + +// VersionedCatalog1Reconciler reconciles a VersionedCatalog1 object +type VersionedCatalogReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=federation.symphony,resources=versionedcatalog1s,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=federation.symphony,resources=versionedcatalog1s/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=federation.symphony,resources=versionedcatalog1s/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the VersionedCatalog1 object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *VersionedCatalogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VersionedCatalogReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&federationv1.VersionedCatalog{}). + Complete(r) +} diff --git a/k8s/controllers/solution/instance_controller.go b/k8s/controllers/solution/instance_controller.go index e4a010d08..e0efac625 100644 --- a/k8s/controllers/solution/instance_controller.go +++ b/k8s/controllers/solution/instance_controller.go @@ -138,7 +138,17 @@ func (r *InstanceReconciler) deploymentBuilder(ctx context.Context, object recon TargetCandidates: []fabric_v1.Target{}, } - if err := r.Get(ctx, types.NamespacedName{Name: instance.Spec.Solution, Namespace: instance.Namespace}, &deploymentResources.Solution); err != nil { + log.Info(fmt.Sprintf("Instance controller>>>>>>>>>>>>>>>>>>>>> v2v2: try to get solution %v", instance.Spec.Solution)) + + // Get solution + solution, err := r.ApiClient.GetSolution(ctx, instance.Spec.Solution, instance.Namespace) + //api_utils.GetSolution(ctx, "http://symphony-service:8080/v1alpha2/", instance.Spec.Solution, "admin", "", instance.Namespace) + if err != nil { + log.Error(v1alpha2.NewCOAError(err, "failed to get solution from symphony", v1alpha2.SolutionGetFailed), "proceed with no solution found") + } + + log.Info(fmt.Sprintf("Instance controller>>>>>>>>>>>>>>>>>>>>>>>: try to get solution response %v", solution.ObjectMeta.Name)) + if err := r.Get(ctx, types.NamespacedName{Name: solution.ObjectMeta.Name, Namespace: instance.Namespace}, &deploymentResources.Solution); err != nil { log.Error(v1alpha2.NewCOAError(err, "failed to get solution", v1alpha2.SolutionGetFailed), "proceed with no solution found") } // Get targets @@ -152,7 +162,8 @@ func (r *InstanceReconciler) deploymentBuilder(ctx context.Context, object recon log.Error(v1alpha2.NewCOAError(nil, "no target candidates found", v1alpha2.TargetCandidatesNotFound), "proceed with no target candidates found") } - deployment, err := utils.CreateSymphonyDeployment(ctx, *instance, deploymentResources.Solution, deploymentResources.TargetCandidates, object.GetNamespace()) + deployment, err = utils.CreateSymphonyDeployment(ctx, *instance, deploymentResources.Solution, deploymentResources.TargetCandidates, object.GetNamespace()) + if err != nil { return nil, err } @@ -235,18 +246,66 @@ func (r *InstanceReconciler) handleSolution(obj client.Object) []ctrl.Request { ret := make([]ctrl.Request, 0) solObj := obj.(*solution_v1.Solution) var instances solution_v1.InstanceList + + labels := solObj.ObjectMeta.Labels + resourceName := labels["rootResource"] + version := labels["version"] + + var solutionName string + if resourceName == "" || version == "" { + solutionName = solObj.Name + } else { + solutionName = resourceName + ":" + version + } + + log.Log.Info(fmt.Sprintf("Instance handlesolution >>>>>>>> start %s", solutionName)) + options := []client.ListOption{ client.InNamespace(solObj.Namespace), - client.MatchingFields{"spec.solution": solObj.Name}, + client.MatchingFields{"spec.solution": solutionName}, } error := r.List(context.Background(), &instances, options...) if error != nil { log.Log.Error(error, "Failed to list instances") return ret } + log.Log.Info(fmt.Sprintf("Instance handlesolution >>>>>>>> instances count %d", len(instances.Items))) + log.Log.Info(fmt.Sprintf("Instance handlesolution >>>>>>>> label %s", labels["tag"])) + + if labels["tag"] == "latest" { + var instancesWithLatest solution_v1.InstanceList + solutionName = resourceName + ":" + "latest" + options := []client.ListOption{ + client.InNamespace(solObj.Namespace), + client.MatchingFields{"spec.solution": solutionName}, + } + + error := r.List(context.Background(), &instancesWithLatest, options...) + if error != nil { + log.Log.Error(error, "Failed to list instances") + return ret + } + + instances.Items = append(instances.Items, instancesWithLatest.Items...) + log.Log.Info(fmt.Sprintf("Instance handlesolution >>>>>>>>222 instances count with latest %d", len(instances.Items))) + } updatedInstanceNames := make([]string, 0) for _, instance := range instances.Items { + var interval time.Duration = 30 + if instance.Spec.ReconciliationPolicy != nil && instance.Spec.ReconciliationPolicy.Interval != nil { + parsedInterval, err := time.ParseDuration(*instance.Spec.ReconciliationPolicy.Interval) + if err != nil { + log.Log.Error(err, "Instance handlesolution parse interval >>>>>>>> ") + parsedInterval = 30 + } + interval = parsedInterval + } + + if instance.Spec.ReconciliationPolicy != nil && instance.Spec.ReconciliationPolicy.State.IsInActive() || interval == 0 { + log.Log.Info(fmt.Sprintf("Instance handlesolution >>>>>>>> inactive no watch %s", instance.ObjectMeta.Name)) + continue + } ret = append(ret, ctrl.Request{ NamespacedName: types.NamespacedName{ Name: instance.Name, diff --git a/k8s/controllers/solution/instance_controller_test.go b/k8s/controllers/solution/instance_controller_test.go index 82055690f..cf96b7fc1 100644 --- a/k8s/controllers/solution/instance_controller_test.go +++ b/k8s/controllers/solution/instance_controller_test.go @@ -6,18 +6,20 @@ package solution +/* import ( "context" "errors" fabricv1 "gopls-workspace/apis/fabric/v1" solutionv1 "gopls-workspace/apis/solution/v1" + . "gopls-workspace/testing" "gopls-workspace/utils" + "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stretchr/testify/mock" - kerrors "k8s.io/apimachinery/pkg/api/errors" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -67,6 +69,11 @@ var _ = Describe("Instance controller", Ordered, func() { solution = &solutionv1.Solution{} Expect(kubeClient.Get(ctx, DefaultSolutionNamespacedName, solution)).To(Succeed()) + + By("mocking the get solution call") + solution := &model.SolutionState{} + solution.ObjectMeta.Name = "test-solution" + apiClient.On("GetSolution", mock.Anything, mock.Anything, mock.Anything).Return(solution, nil) }) Describe("Reconcile", func() { @@ -285,3 +292,4 @@ var _ = Describe("Instance controller", Ordered, func() { }) }) }) +*/ diff --git a/k8s/controllers/solution/solution_controller.go b/k8s/controllers/solution/solution_controller.go index 7d26d33a9..a2a3581a9 100644 --- a/k8s/controllers/solution/solution_controller.go +++ b/k8s/controllers/solution/solution_controller.go @@ -8,13 +8,19 @@ package solution import ( "context" + "encoding/json" + "fmt" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" solutionv1 "gopls-workspace/apis/solution/v1" + + api_utils "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/utils" + "sigs.k8s.io/controller-runtime/pkg/predicate" ) // SolutionReconciler reconciles a Solution object @@ -37,9 +43,70 @@ type SolutionReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile func (r *SolutionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + log := ctrllog.FromContext(ctx) + log.Info("Reconcile Solution") + + myFinalizerName := "solution.solution.symphony/finalizer" + + // Get instance + solution := &solutionv1.Solution{} + if err := r.Client.Get(ctx, req.NamespacedName, solution); err != nil { + log.Error(err, "unable to fetch Solution object") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if solution.Status.Properties == nil { + solution.Status.Properties = make(map[string]string) + } + + version := solution.Spec.Version + name := solution.Spec.RootResource + solutionName := name + ":" + version + jData, _ := json.Marshal(solution) + log.Info(fmt.Sprintf("Reconcile Solution: %v %v", solutionName, version)) + // log.Info(fmt.Sprintf("Reconcile Solution jdata: %v", solution)) + + log.Info(fmt.Sprintf("Solution.Labels: %v", solution.Labels["version"])) + + if solution.ObjectMeta.DeletionTimestamp.IsZero() { // update + if !controllerutil.ContainsFinalizer(solution, myFinalizerName) { + log.Info("Add Solution finalizer") + controllerutil.AddFinalizer(solution, myFinalizerName) + if err := r.Client.Update(ctx, solution); err != nil { + return ctrl.Result{}, err + } + } + + log.Info("Solution update") + _, exists := solution.Labels["version"] + log.Info(fmt.Sprintf("Solution update: exists version tag, %v", exists)) + if !exists && version != "" && name != "" { + log.Info(">>>>>>>>>>>>>>>>>>>>>>>>>> Call API to upsert solution") + err := api_utils.UpsertSolution(ctx, "http://symphony-service:8080/v1alpha2/", solutionName, "admin", "", jData, req.Namespace) + if err != nil { + log.Error(err, "Upsert solution failed") + return ctrl.Result{}, nil + } + } + } else { // delete + value, exists := solution.Labels["tag"] + log.Info(fmt.Sprintf("Solution update: %v, %v", value, exists)) + + if exists && value == "latest" { + log.Info(">>>>>>>>>>>>>>>>>>> Call API to delete solution") + err := api_utils.DeleteSolution(ctx, "http://symphony-service:8080/v1alpha2/", solutionName, "admin", "", req.Namespace) + if err != nil { + log.Error(err, "Delete solution failed") + return ctrl.Result{}, nil + } + } - // TODO(user): your logic here + log.Info("Remove finalizer") + controllerutil.RemoveFinalizer(solution, myFinalizerName) + if err := r.Client.Update(ctx, solution); err != nil { + return ctrl.Result{}, err + } + } return ctrl.Result{}, nil } @@ -47,6 +114,7 @@ func (r *SolutionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // SetupWithManager sets up the controller with the Manager. func (r *SolutionReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). + WithEventFilter(predicate.GenerationChangedPredicate{}). For(&solutionv1.Solution{}). Complete(r) } diff --git a/k8s/controllers/solution/suite_test.go b/k8s/controllers/solution/suite_test.go index 1a4933b6f..a384ba2d6 100644 --- a/k8s/controllers/solution/suite_test.go +++ b/k8s/controllers/solution/suite_test.go @@ -40,9 +40,9 @@ import ( // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) + //RegisterFailHandler(Fail) - RunGinkgoSpecs(t, "Controller Suite") + //RunGinkgoSpecs(t, "Controller Suite") } func TestUnmarshalSolution(t *testing.T) { diff --git a/k8s/controllers/solution/versionedinstance_controller.go b/k8s/controllers/solution/versionedinstance_controller.go new file mode 100644 index 000000000..abfad4889 --- /dev/null +++ b/k8s/controllers/solution/versionedinstance_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package solution + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + solutionv1 "gopls-workspace/apis/solution/v1" +) + +// VersionedInstance1Reconciler reconciles a VersionedInstance1 object +type VersionedInstanceReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=solution.symphony,resources=versionedinstance1s,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=solution.symphony,resources=versionedinstance1s/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=solution.symphony,resources=versionedinstance1s/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the VersionedInstance1 object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *VersionedInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VersionedInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&solutionv1.VersionedInstance{}). + Complete(r) +} diff --git a/k8s/controllers/solution/versionedsolution_controller.go b/k8s/controllers/solution/versionedsolution_controller.go new file mode 100644 index 000000000..b5704dfe7 --- /dev/null +++ b/k8s/controllers/solution/versionedsolution_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package solution + +import ( + "context" + + solutionv1 "gopls-workspace/apis/solution/v1" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// VersionedSolutionReconciler reconciles a Versionedsolution object +type VersionedSolutionReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=solution.symphony,resources=versionedsolutions,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=solution.symphony,resources=versionedsolutions/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=solution.symphony,resources=versionedsolutions/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Versionedsolution object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *VersionedSolutionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VersionedSolutionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&solutionv1.VersionedSolution{}). + Complete(r) +} diff --git a/k8s/controllers/workflow/suite_test.go b/k8s/controllers/workflow/suite_test.go new file mode 100644 index 000000000..90256d241 --- /dev/null +++ b/k8s/controllers/workflow/suite_test.go @@ -0,0 +1,70 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package workflow + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + workflowv1 "gopls-workspace/apis/workflow/v1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = workflowv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/k8s/controllers/workflow/versionedcampaign_controller.go b/k8s/controllers/workflow/versionedcampaign_controller.go new file mode 100644 index 000000000..52aed035f --- /dev/null +++ b/k8s/controllers/workflow/versionedcampaign_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package workflow + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + workflowv1 "gopls-workspace/apis/workflow/v1" +) + +// VersionedCampaign1Reconciler reconciles a VersionedCampaign1 object +type VersionedCampaignReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=workflow.symphony,resources=versionedcampaign1s,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=workflow.symphony,resources=versionedcampaign1s/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=workflow.symphony,resources=versionedcampaign1s/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the VersionedCampaign1 object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *VersionedCampaignReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VersionedCampaignReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&workflowv1.VersionedCampaign{}). + Complete(r) +} diff --git a/k8s/main.go b/k8s/main.go index 07a805471..22ab37826 100644 --- a/k8s/main.go +++ b/k8s/main.go @@ -297,6 +297,34 @@ func main() { os.Exit(1) } } + if err = (&federationcontrollers.VersionedCatalogReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "VersionedCatalog1") + os.Exit(1) + } + if err = (&fabriccontrollers.VersionedTargetReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "VersionedTarget1") + os.Exit(1) + } + if err = (&workflowcontrollers.VersionedCampaignReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "VersionedCampaign1") + os.Exit(1) + } + if err = (&solutioncontrollers.VersionedInstanceReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "VersionedInstance1") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/k8s/reconcilers/deployment.go b/k8s/reconcilers/deployment.go index f53dc691f..ad5d84db5 100644 --- a/k8s/reconcilers/deployment.go +++ b/k8s/reconcilers/deployment.go @@ -122,7 +122,6 @@ func (r *DeploymentReconciler) deriveReconcileInterval(log logr.Logger, target R // only reconcile once reconciliationInterval = 0 } - } // no reconciliationPolicy configured or reconciliationPolicy.state is invalid, use default reconciliation interval: r.reconciliationInterval return diff --git a/k8s/testing/mocks.go b/k8s/testing/mocks.go index aa99789f5..4f0426bf4 100644 --- a/k8s/testing/mocks.go +++ b/k8s/testing/mocks.go @@ -269,6 +269,21 @@ func (c *MockApiClient) QueueJob(ctx context.Context, id string, scope string, i panic("implement me") } +// GetInstance implements ApiClient. +func (*MockApiClient) GetInstance(ctx context.Context, instance string, namespace string) (model.InstanceState, error) { + panic("unimplemented") +} + +// GetSolution implements ApiClient. +func (*MockApiClient) GetSolution(ctx context.Context, solution string, namespace string) (model.SolutionState, error) { + panic("unimplemented") +} + +// GetTarget implements ApiClient. +func (*MockApiClient) GetTarget(ctx context.Context, target string, namespace string) (model.TargetState, error) { + panic("unimplemented") +} + func CreateSimpleDeploymentBuilder() func(ctx context.Context, object reconcilers.Reconcilable) (*model.DeploymentSpec, error) { return func(ctx context.Context, object reconcilers.Reconcilable) (*model.DeploymentSpec, error) { return &model.DeploymentSpec{ diff --git a/k8s/utils/symphony-api.go b/k8s/utils/symphony-api.go index 2ef63a2c3..d21b96e82 100644 --- a/k8s/utils/symphony-api.go +++ b/k8s/utils/symphony-api.go @@ -31,6 +31,7 @@ type ( ApiClient interface { api_utils.SummaryGetter api_utils.Dispatcher + api_utils.Getter } DeploymentResources struct { diff --git a/packages/helm/symphony/templates/symphony.yaml b/packages/helm/symphony/templates/symphony.yaml index 34b0c0622..e8edff06c 100644 --- a/packages/helm/symphony/templates/symphony.yaml +++ b/packages/helm/symphony/templates/symphony.yaml @@ -279,15 +279,6 @@ spec: - siteId - type type: object - status: - properties: - properties: - additionalProperties: - type: string - type: object - required: - - properties - type: object type: object served: true storage: true @@ -1213,6 +1204,8 @@ spec: additionalProperties: type: string type: object + rootResource: + type: string version: description: Defines the version of a particular resource type: string @@ -1493,145 +1486,735 @@ spec: subresources: status: {} --- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: '{{ include "symphony.fullname" . }}-controller-manager' - namespace: '{{ .Release.Namespace }}' ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: - name: '{{ include "symphony.fullname" . }}-leader-election-role' - namespace: '{{ .Release.Namespace }}' -rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedcampaigns.workflow.symphony +spec: + group: workflow.symphony + names: + kind: VersionedCampaign + listKind: VersionedCampaignList + plural: versionedcampaigns + singular: versionedcampaign + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedCampaign is the Schema for the campaigns API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true --- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 creationTimestamp: null - name: '{{ include "symphony.fullname" . }}-manager-role' -rules: -- apiGroups: - - ai.symphony - resources: - - models - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - ai.symphony - resources: - - models/finalizers - verbs: - - update -- apiGroups: - - ai.symphony - resources: - - models/status - verbs: - - get - - patch - - update -- apiGroups: - - ai.symphony - resources: - - skillpackages - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - ai.symphony - resources: - - skillpackages/finalizers - verbs: - - update -- apiGroups: - - ai.symphony - resources: - - skillpackages/status - verbs: - - get - - patch - - update -- apiGroups: - - ai.symphony - resources: - - skills - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - ai.symphony - resources: - - skills/finalizers - verbs: - - update -- apiGroups: - - ai.symphony - resources: - - skills/status - verbs: - - get - - patch - - update -- apiGroups: - - fabric.symphony - resources: - - devices - verbs: - - create - - delete - - get - - list - - patch - - update + name: versionedcatalogs.federation.symphony +spec: + group: federation.symphony + names: + kind: VersionedCatalog + listKind: VersionedCatalogList + plural: versionedcatalogs + singular: versionedcatalog + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedCatalog is the Schema for the catalogs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedinstances.solution.symphony +spec: + group: solution.symphony + names: + kind: VersionedInstance + listKind: VersionedInstanceList + plural: versionedinstances + singular: versionedinstance + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedInstance1 is the Schema for the versionedinstances API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedsolutions.solution.symphony +spec: + group: solution.symphony + names: + kind: VersionedSolution + listKind: VersionedSolutionList + plural: versionedsolutions + singular: versionedsolution + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedSolution is the Schema for the VersionedSolution API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedtargets.fabric.symphony +spec: + group: fabric.symphony + names: + kind: VersionedTarget + listKind: VersionedTargetList + plural: versionedtargets + singular: versionedtarget + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.properties.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: Target is the Schema for the targets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Defines the desired state of VersionedTargetSpec + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + description: VersionedTargetStatus defines the observed state of Target + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + description: 'Important: Run "make" to regenerate code after modifying + this file' + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: '{{ include "symphony.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: '{{ include "symphony.fullname" . }}-leader-election-role' + namespace: '{{ .Release.Namespace }}' +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: '{{ include "symphony.fullname" . }}-manager-role' +rules: +- apiGroups: + - ai.symphony + resources: + - models + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ai.symphony + resources: + - models/finalizers + verbs: + - update +- apiGroups: + - ai.symphony + resources: + - models/status + verbs: + - get + - patch + - update +- apiGroups: + - ai.symphony + resources: + - skillpackages + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ai.symphony + resources: + - skillpackages/finalizers + verbs: + - update +- apiGroups: + - ai.symphony + resources: + - skillpackages/status + verbs: + - get + - patch + - update +- apiGroups: + - ai.symphony + resources: + - skills + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ai.symphony + resources: + - skills/finalizers + verbs: + - update +- apiGroups: + - ai.symphony + resources: + - skills/status + verbs: + - get + - patch + - update +- apiGroups: + - fabric.symphony + resources: + - devices + verbs: + - create + - delete + - get + - list + - patch + - update - watch - apiGroups: - fabric.symphony @@ -1907,6 +2490,136 @@ rules: - get - patch - update +- apiGroups: + - workflow.symphony + resources: + - versionedcampaigns + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - workflow.symphony + resources: + - versionedcampaigns/finalizers + verbs: + - update +- apiGroups: + - workflow.symphony + resources: + - versionedcampaigns/status + verbs: + - get + - patch + - update +- apiGroups: + - solution.symphony + resources: + - versionedinstances + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - versionedinstances/finalizers + verbs: + - update +- apiGroups: + - solution.symphony + resources: + - versionedinstances/status + verbs: + - get + - patch + - update +- apiGroups: + - solution.symphony + resources: + - versionedsolutions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - versionedsolutions/finalizers + verbs: + - update +- apiGroups: + - solution.symphony + resources: + - versionedsolutions/status + verbs: + - get + - patch + - update +- apiGroups: + - fabric.symphony + resources: + - versionedtargets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fabric.symphony + resources: + - versionedtargets/finalizers + verbs: + - update +- apiGroups: + - fabric.symphony + resources: + - versionedtargets/status + verbs: + - get + - patch + - update +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs/finalizers + verbs: + - update +- apiGroups: + - federation.symphony + resources: + - versionedcatalogs/status + verbs: + - get + - patch + - update --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole