github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/tools/simplestreams_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package tools_test
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"flag"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"os"
    15  	"path/filepath"
    16  	"reflect"
    17  	"strings"
    18  	"testing"
    19  
    20  	"github.com/aws/aws-sdk-go-v2/service/ec2"
    21  	"github.com/juju/errors"
    22  	jc "github.com/juju/testing/checkers"
    23  	"github.com/juju/version/v2"
    24  	gc "gopkg.in/check.v1"
    25  
    26  	corebase "github.com/juju/juju/core/base"
    27  	coreos "github.com/juju/juju/core/os"
    28  	"github.com/juju/juju/environs/filestorage"
    29  	"github.com/juju/juju/environs/simplestreams"
    30  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    31  	"github.com/juju/juju/environs/storage"
    32  	"github.com/juju/juju/environs/tools"
    33  	toolstesting "github.com/juju/juju/environs/tools/testing"
    34  	"github.com/juju/juju/juju/keys"
    35  	coretesting "github.com/juju/juju/testing"
    36  	coretools "github.com/juju/juju/tools"
    37  )
    38  
    39  var live = flag.Bool("live", false, "Include live simplestreams tests")
    40  var vendor = flag.String("vendor", "", "The vendor representing the source of the simplestream data")
    41  
    42  type liveTestData struct {
    43  	baseURL        string
    44  	requireSigned  bool
    45  	validCloudSpec simplestreams.CloudSpec
    46  }
    47  
    48  func getLiveURLs() (map[string]liveTestData, error) {
    49  	resolver := ec2.NewDefaultEndpointResolver()
    50  	ep, err := resolver.ResolveEndpoint("us-east-1", ec2.EndpointResolverOptions{})
    51  	if err != nil {
    52  		return nil, errors.Trace(err)
    53  	}
    54  
    55  	return map[string]liveTestData{
    56  		"ec2": {
    57  			baseURL:       tools.DefaultBaseURL,
    58  			requireSigned: true,
    59  			validCloudSpec: simplestreams.CloudSpec{
    60  				Region:   "us-east-1",
    61  				Endpoint: ep.URL,
    62  			},
    63  		},
    64  		"canonistack": {
    65  			baseURL:       "https://swift.canonistack.canonical.com/v1/AUTH_526ad877f3e3464589dc1145dfeaac60/juju-tools",
    66  			requireSigned: false,
    67  			validCloudSpec: simplestreams.CloudSpec{
    68  				Region:   "lcy01",
    69  				Endpoint: "https://keystone.canonistack.canonical.com:443/v1.0/",
    70  			},
    71  		},
    72  	}, nil
    73  }
    74  
    75  func setupSimpleStreamsTests(t *testing.T) {
    76  	if *live {
    77  		if *vendor == "" {
    78  			t.Fatal("missing vendor")
    79  		}
    80  		var ok bool
    81  		var testData liveTestData
    82  		liveURLs, err := getLiveURLs()
    83  		if err != nil {
    84  			t.Fatalf(err.Error())
    85  		}
    86  		if testData, ok = liveURLs[*vendor]; !ok {
    87  			keys := reflect.ValueOf(liveURLs).MapKeys()
    88  			t.Fatalf("Unknown vendor %s. Must be one of %s", *vendor, keys)
    89  		}
    90  		registerLiveSimpleStreamsTests(testData.baseURL,
    91  			tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
    92  				CloudSpec: testData.validCloudSpec,
    93  				Releases:  []string{coreos.HostOSTypeName()},
    94  				Arches:    []string{"amd64"},
    95  				Stream:    "released",
    96  			}), testData.requireSigned)
    97  	}
    98  	registerSimpleStreamsTests()
    99  }
   100  
   101  func registerSimpleStreamsTests() {
   102  	gc.Suite(&simplestreamsSuite{
   103  		LocalLiveSimplestreamsSuite: sstesting.LocalLiveSimplestreamsSuite{
   104  			Source:         sstesting.VerifyDefaultCloudDataSource("test", "test:"),
   105  			RequireSigned:  false,
   106  			DataType:       tools.ContentDownload,
   107  			StreamsVersion: tools.CurrentStreamsVersion,
   108  			ValidConstraint: tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   109  				CloudSpec: simplestreams.CloudSpec{
   110  					Region:   "us-east-1",
   111  					Endpoint: "https://ec2.us-east-1.amazonaws.com",
   112  				},
   113  				Releases: []string{"ubuntu"},
   114  				Arches:   []string{"amd64", "arm"},
   115  				Stream:   "released",
   116  			}),
   117  		},
   118  	})
   119  	gc.Suite(&signedSuite{})
   120  }
   121  
   122  func registerLiveSimpleStreamsTests(baseURL string, validToolsConstraint simplestreams.LookupConstraint, requireSigned bool) {
   123  	factory := sstesting.TestDataSourceFactory()
   124  	gc.Suite(&sstesting.LocalLiveSimplestreamsSuite{
   125  		Source: factory.NewDataSource(simplestreams.Config{
   126  			Description:          "test",
   127  			BaseURL:              baseURL,
   128  			HostnameVerification: true,
   129  			Priority:             simplestreams.DEFAULT_CLOUD_DATA,
   130  			RequireSigned:        requireSigned,
   131  		}),
   132  		RequireSigned:   requireSigned,
   133  		DataType:        tools.ContentDownload,
   134  		StreamsVersion:  tools.CurrentStreamsVersion,
   135  		ValidConstraint: validToolsConstraint,
   136  	})
   137  }
   138  
   139  type simplestreamsSuite struct {
   140  	sstesting.LocalLiveSimplestreamsSuite
   141  	sstesting.TestDataSuite
   142  }
   143  
   144  func (s *simplestreamsSuite) SetUpSuite(c *gc.C) {
   145  	s.LocalLiveSimplestreamsSuite.SetUpSuite(c)
   146  	s.TestDataSuite.SetUpSuite(c)
   147  	s.PatchValue(&corebase.UbuntuDistroInfo, "/path/notexists")
   148  }
   149  
   150  func (s *simplestreamsSuite) TearDownSuite(c *gc.C) {
   151  	s.TestDataSuite.TearDownSuite(c)
   152  	s.LocalLiveSimplestreamsSuite.TearDownSuite(c)
   153  }
   154  
   155  var fetchTests = []struct {
   156  	region  string
   157  	osType  string
   158  	version string
   159  	stream  string
   160  	major   int
   161  	minor   int
   162  	arches  []string
   163  	tools   []*tools.ToolsMetadata
   164  }{{
   165  	osType:  "ubuntu",
   166  	arches:  []string{"amd64", "arm"},
   167  	version: "1.13.0",
   168  	tools: []*tools.ToolsMetadata{
   169  		{
   170  			Release:  "ubuntu",
   171  			Version:  "1.13.0",
   172  			Arch:     "amd64",
   173  			Size:     2973595,
   174  			Path:     "tools/released/20130806/juju-1.13.0-ubuntu-amd64.tgz",
   175  			FileType: "tar.gz",
   176  			SHA256:   "447aeb6a934a5eaec4f703eda4ef2dde",
   177  		},
   178  	},
   179  }, {
   180  	osType:  "ubuntu",
   181  	arches:  []string{"amd64", "arm"},
   182  	version: "1.11.4",
   183  	tools: []*tools.ToolsMetadata{
   184  		{
   185  			Release:  "ubuntu",
   186  			Version:  "1.11.4",
   187  			Arch:     "arm",
   188  			Size:     1951096,
   189  			Path:     "tools/released/20130806/juju-1.11.4-ubuntu-arm.tgz",
   190  			FileType: "tar.gz",
   191  			SHA256:   "f65a92b3b41311bdf398663ee1c5cd0c",
   192  		},
   193  	},
   194  }, {
   195  	osType: "ubuntu",
   196  	arches: []string{"amd64", "arm"},
   197  	major:  2,
   198  	tools: []*tools.ToolsMetadata{
   199  		{
   200  			Release:  "ubuntu",
   201  			Version:  "2.0.1",
   202  			Arch:     "arm",
   203  			Size:     1951096,
   204  			Path:     "tools/released/20130806/juju-2.0.1-ubuntu-arm.tgz",
   205  			FileType: "tar.gz",
   206  			SHA256:   "f65a92b3b41311bdf398663ee1c5cd0c",
   207  		},
   208  	},
   209  }, {
   210  	osType: "ubuntu",
   211  	arches: []string{"amd64", "arm"},
   212  	major:  1,
   213  	minor:  11,
   214  	tools: []*tools.ToolsMetadata{
   215  		{
   216  			Release:  "ubuntu",
   217  			Version:  "1.11.4",
   218  			Arch:     "arm",
   219  			Size:     1951096,
   220  			Path:     "tools/released/20130806/juju-1.11.4-ubuntu-arm.tgz",
   221  			FileType: "tar.gz",
   222  			SHA256:   "f65a92b3b41311bdf398663ee1c5cd0c",
   223  		},
   224  		{
   225  			Release:  "ubuntu",
   226  			Version:  "1.11.5",
   227  			Arch:     "arm",
   228  			Size:     2031281,
   229  			Path:     "tools/released/20130803/juju-1.11.5-ubuntu-arm.tgz",
   230  			FileType: "tar.gz",
   231  			SHA256:   "df07ac5e1fb4232d4e9aa2effa57918a",
   232  		},
   233  	},
   234  }, {
   235  	osType:  "ubuntu",
   236  	arches:  []string{"amd64"},
   237  	version: "1.16.0",
   238  	stream:  "testing",
   239  	tools: []*tools.ToolsMetadata{
   240  		{
   241  			Release:  "ubuntu",
   242  			Version:  "1.16.0",
   243  			Arch:     "amd64",
   244  			Size:     2973512,
   245  			Path:     "tools/testing/20130806/juju-1.16.0-ubuntu-amd64.tgz",
   246  			FileType: "tar.gz",
   247  			SHA256:   "447aeb6a934a5eaec4f703eda4ef2dac",
   248  		},
   249  	},
   250  }}
   251  
   252  func (s *simplestreamsSuite) TestFetch(c *gc.C) {
   253  	for i, t := range fetchTests {
   254  		c.Logf("test %d", i)
   255  		if t.stream == "" {
   256  			t.stream = "released"
   257  		}
   258  		var toolsConstraint *tools.ToolsConstraint
   259  		if t.version == "" {
   260  			toolsConstraint = tools.NewGeneralToolsConstraint(t.major, t.minor, simplestreams.LookupParams{
   261  				CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   262  				Releases:  []string{t.osType},
   263  				Arches:    t.arches,
   264  				Stream:    t.stream,
   265  			})
   266  		} else {
   267  			toolsConstraint = tools.NewVersionedToolsConstraint(version.MustParse(t.version),
   268  				simplestreams.LookupParams{
   269  					CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   270  					Releases:  []string{t.osType},
   271  					Arches:    t.arches,
   272  					Stream:    t.stream,
   273  				})
   274  		}
   275  		// Add invalid datasource and check later that resolveInfo is correct.
   276  		invalidSource := sstesting.InvalidDataSource(s.RequireSigned)
   277  		ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   278  		toolsMetadata, resolveInfo, err := tools.Fetch(ss, []simplestreams.DataSource{invalidSource, s.Source}, toolsConstraint)
   279  		if !c.Check(err, jc.ErrorIsNil) {
   280  			continue
   281  		}
   282  		for _, tm := range t.tools {
   283  			tm.FullPath, err = s.Source.URL(tm.Path)
   284  			c.Assert(err, jc.ErrorIsNil)
   285  		}
   286  		c.Check(toolsMetadata, gc.DeepEquals, t.tools)
   287  		c.Check(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   288  			Source:    "test",
   289  			Signed:    s.RequireSigned,
   290  			IndexURL:  "test:/streams/v1/index.json",
   291  			MirrorURL: "",
   292  		})
   293  	}
   294  }
   295  
   296  func (s *simplestreamsSuite) TestFetchNoMatchingStream(c *gc.C) {
   297  	toolsConstraint := tools.NewGeneralToolsConstraint(2, -1, simplestreams.LookupParams{
   298  		CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
   299  		Releases:  []string{"ubuntu"},
   300  		Arches:    []string{},
   301  		Stream:    "proposed",
   302  	})
   303  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   304  	_, _, err := tools.Fetch(ss,
   305  		[]simplestreams.DataSource{s.Source}, toolsConstraint)
   306  	c.Assert(err, gc.ErrorMatches, `"content-download" data not found`)
   307  }
   308  
   309  func (s *simplestreamsSuite) TestFetchWithMirror(c *gc.C) {
   310  	toolsConstraint := tools.NewGeneralToolsConstraint(1, 13, simplestreams.LookupParams{
   311  		CloudSpec: simplestreams.CloudSpec{"us-west-2", "https://ec2.us-west-2.amazonaws.com"},
   312  		Releases:  []string{"ubuntu"},
   313  		Arches:    []string{"amd64"},
   314  		Stream:    "released",
   315  	})
   316  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   317  	toolsMetadata, resolveInfo, err := tools.Fetch(ss,
   318  		[]simplestreams.DataSource{s.Source}, toolsConstraint)
   319  	c.Assert(err, jc.ErrorIsNil)
   320  	c.Assert(len(toolsMetadata), gc.Equals, 1)
   321  
   322  	expectedMetadata := &tools.ToolsMetadata{
   323  		Release:  "ubuntu",
   324  		Version:  "1.13.0",
   325  		Arch:     "amd64",
   326  		Size:     2973595,
   327  		Path:     "mirrored-path/juju-1.13.0-ubuntu-amd64.tgz",
   328  		FullPath: "test:/mirrored-path/juju-1.13.0-ubuntu-amd64.tgz",
   329  		FileType: "tar.gz",
   330  		SHA256:   "447aeb6a934a5eaec4f703eda4ef2dde",
   331  	}
   332  	c.Assert(err, jc.ErrorIsNil)
   333  	c.Assert(toolsMetadata[0], gc.DeepEquals, expectedMetadata)
   334  	c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
   335  		Source:    "test",
   336  		Signed:    s.RequireSigned,
   337  		IndexURL:  "test:/streams/v1/index.json",
   338  		MirrorURL: "test:/",
   339  	})
   340  }
   341  
   342  func assertMetadataMatches(c *gc.C, stream string, toolList coretools.List, metadata []*tools.ToolsMetadata) {
   343  	var expectedMetadata = make([]*tools.ToolsMetadata, len(toolList))
   344  	for i, tool := range toolList {
   345  		expectedMetadata[i] = &tools.ToolsMetadata{
   346  			Release:  tool.Version.Release,
   347  			Version:  tool.Version.Number.String(),
   348  			Arch:     tool.Version.Arch,
   349  			Size:     tool.Size,
   350  			Path:     fmt.Sprintf("%s/juju-%s.tgz", stream, tool.Version.String()),
   351  			FileType: "tar.gz",
   352  			SHA256:   tool.SHA256,
   353  		}
   354  	}
   355  	c.Assert(metadata, gc.DeepEquals, expectedMetadata)
   356  }
   357  
   358  func (s *simplestreamsSuite) TestWriteMetadataNoFetch(c *gc.C) {
   359  	toolsList := coretools.List{
   360  		{
   361  			Version: version.MustParseBinary("1.2.3-ubuntu-amd64"),
   362  			Size:    123,
   363  			SHA256:  "abcd",
   364  		}, {
   365  			Version: version.MustParseBinary("2.0.1-windows-amd64"),
   366  			Size:    456,
   367  			SHA256:  "xyz",
   368  		},
   369  	}
   370  	expected := toolsList
   371  
   372  	// Add tools with an unknown osType.
   373  	// We need to support this case for times when a new Ubuntu os type
   374  	// is released and jujud does not know about it yet.
   375  	vers, err := version.ParseBinary("3.2.1-xuanhuaceratops-amd64")
   376  	c.Assert(err, jc.ErrorIsNil)
   377  	toolsList = append(toolsList, &coretools.Tools{
   378  		Version: vers,
   379  		Size:    456,
   380  		SHA256:  "wqe",
   381  	})
   382  
   383  	dir := c.MkDir()
   384  	writer, err := filestorage.NewFileStorageWriter(dir)
   385  	c.Assert(err, jc.ErrorIsNil)
   386  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   387  	err = tools.MergeAndWriteMetadata(ss, writer, "proposed", "proposed", toolsList, tools.DoNotWriteMirrors)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	metadata := toolstesting.ParseMetadataFromDir(c, dir, "proposed", false)
   390  	assertMetadataMatches(c, "proposed", expected, metadata)
   391  }
   392  
   393  func (s *simplestreamsSuite) assertWriteMetadata(c *gc.C, withMirrors bool) {
   394  	var versionStrings = []string{
   395  		"1.2.3-ubuntu-amd64",
   396  		"2.0.1-ubuntu-amd64",
   397  	}
   398  	dir := c.MkDir()
   399  	toolstesting.MakeTools(c, dir, "proposed", versionStrings)
   400  
   401  	toolsList := coretools.List{
   402  		{
   403  			// If sha256/size is already known, do not recalculate
   404  			Version: version.MustParseBinary("1.2.3-ubuntu-amd64"),
   405  			Size:    123,
   406  			SHA256:  "abcd",
   407  		}, {
   408  			Version: version.MustParseBinary("2.0.1-ubuntu-amd64"),
   409  			// The URL is not used for generating metadata.
   410  			URL: "bogus://",
   411  		},
   412  	}
   413  	writer, err := filestorage.NewFileStorageWriter(dir)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	writeMirrors := tools.DoNotWriteMirrors
   416  	if withMirrors {
   417  		writeMirrors = tools.WriteMirrors
   418  	}
   419  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   420  	err = tools.MergeAndWriteMetadata(ss, writer, "proposed", "proposed", toolsList, writeMirrors)
   421  	c.Assert(err, jc.ErrorIsNil)
   422  
   423  	metadata := toolstesting.ParseMetadataFromDir(c, dir, "proposed", withMirrors)
   424  	assertMetadataMatches(c, "proposed", toolsList, metadata)
   425  
   426  	// No release stream generated so there will not be a legacy index file created.
   427  	_, err = writer.Get("tools/streams/v1/index.json")
   428  	c.Assert(err, gc.NotNil)
   429  }
   430  
   431  func (s *simplestreamsSuite) TestWriteMetadata(c *gc.C) {
   432  	s.assertWriteMetadata(c, false)
   433  }
   434  
   435  func (s *simplestreamsSuite) TestWriteMetadataWithMirrors(c *gc.C) {
   436  	s.assertWriteMetadata(c, true)
   437  }
   438  
   439  func (s *simplestreamsSuite) TestWriteMetadataMergeWithExisting(c *gc.C) {
   440  	dir := c.MkDir()
   441  	existingToolsList := coretools.List{
   442  		{
   443  			Version: version.MustParseBinary("1.2.3-ubuntu-amd64"),
   444  			Size:    123,
   445  			SHA256:  "abc",
   446  		}, {
   447  			Version: version.MustParseBinary("2.0.1-ubuntu-amd64"),
   448  			Size:    456,
   449  			SHA256:  "xyz",
   450  		},
   451  	}
   452  	writer, err := filestorage.NewFileStorageWriter(dir)
   453  	c.Assert(err, jc.ErrorIsNil)
   454  
   455  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   456  	err = tools.MergeAndWriteMetadata(ss, writer, "testing", "testing", existingToolsList, tools.WriteMirrors)
   457  	c.Assert(err, jc.ErrorIsNil)
   458  
   459  	newToolsList := coretools.List{
   460  		existingToolsList[0],
   461  		{
   462  			Version: version.MustParseBinary("2.1.0-ubuntu-amd64"),
   463  			Size:    789,
   464  			SHA256:  "def",
   465  		},
   466  	}
   467  	err = tools.MergeAndWriteMetadata(ss, writer, "testing", "testing", newToolsList, tools.WriteMirrors)
   468  	c.Assert(err, jc.ErrorIsNil)
   469  	requiredToolsList := append(existingToolsList, newToolsList[1])
   470  	metadata := toolstesting.ParseMetadataFromDir(c, dir, "testing", true)
   471  	assertMetadataMatches(c, "testing", requiredToolsList, metadata)
   472  
   473  	err = tools.MergeAndWriteMetadata(ss, writer, "devel", "devel", newToolsList, tools.WriteMirrors)
   474  	c.Assert(err, jc.ErrorIsNil)
   475  
   476  	metadata = toolstesting.ParseMetadataFromDir(c, dir, "testing", true)
   477  	assertMetadataMatches(c, "testing", requiredToolsList, metadata)
   478  
   479  	metadata = toolstesting.ParseMetadataFromDir(c, dir, "devel", true)
   480  	assertMetadataMatches(c, "devel", newToolsList, metadata)
   481  }
   482  
   483  type productSpecSuite struct{}
   484  
   485  var _ = gc.Suite(&productSpecSuite{})
   486  
   487  func (s *productSpecSuite) TestIndexIdNoStream(c *gc.C) {
   488  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   489  		Releases: []string{"ubuntu"},
   490  		Arches:   []string{"amd64"},
   491  	})
   492  	ids := toolsConstraint.IndexIds()
   493  	c.Assert(ids, gc.HasLen, 0)
   494  }
   495  
   496  func (s *productSpecSuite) TestIndexId(c *gc.C) {
   497  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   498  		Releases: []string{"ubuntu"},
   499  		Arches:   []string{"amd64"},
   500  		Stream:   "proposed",
   501  	})
   502  	ids := toolsConstraint.IndexIds()
   503  	c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:proposed:agents"})
   504  }
   505  
   506  func (s *productSpecSuite) TestProductId(c *gc.C) {
   507  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
   508  		Releases: []string{"ubuntu"},
   509  		Arches:   []string{"amd64"},
   510  	})
   511  	ids, err := toolsConstraint.ProductIds()
   512  	c.Assert(err, jc.ErrorIsNil)
   513  	c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:ubuntu:amd64"})
   514  }
   515  
   516  func (s *productSpecSuite) TestIdMultiArch(c *gc.C) {
   517  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{
   518  		Releases: []string{"ubuntu"},
   519  		Arches:   []string{"amd64", "arm"},
   520  	})
   521  	ids, err := toolsConstraint.ProductIds()
   522  	c.Assert(err, jc.ErrorIsNil)
   523  	c.Assert(ids, gc.DeepEquals, []string{
   524  		"com.ubuntu.juju:ubuntu:amd64",
   525  		"com.ubuntu.juju:ubuntu:arm"})
   526  }
   527  
   528  func (s *productSpecSuite) TestIdMultiOSType(c *gc.C) {
   529  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{
   530  		Releases: []string{"ubuntu", "windows"},
   531  		Arches:   []string{"amd64"},
   532  		Stream:   "released",
   533  	})
   534  	ids, err := toolsConstraint.ProductIds()
   535  	c.Assert(err, jc.ErrorIsNil)
   536  	c.Assert(ids, gc.DeepEquals, []string{
   537  		"com.ubuntu.juju:ubuntu:amd64",
   538  		"com.ubuntu.juju:windows:amd64"})
   539  }
   540  
   541  func (s *productSpecSuite) TestIdIgnoresInvalidOSType(c *gc.C) {
   542  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.11.3"), simplestreams.LookupParams{
   543  		Releases: []string{"ubuntu", "foobar"},
   544  		Arches:   []string{"amd64"},
   545  		Stream:   "released",
   546  	})
   547  	ids, err := toolsConstraint.ProductIds()
   548  	c.Assert(err, jc.ErrorIsNil)
   549  	c.Assert(ids, gc.DeepEquals, []string{"com.ubuntu.juju:ubuntu:amd64"})
   550  }
   551  
   552  func (s *productSpecSuite) TestIdWithMajorVersionOnly(c *gc.C) {
   553  	toolsConstraint := tools.NewGeneralToolsConstraint(1, -1, simplestreams.LookupParams{
   554  		Releases: []string{"ubuntu"},
   555  		Arches:   []string{"amd64"},
   556  		Stream:   "released",
   557  	})
   558  	ids, err := toolsConstraint.ProductIds()
   559  	c.Assert(err, jc.ErrorIsNil)
   560  	c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:ubuntu:amd64`})
   561  }
   562  
   563  func (s *productSpecSuite) TestIdWithMajorMinorVersion(c *gc.C) {
   564  	toolsConstraint := tools.NewGeneralToolsConstraint(1, 2, simplestreams.LookupParams{
   565  		Releases: []string{"ubuntu"},
   566  		Arches:   []string{"amd64"},
   567  		Stream:   "released",
   568  	})
   569  	ids, err := toolsConstraint.ProductIds()
   570  	c.Assert(err, jc.ErrorIsNil)
   571  	c.Assert(ids, gc.DeepEquals, []string{`com.ubuntu.juju:ubuntu:amd64`})
   572  }
   573  
   574  func (s *productSpecSuite) TestLargeNumber(c *gc.C) {
   575  	json := `{
   576          "updated": "Fri, 30 Aug 2013 16:12:58 +0800",
   577          "format": "products:1.0",
   578          "products": {
   579              "com.ubuntu.juju:1.10.0:amd64": {
   580                  "version": "1.10.0",
   581                  "arch": "amd64",
   582                  "versions": {
   583                      "20133008": {
   584                          "items": {
   585                              "1.10.0-ubuntu-amd64": {
   586                                  "release": "ubuntu",
   587                                  "version": "1.10.0",
   588                                  "arch": "amd64",
   589                                  "size": 9223372036854775807,
   590                                  "path": "releases/juju-1.10.0-ubuntu-amd64.tgz",
   591                                  "ftype": "tar.gz",
   592                                  "sha256": ""
   593                              }
   594                          }
   595                      }
   596                  }
   597              }
   598          }
   599      }`
   600  	cloudMetadata, err := simplestreams.ParseCloudMetadata([]byte(json), "products:1.0", "", tools.ToolsMetadata{})
   601  	c.Assert(err, jc.ErrorIsNil)
   602  	c.Assert(cloudMetadata.Products, gc.HasLen, 1)
   603  	product := cloudMetadata.Products["com.ubuntu.juju:1.10.0:amd64"]
   604  	c.Assert(product, gc.NotNil)
   605  	c.Assert(product.Items, gc.HasLen, 1)
   606  	version := product.Items["20133008"]
   607  	c.Assert(version, gc.NotNil)
   608  	c.Assert(version.Items, gc.HasLen, 1)
   609  	item := version.Items["1.10.0-ubuntu-amd64"]
   610  	c.Assert(item, gc.NotNil)
   611  	c.Assert(item, gc.FitsTypeOf, &tools.ToolsMetadata{})
   612  	c.Assert(item.(*tools.ToolsMetadata).Size, gc.Equals, int64(9223372036854775807))
   613  }
   614  
   615  type metadataHelperSuite struct {
   616  	coretesting.BaseSuite
   617  }
   618  
   619  var _ = gc.Suite(&metadataHelperSuite{})
   620  
   621  func (*metadataHelperSuite) TestMetadataFromTools(c *gc.C) {
   622  	metadata := tools.MetadataFromTools(nil, "proposed")
   623  	c.Assert(metadata, gc.HasLen, 0)
   624  
   625  	toolsList := coretools.List{{
   626  		Version: version.MustParseBinary("1.2.3-ubuntu-amd64"),
   627  		Size:    123,
   628  		SHA256:  "abc",
   629  	}, {
   630  		Version: version.MustParseBinary("2.0.1-ubuntu-amd64"),
   631  		URL:     "file:///tmp/proposed/juju-2.0.1-ubuntu-amd64.tgz",
   632  		Size:    456,
   633  		SHA256:  "xyz",
   634  	}}
   635  	metadata = tools.MetadataFromTools(toolsList, "proposed")
   636  	c.Assert(metadata, gc.HasLen, len(toolsList))
   637  	for i, t := range toolsList {
   638  		md := metadata[i]
   639  		c.Assert(md.Release, gc.Equals, "ubuntu")
   640  		c.Assert(md.Version, gc.Equals, t.Version.Number.String())
   641  		c.Assert(md.Arch, gc.Equals, t.Version.Arch)
   642  		// FullPath is only filled out when reading tools using simplestreams.
   643  		// It's not needed elsewhere and requires a URL() call.
   644  		c.Assert(md.FullPath, gc.Equals, "")
   645  		c.Assert(md.Path, gc.Equals, tools.StorageName(t.Version, "proposed")[len("tools/"):])
   646  		c.Assert(md.FileType, gc.Equals, "tar.gz")
   647  		c.Assert(md.Size, gc.Equals, t.Size)
   648  		c.Assert(md.SHA256, gc.Equals, t.SHA256)
   649  	}
   650  }
   651  
   652  type countingStorage struct {
   653  	storage.StorageReader
   654  	counter int
   655  }
   656  
   657  func (c *countingStorage) Get(name string) (io.ReadCloser, error) {
   658  	c.counter++
   659  	return c.StorageReader.Get(name)
   660  }
   661  
   662  func (*metadataHelperSuite) TestResolveMetadata(c *gc.C) {
   663  	var versionStrings = []string{"1.2.3-ubuntu-amd64"}
   664  	dir := c.MkDir()
   665  	toolstesting.MakeTools(c, dir, "released", versionStrings)
   666  	toolsList := coretools.List{{
   667  		Version: version.MustParseBinary(versionStrings[0]),
   668  		Size:    123,
   669  		SHA256:  "abc",
   670  	}}
   671  
   672  	stor, err := filestorage.NewFileStorageReader(dir)
   673  	c.Assert(err, jc.ErrorIsNil)
   674  	err = tools.ResolveMetadata(stor, "released", nil)
   675  	c.Assert(err, jc.ErrorIsNil)
   676  
   677  	// We already have size/sha256, so ensure that storage isn't consulted.
   678  	countingStorage := &countingStorage{StorageReader: stor}
   679  	metadata := tools.MetadataFromTools(toolsList, "released")
   680  	err = tools.ResolveMetadata(countingStorage, "released", metadata)
   681  	c.Assert(err, jc.ErrorIsNil)
   682  	c.Assert(countingStorage.counter, gc.Equals, 0)
   683  
   684  	// Now clear size/sha256, and check that it is called, and
   685  	// the size/sha256 sum are updated.
   686  	metadata[0].Size = 0
   687  	metadata[0].SHA256 = ""
   688  	err = tools.ResolveMetadata(countingStorage, "released", metadata)
   689  	c.Assert(err, jc.ErrorIsNil)
   690  	c.Assert(countingStorage.counter, gc.Equals, 1)
   691  	c.Assert(metadata[0].Size, gc.Not(gc.Equals), 0)
   692  	c.Assert(metadata[0].SHA256, gc.Not(gc.Equals), "")
   693  }
   694  
   695  func (*metadataHelperSuite) TestMergeMetadata(c *gc.C) {
   696  	md1 := &tools.ToolsMetadata{
   697  		Release: "ubuntu",
   698  		Version: "1.2.3",
   699  		Arch:    "amd64",
   700  		Path:    "path1",
   701  	}
   702  	md2 := &tools.ToolsMetadata{
   703  		Release: "ubuntu",
   704  		Version: "1.2.3",
   705  		Arch:    "amd64",
   706  		Path:    "path2",
   707  	}
   708  	md3 := &tools.ToolsMetadata{
   709  		Release: "windows",
   710  		Version: "1.2.3",
   711  		Arch:    "amd64",
   712  		Path:    "path3",
   713  	}
   714  
   715  	withSize := func(md *tools.ToolsMetadata, size int64) *tools.ToolsMetadata {
   716  		clone := *md
   717  		clone.Size = size
   718  		return &clone
   719  	}
   720  	withSHA256 := func(md *tools.ToolsMetadata, sha256 string) *tools.ToolsMetadata {
   721  		clone := *md
   722  		clone.SHA256 = sha256
   723  		return &clone
   724  	}
   725  
   726  	type mdlist []*tools.ToolsMetadata
   727  	type test struct {
   728  		name             string
   729  		lhs, rhs, merged []*tools.ToolsMetadata
   730  		err              string
   731  	}
   732  	tests := []test{{
   733  		name:   "non-empty lhs, empty rhs",
   734  		lhs:    mdlist{md1},
   735  		rhs:    nil,
   736  		merged: mdlist{md1},
   737  	}, {
   738  		name:   "empty lhs, non-empty rhs",
   739  		lhs:    nil,
   740  		rhs:    mdlist{md2},
   741  		merged: mdlist{md2},
   742  	}, {
   743  		name:   "identical lhs, rhs",
   744  		lhs:    mdlist{md1},
   745  		rhs:    mdlist{md1},
   746  		merged: mdlist{md1},
   747  	}, {
   748  		name:   "same tools in lhs and rhs, neither have size: prefer lhs",
   749  		lhs:    mdlist{md1},
   750  		rhs:    mdlist{md2},
   751  		merged: mdlist{md1},
   752  	}, {
   753  		name:   "same tools in lhs and rhs, only lhs has a size: prefer lhs",
   754  		lhs:    mdlist{withSize(md1, 123)},
   755  		rhs:    mdlist{md2},
   756  		merged: mdlist{withSize(md1, 123)},
   757  	}, {
   758  		name:   "same tools in lhs and rhs, only rhs has a size: prefer rhs",
   759  		lhs:    mdlist{md1},
   760  		rhs:    mdlist{withSize(md2, 123)},
   761  		merged: mdlist{withSize(md2, 123)},
   762  	}, {
   763  		name:   "same tools in lhs and rhs, both have the same size: prefer lhs",
   764  		lhs:    mdlist{withSize(md1, 123)},
   765  		rhs:    mdlist{withSize(md2, 123)},
   766  		merged: mdlist{withSize(md1, 123)},
   767  	}, {
   768  		name: "same tools in lhs and rhs, both have different sizes: error",
   769  		lhs:  mdlist{withSize(md1, 123)},
   770  		rhs:  mdlist{withSize(md2, 456)},
   771  		err:  "metadata mismatch for 1\\.2\\.3-ubuntu-amd64: sizes=\\(123,456\\) sha256=\\(,\\)",
   772  	}, {
   773  		name: "same tools in lhs and rhs, both have same size but different sha256: error",
   774  		lhs:  mdlist{withSHA256(withSize(md1, 123), "a")},
   775  		rhs:  mdlist{withSHA256(withSize(md2, 123), "b")},
   776  		err:  "metadata mismatch for 1\\.2\\.3-ubuntu-amd64: sizes=\\(123,123\\) sha256=\\(a,b\\)",
   777  	}, {
   778  		name:   "lhs is a proper superset of rhs: union of lhs and rhs",
   779  		lhs:    mdlist{md1, md3},
   780  		rhs:    mdlist{md1},
   781  		merged: mdlist{md1, md3},
   782  	}, {
   783  		name:   "rhs is a proper superset of lhs: union of lhs and rhs",
   784  		lhs:    mdlist{md1},
   785  		rhs:    mdlist{md1, md3},
   786  		merged: mdlist{md1, md3},
   787  	}}
   788  	for i, test := range tests {
   789  		c.Logf("test %d: %s", i, test.name)
   790  		merged, err := tools.MergeMetadata(test.lhs, test.rhs)
   791  		if test.err == "" {
   792  			c.Assert(err, jc.ErrorIsNil)
   793  			c.Assert(merged, gc.DeepEquals, test.merged)
   794  		} else {
   795  			c.Assert(err, gc.ErrorMatches, test.err)
   796  			c.Assert(merged, gc.IsNil)
   797  		}
   798  	}
   799  }
   800  
   801  func (*metadataHelperSuite) TestReadWriteMetadataSingleStream(c *gc.C) {
   802  	metadata := map[string][]*tools.ToolsMetadata{
   803  		"released": {{
   804  			Release: "ubuntu",
   805  			Version: "1.2.3",
   806  			Arch:    "amd64",
   807  			Path:    "path1",
   808  		}},
   809  	}
   810  
   811  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   812  	store, err := filestorage.NewFileStorageWriter(c.MkDir())
   813  	c.Assert(err, jc.ErrorIsNil)
   814  
   815  	out, err := tools.ReadAllMetadata(ss, store)
   816  	c.Assert(err, jc.ErrorIsNil) // non-existence is not an error
   817  	c.Assert(out, gc.HasLen, 0)
   818  
   819  	err = tools.WriteMetadata(store, metadata, []string{"released"}, tools.DoNotWriteMirrors)
   820  	c.Assert(err, jc.ErrorIsNil)
   821  
   822  	// Read back what was just written.
   823  	out, err = tools.ReadAllMetadata(ss, store)
   824  	c.Assert(err, jc.ErrorIsNil)
   825  	for _, outMetadata := range out {
   826  		for _, md := range outMetadata {
   827  			// FullPath is set by ReadAllMetadata.
   828  			c.Assert(md.FullPath, gc.Not(gc.Equals), "")
   829  			md.FullPath = ""
   830  		}
   831  	}
   832  	c.Assert(out, jc.DeepEquals, metadata)
   833  }
   834  
   835  func (*metadataHelperSuite) writeMetadataMultipleStream(c *gc.C) (*simplestreams.Simplestreams, storage.StorageReader, map[string][]*tools.ToolsMetadata) {
   836  	metadata := map[string][]*tools.ToolsMetadata{
   837  		"released": {{
   838  			Release: "ubuntu",
   839  			Version: "1.2.3",
   840  			Arch:    "amd64",
   841  			Path:    "path1",
   842  		}},
   843  		"proposed": {{
   844  			Release: "ubuntu",
   845  			Version: "1.2.3",
   846  			Arch:    "amd64",
   847  			Path:    "path2",
   848  		}},
   849  	}
   850  
   851  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   852  	store, err := filestorage.NewFileStorageWriter(c.MkDir())
   853  	c.Assert(err, jc.ErrorIsNil)
   854  
   855  	out, err := tools.ReadAllMetadata(ss, store)
   856  	c.Assert(out, gc.HasLen, 0)
   857  	c.Assert(err, jc.ErrorIsNil) // non-existence is not an error
   858  
   859  	err = tools.WriteMetadata(store, metadata, []string{"released", "proposed"}, tools.DoNotWriteMirrors)
   860  	c.Assert(err, jc.ErrorIsNil)
   861  	return ss, store, metadata
   862  }
   863  
   864  func (s *metadataHelperSuite) TestReadWriteMetadataMultipleStream(c *gc.C) {
   865  	ss, store, metadata := s.writeMetadataMultipleStream(c)
   866  	// Read back what was just written.
   867  	out, err := tools.ReadAllMetadata(ss, store)
   868  	c.Assert(err, jc.ErrorIsNil)
   869  	for _, outMetadata := range out {
   870  		for _, md := range outMetadata {
   871  			// FullPath is set by ReadAllMetadata.
   872  			c.Assert(md.FullPath, gc.Not(gc.Equals), "")
   873  			md.FullPath = ""
   874  		}
   875  	}
   876  	c.Assert(out, jc.DeepEquals, metadata)
   877  }
   878  
   879  func (s *metadataHelperSuite) TestWriteMetadataLegacyIndex(c *gc.C) {
   880  	_, stor, _ := s.writeMetadataMultipleStream(c)
   881  	// Read back the legacy index
   882  	rdr, err := stor.Get("tools/streams/v1/index.json")
   883  	defer rdr.Close()
   884  	c.Assert(err, jc.ErrorIsNil)
   885  	data, err := io.ReadAll(rdr)
   886  	c.Assert(err, jc.ErrorIsNil)
   887  	var indices simplestreams.Indices
   888  	err = json.Unmarshal(data, &indices)
   889  	c.Assert(err, jc.ErrorIsNil)
   890  	c.Assert(indices.Indexes, gc.HasLen, 1)
   891  	indices.Updated = ""
   892  	c.Assert(indices.Indexes["com.ubuntu.juju:released:agents"], gc.NotNil)
   893  	indices.Indexes["com.ubuntu.juju:released:agents"].Updated = ""
   894  	expected := simplestreams.Indices{
   895  		Format: "index:1.0",
   896  		Indexes: map[string]*simplestreams.IndexMetadata{
   897  			"com.ubuntu.juju:released:agents": {
   898  				Format:           "products:1.0",
   899  				DataType:         "content-download",
   900  				ProductsFilePath: "streams/v1/com.ubuntu.juju-released-agents.json",
   901  				ProductIds:       []string{"com.ubuntu.juju:ubuntu:amd64"},
   902  			},
   903  		},
   904  	}
   905  	c.Assert(indices, jc.DeepEquals, expected)
   906  }
   907  
   908  func (s *metadataHelperSuite) TestReadWriteMetadataUnchanged(c *gc.C) {
   909  	metadata := map[string][]*tools.ToolsMetadata{
   910  		"released": {{
   911  			Release: "ubuntu",
   912  			Version: "1.2.3",
   913  			Arch:    "amd64",
   914  			Path:    "path1",
   915  		}, {
   916  			Release: "ubuntu",
   917  			Version: "1.2.4",
   918  			Arch:    "amd64",
   919  			Path:    "path2",
   920  		}},
   921  	}
   922  
   923  	stor, err := filestorage.NewFileStorageWriter(c.MkDir())
   924  	c.Assert(err, jc.ErrorIsNil)
   925  	err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors)
   926  	c.Assert(err, jc.ErrorIsNil)
   927  
   928  	s.PatchValue(tools.WriteMetadataFiles, func(stor storage.Storage, metadataInfo []tools.MetadataFile) error {
   929  		// The product data is the same, we only write the indices.
   930  		c.Assert(metadataInfo, gc.HasLen, 2)
   931  		c.Assert(metadataInfo[0].Path, gc.Equals, "streams/v1/index2.json")
   932  		c.Assert(metadataInfo[1].Path, gc.Equals, "streams/v1/index.json")
   933  		return nil
   934  	})
   935  	err = tools.WriteMetadata(stor, metadata, []string{"released"}, tools.DoNotWriteMirrors)
   936  	c.Assert(err, jc.ErrorIsNil)
   937  }
   938  
   939  func (*metadataHelperSuite) TestReadMetadataPrefersNewIndex(c *gc.C) {
   940  	metadataDir := c.MkDir()
   941  
   942  	// Generate metadata and rename index to index.json
   943  	metadata := map[string][]*tools.ToolsMetadata{
   944  		"proposed": {{
   945  			Release: "ubuntu",
   946  			Version: "1.2.3",
   947  			Arch:    "amd64",
   948  			Path:    "path1",
   949  		}},
   950  		"released": {{
   951  			Release: "ubuntu",
   952  			Version: "1.2.3",
   953  			Arch:    "amd64",
   954  			Path:    "path1",
   955  		}},
   956  	}
   957  	store, err := filestorage.NewFileStorageWriter(metadataDir)
   958  	c.Assert(err, jc.ErrorIsNil)
   959  	err = tools.WriteMetadata(store, metadata, []string{"proposed", "released"}, tools.DoNotWriteMirrors)
   960  	c.Assert(err, jc.ErrorIsNil)
   961  	err = os.Rename(
   962  		filepath.Join(metadataDir, "tools", "streams", "v1", "index2.json"),
   963  		filepath.Join(metadataDir, "tools", "streams", "v1", "index.json"),
   964  	)
   965  	c.Assert(err, jc.ErrorIsNil)
   966  
   967  	// Generate different metadata with index2.json
   968  	metadata = map[string][]*tools.ToolsMetadata{
   969  		"released": {{
   970  			Release: "ubuntu",
   971  			Version: "1.2.3",
   972  			Arch:    "amd64",
   973  			Path:    "path1",
   974  		}},
   975  	}
   976  	err = tools.WriteMetadata(store, metadata, []string{"released"}, tools.DoNotWriteMirrors)
   977  	c.Assert(err, jc.ErrorIsNil)
   978  
   979  	// Read back all metadata, expecting to find metadata in index2.json.
   980  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   981  	out, err := tools.ReadAllMetadata(ss, store)
   982  	c.Assert(err, jc.ErrorIsNil)
   983  	for _, outMetadata := range out {
   984  		for _, md := range outMetadata {
   985  			// FullPath is set by ReadAllMetadata.
   986  			c.Assert(md.FullPath, gc.Not(gc.Equals), "")
   987  			md.FullPath = ""
   988  		}
   989  	}
   990  	c.Assert(out, jc.DeepEquals, metadata)
   991  }
   992  
   993  type signedSuite struct {
   994  	coretesting.BaseSuite
   995  }
   996  
   997  func (s *signedSuite) SetUpSuite(c *gc.C) {
   998  	s.BaseSuite.SetUpSuite(c)
   999  	s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
  1000  }
  1001  
  1002  func (s *signedSuite) TearDownSuite(c *gc.C) {
  1003  	s.BaseSuite.TearDownSuite(c)
  1004  }
  1005  
  1006  func (s *signedSuite) TestSignedToolsMetadata(c *gc.C) {
  1007  	ts := httptest.NewServer(&sstreamsHandler{})
  1008  	defer ts.Close()
  1009  	signedSource := simplestreams.NewDataSource(
  1010  		simplestreams.Config{
  1011  			Description:          "test",
  1012  			BaseURL:              fmt.Sprintf("%s/signed", ts.URL),
  1013  			PublicSigningKey:     sstesting.SignedMetadataPublicKey,
  1014  			HostnameVerification: true,
  1015  			Priority:             simplestreams.DEFAULT_CLOUD_DATA,
  1016  			RequireSigned:        true,
  1017  		},
  1018  	)
  1019  	toolsConstraint := tools.NewVersionedToolsConstraint(version.MustParse("1.13.0"), simplestreams.LookupParams{
  1020  		CloudSpec: simplestreams.CloudSpec{"us-east-1", "https://ec2.us-east-1.amazonaws.com"},
  1021  		Releases:  []string{"ubuntu"},
  1022  		Arches:    []string{"amd64"},
  1023  		Stream:    "released",
  1024  	})
  1025  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
  1026  	toolsMetadata, resolveInfo, err := tools.Fetch(ss,
  1027  		[]simplestreams.DataSource{signedSource}, toolsConstraint)
  1028  	c.Assert(err, jc.ErrorIsNil)
  1029  	c.Assert(len(toolsMetadata), gc.Equals, 1)
  1030  	c.Assert(toolsMetadata[0].Path, gc.Equals, "tools/releases/20130806/juju-1.13.1-ubuntu-amd64.tgz")
  1031  	c.Assert(resolveInfo, gc.DeepEquals, &simplestreams.ResolveInfo{
  1032  		Source:    "test",
  1033  		Signed:    true,
  1034  		IndexURL:  fmt.Sprintf("%s/signed/streams/v1/index.sjson", ts.URL),
  1035  		MirrorURL: "",
  1036  	})
  1037  }
  1038  
  1039  type sstreamsHandler struct{}
  1040  
  1041  func (h *sstreamsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  1042  	switch r.URL.Path {
  1043  	case "/unsigned/streams/v1/index.json":
  1044  		w.Header().Set("Content-Type", "application/json")
  1045  		w.WriteHeader(http.StatusOK)
  1046  		_, _ = io.WriteString(w, unsignedIndex)
  1047  	case "/unsigned/streams/v1/image_metadata.json":
  1048  		w.Header().Set("Content-Type", "application/json")
  1049  		w.WriteHeader(http.StatusOK)
  1050  		_, _ = io.WriteString(w, unsignedProduct)
  1051  	case "/signed/streams/v1/tools_metadata.sjson":
  1052  		w.Header().Set("Content-Type", "application/json")
  1053  		w.WriteHeader(http.StatusOK)
  1054  		rawUnsignedProduct := strings.Replace(
  1055  			unsignedProduct, "juju-1.13.0", "juju-1.13.1", -1)
  1056  		_, _ = io.WriteString(w, encode(rawUnsignedProduct))
  1057  		return
  1058  	case "/signed/streams/v1/index.sjson":
  1059  		w.Header().Set("Content-Type", "application/json")
  1060  		w.WriteHeader(http.StatusOK)
  1061  		rawUnsignedIndex := strings.Replace(
  1062  			unsignedIndex, "streams/v1/tools_metadata.json", "streams/v1/tools_metadata.sjson", -1)
  1063  		_, _ = io.WriteString(w, encode(rawUnsignedIndex))
  1064  		return
  1065  	default:
  1066  		http.Error(w, r.URL.Path, 404)
  1067  		return
  1068  	}
  1069  }
  1070  
  1071  func encode(data string) string {
  1072  	reader := bytes.NewReader([]byte(data))
  1073  	signedData, _ := simplestreams.Encode(
  1074  		reader, sstesting.SignedMetadataPrivateKey, sstesting.PrivateKeyPassphrase)
  1075  	return string(signedData)
  1076  }
  1077  
  1078  var unsignedIndex = `
  1079  {
  1080   "index": {
  1081    "com.ubuntu.juju:released:agents": {
  1082     "updated": "Mon, 05 Aug 2013 11:07:04 +0000",
  1083     "datatype": "content-download",
  1084     "format": "products:1.0",
  1085     "products": [
  1086       "com.ubuntu.juju:ubuntu:amd64"
  1087     ],
  1088     "path": "streams/v1/tools_metadata.json"
  1089    }
  1090   },
  1091   "updated": "Wed, 01 May 2013 13:31:26 +0000",
  1092   "format": "index:1.0"
  1093  }
  1094  `
  1095  var unsignedProduct = `
  1096  {
  1097   "updated": "Wed, 01 May 2013 13:31:26 +0000",
  1098   "content_id": "com.ubuntu.juju:released:aws",
  1099   "datatype": "content-download",
  1100   "products": {
  1101     "com.ubuntu.juju:ubuntu:amd64": {
  1102      "arch": "amd64",
  1103      "release": "ubuntu",
  1104      "versions": {
  1105       "20130806": {
  1106        "items": {
  1107         "1130ubuntuamd64": {
  1108          "version": "1.13.0",
  1109          "size": 2973595,
  1110          "path": "tools/releases/20130806/juju-1.13.0-ubuntu-amd64.tgz",
  1111          "ftype": "tar.gz",
  1112          "sha256": "447aeb6a934a5eaec4f703eda4ef2dde"
  1113         }
  1114        }
  1115       }
  1116      }
  1117     }
  1118   },
  1119   "format": "products:1.0"
  1120  }
  1121  `