github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/imagedownloads/simplestreams_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package imagedownloads_test 5 6 import ( 7 "fmt" 8 "io" 9 "net/http" 10 "net/http/httptest" 11 "strings" 12 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "golang.org/x/crypto/openpgp" 16 openpgperrors "golang.org/x/crypto/openpgp/errors" 17 gc "gopkg.in/check.v1" 18 19 corebase "github.com/juju/juju/core/base" 20 . "github.com/juju/juju/environs/imagedownloads" 21 "github.com/juju/juju/environs/imagemetadata" 22 "github.com/juju/juju/environs/simplestreams" 23 streamstesting "github.com/juju/juju/environs/simplestreams/testing" 24 ) 25 26 type Suite struct { 27 testing.IsolationSuite 28 } 29 30 var _ = gc.Suite(&Suite{}) 31 32 func newTestDataSource(factory simplestreams.DataSourceFactory, s string) simplestreams.DataSource { 33 return NewDataSource(factory, s+"/"+imagemetadata.ReleasedImagesPath) 34 } 35 36 func newTestDataSourceFunc(s string) func() simplestreams.DataSource { 37 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 38 return func() simplestreams.DataSource { 39 return NewDataSource(ss, s+"/releases/") 40 } 41 } 42 43 func (s *Suite) SetUpTest(c *gc.C) { 44 s.PatchValue(&corebase.UbuntuDistroInfo, "/path/notexists") 45 imagemetadata.SimplestreamsImagesPublicKey = streamstesting.SignedMetadataPublicKey 46 47 // The index.sjson file used by these tests have been regenerated using 48 // the test keys in environs/simplestreams/testing/testing.go. As this 49 // signature is not trusted, we need to override the signature check 50 // implementation and suppress the ErrUnkownIssuer error. 51 s.PatchValue(&simplestreams.PGPSignatureCheckFn, func(keyring openpgp.KeyRing, signed, signature io.Reader) (*openpgp.Entity, error) { 52 ent, err := openpgp.CheckDetachedSignature(keyring, signed, signature) 53 c.Assert(err, gc.Equals, openpgperrors.ErrUnknownIssuer, gc.Commentf("expected the signature verification to return ErrUnknownIssuer when the index file is signed with the test pgp key")) 54 return ent, nil 55 }) 56 } 57 58 func (*Suite) TestNewSignedImagesSource(c *gc.C) { 59 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 60 got := DefaultSource(ss)() 61 c.Check(got.Description(), jc.DeepEquals, "ubuntu cloud images") 62 c.Check(got.PublicSigningKey(), jc.DeepEquals, imagemetadata.SimplestreamsImagesPublicKey) 63 c.Check(got.RequireSigned(), jc.IsTrue) 64 gotURL, err := got.URL("") 65 c.Assert(err, jc.ErrorIsNil) 66 c.Assert(gotURL, jc.DeepEquals, "http://cloud-images.ubuntu.com/releases/") 67 } 68 69 func (*Suite) TestFetchManyDefaultFilter(c *gc.C) { 70 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 71 ts := httptest.NewServer(&sstreamsHandler{}) 72 defer ts.Close() 73 tds := []simplestreams.DataSource{ 74 newTestDataSource(ss, ts.URL)} 75 constraints, err := imagemetadata.NewImageConstraint( 76 simplestreams.LookupParams{ 77 Arches: []string{"amd64", "arm64", "ppc64el"}, 78 Releases: []string{"16.04"}, 79 Stream: "released", 80 }, 81 ) 82 c.Assert(err, jc.ErrorIsNil) 83 got, resolveInfo, err := Fetch(ss, tds, constraints, nil) 84 c.Check(resolveInfo.Signed, jc.IsTrue) 85 c.Check(err, jc.ErrorIsNil) 86 c.Assert(len(got), jc.DeepEquals, 27) 87 for _, v := range got { 88 gotURL, err := v.DownloadURL(ts.URL) 89 c.Check(err, jc.ErrorIsNil) 90 c.Check(strings.HasSuffix(gotURL.String(), v.FType), jc.IsTrue) 91 c.Check(strings.Contains(gotURL.String(), v.Release), jc.IsTrue) 92 c.Check(strings.Contains(gotURL.String(), v.Version), jc.IsTrue) 93 } 94 } 95 96 func (*Suite) TestFetchManyDefaultFilterAndCustomImageDownloadURL(c *gc.C) { 97 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 98 ts := httptest.NewServer(&sstreamsHandler{}) 99 defer ts.Close() 100 tds := []simplestreams.DataSource{ 101 newTestDataSource(ss, ts.URL)} 102 constraints, err := imagemetadata.NewImageConstraint( 103 simplestreams.LookupParams{ 104 Arches: []string{"amd64", "arm64", "ppc64el"}, 105 Releases: []string{"16.04"}, 106 Stream: "released", 107 }, 108 ) 109 c.Assert(err, jc.ErrorIsNil) 110 got, resolveInfo, err := Fetch(ss, tds, constraints, nil) 111 c.Check(resolveInfo.Signed, jc.IsTrue) 112 c.Check(err, jc.ErrorIsNil) 113 c.Assert(len(got), jc.DeepEquals, 27) 114 for _, v := range got { 115 // Note: instead of the index URL, we are pulling the actual 116 // images from a different operator-provided URL. 117 gotURL, err := v.DownloadURL("https://tasty-cloud-images.ubuntu.com") 118 c.Check(err, jc.ErrorIsNil) 119 c.Check(strings.HasPrefix(gotURL.String(), "https://tasty-cloud-images.ubuntu.com"), jc.IsTrue, gc.Commentf("expected image download URL to use the operator-provided URL")) 120 c.Check(strings.HasSuffix(gotURL.String(), v.FType), jc.IsTrue) 121 c.Check(strings.Contains(gotURL.String(), v.Release), jc.IsTrue) 122 c.Check(strings.Contains(gotURL.String(), v.Version), jc.IsTrue) 123 } 124 } 125 126 func (*Suite) TestFetchSingleDefaultFilter(c *gc.C) { 127 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 128 ts := httptest.NewServer(&sstreamsHandler{}) 129 defer ts.Close() 130 tds := []simplestreams.DataSource{ 131 newTestDataSource(ss, ts.URL)} 132 constraints := &imagemetadata.ImageConstraint{ 133 LookupParams: simplestreams.LookupParams{ 134 Arches: []string{"ppc64el"}, 135 Releases: []string{"16.04"}, 136 }} 137 got, resolveInfo, err := Fetch(ss, tds, constraints, nil) 138 c.Check(resolveInfo.Signed, jc.IsTrue) 139 c.Check(err, jc.ErrorIsNil) 140 c.Assert(len(got), jc.DeepEquals, 8) 141 c.Check(got[0].Arch, jc.DeepEquals, "ppc64el") 142 c.Check(err, jc.ErrorIsNil) 143 for _, v := range got { 144 _, err := v.DownloadURL(ts.URL) 145 c.Check(err, jc.ErrorIsNil) 146 } 147 } 148 149 func (*Suite) TestFetchOneWithFilter(c *gc.C) { 150 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 151 ts := httptest.NewServer(&sstreamsHandler{}) 152 defer ts.Close() 153 tds := []simplestreams.DataSource{ 154 newTestDataSource(ss, ts.URL)} 155 constraints := &imagemetadata.ImageConstraint{ 156 LookupParams: simplestreams.LookupParams{ 157 Arches: []string{"ppc64el"}, 158 Releases: []string{"16.04"}, 159 }} 160 got, resolveInfo, err := Fetch(ss, tds, constraints, Filter("disk1.img")) 161 c.Check(resolveInfo.Signed, jc.IsTrue) 162 c.Check(err, jc.ErrorIsNil) 163 c.Assert(len(got), jc.DeepEquals, 1) 164 c.Check(got[0].Arch, jc.DeepEquals, "ppc64el") 165 // Assuming that the operator has not overridden the image download URL 166 // parameter we pass the default empty value which should fall back to 167 // the default cloud-images.ubuntu.com URL. 168 gotURL, err := got[0].DownloadURL("") 169 c.Assert(err, jc.ErrorIsNil) 170 c.Assert( 171 gotURL.String(), 172 jc.DeepEquals, 173 "http://cloud-images.ubuntu.com/server/releases/xenial/release-20211001/ubuntu-16.04-server-cloudimg-ppc64el-disk1.img") 174 } 175 176 func (*Suite) TestFetchManyWithFilter(c *gc.C) { 177 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 178 ts := httptest.NewServer(&sstreamsHandler{}) 179 defer ts.Close() 180 tds := []simplestreams.DataSource{ 181 newTestDataSource(ss, ts.URL)} 182 constraints := &imagemetadata.ImageConstraint{ 183 LookupParams: simplestreams.LookupParams{ 184 Arches: []string{"amd64", "arm64", "ppc64el"}, 185 Releases: []string{"16.04"}, 186 }} 187 got, resolveInfo, err := Fetch(ss, tds, constraints, Filter("disk1.img")) 188 c.Check(resolveInfo.Signed, jc.IsTrue) 189 c.Check(err, jc.ErrorIsNil) 190 c.Assert(len(got), jc.DeepEquals, 3) 191 c.Check(got[0].Arch, jc.DeepEquals, "amd64") 192 c.Check(got[1].Arch, jc.DeepEquals, "arm64") 193 c.Check(got[2].Arch, jc.DeepEquals, "ppc64el") 194 for i, arch := range []string{"amd64", "arm64", "ppc64el"} { 195 wantURL := fmt.Sprintf("http://cloud-images.ubuntu.com/server/releases/xenial/release-20211001/ubuntu-16.04-server-cloudimg-%s-disk1.img", arch) 196 // Assuming that the operator has not overridden the image 197 // download URL parameter we pass the default empty value which 198 // should fall back to the default cloud-images.ubuntu.com URL. 199 gotURL, err := got[i].DownloadURL("") 200 c.Check(err, jc.ErrorIsNil) 201 c.Check(gotURL.String(), jc.DeepEquals, wantURL) 202 } 203 } 204 205 func (*Suite) TestOneAmd64XenialTarGz(c *gc.C) { 206 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 207 ts := httptest.NewServer(&sstreamsHandler{}) 208 defer ts.Close() 209 got, err := One(ss, "amd64", "16.04", "", "tar.gz", newTestDataSourceFunc(ts.URL)) 210 c.Check(err, jc.ErrorIsNil) 211 c.Assert(got, jc.DeepEquals, &Metadata{ 212 Arch: "amd64", 213 Release: "xenial", 214 Version: "16.04", 215 FType: "tar.gz", 216 SHA256: "c48036699274351be132f2aec7fec9fd2da936b6b512c65b2d9fd6531e5623ea", 217 Path: "server/releases/xenial/release-20211001/ubuntu-16.04-server-cloudimg-amd64.tar.gz", 218 Size: 287684992, 219 }) 220 } 221 222 func (*Suite) TestOneArm64JammyImg(c *gc.C) { 223 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 224 ts := httptest.NewServer(&sstreamsHandler{}) 225 defer ts.Close() 226 got, err := One(ss, "arm64", "22.04", "released", "disk1.img", newTestDataSourceFunc(ts.URL)) 227 c.Check(err, jc.ErrorIsNil) 228 c.Assert(got, jc.DeepEquals, &Metadata{ 229 Arch: "arm64", 230 Release: "jammy", 231 Version: "22.04", 232 FType: "disk1.img", 233 SHA256: "78b5ca0da456b228e2441bdca0cca1eab30b1b6a3eaf9594eabcb2cfc21275f3", 234 Path: "server/releases/jammy/release-20220923/ubuntu-22.04-server-cloudimg-arm64.img", 235 Size: 642646016, 236 }) 237 } 238 239 func (*Suite) TestOneArm64FocalImg(c *gc.C) { 240 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 241 ts := httptest.NewServer(&sstreamsHandler{}) 242 defer ts.Close() 243 got, err := One(ss, "arm64", "20.04", "released", "disk1.img", newTestDataSourceFunc(ts.URL)) 244 c.Check(err, jc.ErrorIsNil) 245 c.Assert(got, jc.DeepEquals, &Metadata{ 246 Arch: "arm64", 247 Release: "focal", 248 Version: "20.04", 249 FType: "disk1.img", 250 SHA256: "b8176161962c4f54e59366444bb696e92406823f643ed7bdcdd3d15d38dc0d53", 251 Path: "server/releases/focal/release-20221003/ubuntu-20.04-server-cloudimg-arm64.img", 252 Size: 569901056, 253 }) 254 } 255 256 func (*Suite) TestOneErrors(c *gc.C) { 257 ss := simplestreams.NewSimpleStreams(streamstesting.TestDataSourceFactory()) 258 table := []struct { 259 description, arch, version, stream, ftype, errorMatch string 260 }{ 261 {"empty arch", "", "20.04", "", "disk1.img", `invalid parameters supplied arch=""`}, 262 {"invalid arch", "<F7>", "20.04", "", "disk1.img", `invalid parameters supplied arch="<F7>"`}, 263 {"empty series", "arm64", "", "released", "disk1.img", `invalid parameters supplied version=""`}, 264 {"invalid series", "amd64", "rusty", "", "disk1.img", `invalid parameters supplied version="rusty"`}, 265 {"empty ftype", "ppc64el", "20.04", "", "", `invalid parameters supplied ftype=""`}, 266 {"invalid file type", "amd64", "22.04", "", "tragedy", `invalid parameters supplied ftype="tragedy"`}, 267 {"all wrong except stream", "a", "t", "", "y", `invalid parameters supplied arch="a" version="t" ftype="y"`}, 268 {"stream not found", "amd64", "22.04", "hourly", "disk1.img", `no results for "amd64", "22.04", "hourly", "disk1.img"`}, 269 } 270 ts := httptest.NewServer(&sstreamsHandler{}) 271 defer ts.Close() 272 for i, test := range table { 273 c.Logf("test % 1d: %s\n", i+1, test.description) 274 _, err := One(ss, test.arch, test.version, test.stream, test.ftype, newTestDataSourceFunc(ts.URL)) 275 c.Check(err, gc.ErrorMatches, test.errorMatch) 276 } 277 } 278 279 type sstreamsHandler struct{} 280 281 func (h sstreamsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 282 switch r.URL.Path { 283 case "/releases/streams/v1/index.sjson": 284 w.Header().Set("Content-Type", "application/json") 285 http.ServeFile(w, r, "testdata/index.sjson") 286 return 287 case "/releases/streams/v1/com.ubuntu.cloud:released:download.sjson": 288 w.Header().Set("Content-Type", "application/json") 289 http.ServeFile(w, r, "testdata/com.ubuntu.cloud-released-download.sjson") 290 return 291 default: 292 http.Error(w, r.URL.Path, 404) 293 return 294 } 295 }