blob: e83e4e41a59ac626456733071c84543f8b039641 [file] [log] [blame]
package conversion
import (
"context"
_ "embed"
"encoding/base64"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"go.skia.org/infra/autoroll/go/config"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/kube/go/kube_conf_gen_lib"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext"
)
const (
// Parent repo name for Google3 rollers.
google3ParentName = "Google3"
)
var (
// backendTemplate is the template used to generate the k8s YAML config file
// for autoroll backends.
//go:embed autoroll-be.yaml.template
backendTemplate string
// namespaceTemplate is the template used to generate the k8s YAML config
// file for autoroll namespaces.
//go:embed autoroll-ns.yaml.template
namespaceTemplate string
)
// ConvertConfig converts the given roller config file to a Kubernetes config.
func ConvertConfig(ctx context.Context, cfgBytes []byte, relPath, dstDir string) error {
if backendTemplate == "" {
return skerr.Fmt("internal error; embedded template is empty")
}
// Decode the config file.
var cfg config.Config
if err := prototext.Unmarshal(cfgBytes, &cfg); err != nil {
return skerr.Wrapf(err, "failed to parse roller config")
}
// Google3 uses a different type of backend.
if cfg.ParentDisplayName == google3ParentName {
return nil
}
// kube-conf-gen wants a JSON-ish version of the config in order to build
// the config map.
cfgJsonBytes, err := protojson.MarshalOptions{
AllowPartial: true,
EmitUnpopulated: true,
}.Marshal(&cfg)
if err != nil {
return skerr.Wrap(err)
}
cfgJson := map[string]interface{}{}
if err := json.Unmarshal(cfgJsonBytes, &cfgJson); err != nil {
return skerr.Wrap(err)
}
cfgMap := map[string]interface{}{}
if err := kube_conf_gen_lib.ParseConfigHelper(cfgJson, cfgMap, false); err != nil {
return skerr.Wrap(err)
}
// Encode the roller config file as base64.
// Note that we could re-read the config file from disk
// and base64-encode its contents. In practice, the
// behavior of the autoroll frontend and backends would
// be the same, so we consider it preferable to encode
// the parsed config, which will strip things like
// comments and whitespace that would otherwise produce
// a "different" config.
b, err := prototext.MarshalOptions{
AllowPartial: true,
EmitUnknown: true,
// This causes the emitted config to be pretty-printed. It isn't needed
// by the roller itself, but it's helpful for a human to debug issues
// related to the config. Remove this if we start getting errors about
// command lines being too long.
Indent: " ",
}.Marshal(&cfg)
if err != nil {
return skerr.Wrapf(err, "Failed to encode roller config as text proto")
}
cfgFileBase64 := base64.StdEncoding.EncodeToString(b)
cfgMap["configBase64"] = cfgFileBase64
// Run kube-conf-gen to generate the backend config file.
baseName, relDir := splitAndProcessPath(relPath)
baseNameParts := strings.Split(baseName, ".")
baseNameSuffix := strings.Join(baseNameParts[:len(baseNameParts)-1], ".")
dstPath := filepath.Join(dstDir, relDir, fmt.Sprintf("autoroll-be-%s.yaml", baseNameSuffix))
if err := kube_conf_gen_lib.GenerateOutputFromTemplateString(backendTemplate, false, cfgMap, dstPath); err != nil {
return skerr.Wrapf(err, "failed to write output")
}
sklog.Infof("Wrote %s", dstPath)
// Run kube-conf-gen to generate the namespace config file. Note that we'll
// overwrite this file for every roller in the namespace, but that shouldn't
// be a problem, since the generated files will be the same.
namespace := strings.Split(cfg.ServiceAccount, "@")[0]
dstNsPath := filepath.Join(dstDir, relDir, fmt.Sprintf("%s-ns.yaml", namespace))
if err := kube_conf_gen_lib.GenerateOutputFromTemplateString(namespaceTemplate, false, cfgMap, dstNsPath); err != nil {
return skerr.Wrapf(err, "failed to write output")
}
sklog.Infof("Wrote %s", dstNsPath)
return nil
}
func splitAndProcessPath(path string) (string, string) {
splitPath := strings.Split(path, string(filepath.Separator))
baseName := splitPath[len(splitPath)-1]
relDirParts := make([]string, 0, len(splitPath)-1)
for _, part := range splitPath[:len(splitPath)-1] {
if part != "generated" {
relDirParts = append(relDirParts, part)
}
}
relDir := strings.Join(relDirParts, string(filepath.Separator))
return baseName, relDir
}