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 }