// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. See the License for the specific language governing // permissions and limitations under the License. package birdwatcherservice import ( "errors" "fmt" "testing" "github.com/aws/amazon-ssm-agent/agent/appconfig" "github.com/aws/amazon-ssm-agent/agent/fileutil/artifact" "github.com/aws/amazon-ssm-agent/agent/mocks/log" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/birdwatcher" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/birdwatcher/archive" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/birdwatcher/birdwatcherarchive" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/birdwatcher/documentarchive" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/birdwatcher/facade" facade_mock "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/birdwatcher/facade/mocks" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/envdetect" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/envdetect/ec2infradetect" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/envdetect/osdetect" envdetect2 "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/mocks/envdetect" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/packageservice" cache_mock "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/packageservice/mock" "github.com/aws/amazon-ssm-agent/agent/plugins/configurepackage/trace" "github.com/aws/aws-sdk-go/service/ssm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) var platformName = "testplatform" var platformVersion = "testversion" var architecture = "testarch" type TimeMock struct { mock.Mock } func (t *TimeMock) NowUnixNano() int64 { args := t.Called() return int64(args.Int(0)) } type pkgtree map[string]map[string]map[string]*birdwatcher.PackageInfo type pkgselector struct { platform string version string architecture string pkginfo *birdwatcher.PackageInfo } func manifestPackageGen(sel *[]pkgselector) pkgtree { result := pkgtree{} for _, s := range *sel { if _, ok := result[s.platform]; !ok { result[s.platform] = map[string]map[string]*birdwatcher.PackageInfo{} } if _, ok := result[s.platform][s.version]; !ok { result[s.platform][s.version] = map[string]*birdwatcher.PackageInfo{} } if _, ok := result[s.platform][s.version][s.architecture]; !ok { result[s.platform][s.version][s.architecture] = s.pkginfo } else { panic("invalid test data") } } return result } func TestExtractPackageInfo(t *testing.T) { tracer := trace.NewTracer(log.NewMockLog()) tracer.BeginSection("test segment root") data := []struct { name string manifest *birdwatcher.Manifest osInfo *osdetect.OperatingSystem expected *birdwatcher.PackageInfo expectedErr bool }{ { "single entry, matching manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, platformVersion, architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "non-matching name in manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {"nonexistname", platformVersion, architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, nil, true, }, { "non-matching version in manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "nonexistversion", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, nil, true, }, { "non-matching arch in manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, platformVersion, "nonexistarch", &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, nil, true, }, { "multiple entry, matching manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, platformVersion, "nonexistarch", &birdwatcher.PackageInfo{FileName: "wrongfilename"}}, {platformName, platformVersion, architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "`_any` platform entry, matching manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {"_any", platformVersion, architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "`_any` version entry, matching manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "_any", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "`_any` arch entry, matching manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, platformVersion, "_any", &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "`_any` entry and concrete entry, matching manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, platformVersion, "_any", &birdwatcher.PackageInfo{FileName: "wrongfilename"}}, {platformName, platformVersion, architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "multi-level`_any` entry, matching manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {"_any", "_any", "_any", &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "`_any` entry and non-matching entry, non-matching manifest", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {"_any", platformVersion, architecture, &birdwatcher.PackageInfo{FileName: "wrongfilename"}}, {platformName, platformVersion, "nonexistarch", &birdwatcher.PackageInfo{FileName: "alsowrongfilename"}}, }), }, &osdetect.OperatingSystem{platformName, platformVersion, "", architecture, "", ""}, nil, true, }, { "os version more specific than manifest platform version, platfrom version no wildcard, no matching", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "6.2", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2.4", "", architecture, "", ""}, nil, true, }, { "manifest platform version more specific, wild card, no matching", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "6.2.4.1", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, {platformName, "6.2.4.*", architecture, &birdwatcher.PackageInfo{FileName: "filename1"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2", "", architecture, "", ""}, nil, true, }, { "manifest platform MINOR version wild card", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "6.*", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2.4", "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "manifest platform PATCH version wild card", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "6.2.*", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2.4", "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "manifest platform build version wild card", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "6.2.4.*", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2.4", "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "all plaftorm version have wildcard, match the more specific one", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "6.*", architecture, &birdwatcher.PackageInfo{FileName: "filename6.*"}}, {platformName, "6.2.*", architecture, &birdwatcher.PackageInfo{FileName: "filename6.2.*"}}, {platformName, "6.2.4.*", architecture, &birdwatcher.PackageInfo{FileName: "filename6.2.4.*"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2.4", "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename6.2.4.*"}, false, }, { "manifest platform MAJOR version wild card", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "*", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2.4", "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "manifest platform MAJOR version .*", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, ".*", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2.4", "", architecture, "", ""}, nil, true, }, { "manifest platform PATCH version wild card, MINOR version does not match os MINOR version", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "6.1.*", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "6.13.4", "", architecture, "", ""}, nil, true, }, { "wild card in the middle, don't support, no match.", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "6.*.4", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "6.2.4", "", architecture, "", ""}, nil, true, }, { "nano, os version more specific than manifest platform version", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "2018nano", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "2018.04.11nano", "", architecture, "", ""}, nil, true, }, { "nano, manifest MINOR version does not match os MINOR version", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "2018.05nano", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "2018.04.11nano", "", architecture, "", ""}, nil, true, }, { "nano os verion, non-nano manifest version", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "2018.04.11", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "2018.04.11nano", "", architecture, "", ""}, nil, true, }, { "nano exact match", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "2018.04.11nano", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "2018.04.11nano", "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, { "nano wildcard match", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {platformName, "2018.*nano", architecture, &birdwatcher.PackageInfo{FileName: "filename"}}, }), }, &osdetect.OperatingSystem{platformName, "2018.04.11nano", "", architecture, "", ""}, &birdwatcher.PackageInfo{FileName: "filename"}, false, }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { mockedCollector := envdetect2.CollectorMock{} mockedCollector.On("CollectData", mock.Anything).Return(&envdetect.Environment{ testdata.osInfo, nil, }, nil).Once() facadeClientMock := facade.FacadeStub{ PutConfigurePackageResultOutput: &ssm.PutConfigurePackageResultOutput{}, } ds := &PackageService{facadeClient: &facadeClientMock, manifestCache: packageservice.ManifestCacheMemNew(), collector: &mockedCollector} result, err := ds.extractPackageInfo(tracer, testdata.manifest) if testdata.expectedErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, testdata.expected, result) } }) } } func defaultCollectDataResponseObj() *envdetect.Environment { return &envdetect.Environment{ &osdetect.OperatingSystem{"abc", "567", "", "xyz", "", ""}, &ec2infradetect.Ec2Infrastructure{"instanceIDX", "Reg1", "", "AZ1", "instanceTypeZ"}, } } func TestReportResult(t *testing.T) { now := 420000 timemock := &TimeMock{} timemock.On("NowUnixNano").Return(now) tracer := trace.NewTracer(log.NewMockLog()) tracer.BeginSection("test segment root") data := []struct { name string facadeClient facade.FacadeStub expectedErr bool packageResult packageservice.PackageResult collectDataResponseObj *envdetect.Environment collectDataResponseErr error }{ { "successful api call", facade.FacadeStub{ PutConfigurePackageResultOutput: &ssm.PutConfigurePackageResultOutput{}, }, false, packageservice.PackageResult{ PackageName: "name", Version: "1234", PreviousPackageVersion: "5678", Timing: 29347, Exitcode: 815, }, defaultCollectDataResponseObj(), nil, }, { "successful api call without previous version", facade.FacadeStub{ PutConfigurePackageResultOutput: &ssm.PutConfigurePackageResultOutput{}, }, false, packageservice.PackageResult{ PackageName: "name", Version: "1234", Timing: 29347, Exitcode: 815, }, defaultCollectDataResponseObj(), nil, }, { "failing api call", facade.FacadeStub{ PutConfigurePackageResultError: errors.New("testerror"), }, true, packageservice.PackageResult{ PackageName: "name", Version: "1234", PreviousPackageVersion: "5678", Timing: 29347, Exitcode: 815, }, defaultCollectDataResponseObj(), nil, }, { "failing collect data call", facade.FacadeStub{ PutConfigurePackageResultOutput: &ssm.PutConfigurePackageResultOutput{}, }, true, packageservice.PackageResult{ PackageName: "name", Version: "1234", PreviousPackageVersion: "5678", Timing: 29347, Exitcode: 815, }, nil, fmt.Errorf("some data collection error"), }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { mockedCollector := envdetect2.CollectorMock{} mockedCollector.On("CollectData", mock.Anything).Return(testdata.collectDataResponseObj, testdata.collectDataResponseErr).Once() ds := &PackageService{facadeClient: &testdata.facadeClient, manifestCache: packageservice.ManifestCacheMemNew(), collector: &mockedCollector, timeProvider: timemock} err := ds.ReportResult(tracer, testdata.packageResult) if testdata.expectedErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, testdata.packageResult.PackageName, *testdata.facadeClient.PutConfigurePackageResultInput.PackageName) assert.Equal(t, testdata.packageResult.Version, *testdata.facadeClient.PutConfigurePackageResultInput.PackageVersion) assert.Equal(t, testdata.packageResult.Operation, *testdata.facadeClient.PutConfigurePackageResultInput.Operation) if testdata.packageResult.PreviousPackageVersion == "" { assert.Nil(t, testdata.facadeClient.PutConfigurePackageResultInput.PreviousPackageVersion) } else { assert.EqualValues(t, &testdata.packageResult.PreviousPackageVersion, testdata.facadeClient.PutConfigurePackageResultInput.PreviousPackageVersion) } assert.Equal(t, (int64(now)-testdata.packageResult.Timing)/1000000, *testdata.facadeClient.PutConfigurePackageResultInput.OverallTiming) assert.Equal(t, testdata.packageResult.Exitcode, *testdata.facadeClient.PutConfigurePackageResultInput.Result) assert.Equal(t, "abc", *testdata.facadeClient.PutConfigurePackageResultInput.Attributes["platformName"]) assert.Equal(t, "567", *testdata.facadeClient.PutConfigurePackageResultInput.Attributes["platformVersion"]) assert.Equal(t, "xyz", *testdata.facadeClient.PutConfigurePackageResultInput.Attributes["architecture"]) assert.Equal(t, "instanceIDX", *testdata.facadeClient.PutConfigurePackageResultInput.Attributes["instanceID"]) assert.Equal(t, "instanceTypeZ", *testdata.facadeClient.PutConfigurePackageResultInput.Attributes["instanceType"]) assert.Equal(t, "AZ1", *testdata.facadeClient.PutConfigurePackageResultInput.Attributes["availabilityZone"]) assert.Equal(t, "Reg1", *testdata.facadeClient.PutConfigurePackageResultInput.Attributes["region"]) } }) } } func TestDownloadManifest(t *testing.T) { manifestStrErr := "xkj]{}[" manifestStr := "{\"version\": \"1234\",\"packageArn\":\"packagearn\"}" tracer := trace.NewTracer(log.NewMockLog()) data := []struct { name string packageName string packageVersion string facadeClient facade.FacadeStub manifest string expectedErr bool }{ { "successful getManifest with concrete version", "packagename", "1234", facade.FacadeStub{ GetManifestOutput: &ssm.GetManifestOutput{ Manifest: &manifestStr, }, }, "", false, }, { "successful getManifest with latest", "packagename", packageservice.Latest, facade.FacadeStub{ GetManifestOutput: &ssm.GetManifestOutput{ Manifest: &manifestStr, }, }, "", false, }, { "error in getManifest", "packagename", packageservice.Latest, facade.FacadeStub{ GetManifestError: errors.New("testerror"), }, "", true, }, { "error in parsing manifest", "packagename", packageservice.Latest, facade.FacadeStub{ GetManifestOutput: &ssm.GetManifestOutput{ Manifest: &manifestStrErr, }, }, "", true, }, { "Manifest already stored in package service", "packagename", packageservice.Latest, facade.FacadeStub{}, manifestStr, false, }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { context := make(map[string]string) context["packageName"] = testdata.packageName context["packageVersion"] = testdata.packageVersion context["manifest"] = testdata.manifest testArchive := birdwatcherarchive.New(&testdata.facadeClient, context) mockedCollector := envdetect2.CollectorMock{} envdata := &envdetect.Environment{ &osdetect.OperatingSystem{"abc", "567", "", "xyz", "", ""}, &ec2infradetect.Ec2Infrastructure{"instanceIDX", "Reg1", "", "AZ1", "instanceTypeZ"}, } mockedCollector.On("CollectData", mock.Anything).Return(envdata, nil).Once() cache := packageservice.ManifestCacheMemNew() testArchive.SetManifestCache(cache) ds := &PackageService{facadeClient: &testdata.facadeClient, manifestCache: cache, collector: &mockedCollector, packageArchive: testArchive} _, result, isSameAsCache, err := ds.DownloadManifest(tracer, testdata.packageName, testdata.packageVersion) if testdata.expectedErr { assert.Error(t, err) } else { if testdata.manifest == "" { // verify parameter for api call assert.Equal(t, testdata.packageName, *testdata.facadeClient.GetManifestInput.PackageName) assert.Equal(t, testdata.packageVersion, *testdata.facadeClient.GetManifestInput.PackageVersion) } // verify result assert.Equal(t, "1234", result) assert.NoError(t, err) assert.False(t, isSameAsCache) // verify cache cachedManifest, cacheErr := cache.ReadManifest("packagearn", "1234") assert.Equal(t, []byte(manifestStr), cachedManifest) assert.NoError(t, cacheErr) } }) } } func TestDownloadDocument(t *testing.T) { manifestStr := "{\"version\": \"1234\"}" manifest := "{\"version\": \"123\"}" documentActive := ssm.DocumentStatusActive tracer := trace.NewTracer(log.NewMockLog()) packageName := "documentarn" packageVersion := "1234" docVersionForGetDoc := "2" docVersion := "1" hash := "hash" documentDescription := ssm.DocumentDescription{ Name: &packageName, DocumentVersion: &docVersion, VersionName: &packageVersion, Hash: &hash, Status: &documentActive, } data := []struct { name string packageName string packageVersion string getDocumentExpected bool hashVal string }{ { "successful describeDocument with concrete version", packageName, packageVersion, false, hash, }, { "unsuccessful describeDocument followed by successful get document with concrete version", packageName, packageVersion, true, "different_hash", }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { mockedCollector := envdetect2.CollectorMock{} envdata := &envdetect.Environment{ &osdetect.OperatingSystem{"abc", "567", "", "xyz", "", ""}, &ec2infradetect.Ec2Infrastructure{"instanceIDX", "Reg1", "", "AZ1", "instanceTypeZ"}, } getDocumentOutput := &ssm.GetDocumentOutput{ Content: &manifestStr, Status: &documentActive, Name: &packageName, VersionName: &packageVersion, DocumentVersion: &docVersionForGetDoc, } getDocumentInput := &ssm.GetDocumentInput{ Name: &packageName, VersionName: &packageVersion, } describeDocumentInput := &ssm.DescribeDocumentInput{ Name: &packageName, VersionName: &packageVersion, } describeDocumentOutput := &ssm.DescribeDocumentOutput{ Document: &documentDescription, } mockedCollector.On("CollectData", mock.Anything).Return(envdata, nil).Once() cache := cache_mock.ManifestCache{} facadeMock := facade_mock.BirdwatcherFacade{} facadeMock.On("DescribeDocument", describeDocumentInput).Return(describeDocumentOutput, nil) if testdata.getDocumentExpected { facadeMock.On("GetDocument", getDocumentInput).Return(getDocumentOutput, nil) } cache.On("ReadManifestHash", packageName, docVersion).Return([]byte(testdata.hashVal), nil) if !testdata.getDocumentExpected { cache.On("ReadManifest", packageName, docVersion).Return([]byte(manifestStr), nil).Twice() cache.On("WriteManifest", packageName, docVersion, []byte(manifestStr)).Return(nil) } else { cache.On("WriteManifestHash", packageName, docVersionForGetDoc, mock.Anything).Return(nil) cache.On("ReadManifest", packageName, docVersionForGetDoc).Return([]byte(manifest), nil) cache.On("WriteManifest", packageName, docVersionForGetDoc, []byte(manifestStr)).Return(nil) } testArchive := documentarchive.NewDocumentArchive(&facadeMock, nil, &documentDescription, &cache, manifestStr) ds := &PackageService{facadeClient: &facadeMock, manifestCache: &cache, collector: &mockedCollector, packageArchive: testArchive} _, manifestVersion, isSameAsCache, err := ds.DownloadManifest(tracer, testdata.packageName, testdata.packageVersion) // verify manifestVersion assert.Equal(t, "1234", manifestVersion) assert.NoError(t, err) if !testdata.getDocumentExpected { assert.True(t, isSameAsCache) } else { assert.False(t, isSameAsCache) } cache.AssertExpectations(t) facadeMock.AssertExpectations(t) }) } } func TestDownloadManifestSameAsCacheManifest(t *testing.T) { manifestStr := "{\"version\": \"1234\",\"packageArn\":\"packagearn\"}" tracer := trace.NewTracer(log.NewMockLog()) data := []struct { name string packageName string packageVersion string facadeClient facade.FacadeStub expectedErr bool }{ { "successful getManifest same as cache", "packagearn", "1234", facade.FacadeStub{ GetManifestOutput: &ssm.GetManifestOutput{ Manifest: &manifestStr, }, }, false, }, { "successful getManifest same as cache for latest version", "packagearn", packageservice.Latest, facade.FacadeStub{ GetManifestOutput: &ssm.GetManifestOutput{ Manifest: &manifestStr, }, }, false, }, { "successful getManifest same as cache if name != returned arn", "packagename", packageservice.Latest, facade.FacadeStub{ GetManifestOutput: &ssm.GetManifestOutput{ Manifest: &manifestStr, }, }, false, }, } tracer.BeginSection("test successful getManifest same as cache") mockedCollector := envdetect2.CollectorMock{} for _, testdata := range data { context := make(map[string]string) context["packageName"] = testdata.packageName context["packageVersion"] = testdata.packageVersion context["manifest"] = "" testArchive := birdwatcherarchive.New(&testdata.facadeClient, context) cache := packageservice.ManifestCacheMemNew() testArchive.SetManifestCache(cache) ds := &PackageService{facadeClient: &testdata.facadeClient, manifestCache: cache, collector: &mockedCollector, packageArchive: testArchive} // first call has empty cache and is expected to come back with isSameAsCache == false _, result, isSameAsCache, err := ds.DownloadManifest(tracer, testdata.packageName, testdata.packageVersion) assert.NoError(t, err) assert.False(t, isSameAsCache) // second call has the cache already populated by the first call _, result, isSameAsCache, err = ds.DownloadManifest(tracer, testdata.packageName, testdata.packageVersion) // verify parameter for api call assert.Equal(t, testdata.packageName, *testdata.facadeClient.GetManifestInput.PackageName) assert.Equal(t, testdata.packageVersion, *testdata.facadeClient.GetManifestInput.PackageVersion) // verify result assert.Equal(t, "1234", result) assert.NoError(t, err) assert.True(t, isSameAsCache) // verify cache cachedManifest, cacheErr := cache.ReadManifest("packagearn", "1234") assert.Equal(t, []byte(manifestStr), cachedManifest) assert.NoError(t, cacheErr) } } func TestDownloadManifestDifferentFromCacheManifest(t *testing.T) { cachedManifestStr := "{\"version\": \"123\",\"packageArn\":\"packagearn\"}" manifestStr := "{\"version\": \"1234\",\"packageArn\":\"packagearn\"}" tracer := trace.NewTracer(log.NewMockLog()) testdata := struct { name string packageName string packageVersion string facadeClient facade.FacadeStub expectedErr bool }{ "successful getManifest different from cache", "packagenameorarndoesnotmatter", "packageversiondoesnotmatter", facade.FacadeStub{ GetManifestOutput: &ssm.GetManifestOutput{ Manifest: &manifestStr, }, }, false, } tracer.BeginSection("test successful getManifest different from cache") context := make(map[string]string) context["packageName"] = testdata.packageName context["packageVersion"] = testdata.packageVersion context["manifest"] = "" testArchive := birdwatcherarchive.New(&testdata.facadeClient, context) mockedCollector := envdetect2.CollectorMock{} envdata := &envdetect.Environment{ &osdetect.OperatingSystem{"abc", "567", "", "xyz", "", ""}, &ec2infradetect.Ec2Infrastructure{"instanceIDX", "Reg1", "", "AZ1", "instanceTypeZ"}, } mockedCollector.On("CollectData", mock.Anything).Return(envdata, nil).Once() cache := packageservice.ManifestCacheMemNew() testArchive.SetManifestCache(cache) err := cache.WriteManifest("packagearn", "1234", []byte(cachedManifestStr)) assert.NoError(t, err) ds := &PackageService{facadeClient: &testdata.facadeClient, manifestCache: cache, collector: &mockedCollector, packageArchive: testArchive} _, result, isSameAsCache, err := ds.DownloadManifest(tracer, testdata.packageName, testdata.packageVersion) // verify parameter for api call assert.Equal(t, testdata.packageName, *testdata.facadeClient.GetManifestInput.PackageName) assert.Equal(t, testdata.packageVersion, *testdata.facadeClient.GetManifestInput.PackageVersion) // verify result assert.Equal(t, "1234", result) assert.NoError(t, err) assert.False(t, isSameAsCache) // verify cache cachedManifest, cacheErr := cache.ReadManifest("packagearn", "1234") assert.Equal(t, []byte(manifestStr), cachedManifest) assert.NoError(t, cacheErr) } func TestFindFileFromManifest(t *testing.T) { tracer := trace.NewTracer(log.NewMockLog()) tracer.BeginSection("test segment root") fileName := "test.zip" data := []struct { name string manifest *birdwatcher.Manifest file archive.File expectedErr bool }{ { "successful file read", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {"platformName", "platformVersion", "architecture", &birdwatcher.PackageInfo{FileName: fileName}}, }), Files: map[string]*birdwatcher.FileInfo{"test.zip": &birdwatcher.FileInfo{DownloadLocation: "https://example.com/agent"}}, }, archive.File{ fileName, birdwatcher.FileInfo{ DownloadLocation: "https://example.com/agent", }, }, false, }, { "fail to find match in file", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{}), Files: map[string]*birdwatcher.FileInfo{}, }, archive.File{}, true, }, { "fail to find file name", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {"platformName", "platformVersion", "architecture", &birdwatcher.PackageInfo{FileName: "test.zip"}}, }), Files: map[string]*birdwatcher.FileInfo{}, }, archive.File{}, true, }, { "fail to find matching file name", &birdwatcher.Manifest{ Packages: manifestPackageGen(&[]pkgselector{ {"platformName", "platformVersion", "architecture", &birdwatcher.PackageInfo{FileName: "test.zip"}}, }), Files: map[string]*birdwatcher.FileInfo{"nomatch": &birdwatcher.FileInfo{DownloadLocation: "https://example.com/agent"}}, }, archive.File{}, true, }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { mockedCollector := envdetect2.CollectorMock{} mockedCollector.On("CollectData", mock.Anything).Return(&envdetect.Environment{ &osdetect.OperatingSystem{"platformName", "platformVersion", "", "architecture", "", ""}, &ec2infradetect.Ec2Infrastructure{"instanceID", "region", "", "availabilityZone", "instanceType"}, }, nil).Once() facadeClientMock := facade.FacadeStub{ PutConfigurePackageResultOutput: &ssm.PutConfigurePackageResultOutput{}, } ds := &PackageService{facadeClient: &facadeClientMock, manifestCache: packageservice.ManifestCacheMemNew(), collector: &mockedCollector} result, err := ds.findFileFromManifest(tracer, testdata.manifest) if testdata.expectedErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, testdata.file, *result) } }) } } func TestDownloadFile(t *testing.T) { tracer := trace.NewTracer(log.NewMockLog()) tracer.BeginSection("test segment root") packagename := "packagename" version := "version" fileName := "fileName.zip" data := []struct { name string network networkMock file *archive.File expectedErr bool }{ { "working file download", networkMock{ downloadOutput: artifact.DownloadOutput{ LocalFilePath: "agent.zip", }, }, &archive.File{ fileName, birdwatcher.FileInfo{ DownloadLocation: "https://example.com/agent", Checksums: map[string]string{ "sha256": "asdf", }, }, }, false, }, { "empty local file location", networkMock{ downloadOutput: artifact.DownloadOutput{ LocalFilePath: "", }, }, &archive.File{ fileName, birdwatcher.FileInfo{ DownloadLocation: "https://example.com/agent", Checksums: map[string]string{ "sha256": "asdf", }, }, }, true, }, { "error during file download", networkMock{ downloadError: errors.New("testerror"), }, &archive.File{ fileName, birdwatcher.FileInfo{ DownloadLocation: "https://example.com/agent", Checksums: map[string]string{ "sha256": "asdf", }, }, }, true, }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { birdwatcher.Networkdep = &testdata.network cache := packageservice.ManifestCacheMemNew() context := make(map[string]string) context["packageName"] = packagename context["packageVersion"] = version context["manifest"] = "manifest" testArchive := birdwatcherarchive.New(&facade.FacadeStub{}, context) mockedCollector := envdetect2.CollectorMock{} ds := &PackageService{manifestCache: cache, collector: &mockedCollector, packageArchive: testArchive} result, err := downloadFile(ds, tracer, testdata.file, packagename, version, true) if testdata.expectedErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, "agent.zip", result) // verify download input input := artifact.DownloadInput{ SourceURL: testdata.file.Info.DownloadLocation, DestinationDirectory: appconfig.DownloadRoot, SourceChecksums: map[string]string{"sha256": "asdf"}, } assert.Equal(t, input, testdata.network.downloadInput) } }) } } func TestDownloadFileFromDocumentArchive(t *testing.T) { tracer := trace.NewTracer(log.NewMockLog()) tracer.BeginSection("test segment root") packagename := "packagename" version := "version" fileName := "fileName.zip" url := "url" documentActive := ssm.DocumentStatusActive hash := "hash" hashtype := "sha256" docVersion := "2" documentDescription := ssm.DocumentDescription{ Name: &packagename, DocumentVersion: &docVersion, VersionName: &version, Hash: &hash, Status: &documentActive, } data := []struct { name string network networkMock file *archive.File attachments []*ssm.AttachmentContent expectedErr bool }{ { "working file download", networkMock{ downloadOutput: artifact.DownloadOutput{ LocalFilePath: "agent.zip", }, }, &archive.File{ fileName, birdwatcher.FileInfo{}, }, []*ssm.AttachmentContent{ { Name: &fileName, Url: &url, Hash: &hash, HashType: &hashtype, }, }, false, }, { "empty local file location", networkMock{ downloadOutput: artifact.DownloadOutput{ LocalFilePath: "", }, }, &archive.File{ fileName, birdwatcher.FileInfo{}, }, []*ssm.AttachmentContent{ { Name: &fileName, Url: &url, Hash: &hash, HashType: &hashtype, }, }, true, }, { "error during file download", networkMock{ downloadError: errors.New("testerror"), }, &archive.File{ fileName, birdwatcher.FileInfo{}, }, []*ssm.AttachmentContent{ { Name: &fileName, Url: &url, Hash: &hash, HashType: &hashtype, }, }, true, }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { birdwatcher.Networkdep = &testdata.network cache := packageservice.ManifestCacheMemNew() facadeClient := facade.FacadeStub{ GetDocumentOutput: &ssm.GetDocumentOutput{ Status: &documentActive, Name: &packagename, AttachmentsContent: []*ssm.AttachmentContent{ { Name: &fileName, Url: &url, }, }, }, } testArchive := documentarchive.NewDocumentArchive(&facadeClient, testdata.attachments, &documentDescription, cache, "manifestStr") mockedCollector := envdetect2.CollectorMock{} ds := &PackageService{manifestCache: cache, collector: &mockedCollector, packageArchive: testArchive} result, err := downloadFile(ds, tracer, testdata.file, packagename, version, true) if testdata.expectedErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, "agent.zip", result) } }) } } func TestDownloadArtifact(t *testing.T) { manifestStr := ` { "packages": { "platformName": { "platformVersion": { "architecture": { "file": "test.zip" } } } }, "files": { "test.zip": { "downloadLocation": "https://example.com/agent" } } } ` tracer := trace.NewTracer(log.NewMockLog()) tracer.BeginSection("test segment root") data := []struct { name string packageName string packageVersion string network networkMock expectedErr bool }{ { "successful download", "packageName", "1234", networkMock{ downloadOutput: artifact.DownloadOutput{ LocalFilePath: "agent.zip", }, }, false, }, { "failed manifest loading", "packageName", "1234", networkMock{}, true, }, } for _, testdata := range data { t.Run(testdata.name, func(t *testing.T) { cache := packageservice.ManifestCacheMemNew() cache.WriteManifest(testdata.packageName, testdata.packageVersion, []byte(manifestStr)) context := make(map[string]string) context["packageName"] = testdata.packageName context["packageVersion"] = testdata.packageVersion context["manifest"] = manifestStr testArchive := birdwatcherarchive.New(&facade.FacadeStub{}, context) mockedCollector := envdetect2.CollectorMock{} mockedCollector.On("CollectData", mock.Anything).Return(&envdetect.Environment{ &osdetect.OperatingSystem{"platformName", "platformVersion", "", "architecture", "", ""}, &ec2infradetect.Ec2Infrastructure{"instanceID", "region", "", "availabilityZone", "instanceType"}, }, nil).Twice() testArchive.SetManifestCache(cache) ds := &PackageService{manifestCache: cache, collector: &mockedCollector, packageArchive: testArchive} birdwatcher.Networkdep = &testdata.network result, err := ds.DownloadArtifact(tracer, testdata.packageName, testdata.packageVersion) if testdata.expectedErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, "agent.zip", result) } }) } }