// Copyright 2023 Cloudbase Solutions SRL // // 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 provider import ( "context" "encoding/json" "fmt" "log" "net/http" "strings" "edp.buildth.ing/DevFW-CICD/garm-provider-edge-connect/config" "edp.buildth.ing/DevFW-CICD/garm-provider-edge-connect/internal/client" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" execution "github.com/cloudbase/garm-provider-common/execution/v0.1.0" "github.com/cloudbase/garm-provider-common/params" ) var Version = "v0.0.0-unknown" func NewEdgeConnectProvider(configPath, controllerID string) (execution.ExternalProvider, string, error) { conf, err := config.NewConfig(configPath) if err != nil { return nil, "", fmt.Errorf("error loading config: %w", err) } creds, err := config.NewCredentials(conf.CredentialsFile) if err != nil { return nil, "", fmt.Errorf("error loading config: %w", err) } client := client.EdgeConnect{ BaseURL: "https://hub.apps.edge.platform.mg3.mdb.osc.live", HttpClient: &http.Client{}, Credentials: client.Credentials{ Username: creds.Username, Password: creds.Password, }, } return &edgeConnectProvider{ client: client, controllerID: controllerID, cfg: conf, }, conf.LogFile, nil } type edgeConnectProvider struct { client client.EdgeConnect controllerID string cfg *config.Config } // CreateInstance creates a new compute instance in the provider. func (a *edgeConnectProvider) CreateInstance(ctx context.Context, bootstrapParams params.BootstrapInstance) (params.ProviderInstance, error) { log.Printf("Executing CreateInstance with %v\n", bootstrapParams) instancename := fmt.Sprintf("garm-%v-%v-%v", a.controllerID[:8], bootstrapParams.PoolID[:8], strings.ToLower(bootstrapParams.Name)) podv1 := corev1.Pod{ TypeMeta: metav1.TypeMeta{ Kind: "Pod", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: instancename, Labels: map[string]string{"run": instancename}, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ corev1.Container{ Name: "mganter-test", Image: "edp.buildth.ing/devfw-cicd/nginx", ImagePullPolicy: "Always", Ports: []corev1.ContainerPort{ corev1.ContainerPort{ Name: "HTTP", ContainerPort: 80, Protocol: "TCP", }, }, }, }, }, } service := corev1.Service{ TypeMeta: metav1.TypeMeta{ Kind: "Service", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: instancename, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeLoadBalancer, Ports: []corev1.ServicePort{ corev1.ServicePort{ Name: "tcp80", Protocol: "TCP", Port: 80, TargetPort: intstr.FromInt(80), }, }, Selector: map[string]string{"run": instancename}, }, } podjson, err := json.Marshal(podv1) if err != nil { return params.ProviderInstance{}, err } servicejson, err := json.Marshal(service) if err != nil { return params.ProviderInstance{}, err } manifest := fmt.Sprintf("%s\n---\n%s", string(podjson), string(servicejson)) err = a.client.NewApp(ctx, client.NewAppInput{ Region: a.cfg.Region, App: client.App{ Key: client.AppKey{ Organization: a.cfg.Organization, Name: instancename, Version: "0.0.1", }, Deployment: "kubernetes", ImageType: "Docker", ImagePath: "edp.buildth.ing/devfw-cicd/nginx", AllowServerless: true, ServerlessConfig: struct{}{}, DefaultFlavor: client.Flavor{ Name: "EU.small", }, DeploymentGenerator: "kubernetes-basic", DeploymentManifest: manifest, }, }) if err != nil { return params.ProviderInstance{}, err } err = a.client.NewAppInstance(ctx, client.NewAppInstanceInput{ Region: a.cfg.Region, AppInst: client.AppInstance{ Key: client.AppInstanceKey{ Organization: a.cfg.Organization, Name: instancename, CloudletKey: client.CloudletKey(a.cfg.CloudletKey), }, AppKey: client.AppKey{ Organization: a.cfg.Organization, Name: instancename, Version: "0.0.1", }, Flavor: client.Flavor{ Name: "EU.small", }, }, }) if err != nil { return params.ProviderInstance{}, err } instance := params.ProviderInstance{ ProviderID: a.controllerID, Name: instancename, OSType: params.Linux, OSArch: params.Amd64, OSName: "lala", OSVersion: "lalatest", Status: params.InstanceCreating, } return instance, nil } // Delete instance will delete the instance in a provider. func (a *edgeConnectProvider) DeleteInstance(ctx context.Context, instance string) error { log.Printf("Executing DeleteInstance %s\n", instance) appsinstances, err := a.client.ShowAppInstances(ctx, client.AppInstanceKey{ Organization: a.cfg.Organization, }, a.cfg.Region) if err != nil { return err } myappintances := filter(appsinstances, func(app client.AppInstance) bool { return strings.HasSuffix(app.Key.Name, strings.ToLower(instance)) }) for _, v := range myappintances { err = a.client.DeleteAppInstance(ctx, v.Key, a.cfg.Region) if err != nil { return err } } apps, err := a.client.ShowApps(ctx, client.AppKey{ Organization: a.cfg.Organization, }, a.cfg.Region) if err != nil { return err } myapps := filter(apps, func(app client.App) bool { return strings.HasSuffix(app.Key.Name, strings.ToLower(instance)) }) for _, v := range myapps { err = a.client.DeleteApp(ctx, v.Key, a.cfg.Region) if err != nil { return err } } appkey := client.AppKey{ Organization: a.cfg.Organization, Name: instance, Version: "0.0.1", } err = a.client.DeleteApp(ctx, appkey, a.cfg.Region) if err != nil { return err } return nil } // GetInstance will return details about one instance. func (a *edgeConnectProvider) GetInstance(ctx context.Context, instance string) (params.ProviderInstance, error) { log.Printf("Executing GetInstance %s\n", instance) providerInstance := params.ProviderInstance{ ProviderID: a.controllerID, Name: instance, OSType: params.Linux, OSArch: params.Amd64, OSName: "lala", OSVersion: "lalatest", Status: params.InstanceStatusUnknown, } appinstances, err := a.client.ShowAppInstances(ctx, client.AppInstanceKey{ Organization: a.cfg.Organization, }, a.cfg.Region) if err != nil { return params.ProviderInstance{}, err } myappintances := filter(appinstances, func(app client.AppInstance) bool { return strings.HasSuffix(app.Key.Name, strings.ToLower(instance)) }) if len(myappintances) == 0 { return params.ProviderInstance{}, fmt.Errorf("AppInstance not found!") } appinst := myappintances[0] if appinst.State == "Ready" { providerInstance.Status = params.InstanceRunning } return providerInstance, nil } // ListInstances will list all instances for a provider. func (a *edgeConnectProvider) ListInstances(ctx context.Context, poolID string) ([]params.ProviderInstance, error) { log.Printf("Executing ListInstances for PoolID %s\n", poolID) apps, err := a.client.ShowAppInstances(ctx, client.AppInstanceKey{ Organization: a.cfg.Organization, }, a.cfg.Region) if err != nil { return nil, err } myappintances := filter(apps, func(app client.AppInstance) bool { return strings.HasPrefix(app.Key.Name, fmt.Sprintf("garm-%v-%v", a.controllerID[:8], poolID[:8])) }) providerinstances := []params.ProviderInstance{} for _, v := range myappintances { providerInstance := params.ProviderInstance{ ProviderID: a.controllerID, Name: v.Key.Name, OSType: params.Linux, OSArch: params.Amd64, OSName: "lala", OSVersion: "lalatest", Status: params.InstanceStatusUnknown, } if v.State == "Ready" { providerInstance.Status = params.InstanceRunning } providerinstances = append(providerinstances, providerInstance) } return providerinstances, nil } func filter[T any](s []T, predicate func(T) bool) []T { result := make([]T, 0, len(s)) // Pre-allocate for efficiency for _, v := range s { if predicate(v) { result = append(result, v) } } return result } // RemoveAllInstances will remove all instances created by this provider. func (a *edgeConnectProvider) RemoveAllInstances(ctx context.Context) error { log.Printf("Executing RemoveAllInstances\n") apps, err := a.client.ShowAppInstances(ctx, client.AppInstanceKey{ Organization: a.cfg.Organization, }, a.cfg.Region) if err != nil { return err } myappintances := filter(apps, func(app client.AppInstance) bool { return strings.HasPrefix(app.Key.Name, fmt.Sprintf("garm-%v", a.controllerID[:8])) }) for _, v := range myappintances { err = a.DeleteInstance(ctx, v.Key.Name) if err != nil { return err } } return nil } // Stop shuts down the instance. func (a *edgeConnectProvider) Stop(ctx context.Context, instance string, force bool) error { log.Printf("Executing Stop %s\n", instance) panic(fmt.Sprintf("Stop() not implemented, called with instance: %s, force: %t", instance, force)) } // Start boots up an instance. func (a *edgeConnectProvider) Start(ctx context.Context, instance string) error { log.Printf("Executing Start %s\n", instance) panic(fmt.Sprintf("Start() not implemented, called with instance: %s", instance)) } // GetVersion returns the version of the provider. func (a *edgeConnectProvider) GetVersion(ctx context.Context) string { return Version }