Implement Application CR and CR controller.
Call Application RESTful API to add rules for Application deployment.
Handle missing errors.
Signed-off-by: Le Yao <le.yao@intel.com>
Change-Id: I425f431bea20f372dc95cde1439259ad29e93773
- group: batch
kind: CNFService
version: v1alpha1
+- group: batch
+ kind: SdewanApplication
+ version: v1alpha1
version: "2"
--- /dev/null
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// SdewanApplicationSpec defines the desired state of SdewanApplication
+type SdewanApplicationSpec struct {
+ PodSelector *metav1.LabelSelector `json:"podSelector,omitempty"`
+ AppNamespace string `json:"appNamespace,omitempty"`
+}
+
+type ApplicationInfo struct {
+ IpList string
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// SdewanApplication is the Schema for the sdewanapplications API
+type SdewanApplication struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec SdewanApplicationSpec `json:"spec,omitempty"`
+ Status SdewanStatus `json:"status,omitempty"`
+ AppInfo ApplicationInfo `json:"-"`
+}
+
+// +kubebuilder:object:root=true
+
+// SdewanApplicationList contains a list of SdewanApplication
+type SdewanApplicationList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []SdewanApplication `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&SdewanApplication{}, &SdewanApplicationList{})
+}
package v1alpha1
import (
+ "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ApplicationInfo) DeepCopyInto(out *ApplicationInfo) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationInfo.
+func (in *ApplicationInfo) DeepCopy() *ApplicationInfo {
+ if in == nil {
+ return nil
+ }
+ out := new(ApplicationInfo)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in BucketPermission) DeepCopyInto(out *BucketPermission) {
{
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SdewanApplication) DeepCopyInto(out *SdewanApplication) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+ out.AppInfo = in.AppInfo
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanApplication.
+func (in *SdewanApplication) DeepCopy() *SdewanApplication {
+ if in == nil {
+ return nil
+ }
+ out := new(SdewanApplication)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SdewanApplication) 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 *SdewanApplicationList) DeepCopyInto(out *SdewanApplicationList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]SdewanApplication, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanApplicationList.
+func (in *SdewanApplicationList) DeepCopy() *SdewanApplicationList {
+ if in == nil {
+ return nil
+ }
+ out := new(SdewanApplicationList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SdewanApplicationList) 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 *SdewanApplicationSpec) DeepCopyInto(out *SdewanApplicationSpec) {
+ *out = *in
+ if in.PodSelector != nil {
+ in, out := &in.PodSelector, &out.PodSelector
+ *out = new(v1.LabelSelector)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanApplicationSpec.
+func (in *SdewanApplicationSpec) DeepCopy() *SdewanApplicationSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(SdewanApplicationSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SdewanStatus) DeepCopyInto(out *SdewanStatus) {
*out = *in
--- /dev/null
+
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.2.5
+ creationTimestamp: null
+ name: sdewanapplications.batch.sdewan.akraino.org
+spec:
+ group: batch.sdewan.akraino.org
+ names:
+ kind: SdewanApplication
+ listKind: SdewanApplicationList
+ plural: sdewanapplications
+ singular: sdewanapplication
+ scope: Namespaced
+ subresources:
+ status: {}
+ validation:
+ openAPIV3Schema:
+ description: SdewanApplication is the Schema for the sdewanapplications 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: SdewanApplicationSpec defines the desired state of SdewanApplication
+ properties:
+ appNamespace:
+ type: string
+ podSelector:
+ description: A label selector is a label query over a set of resources.
+ The result of matchLabels and matchExpressions are ANDed. An empty
+ label selector matches all objects. A null label selector matches
+ no objects.
+ properties:
+ matchExpressions:
+ description: matchExpressions is a list of label selector requirements.
+ The requirements are ANDed.
+ items:
+ description: A label selector requirement is a selector that contains
+ values, a key, and an operator that relates the key and values.
+ properties:
+ key:
+ description: key is the label key that the selector applies
+ to.
+ type: string
+ operator:
+ description: operator represents a key's relationship to a
+ set of values. Valid operators are In, NotIn, Exists and
+ DoesNotExist.
+ type: string
+ values:
+ description: values is an array of string values. If the operator
+ is In or NotIn, the values array must be non-empty. If the
+ operator is Exists or DoesNotExist, the values array must
+ be empty. This array is replaced during a strategic merge
+ patch.
+ items:
+ type: string
+ type: array
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ matchLabels:
+ additionalProperties:
+ type: string
+ description: matchLabels is a map of {key,value} pairs. A single
+ {key,value} in the matchLabels map is equivalent to an element
+ of matchExpressions, whose key field is "key", the operator is
+ "In", and the values array contains only "value". The requirements
+ are ANDed.
+ type: object
+ type: object
+ type: object
+ status:
+ description: status subsource used for Sdewan rule CRDs
+ properties:
+ appliedGeneration:
+ format: int64
+ type: integer
+ appliedTime:
+ format: date-time
+ type: string
+ message:
+ type: string
+ state:
+ type: string
+ required:
+ - state
+ type: object
+ type: object
+ version: v1alpha1
+ versions:
+ - name: v1alpha1
+ served: true
+ storage: true
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: []
+ storedVersions: []
- bases/batch.sdewan.akraino.org_ipsechosts.yaml
- bases/batch.sdewan.akraino.org_ipsecsites.yaml
- bases/batch.sdewan.akraino.org_cnfservices.yaml
+- bases/batch.sdewan.akraino.org_sdewanapplications.yaml
# +kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge:
#- patches/webhook_in_ipsechosts.yaml
#- patches/webhook_in_ipsecsites.yaml
#- patches/webhook_in_cnfservices.yaml
+#- patches/webhook_in_sdewanapplications.yaml
# +kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
#- patches/cainjection_in_ipsechosts.yaml
#- patches/cainjection_in_ipsecsites.yaml
#- patches/cainjection_in_cnfservices.yaml
+#- patches/cainjection_in_sdewanapplications.yaml
# +kubebuilder:scaffold:crdkustomizecainjectionpatch
# the following config is for teaching kustomize how to do kustomization for CRDs.
--- /dev/null
+# The following patch adds a directive for certmanager to inject CA into the CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: sdewanapplications.batch.sdewan.akraino.org
--- /dev/null
+# The following patch enables conversion webhook for CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+ name: sdewanapplications.batch.sdewan.akraino.org
+spec:
+ conversion:
+ strategy: Webhook
+ webhookClientConfig:
+ # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
+ # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
+ caBundle: Cg==
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
- get
- patch
- update
+- apiGroups:
+ - batch.sdewan.akraino.org
+ resources:
+ - sdewanapplications
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - batch.sdewan.akraino.org
+ resources:
+ - sdewanapplications/status
+ verbs:
+ - get
+ - patch
+ - update
- apiGroups:
- rbac.authorization.k8s.io
resources:
--- /dev/null
+# permissions for end users to edit sdewanapplications.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: sdewanapplication-editor-role
+rules:
+- apiGroups:
+ - batch.sdewan.akraino.org
+ resources:
+ - sdewanapplications
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - batch.sdewan.akraino.org
+ resources:
+ - sdewanapplications/status
+ verbs:
+ - get
--- /dev/null
+# permissions for end users to view sdewanapplications.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: sdewanapplication-viewer-role
+rules:
+- apiGroups:
+ - batch.sdewan.akraino.org
+ resources:
+ - sdewanapplications
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - batch.sdewan.akraino.org
+ resources:
+ - sdewanapplications/status
+ verbs:
+ - get
--- /dev/null
+apiVersion: batch.sdewan.akraino.org/v1alpha1
+kind: SdewanApplication
+metadata:
+ name: sdewanapplication-sample
+ labels:
+ sdewanPurpose: cnf1
+spec:
+ appNamespace: default
+ podSelector:
+ matchLabels:
+ app: myapp
+ #key: value
// No instance
return ctrl.Result{}, nil
}
+
+ err1, ok := err.(*AppCRError)
+ if ok && err1.Code == 404 {
+ return ctrl.Result{}, nil
+ }
// Error reading the object - requeue the request.
return ctrl.Result{RequeueAfter: during}, nil
}
_, err := cnf.DeleteObject(handler, instance)
if err != nil {
- if err.(*openwrt.OpenwrtError).Code != 404 {
+ err2, ok := err.(*openwrt.OpenwrtError)
+ if !ok || err2.Code != 404 {
log.Error(err, "Failed to delete "+handler.GetType())
setStatus(instance, batchv1alpha1.SdewanStatus{State: batchv1alpha1.Deleting, Message: err.Error()})
err = r.Status().Update(ctx, instance)
--- /dev/null
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+
+ "github.com/go-logr/logr"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/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/handler"
+ "sigs.k8s.io/controller-runtime/pkg/source"
+
+ batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+ "sdewan.akraino.org/sdewan/openwrt"
+)
+
+var sdewanApplicationHandler = new(SdewanApplicationHandler)
+
+type SdewanApplicationHandler struct {
+}
+
+type AppCRError struct {
+ Code int
+ Message string
+}
+
+func (e AppCRError) Error() string {
+ return fmt.Sprintf("Error Code: %d, Error Message: %s", e.Code, e.Message)
+}
+
+func (m *SdewanApplicationHandler) GetType() string {
+ return "sdewanApplication"
+}
+
+func (m *SdewanApplicationHandler) GetName(instance runtime.Object) string {
+ app := instance.(*batchv1alpha1.SdewanApplication)
+ return app.Name
+}
+
+func (m *SdewanApplicationHandler) GetFinalizer() string {
+ return "rule.finalizers.sdewan.akraino.org"
+}
+
+func (m *SdewanApplicationHandler) GetInstance(r client.Client, ctx context.Context, req ctrl.Request) (runtime.Object, error) {
+ instance := &batchv1alpha1.SdewanApplication{}
+ err := r.Get(ctx, req.NamespacedName, instance)
+ if err == nil {
+ ps := instance.Spec.PodSelector.MatchLabels
+ ns := instance.Spec.AppNamespace
+ podList := &corev1.PodList{}
+ r.List(ctx, podList, client.MatchingLabels(ps), client.InNamespace(ns))
+ ips := ""
+ for _, item := range podList.Items {
+ if ips == "" {
+ ips = item.Status.PodIP
+ } else {
+ ips = ips + "," + item.Status.PodIP
+ }
+ }
+ instance.AppInfo.IpList = ips
+ }
+
+ if instance.AppInfo.IpList == "" {
+ return instance, &AppCRError{Code: 404, Message: "Application not found"}
+ }
+
+ return instance, err
+}
+
+func (m *SdewanApplicationHandler) Convert(instance runtime.Object, deployment appsv1.Deployment) (openwrt.IOpenWrtObject, error) {
+ app := instance.(*batchv1alpha1.SdewanApplication)
+ openwrtapp := openwrt.SdewanApp{
+ Name: app.Name,
+ IpList: app.AppInfo.IpList,
+ }
+ return &openwrtapp, nil
+}
+
+func (m *SdewanApplicationHandler) IsEqual(instance1 openwrt.IOpenWrtObject, instance2 openwrt.IOpenWrtObject) bool {
+ app1 := instance1.(*openwrt.SdewanApp)
+ app2 := instance2.(*openwrt.SdewanApp)
+ return reflect.DeepEqual(*app1, *app2)
+}
+
+func (m *SdewanApplicationHandler) GetObject(clientInfo *openwrt.OpenwrtClientInfo, name string) (openwrt.IOpenWrtObject, error) {
+ openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+ app := openwrt.AppClient{OpenwrtClient: openwrtClient}
+ ret, err := app.GetApp(name)
+ return ret, err
+}
+
+func (m *SdewanApplicationHandler) CreateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {
+ openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+ app := openwrt.AppClient{OpenwrtClient: openwrtClient}
+ application := instance.(*openwrt.SdewanApp)
+ return app.CreateApp(*application)
+}
+
+func (m *SdewanApplicationHandler) UpdateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {
+ openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+ app := openwrt.AppClient{OpenwrtClient: openwrtClient}
+ application := instance.(*openwrt.SdewanApp)
+ return app.UpdateApp(*application)
+}
+
+func (m *SdewanApplicationHandler) DeleteObject(clientInfo *openwrt.OpenwrtClientInfo, name string) error {
+ openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+ app := openwrt.AppClient{OpenwrtClient: openwrtClient}
+ return app.DeleteApp(name)
+}
+
+func (m *SdewanApplicationHandler) Restart(clientInfo *openwrt.OpenwrtClientInfo) (bool, error) {
+ return true, nil
+}
+
+// SdewanApplicationReconciler reconciles a SdewanApplication object
+type SdewanApplicationReconciler struct {
+ client.Client
+ Log logr.Logger
+ Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=sdewanapplications,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=sdewanapplications/status,verbs=get;update;patch
+
+func (r *SdewanApplicationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
+ return ProcessReconcile(r, r.Log, req, sdewanApplicationHandler)
+}
+
+func (r *SdewanApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&batchv1alpha1.SdewanApplication{}).
+ Watches(
+ &source.Kind{Type: &corev1.Service{}},
+ &handler.EnqueueRequestsFromMapFunc{
+ ToRequests: handler.ToRequestsFunc(GetServiceToRequestsFunc(r)),
+ },
+ IPFilter).
+ Complete(r)
+}
err = batchv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
+ err = batchv1alpha1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
setupLog.Error(err, "unable to create controller", "controller", "CNFService")
os.Exit(1)
}
+ if err = (&controllers.SdewanApplicationReconciler{
+ Client: mgr.GetClient(),
+ Log: ctrl.Log.WithName("controllers").WithName("SdewanApplication"),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "SdewanApplication")
+ os.Exit(1)
+ }
// +kubebuilder:scaffold:builder
setupLog.Info("starting manager")
--- /dev/null
+package openwrt
+
+import (
+ "encoding/json"
+)
+
+const (
+ appBaseURL = "sdewan/application/v1/"
+)
+
+type AppClient struct {
+ OpenwrtClient *openwrtClient
+}
+
+// App Info
+type SdewanApp struct {
+ Name string `json:"name"`
+ IpList string `json:"iplist"`
+}
+
+type SdewanApps struct {
+ Apps []SdewanApp `json:"apps"`
+}
+
+func (o *SdewanApp) GetName() string {
+ return o.Name
+}
+
+// App APIs
+// get apps
+func (m *AppClient) GetApps() (*SdewanApps, error) {
+ response, err := m.OpenwrtClient.Get(appBaseURL + "applications")
+ if err != nil {
+ return nil, err
+ }
+
+ var sdewanApps SdewanApps
+ err2 := json.Unmarshal([]byte(response), &sdewanApps)
+ if err2 != nil {
+ return nil, err2
+ }
+
+ return &sdewanApps, nil
+}
+
+// get app
+func (m *AppClient) GetApp(app_name string) (*SdewanApp, error) {
+ response, err := m.OpenwrtClient.Get(appBaseURL + "applications/" + app_name)
+ if err != nil {
+ return nil, err
+ }
+
+ var sdewanApp SdewanApp
+ err2 := json.Unmarshal([]byte(response), &sdewanApp)
+ if err2 != nil {
+ return nil, err2
+ }
+
+ return &sdewanApp, nil
+}
+
+// create app
+func (m *AppClient) CreateApp(app SdewanApp) (*SdewanApp, error) {
+ app_obj, _ := json.Marshal(app)
+ response, err := m.OpenwrtClient.Post(appBaseURL+"applications/", string(app_obj))
+ if err != nil {
+ return nil, err
+ }
+
+ var sdewanApp SdewanApp
+ err2 := json.Unmarshal([]byte(response), &sdewanApp)
+ if err2 != nil {
+ return nil, err2
+ }
+
+ return &sdewanApp, nil
+}
+
+// delete app
+func (m *AppClient) DeleteApp(app_name string) error {
+ _, err := m.OpenwrtClient.Delete(appBaseURL + "applications/" + app_name)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// update app
+func (m *AppClient) UpdateApp(app SdewanApp) (*SdewanApp, error) {
+ app_obj, _ := json.Marshal(app)
+ app_name := app.Name
+ response, err := m.OpenwrtClient.Put(appBaseURL+"applications/"+app_name, string(app_obj))
+ if err != nil {
+ return nil, err
+ }
+
+ var sdewanApp SdewanApp
+ err2 := json.Unmarshal([]byte(response), &sdewanApp)
+ if err2 != nil {
+ return nil, err2
+ }
+
+ return &sdewanApp, nil
+}