cost-optimization/gke-shift-left-cost/api/manifests.go (189 lines of code) (raw):

// Copyright 2021 Google LLC // // 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 api import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "strings" log "github.com/sirupsen/logrus" ) // Manifests holds all deployments and executes cost estimation type Manifests struct { Deployments []*Deployment deploymentsRef map[string]*Deployment ReplicaSets []*ReplicaSet replicaSetsRef map[string]*ReplicaSet StatefulSets []*StatefulSet statefulsetsRef map[string]*StatefulSet DaemonSets []*DaemonSet VolumeClaims []*VolumeClaim hpas []HPA } // LoadObjectsFromPath loads all files from folder and subfolder finishing with yaml or yml func (m *Manifests) LoadObjectsFromPath(path string, conf CostimatorConfig) error { err := filepath.Walk(path, func(path string, f os.FileInfo, err error) error { if !f.IsDir() { if strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") { data, err := ioutil.ReadFile(path) if err != nil { return err } log.Tracef("Loading yaml file '%s'", path) return m.LoadObjects(data, conf) } log.Tracef("Skipping non yaml file '%s'", path) } return nil }) if err != nil { return err } return nil } // LoadObjects allow you to decode and load into Manifests your k8s objects // For now, it only understands Deployment and HPA func (m *Manifests) LoadObjects(data []byte, conf CostimatorConfig) error { objects := bytes.Split(data, []byte("---")) for _, object := range objects { err := m.loadObject(object, conf) if err != nil { return err } } return nil } // EstimateCost loop through all resources and group it by kind func (m *Manifests) EstimateCost(pc GCPPriceCatalog) Cost { m.prepareForCostEstimation() monthlyRanges := []CostRange{} if len(m.Deployments) > 0 { monthlyRanges = append(monthlyRanges, m.estimateDeploymentCost(&pc)) } if len(m.ReplicaSets) > 0 { monthlyRanges = append(monthlyRanges, m.estimateReplicaSetCost(&pc)) } if len(m.StatefulSets) > 0 { monthlyRanges = append(monthlyRanges, m.estimateStatefulSetCost(&pc)) } if len(m.DaemonSets) > 0 { monthlyRanges = append(monthlyRanges, m.estimateDaemonSetCost(&pc)) } if len(m.VolumeClaims) > 0 { monthlyRanges = append(monthlyRanges, m.estimateVolumeClaimCost(&pc)) } return Cost{ MonthlyRanges: monthlyRanges, } } func (m *Manifests) estimateDeploymentCost(rp ResourcePrice) CostRange { deploymentRange := CostRange{Kind: DeploymentKind} for _, deploy := range m.Deployments { deploymentRange = deploymentRange.Add(deploy.estimateCost(rp)) } return deploymentRange } func (m *Manifests) estimateReplicaSetCost(rp ResourcePrice) CostRange { replicasetRange := CostRange{Kind: ReplicaSetKind} for _, replicaset := range m.ReplicaSets { replicasetRange = replicasetRange.Add(replicaset.estimateCost(rp)) } return replicasetRange } func (m *Manifests) estimateStatefulSetCost(rp ResourcePrice) CostRange { statefulsetRange := CostRange{Kind: StatefulSetKind} for _, statefulset := range m.StatefulSets { statefulsetRange = statefulsetRange.Add(statefulset.estimateCost(rp)) } return statefulsetRange } func (m *Manifests) estimateDaemonSetCost(rp ResourcePrice) CostRange { daemonsetRange := CostRange{Kind: DaemonSetKind} for _, daemonset := range m.DaemonSets { daemonsetRange = daemonsetRange.Add(daemonset.estimateCost(rp)) } return daemonsetRange } func (m *Manifests) estimateVolumeClaimCost(sp StoragePrice) CostRange { volumeClaimRange := CostRange{Kind: VolumeClaimKind} for _, volumeClaim := range m.VolumeClaims { volumeClaimRange = volumeClaimRange.Add(volumeClaim.estimateCost(sp)) } return volumeClaimRange } func (m *Manifests) prepareForCostEstimation() { for _, hpa := range m.hpas { key := hpa.TargetRef if deploy, ok := m.deploymentsRef[key]; ok { deploy.hpa = hpa } if replicaset, ok := m.replicaSetsRef[key]; ok { replicaset.hpa = hpa } if statefulset, ok := m.statefulsetsRef[key]; ok { statefulset.hpa = hpa } } } func (m *Manifests) loadObject(data []byte, conf CostimatorConfig) error { if ak, bol := isObjectSupported(data); !bol { log.Debugf("Skipping unsupported k8s object: %+v", ak) return nil } obj, groupVersionKind, err := decode(data) if err != nil { return fmt.Errorf("Error Decoding. Check if your GroupVersionKind is defined in api/k8s_decoder.go. Root cause %+v", err) } switch groupVersionKind.Kind { case HPAKind: hpa, err := buildHPA(obj, groupVersionKind) if err != nil { return err } m.hpas = append(m.hpas, hpa) case DeploymentKind: deploy, err := buildDeployment(obj, groupVersionKind, conf) if err != nil { return err } m.Deployments = append(m.Deployments, &deploy) if m.deploymentsRef == nil { m.deploymentsRef = make(map[string]*Deployment) } m.deploymentsRef[deploy.APIVersionKindName] = &deploy m.deploymentsRef[deploy.getKindName()] = &deploy case ReplicaSetKind: replicaset, err := buildReplicaSet(obj, groupVersionKind, conf) if err != nil { return err } m.ReplicaSets = append(m.ReplicaSets, &replicaset) if m.replicaSetsRef == nil { m.replicaSetsRef = make(map[string]*ReplicaSet) } m.replicaSetsRef[replicaset.APIVersionKindName] = &replicaset m.replicaSetsRef[replicaset.getKindName()] = &replicaset case StatefulSetKind: statefulset, err := buildStatefulSet(obj, groupVersionKind, conf) if err != nil { return err } m.StatefulSets = append(m.StatefulSets, &statefulset) if m.statefulsetsRef == nil { m.statefulsetsRef = make(map[string]*StatefulSet) } m.statefulsetsRef[statefulset.APIVersionKindName] = &statefulset m.statefulsetsRef[statefulset.getKindName()] = &statefulset if len(statefulset.VolumeClaims) > 0 { m.VolumeClaims = append(m.VolumeClaims, statefulset.VolumeClaims...) } case DaemonSetKind: daemonset, err := buildDaemonSet(obj, groupVersionKind, conf) if err != nil { return err } m.DaemonSets = append(m.DaemonSets, &daemonset) case VolumeClaimKind: volume, err := buildVolumeClaim(obj, groupVersionKind, conf) if err != nil { return err } m.VolumeClaims = append(m.VolumeClaims, &volume) } return nil }