blob: 8f1750b7c62ff780d3aacf0cc52f8d4ad6ebf78b [file] [log] [blame]
package k8s_config
import (
"bytes"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
apps "k8s.io/api/apps/v1"
batch "k8s.io/api/batch/v1beta1"
core "k8s.io/api/core/v1"
policy "k8s.io/api/policy/v1beta1"
rbac "k8s.io/api/rbac/v1"
"sigs.k8s.io/yaml"
)
const (
// K8s config kinds.
ClusterRoleKind = "ClusterRole"
ClusterRoleBindingKind = "ClusterRoleBinding"
CronJobKind = "CronJob"
DaemonSetKind = "DaemonSet"
DeploymentKind = "Deployment"
NamespaceKind = "Namespace"
PodSecurityPolicyKind = "PodSecurityPolicy"
RoleBindingKind = "RoleBinding"
ServiceKind = "Service"
ServiceAccountKind = "ServiceAccount"
StatefulSetKind = "StatefulSet"
// Unsupported kinds.
BackendConfigKind = "BackendConfig"
ClusterPodMonitoringKind = "ClusterPodMonitoring"
ClusterRulesKind = "ClusterRules"
ConfigMapKind = "ConfigMap"
IngressKind = "Ingress"
OperatorConfigKind = "OperatorConfig"
PodDisruptionBudgetKind = "PodDisruptionBudget"
StorageClassKind = "StorageClass"
)
type K8sConfigFile struct {
ClusterRole []*rbac.ClusterRole
ClusterRoleBinding []*rbac.ClusterRoleBinding
CronJob []*batch.CronJob
DaemonSet []*apps.DaemonSet
Deployment []*apps.Deployment
Namespace []*core.Namespace
PodSecurityPolicy []*policy.PodSecurityPolicy
RoleBinding []*rbac.RoleBinding
Service []*core.Service
ServiceAccount []*core.ServiceAccount
StatefulSet []*apps.StatefulSet
}
type ByteRange struct {
Start int
End int
}
// SplitYAMLDocs splits the given YAML file contents into its component YAML
// documents. Returns the documents and their line ranges within the file.
func SplitYAMLDocs(contents []byte) ([][]byte, []*ByteRange) {
var yamlDocs [][]byte
var byteRanges []*ByteRange
docStart := 0
sepChars := 0
for idx, b := range contents {
if b == '-' {
sepChars++
} else {
sepChars = 0
}
if sepChars == 3 {
docEnd := idx - 2
doc := bytes.TrimSpace(contents[docStart:docEnd])
if len(doc) > 0 {
yamlDocs = append(yamlDocs, doc)
byteRanges = append(byteRanges, &ByteRange{
Start: docStart,
End: docEnd,
})
}
docStart = idx + 1
}
}
doc := bytes.TrimSpace(contents[docStart:])
if len(doc) > 0 {
yamlDocs = append(yamlDocs, doc)
byteRanges = append(byteRanges, &ByteRange{
Start: docStart,
End: len(contents),
})
}
return yamlDocs, byteRanges
}
// ParseK8sConfigFile parses the given config file contents and returns the
// configs it contains.
func ParseK8sConfigFile(contents []byte) (*K8sConfigFile, map[interface{}]*ByteRange, error) {
yamlDocs, lineRanges := SplitYAMLDocs(contents)
rv := new(K8sConfigFile)
byteRanges := make(map[interface{}]*ByteRange, len(yamlDocs))
for idx, yamlDoc := range yamlDocs {
obj, err := parseYamlDoc(yamlDoc, rv)
if err != nil {
return nil, nil, skerr.Wrap(err)
}
if obj != nil {
byteRanges[obj] = lineRanges[idx]
}
}
return rv, byteRanges, nil
}
// ParseYamlDoc parses a single YAML document and returns the configs it
// contains.
func ParseYamlDoc(yamlDoc []byte) (*K8sConfigFile, error) {
rv := new(K8sConfigFile)
if _, err := parseYamlDoc(yamlDoc, rv); err != nil {
return nil, skerr.Wrap(err)
}
return rv, nil
}
func parseYamlDoc(yamlDoc []byte, rv *K8sConfigFile) (interface{}, error) {
// Ignore empty documents.
if len(bytes.TrimSpace(yamlDoc)) == 0 {
return nil, nil
}
// Arbitrarily parse the document as a Deployment. This makes it easy to
// reference the TypeMeta.Kind, so that we can parse the correct type.
parsed := new(apps.Deployment)
if err := yaml.Unmarshal(yamlDoc, parsed); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
switch parsed.TypeMeta.Kind {
case ClusterRoleKind:
v := new(rbac.ClusterRole)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.ClusterRole = append(rv.ClusterRole, v)
return v, nil
case ClusterRoleBindingKind:
v := new(rbac.ClusterRoleBinding)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.ClusterRoleBinding = append(rv.ClusterRoleBinding, v)
return v, nil
case CronJobKind:
v := new(batch.CronJob)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.CronJob = append(rv.CronJob, v)
return v, nil
case DaemonSetKind:
v := new(apps.DaemonSet)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.DaemonSet = append(rv.DaemonSet, v)
return v, nil
case DeploymentKind:
v := new(apps.Deployment)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.Deployment = append(rv.Deployment, v)
return v, nil
case NamespaceKind:
v := new(core.Namespace)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.Namespace = append(rv.Namespace, v)
return v, nil
case PodSecurityPolicyKind:
v := new(policy.PodSecurityPolicy)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.PodSecurityPolicy = append(rv.PodSecurityPolicy, v)
return v, nil
case RoleBindingKind:
v := new(rbac.RoleBinding)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.RoleBinding = append(rv.RoleBinding, v)
return v, nil
case ServiceKind:
v := new(core.Service)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.Service = append(rv.Service, v)
return v, nil
case ServiceAccountKind:
v := new(core.ServiceAccount)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.ServiceAccount = append(rv.ServiceAccount, v)
return v, nil
case StatefulSetKind:
v := new(apps.StatefulSet)
if err := yaml.Unmarshal(yamlDoc, v); err != nil {
return nil, skerr.Wrapf(err, "failed to parse config file")
}
rv.StatefulSet = append(rv.StatefulSet, v)
return v, nil
case BackendConfigKind, ClusterPodMonitoringKind, ClusterRulesKind, ConfigMapKind, IngressKind, OperatorConfigKind, PodDisruptionBudgetKind, StorageClassKind:
// We ignore these Kinds because we don't do anything with them at
// this time.
return nil, nil
default:
// Log a warning to indicate that we don't know about this Kind.
sklog.Warningf("Unknown Kind %q", parsed.TypeMeta.Kind)
return nil, nil
}
}