github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/lxd/image.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd 5 6 import ( 7 "fmt" 8 "path" 9 10 "github.com/juju/errors" 11 "github.com/juju/juju/core/status" 12 "github.com/juju/juju/environs" 13 jujuarch "github.com/juju/utils/arch" 14 jujuos "github.com/juju/utils/os" 15 jujuseries "github.com/juju/utils/series" 16 "github.com/lxc/lxd/client" 17 "github.com/lxc/lxd/shared/api" 18 ) 19 20 // SourcedImage is the result of a successful image acquisition. 21 // It includes the relevant data that located the image. 22 type SourcedImage struct { 23 // Image is the actual image data that was located. 24 Image *api.Image 25 // LXDServer is the image server that supplied the image. 26 LXDServer lxd.ImageServer 27 } 28 29 // FindImage searches the input sources in supplied order, looking for an OS 30 // image matching the supplied series and architecture. 31 // If found, the image and the server from which it was acquired are returned. 32 // If the server is remote the image will be cached by LXD when used to create 33 // a container. 34 // Supplying true for copyLocal will copy the image to the local cache. 35 // Copied images will have the juju/series/arch alias added to them. 36 // The callback argument is used to report copy progress. 37 func (s *Server) FindImage( 38 series, arch string, 39 sources []ServerSpec, 40 copyLocal bool, 41 callback environs.StatusCallbackFunc, 42 ) (SourcedImage, error) { 43 if callback != nil { 44 callback(status.Provisioning, "acquiring LXD image", nil) 45 } 46 47 // First we check if we have the image locally. 48 localAlias := seriesLocalAlias(series, arch) 49 var target string 50 entry, _, err := s.GetImageAlias(localAlias) 51 if entry != nil { 52 // We already have an image with the given alias, so just use that. 53 target = entry.Target 54 image, _, err := s.GetImage(target) 55 if err == nil { 56 logger.Debugf("Found image locally - %q %q", image.Filename, target) 57 return SourcedImage{ 58 Image: image, 59 LXDServer: s.ContainerServer, 60 }, nil 61 } 62 } 63 64 sourced := SourcedImage{} 65 lastErr := fmt.Errorf("no matching image found") 66 67 // We don't have an image locally with the juju-specific alias, 68 // so look in each of the provided remote sources for any of the aliases 69 // that might identify the image we want. 70 aliases, err := seriesRemoteAliases(series, arch) 71 if err != nil { 72 return sourced, errors.Trace(err) 73 } 74 for _, remote := range sources { 75 source, err := ConnectImageRemote(remote) 76 if err != nil { 77 logger.Infof("failed to connect to %q: %s", remote.Host, err) 78 lastErr = errors.Trace(err) 79 continue 80 } 81 for _, alias := range aliases { 82 if result, _, err := source.GetImageAlias(alias); err == nil && result != nil && result.Target != "" { 83 target = result.Target 84 break 85 } 86 } 87 if target != "" { 88 image, _, err := source.GetImage(target) 89 if err == nil { 90 logger.Debugf("Found image remotely - %q %q %q", remote.Name, image.Filename, target) 91 sourced.Image = image 92 sourced.LXDServer = source 93 break 94 } else { 95 lastErr = errors.Trace(err) 96 } 97 } 98 } 99 100 if sourced.Image == nil { 101 return sourced, lastErr 102 } 103 104 // If requested, copy the image to the local cache, adding the local alias. 105 if copyLocal { 106 if err := s.CopyRemoteImage(sourced, []string{localAlias}, callback); err != nil { 107 return sourced, errors.Trace(err) 108 } 109 110 // Now that we have the image cached locally, we indicate in the return 111 // that the source is local instead of the remote where we found it. 112 sourced.LXDServer = s.ContainerServer 113 } 114 115 return sourced, nil 116 } 117 118 // CopyRemoteImage accepts an image sourced from a remote server and copies it 119 // to the local cache 120 func (s *Server) CopyRemoteImage( 121 sourced SourcedImage, aliases []string, callback environs.StatusCallbackFunc, 122 ) error { 123 logger.Debugf("Copying image from remote server") 124 125 newAliases := make([]api.ImageAlias, len(aliases)) 126 for i, a := range aliases { 127 newAliases[i] = api.ImageAlias{Name: a} 128 } 129 130 req := &lxd.ImageCopyArgs{Aliases: newAliases} 131 op, err := s.CopyImage(sourced.LXDServer, *sourced.Image, req) 132 if err != nil { 133 return errors.Trace(err) 134 } 135 136 // Report progress via callback if supplied. 137 if callback != nil { 138 progress := func(op api.Operation) { 139 if op.Metadata == nil { 140 return 141 } 142 for _, key := range []string{"fs_progress", "download_progress"} { 143 if value, ok := op.Metadata[key]; ok { 144 callback(status.Provisioning, fmt.Sprintf("Retrieving image: %s", value.(string)), nil) 145 return 146 } 147 } 148 } 149 _, err = op.AddHandler(progress) 150 if err != nil { 151 return errors.Trace(err) 152 } 153 } 154 155 if err := op.Wait(); err != nil { 156 return errors.Trace(err) 157 } 158 opInfo, err := op.GetTarget() 159 if err != nil { 160 return errors.Trace(err) 161 } 162 if opInfo.StatusCode != api.Success { 163 return fmt.Errorf("image copy failed: %s", opInfo.Err) 164 } 165 return nil 166 } 167 168 // seriesLocalAlias returns the alias to assign to images for the 169 // specified series. The alias is juju-specific, to support the 170 // user supplying a customised image (e.g. CentOS with cloud-init). 171 func seriesLocalAlias(series, arch string) string { 172 return fmt.Sprintf("juju/%s/%s", series, arch) 173 } 174 175 // seriesRemoteAliases returns the aliases to look for in remotes. 176 func seriesRemoteAliases(series, arch string) ([]string, error) { 177 seriesOS, err := jujuseries.GetOSFromSeries(series) 178 if err != nil { 179 return nil, errors.Trace(err) 180 } 181 switch seriesOS { 182 case jujuos.Ubuntu: 183 return []string{path.Join(series, arch)}, nil 184 case jujuos.CentOS: 185 if series == "centos7" && arch == jujuarch.AMD64 { 186 return []string{"centos/7/amd64"}, nil 187 } 188 case jujuos.OpenSUSE: 189 if series == "opensuseleap" && arch == jujuarch.AMD64 { 190 return []string{"opensuse/42.2/amd64"}, nil 191 } 192 } 193 return nil, errors.NotSupportedf("series %q", series) 194 }