github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/specs/types.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package specs
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	core "k8s.io/api/core/v1"
    13  	"k8s.io/apimachinery/pkg/util/intstr"
    14  	"k8s.io/apimachinery/pkg/util/validation"
    15  
    16  	"github.com/juju/juju/caas/specs"
    17  	"github.com/juju/juju/core/annotations"
    18  )
    19  
    20  var logger = loggo.GetLogger("juju.kubernetes.provider.specs")
    21  
    22  type (
    23  	// K8sPodSpec is the current k8s pod spec.
    24  	K8sPodSpec = K8sPodSpecV3
    25  )
    26  
    27  type k8sContainer struct {
    28  	specs.ContainerSpec `json:",inline" yaml:",inline"`
    29  	Kubernetes          *K8sContainerSpec `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty"`
    30  }
    31  
    32  // Validate validates k8sContainer.
    33  func (c *k8sContainer) Validate() error {
    34  	if err := c.ContainerSpec.Validate(); err != nil {
    35  		return errors.Trace(err)
    36  	}
    37  	if c.Kubernetes != nil {
    38  		if err := c.Kubernetes.Validate(); err != nil {
    39  			return errors.Trace(err)
    40  		}
    41  	}
    42  	return nil
    43  }
    44  
    45  func (c *k8sContainer) ToContainerSpec() specs.ContainerSpec {
    46  	result := specs.ContainerSpec{
    47  		ImageDetails:    c.ImageDetails,
    48  		Name:            c.Name,
    49  		Init:            c.Init,
    50  		Image:           c.Image,
    51  		Ports:           c.Ports,
    52  		Command:         c.Command,
    53  		Args:            c.Args,
    54  		WorkingDir:      c.WorkingDir,
    55  		EnvConfig:       c.EnvConfig,
    56  		VolumeConfig:    c.VolumeConfig,
    57  		ImagePullPolicy: c.ImagePullPolicy,
    58  	}
    59  	if c.Kubernetes != nil {
    60  		result.ProviderContainer = c.Kubernetes
    61  	}
    62  	return result
    63  }
    64  
    65  // K8sContainerSpec is a subset of v1.Container which defines
    66  // attributes we expose for charms to set.
    67  type K8sContainerSpec struct {
    68  	LivenessProbe   *core.Probe           `json:"livenessProbe,omitempty" yaml:"livenessProbe,omitempty"`
    69  	ReadinessProbe  *core.Probe           `json:"readinessProbe,omitempty" yaml:"readinessProbe,omitempty"`
    70  	StartupProbe    *core.Probe           `json:"startupProbe,omitempty" yaml:"startupProbe,omitempty"`
    71  	SecurityContext *core.SecurityContext `json:"securityContext,omitempty" yaml:"securityContext,omitempty"`
    72  }
    73  
    74  // Validate validates K8sContainerSpec.
    75  func (*K8sContainerSpec) Validate() error {
    76  	return nil
    77  }
    78  
    79  // PodSpecWithAnnotations wraps a k8s podspec to add annotations and labels.
    80  type PodSpecWithAnnotations struct {
    81  	Labels      map[string]string
    82  	Annotations annotations.Annotation
    83  	core.PodSpec
    84  }
    85  
    86  // PodSpec is a subset of v1.PodSpec which defines
    87  // attributes we expose for charms to set.
    88  type PodSpec struct {
    89  	Labels                        map[string]string        `json:"labels,omitempty" yaml:"labels,omitempty"`
    90  	Annotations                   annotations.Annotation   `json:"annotations,omitempty" yaml:"annotations,omitempty"`
    91  	RestartPolicy                 core.RestartPolicy       `json:"restartPolicy,omitempty" yaml:"restartPolicy,omitempty"`
    92  	ActiveDeadlineSeconds         *int64                   `json:"activeDeadlineSeconds,omitempty" yaml:"activeDeadlineSeconds,omitempty"`
    93  	TerminationGracePeriodSeconds *int64                   `json:"terminationGracePeriodSeconds,omitempty" yaml:"terminationGracePeriodSeconds,omitempty"`
    94  	SecurityContext               *core.PodSecurityContext `json:"securityContext,omitempty" yaml:"securityContext,omitempty"`
    95  	ReadinessGates                []core.PodReadinessGate  `json:"readinessGates,omitempty" yaml:"readinessGates,omitempty"`
    96  	DNSPolicy                     core.DNSPolicy           `json:"dnsPolicy,omitempty" yaml:"dnsPolicy,omitempty"`
    97  	HostNetwork                   bool                     `json:"hostNetwork,omitempty" yaml:"hostNetwork,omitempty"`
    98  	HostPID                       bool                     `json:"hostPID,omitempty" yaml:"hostPID,omitempty"`
    99  	PriorityClassName             string                   `json:"priorityClassName,omitempty"`
   100  	Priority                      *int32                   `json:"priority,omitempty"`
   101  }
   102  
   103  // IsEmpty checks if PodSpec is empty or not.
   104  func (ps PodSpec) IsEmpty() bool {
   105  	return ps.RestartPolicy == "" &&
   106  		ps.ActiveDeadlineSeconds == nil &&
   107  		ps.TerminationGracePeriodSeconds == nil &&
   108  		ps.SecurityContext == nil &&
   109  		len(ps.ReadinessGates) == 0 &&
   110  		len(ps.Labels) == 0 &&
   111  		len(ps.Annotations) == 0 &&
   112  		ps.DNSPolicy == ""
   113  }
   114  
   115  type k8sContainers struct {
   116  	Containers []k8sContainer `json:"containers" yaml:"containers"`
   117  }
   118  
   119  // Validate is defined on ProviderContainer.
   120  func (cs *k8sContainers) Validate() error {
   121  	if len(cs.Containers) == 0 {
   122  		return errors.New("require at least one container spec")
   123  	}
   124  	for _, c := range cs.Containers {
   125  		if err := c.Validate(); err != nil {
   126  			return errors.Trace(err)
   127  		}
   128  	}
   129  	return nil
   130  }
   131  
   132  func validateLabels(labels map[string]string) error {
   133  	for k, v := range labels {
   134  		if errs := validation.IsQualifiedName(k); len(errs) != 0 {
   135  			return errors.NotValidf("label key %q: %s", k, strings.Join(errs, "; "))
   136  		}
   137  		if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
   138  			return errors.NotValidf("label value: %q: at key: %q: %s", v, k, strings.Join(errs, "; "))
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  // ParseRawK8sSpec parses a k8s format of YAML file which defines how to
   145  // configure a CAAS pod. We allow for generic container
   146  // set up plus k8s select specific features.
   147  func ParseRawK8sSpec(in string) (string, error) {
   148  	// TODO(caas): implement raw k8s spec parser.
   149  	return in, nil
   150  }
   151  
   152  // ParsePodSpec parses a YAML file which defines how to
   153  // configure a CAAS pod. We allow for generic container
   154  // set up plus k8s select specific features.
   155  func ParsePodSpec(in string) (*specs.PodSpec, error) {
   156  	return parsePodSpec(in, getParser)
   157  }
   158  
   159  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination ./mocks/parsers_mock.go github.com/juju/juju/caas/kubernetes/provider/specs PodSpecConverter
   160  func parsePodSpec(
   161  	in string,
   162  	getParser func(specVersion specs.Version) (parserType, error),
   163  ) (*specs.PodSpec, error) {
   164  	version, err := specs.GetVersion(in)
   165  	if err != nil {
   166  		return nil, errors.Trace(err)
   167  	}
   168  	parser, err := getParser(version)
   169  	if err != nil {
   170  		return nil, errors.Trace(err)
   171  	}
   172  	k8sspec, err := parser(in)
   173  	if err != nil {
   174  		return nil, errors.Trace(err)
   175  	}
   176  	if err = k8sspec.Validate(); err != nil {
   177  		return nil, errors.Trace(err)
   178  	}
   179  	caaSSpec := k8sspec.ToLatest()
   180  	if err := caaSSpec.Validate(); err != nil {
   181  		return nil, errors.Trace(err)
   182  	}
   183  	return caaSSpec, nil
   184  }
   185  
   186  type parserType func(string) (PodSpecConverter, error)
   187  
   188  // PodSpecConverter defines methods to validate and convert a specific version of podspec to latest version.
   189  type PodSpecConverter interface {
   190  	Validate() error
   191  	ToLatest() *specs.PodSpec
   192  }
   193  
   194  func getParser(specVersion specs.Version) (parserType, error) {
   195  	switch specVersion {
   196  	case specs.Version3:
   197  		return parsePodSpecV3, nil
   198  	case specs.Version2:
   199  		return parsePodSpecV2, nil
   200  	case specs.VersionLegacy:
   201  		return parsePodSpecLegacy, nil
   202  	default:
   203  		return nil, errors.NewNotSupported(nil, fmt.Sprintf("latest supported version %d, but got podspec version %d", specs.CurrentVersion, specVersion))
   204  	}
   205  }
   206  
   207  // IntOrStringToK8s converts IntOrString to k8s version.
   208  func IntOrStringToK8s(in specs.IntOrString) *intstr.IntOrString {
   209  	o := intstr.Parse(in.String())
   210  	return &o
   211  }