github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/upgrade.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provider 5 6 import ( 7 "context" 8 "fmt" 9 10 "github.com/juju/errors" 11 "github.com/juju/names/v5" 12 "github.com/juju/version/v2" 13 apps "k8s.io/api/apps/v1" 14 core "k8s.io/api/core/v1" 15 k8serrors "k8s.io/apimachinery/pkg/api/errors" 16 meta "k8s.io/apimachinery/pkg/apis/meta/v1" 17 appstyped "k8s.io/client-go/kubernetes/typed/apps/v1" 18 19 "github.com/juju/juju/caas" 20 "github.com/juju/juju/caas/kubernetes/provider/utils" 21 "github.com/juju/juju/cloudconfig/podcfg" 22 k8sannotations "github.com/juju/juju/core/annotations" 23 "github.com/juju/juju/core/paths" 24 ) 25 26 func (k *kubernetesClient) Upgrade(agentTag string, vers version.Number) error { 27 tag, err := names.ParseTag(agentTag) 28 if err != nil { 29 return errors.Annotate(err, "parsing agent tag to upgrade") 30 } 31 32 logger.Infof("handling upgrade request for tag %q to %s", tag, vers.String()) 33 34 switch tag.Kind() { 35 case names.MachineTagKind: 36 case names.ControllerAgentTagKind: 37 return k.upgradeController(vers) 38 case names.ApplicationTagKind: 39 return k.upgradeOperator(tag, vers) 40 case names.ModelTagKind: 41 return k.upgradeModelOperator(tag, vers) 42 case names.UnitTagKind: 43 // Sidecar charms don't have an upgrade step. 44 // See PR 14974 45 return nil 46 } 47 return errors.NotImplementedf("k8s upgrade for agent tag %q", agentTag) 48 } 49 50 func upgradeDeployment( 51 name, 52 imagePath string, 53 vers version.Number, 54 legacyLabels bool, 55 broker appstyped.DeploymentInterface, 56 ) error { 57 de, err := broker.Get(context.TODO(), name, meta.GetOptions{}) 58 if k8serrors.IsNotFound(err) { 59 return errors.NotFoundf( 60 "deployment %q", name) 61 } else if err != nil { 62 return errors.Annotatef(err, 63 "getting deployment to upgrade for name %q", name) 64 } 65 66 newInitContainers, err := upgradePodTemplateSpec(de.Spec.Template.Spec.InitContainers, imagePath, vers) 67 if err != nil { 68 return errors.Annotatef(err, "deployment %q", name) 69 } 70 de.Spec.Template.Spec.InitContainers = newInitContainers 71 72 newContainers, err := upgradePodTemplateSpec(de.Spec.Template.Spec.Containers, imagePath, vers) 73 if err != nil { 74 return errors.Annotatef(err, "deployment %q", name) 75 } 76 de.Spec.Template.Spec.Containers = newContainers 77 78 // update juju-version annotation. 79 // TODO(caas): consider how to upgrade to current annotations format safely. 80 // just ensure juju-version to current version for now. 81 de.SetAnnotations( 82 k8sannotations.New(de.GetAnnotations()). 83 Merge(utils.AnnotationsForVersion(vers.String(), legacyLabels)).ToMap(), 84 ) 85 de.Spec.Template.SetAnnotations( 86 k8sannotations.New(de.Spec.Template.GetAnnotations()). 87 Merge(utils.AnnotationsForVersion(vers.String(), legacyLabels)).ToMap(), 88 ) 89 90 if _, err := broker.Update(context.TODO(), de, meta.UpdateOptions{}); err != nil { 91 return errors.Annotatef(err, "updating deployment %q to %s", 92 name, vers) 93 } 94 return nil 95 } 96 97 func upgradeOperatorOrControllerStatefulSet( 98 appName string, 99 name string, 100 isOperator bool, 101 imagePath string, 102 baseImagePath string, 103 vers version.Number, 104 legacyLabels bool, 105 broker appstyped.StatefulSetInterface, 106 ) error { 107 ss, err := broker.Get(context.TODO(), name, meta.GetOptions{}) 108 if k8serrors.IsNotFound(err) { 109 return errors.NotFoundf( 110 "statefulset %q", name) 111 } else if err != nil { 112 return errors.Annotatef(err, 113 "getting statefulset to upgrade for name %q", name) 114 } 115 116 newInitContainers, err := upgradePodTemplateSpec(ss.Spec.Template.Spec.InitContainers, imagePath, vers) 117 if err != nil { 118 return errors.Annotatef(err, "statefulset %q", name) 119 } 120 ss.Spec.Template.Spec.InitContainers = newInitContainers 121 122 newContainers, err := upgradePodTemplateSpec(ss.Spec.Template.Spec.Containers, imagePath, vers) 123 if err != nil { 124 return errors.Annotatef(err, "statefulset %q", name) 125 } 126 ss.Spec.Template.Spec.Containers = newContainers 127 128 if isOperator { 129 err := patchOperatorToCharmBase(ss, appName, imagePath, baseImagePath) 130 if err != nil { 131 return errors.Annotatef(err, "unable to patch operator to charm base") 132 } 133 } 134 135 // update juju-version annotation. 136 // TODO(caas): consider how to upgrade to current annotations format safely. 137 // just ensure juju-version to current version for now. 138 ss.SetAnnotations( 139 k8sannotations.New(ss.GetAnnotations()). 140 Merge(utils.AnnotationsForVersion(vers.String(), legacyLabels)).ToMap(), 141 ) 142 ss.Spec.Template.SetAnnotations( 143 k8sannotations.New(ss.Spec.Template.GetAnnotations()). 144 Merge(utils.AnnotationsForVersion(vers.String(), legacyLabels)).ToMap(), 145 ) 146 147 if _, err := broker.Update(context.TODO(), ss, meta.UpdateOptions{}); err != nil { 148 return errors.Annotatef(err, "updating statefulset %q to %s", 149 name, vers) 150 } 151 return nil 152 } 153 154 func upgradePodTemplateSpec( 155 containers []core.Container, 156 imagePath string, 157 vers version.Number, 158 ) ([]core.Container, error) { 159 jujudContainerIdx, found := findJujudContainer(containers) 160 if !found { 161 return containers, nil 162 } 163 164 if imagePath == "" { 165 var err error 166 imagePath, err = podcfg.RebuildOldOperatorImagePath( 167 containers[jujudContainerIdx].Image, vers) 168 if err != nil { 169 return nil, errors.Trace(err) 170 } 171 } 172 173 containersCopy := append([]core.Container(nil), containers...) 174 if containersCopy[jujudContainerIdx].Image != imagePath { 175 containersCopy[jujudContainerIdx].Image = imagePath 176 } 177 return containersCopy, nil 178 } 179 180 func findJujudContainer(containers []core.Container) (int, bool) { 181 for i, c := range containers { 182 if podcfg.IsJujuOCIImage(c.Image) { 183 return i, true 184 } 185 } 186 return -1, false 187 } 188 189 func patchOperatorToCharmBase(ss *apps.StatefulSet, appName string, imagePath string, baseImagePath string) error { 190 for _, container := range ss.Spec.Template.Spec.InitContainers { 191 if container.Name == operatorInitContainerName { 192 // Already patched. 193 return nil 194 } 195 } 196 197 ss.Spec.Template.Spec.InitContainers = append(ss.Spec.Template.Spec.InitContainers, core.Container{ 198 Name: operatorInitContainerName, 199 ImagePullPolicy: core.PullIfNotPresent, 200 Image: imagePath, 201 Command: []string{ 202 "/bin/sh", 203 }, 204 Args: []string{ 205 "-c", 206 fmt.Sprintf( 207 caas.JujudCopySh, 208 "/opt/juju", 209 "", 210 ), 211 }, 212 VolumeMounts: []core.VolumeMount{{ 213 Name: "juju-bins", 214 MountPath: "/opt/juju", 215 }}, 216 }) 217 218 ss.Spec.Template.Spec.Volumes = append(ss.Spec.Template.Spec.Volumes, core.Volume{ 219 Name: "juju-bins", 220 VolumeSource: core.VolumeSource{ 221 EmptyDir: &core.EmptyDirVolumeSource{}, 222 }, 223 }) 224 225 for i, container := range ss.Spec.Template.Spec.Containers { 226 if !podcfg.IsJujuOCIImage(container.Image) { 227 continue 228 } 229 230 jujudCmd := fmt.Sprintf("exec $JUJU_TOOLS_DIR/jujud caasoperator --application-name=%s --debug", appName) 231 jujuDataDir := paths.DataDir(paths.OSUnixLike) 232 container.Image = baseImagePath 233 container.Args = []string{ 234 "-c", 235 fmt.Sprintf( 236 caas.JujudStartUpAltSh, 237 jujuDataDir, 238 "tools", 239 "/opt/juju", 240 jujudCmd, 241 ), 242 } 243 container.VolumeMounts = append(container.VolumeMounts, core.VolumeMount{ 244 Name: "juju-bins", 245 MountPath: "/opt/juju", 246 }) 247 248 ss.Spec.Template.Spec.Containers[i] = container 249 break 250 } 251 252 return nil 253 }