pkg/provider/provider.go (110 lines of code) (raw):
// Copyright 2019 The Prometheus Authors
// 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 (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
const (
EKSRetryCount = 100
GlobalRetryCount = 50
Separator = "---"
globalRetryTime = 10 * time.Second
)
// DeploymentResource holds list of variables and corresponding files.
type DeploymentResource struct {
// DeploymentFiles files provided from the cli.
DeploymentFiles []string
// DeploymentVars provided from the cli.
FlagDeploymentVars map[string]string
// Default DeploymentVars.
DefaultDeploymentVars map[string]string
}
// NewDeploymentResource returns DeploymentResource with default values.
func NewDeploymentResource() *DeploymentResource {
return &DeploymentResource{
DeploymentFiles: []string{},
FlagDeploymentVars: map[string]string{},
DefaultDeploymentVars: map[string]string{
"NGINX_SERVICE_TYPE": "LoadBalancer",
"LOADGEN_SCALE_UP_REPLICAS": "10",
"SEPARATOR": ",",
"SERVICEACCOUNT_CLIENT_EMAIL": "example@example.com",
},
}
}
// Resource holds the file content after parsing the template variables.
type Resource struct {
FileName string
Content []byte
}
// RetryUntilTrue returns when there is an error or the requested operation returns true.
func RetryUntilTrue(name string, retryCount int, fn func() (bool, error)) error {
for i := 1; i <= retryCount; i++ {
time.Sleep(globalRetryTime)
if ready, err := fn(); err != nil {
return err
} else if !ready {
log.Printf("Request for '%v' is in progress. Checking in %v", name, globalRetryTime)
continue
}
log.Printf("Request for '%v' is done!", name)
return nil
}
return fmt.Errorf("Request for '%v' hasn't completed after retrying %d times", name, retryCount)
}
// applyTemplateVars applies golang templates to deployment files.
func applyTemplateVars(content []byte, deploymentVars map[string]string) ([]byte, error) {
fileContentParsed := bytes.NewBufferString("")
t := template.New("resource").Option("missingkey=error")
t = t.Funcs(template.FuncMap{
// k8s objects can't have dots(.) se we add a custom function to allow normalising the variable values.
"normalise": func(t string) string {
return strings.Replace(t, ".", "-", -1)
},
"split": func(rangeVars, separator string) []string {
return strings.Split(rangeVars, separator)
},
})
if err := template.Must(t.Parse(string(content))).Execute(fileContentParsed, deploymentVars); err != nil {
return nil, fmt.Errorf("Failed to execute parse file err: %s", err)
}
return fileContentParsed.Bytes(), nil
}
// DeploymentsParse parses the deployment files and returns the result as bytes grouped by the filename.
// Any variables passed to the cli will be replaced in the resources files following the golang text template format.
func DeploymentsParse(deploymentFiles []string, deploymentVars map[string]string) ([]Resource, error) {
var fileList []string
for _, name := range deploymentFiles {
if file, err := os.Stat(name); err == nil && file.IsDir() {
if err := filepath.Walk(name, func(path string, f os.FileInfo, err error) error {
if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" {
fileList = append(fileList, path)
}
return nil
}); err != nil {
return nil, fmt.Errorf("error reading directory: %v", err)
}
} else {
fileList = append(fileList, name)
}
}
deploymentObjects := make([]Resource, 0)
for _, name := range fileList {
absFileName := strings.TrimSuffix(filepath.Base(name), filepath.Ext(name))
content, err := os.ReadFile(name)
if err != nil {
log.Fatalf("Error reading file %v:%v", name, err)
}
// Don't parse file with the suffix "noparse".
if !strings.HasSuffix(absFileName, "noparse") {
content, err = applyTemplateVars(content, deploymentVars)
if err != nil {
return nil, fmt.Errorf("couldn't apply template to file %s: %v", name, err)
}
}
deploymentObjects = append(deploymentObjects, Resource{FileName: name, Content: content})
}
return deploymentObjects, nil
}
// MergeDeploymentVars merges multiple maps based on the order.
func MergeDeploymentVars(ms ...map[string]string) map[string]string {
res := map[string]string{}
for _, m := range ms {
for k, v := range m {
res[k] = v
}
}
return res
}