From 7fe76c5381c0a43baddb15a873ead807e43cf858 Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Fri, 31 Jan 2020 14:29:29 +0100 Subject: [PATCH 01/12] Uppercased Mender and Artifact in some comments Changelog: None Signed-off-by: Ole Petter --- app/mender.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/mender.go b/app/mender.go index b6e95aa7e..a8ebb98ca 100644 --- a/app/mender.go +++ b/app/mender.go @@ -330,11 +330,11 @@ func verifyArtifactDependencies(depends, provides map[string]interface{}) error func (m *Mender) CheckUpdate() (*datastore.UpdateInfo, menderError) { currentArtifactName, err := m.GetCurrentArtifactName() if err != nil || currentArtifactName == "" { - log.Error("could not get the current artifact name") + log.Error("could not get the current Artifact name") if err == nil { err = errors.New("artifact name is empty") } - return nil, NewTransientError(fmt.Errorf("could not read the artifact name. This is a necessary condition in order for a mender update to finish safely. Please give the current artifact a name (This can be done by adding a name to the file /etc/mender/artifact_info) err: %v", err)) + return nil, NewTransientError(fmt.Errorf("could not read the Artifact name. This is a necessary condition in order for a Mender update to finish safely. Please give the current Artifact a name (This can be done by adding a name to the file /etc/mender/artifact_info) err: %v", err)) } deviceType, err := m.GetDeviceType() @@ -640,9 +640,9 @@ func (m *Mender) InventoryRefresh() error { artifactName, err := m.GetCurrentArtifactName() if err != nil || artifactName == "" { if err == nil { - err = errors.New("artifact name is empty") + err = errors.New("Artifact name is empty") } - errstr := fmt.Sprintf("could not read the artifact name. This is a necessary condition in order for a mender update to finish safely. Please give the current artifact a name (This can be done by adding a name to the file /etc/mender/artifact_info) err: %v", err) + errstr := fmt.Sprintf("could not read the artifact name. This is a necessary condition in order for a Mender update to finish safely. Please give the current Artifact a name (This can be done by adding a name to the file /etc/mender/artifact_info) err: %v", err) return errors.Wrap(errNoArtifactName, errstr) } From 31bbaa3d0d32fe8819b40ca6a4848df72b39705b Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Fri, 31 Jan 2020 14:38:45 +0100 Subject: [PATCH 02/12] MEN-2587: Send Provides in the deployments API call This enables the client to send provides data to the deployments endpoint during an update check. The way it works is that the client will first do a POST to the deployments/next endpoints, and then if the server is not Enterprise, a 404 will be returned, upon which the client falls back to the standard GET deployments/next functionality. Changelog: Title Signed-off-by: Ole Petter --- app/mender.go | 23 +++++-- client/client_update.go | 59 ++++++++++++++--- client/client_update_test.go | 122 +++++++++++++++++++++++++++++++++-- client/test/server.go | 13 +++- device/device.go | 4 ++ 5 files changed, 200 insertions(+), 21 deletions(-) diff --git a/app/mender.go b/app/mender.go index a8ebb98ca..15a96127a 100644 --- a/app/mender.go +++ b/app/mender.go @@ -324,9 +324,9 @@ func verifyArtifactDependencies(depends, provides map[string]interface{}) error return nil } -// Check if new update is available. In case of errors, returns nil and error -// that occurred. If no update is available *UpdateInfo is nil, otherwise it -// contains update information. +// CheckUpdate Check if new update is available. In case of errors, returns nil +// and error that occurred. If no update is available *UpdateInfo is nil, +// otherwise it contains update information. func (m *Mender) CheckUpdate() (*datastore.UpdateInfo, menderError) { currentArtifactName, err := m.GetCurrentArtifactName() if err != nil || currentArtifactName == "" { @@ -339,12 +339,23 @@ func (m *Mender) CheckUpdate() (*datastore.UpdateInfo, menderError) { deviceType, err := m.GetDeviceType() if err != nil { - log.Errorf("Unable to verify the existing hardware. Update will continue anyways: %v : %v", m.Config.DeviceTypeFile, err) + log.Errorf("Unable to verify the existing hardware. Update will continue anyways: %v : %v", + m.Config.DeviceTypeFile, err) } - haveUpdate, err := m.updater.GetScheduledUpdate(m.api.Request(m.authToken, nextServerIterator(m), reauthorize(m)), - m.Config.Servers[0].ServerURL, client.CurrentUpdate{ + provides, err := m.DeviceManager.GetProvides() + if err != nil { + log.Errorf("Failed to load the device provides parameters from the datastore. Error: %v. Continuing...", + err) + } + haveUpdate, err := m.updater.GetScheduledUpdate( + m.api.Request(m.authToken, + nextServerIterator(m), + reauthorize(m)), + m.Config.Servers[0].ServerURL, + client.CurrentUpdate{ Artifact: currentArtifactName, DeviceType: deviceType, + Provides: provides, }) if err != nil { diff --git a/client/client_update.go b/client/client_update.go index 4a4e33431..cb5c680ff 100644 --- a/client/client_update.go +++ b/client/client_update.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package client import ( "bytes" "encoding/json" + "fmt" "io" "io/ioutil" "net/http" @@ -56,6 +57,7 @@ func NewUpdate() *UpdateClient { type CurrentUpdate struct { Artifact string DeviceType string + Provides map[string]interface{} } func (u *UpdateClient) GetScheduledUpdate(api ApiRequester, server string, @@ -64,22 +66,44 @@ func (u *UpdateClient) GetScheduledUpdate(api ApiRequester, server string, return u.getUpdateInfo(api, processUpdateResponse, server, current) } +// getUpdateInfo Tries to get the next update information from the backend. This +// is done in two stages. First it tries a POST request with the devices provide +// parameters. Then if this fails with a 404 response, then it falls back to the +// open source version with GET, and the parameters encoded in the URL. func (u *UpdateClient) getUpdateInfo(api ApiRequester, process RequestProcessingFunc, server string, current CurrentUpdate) (interface{}, error) { - req, err := makeUpdateCheckRequest(server, current) + ent_req, req, err := makeUpdateCheckRequest(server, current) if err != nil { return nil, errors.Wrapf(err, "failed to create update check request") } - r, err := api.Do(req) - + r, err := api.Do(ent_req) if err != nil { - log.Debug("Sending request error: ", err) - return nil, errors.Wrapf(err, "update check request failed") + log.Debugf("Failed sending device provides to the backend: Error: %v", err) + return nil, errors.Wrapf(err, "enterprise update check request failed") } defer r.Body.Close() + if r.StatusCode != http.StatusOK && r.StatusCode != http.StatusNoContent { + if r.StatusCode == http.StatusNotFound { + // 404 - Fall back to the old GET request + log.Debug("device provides not accepted by the server") + + r, err = api.Do(req) + + if err != nil { + log.Debug("Sending request error: ", err) + return nil, errors.Wrapf(err, "update check request failed") + } + + defer r.Body.Close() + + } else { + return nil, fmt.Errorf("failed to post update info to the server. Response: %v", r) + } + } + respdata, err := ioutil.ReadAll(r.Body) if err != nil { return nil, errors.Wrap(err, "failed to read the request body") @@ -180,7 +204,7 @@ func processUpdateResponse(response *http.Response) (interface{}, error) { } } -func makeUpdateCheckRequest(server string, current CurrentUpdate) (*http.Request, error) { +func makeUpdateCheckRequest(server string, current CurrentUpdate) (*http.Request, *http.Request, error) { vals := url.Values{} if current.DeviceType != "" { vals.Add("device_type", current.DeviceType) @@ -189,16 +213,31 @@ func makeUpdateCheckRequest(server string, current CurrentUpdate) (*http.Request vals.Add("artifact_name", current.Artifact) } + providesBody, err := json.Marshal(current.Provides) + if err != nil { + return nil, nil, err + } + + r := bytes.NewBuffer(providesBody) + ep := "/deployments/device/deployments/next" + + url := buildApiURL(server, ep) + + ent_req, err := http.NewRequest(http.MethodPost, url, r) + if err != nil { + return nil, nil, err + } + if len(vals) != 0 { ep = ep + "?" + vals.Encode() } - url := buildApiURL(server, ep) + url = buildApiURL(server, ep) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { - return nil, err + return nil, nil, err } - return req, nil + return ent_req, req, nil } func makeUpdateFetchRequest(url string) (*http.Request, error) { diff --git a/client/client_update_test.go b/client/client_update_test.go index 09a633c3c..f451a9461 100644 --- a/client/client_update_test.go +++ b/client/client_update_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,9 +14,11 @@ package client import ( + "encoding/json" "errors" "fmt" "io" + "io/ioutil" "net/http" "net/http/httptest" "strconv" @@ -328,7 +330,8 @@ func Test_UpdateApiClientError(t *testing.T) { } func TestMakeUpdateCheckRequest(t *testing.T) { - req, err := makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{}) + ent_req, req, err := makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{}) + assert.NotNil(t, ent_req) assert.NotNil(t, req) assert.NoError(t, err) @@ -336,24 +339,135 @@ func TestMakeUpdateCheckRequest(t *testing.T) { req.URL.String()) t.Logf("%s\n", req.URL.String()) - req, err = makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{ + ent_req, req, err = makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{ Artifact: "foo", + Provides: map[string]interface{}{ + "artifact_name": "release-1", + }, }) + assert.NotNil(t, ent_req) assert.NotNil(t, req) assert.NoError(t, err) assert.Equal(t, "http://foo.bar/api/devices/v1/deployments/device/deployments/next?artifact_name=foo", req.URL.String()) t.Logf("%s\n", req.URL.String()) + body, err := ioutil.ReadAll(ent_req.Body) + assert.NoError(t, err) + provides := make(map[string]interface{}) + err = json.Unmarshal(body, &provides) + assert.NoError(t, err) + assert.Equal(t, "release-1", provides["artifact_name"], string(body)) - req, err = makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{ + ent_req, req, err = makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{ Artifact: "foo", DeviceType: "hammer", + Provides: map[string]interface{}{ + "artifact_name": "release-2", + "device_type": "qemu", + }, }) + assert.NotNil(t, ent_req) assert.NotNil(t, req) assert.NoError(t, err) assert.Equal(t, "http://foo.bar/api/devices/v1/deployments/device/deployments/next?artifact_name=foo&device_type=hammer", req.URL.String()) t.Logf("%s\n", req.URL.String()) + body, err = ioutil.ReadAll(ent_req.Body) + assert.NoError(t, err) + provides = make(map[string]interface{}) + err = json.Unmarshal(body, &provides) + assert.NoError(t, err) + assert.Equal(t, "release-2", provides["artifact_name"], string(body)) + assert.Equal(t, "qemu", provides["device_type"], string(body)) +} + +func TestGetUpdateInfo(t *testing.T) { + + tests := map[string]struct { + httpHandlerFunc http.HandlerFunc + currentUpdateInfo CurrentUpdate + errorFunc func(t assert.TestingT, err error, msgAndArgs ...interface{}) bool + }{ + "Enterprise - Success - Update available": { + httpHandlerFunc: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, "") + }, + currentUpdateInfo: CurrentUpdate{ + Provides: map[string]interface{}{ + "artifact_name": "release-1", + "device_type": "qemu"}, + }, + errorFunc: assert.NoError, + }, + "Enterprise - Success 204 - No Content": { + httpHandlerFunc: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(204) + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, "") + }, + currentUpdateInfo: CurrentUpdate{ + Provides: map[string]interface{}{ + "artifact_name": "release-1", + "device_type": "qemu"}, + }, + errorFunc: assert.NoError, + }, + "Enterprise - Failure 500": { + httpHandlerFunc: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, "") + }, + currentUpdateInfo: CurrentUpdate{ + Provides: map[string]interface{}{ + "artifact_name": "release-1", + "device_type": "qemu"}, + }, + errorFunc: assert.Error, + }, + "Open source - Success": { + httpHandlerFunc: func(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + w.WriteHeader(404) + } else { + w.WriteHeader(200) + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, "") + } + }, + currentUpdateInfo: CurrentUpdate{ + Provides: map[string]interface{}{ + "artifact_name": "release-1", + "device_type": "qemu"}, + }, + errorFunc: assert.NoError, + }, + } + + for _, test := range tests { + + // Test server that always responds with 200 code, and specific payload + ts := httptest.NewTLSServer(http.HandlerFunc(test.httpHandlerFunc)) + defer ts.Close() + + ac, err := NewApiClient( + Config{"server.crt", true, false}, + ) + assert.NotNil(t, ac) + assert.NoError(t, err) + + client := NewUpdate() + assert.NotNil(t, client) + + fakeProcessUpdate := func(response *http.Response) (interface{}, error) { return nil, nil } + + _, err = client.getUpdateInfo(ac, fakeProcessUpdate, ts.URL, test.currentUpdateInfo) + test.errorFunc(t, err) + + } + } diff --git a/client/test/server.go b/client/test/server.go index 3e4a7e7a4..c591011d2 100644 --- a/client/test/server.go +++ b/client/test/server.go @@ -21,6 +21,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "reflect" "strconv" "strings" @@ -317,17 +318,25 @@ func (cts *ClientTestServer) updateReq(w http.ResponseWriter, r *http.Request) { log.Infof("got update request %v", r) cts.Update.Called = true + // Enterprise client device provides post is not supported yet + if r.Method == "POST" { + w.WriteHeader(404) + return + } + if !isMethod(http.MethodGet, w, r) { return } + log.Infof("Valid update request GET: %v", r) + if !cts.verifyAuth(w, r) { return } log.Infof("parsed URL query: %v", r.URL.Query()) - if current := urlQueryToCurrentUpdate(r.URL.Query()); current != cts.Update.Current { + if current := urlQueryToCurrentUpdate(r.URL.Query()); !reflect.DeepEqual(current, cts.Update.Current) { log.Errorf("incorrect current update info, got %+v, expected %+v", current, cts.Update.Current) w.WriteHeader(http.StatusBadRequest) @@ -356,6 +365,8 @@ func (cts *ClientTestServer) updateReq(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") writeJSON(w, &cts.Update.Data) + default: + log.Errorf("Unrecognized update status: %v", cts.Update) } } diff --git a/device/device.go b/device/device.go index f8ed6063f..fec773a01 100644 --- a/device/device.go +++ b/device/device.go @@ -115,6 +115,10 @@ func GetManifestData(dataType, manifestFile string) (string, error) { } } +func (d *DeviceManager) GetProvides() (map[string]interface{}, error) { + return datastore.LoadProvides(d.Store) +} + func (d *DeviceManager) GetCurrentArtifactName() (string, error) { if d.Store != nil { dbname, err := d.Store.ReadAll(datastore.ArtifactNameKey) From 22a3133e5a409db1997446252d867f71e1fb775e Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Tue, 11 Feb 2020 09:10:26 +0100 Subject: [PATCH 03/12] Added Content-Type application/json to the POST /next update call Otherwise the server will return 415 - unsupported media type. Changelog: None Signed-off-by: Ole Petter --- client/client_update.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/client_update.go b/client/client_update.go index cb5c680ff..44cc714e9 100644 --- a/client/client_update.go +++ b/client/client_update.go @@ -229,6 +229,8 @@ func makeUpdateCheckRequest(server string, current CurrentUpdate) (*http.Request return nil, nil, err } + ent_req.Header.Add("Content-Type", "application/json") + if len(vals) != 0 { ep = ep + "?" + vals.Encode() } From 7fafe17719fa62ac498e9510d2251283f922c4f4 Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Tue, 11 Feb 2020 09:50:00 +0100 Subject: [PATCH 04/12] Fall back to GET deployments/next in case of a HTTP 405 response The deployments service currently returns HTTP 405 in case of a POST request to the deployments/next endpoint. This commit makes sure that it falls back to the GET request in this case. Changelog: None Signed-off-by: Ole Petter --- client/client_update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/client_update.go b/client/client_update.go index 44cc714e9..1cb7bb6d5 100644 --- a/client/client_update.go +++ b/client/client_update.go @@ -86,7 +86,7 @@ func (u *UpdateClient) getUpdateInfo(api ApiRequester, process RequestProcessing defer r.Body.Close() if r.StatusCode != http.StatusOK && r.StatusCode != http.StatusNoContent { - if r.StatusCode == http.StatusNotFound { + if r.StatusCode == http.StatusNotFound || r.StatusCode == http.StatusMethodNotAllowed { // 404 - Fall back to the old GET request log.Debug("device provides not accepted by the server") From e3997e4adddcea4ca1669fb6a1fd91cedfb5bfef Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Tue, 11 Feb 2020 17:54:20 +0100 Subject: [PATCH 05/12] Fall back to GET request in case of any server error Changelog: None Signed-off-by: Ole Petter --- client/client_update.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/client/client_update.go b/client/client_update.go index 1cb7bb6d5..a46b5d977 100644 --- a/client/client_update.go +++ b/client/client_update.go @@ -68,8 +68,9 @@ func (u *UpdateClient) GetScheduledUpdate(api ApiRequester, server string, // getUpdateInfo Tries to get the next update information from the backend. This // is done in two stages. First it tries a POST request with the devices provide -// parameters. Then if this fails with a 404 response, then it falls back to the -// open source version with GET, and the parameters encoded in the URL. +// parameters. Then if this fails with an error code response, then it falls +// back to the open source version with GET, and the parameters encoded in the +// URL. func (u *UpdateClient) getUpdateInfo(api ApiRequester, process RequestProcessingFunc, server string, current CurrentUpdate) (interface{}, error) { ent_req, req, err := makeUpdateCheckRequest(server, current) @@ -86,9 +87,11 @@ func (u *UpdateClient) getUpdateInfo(api ApiRequester, process RequestProcessing defer r.Body.Close() if r.StatusCode != http.StatusOK && r.StatusCode != http.StatusNoContent { - if r.StatusCode == http.StatusNotFound || r.StatusCode == http.StatusMethodNotAllowed { - // 404 - Fall back to the old GET request - log.Debug("device provides not accepted by the server") + + // Fall back to the GET (Open-Source) functionality on all error codes + if r.StatusCode >= 400 && r.StatusCode < 600 { + + log.Debugf("device provides not accepted by the server. Response code: %d", r.StatusCode) r, err = api.Do(req) From 8808e4ee8180f3ebf6fdd9b5c2a28567d1f33cb1 Mon Sep 17 00:00:00 2001 From: Ole Petter Date: Tue, 11 Feb 2020 21:52:08 +0100 Subject: [PATCH 06/12] Removed outdated test The client now falls back to OS functionality on an HTTP error code Changelog: None Signed-off-by: Ole Petter --- client/client_update_test.go | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/client/client_update_test.go b/client/client_update_test.go index f451a9461..1f047de91 100644 --- a/client/client_update_test.go +++ b/client/client_update_test.go @@ -416,19 +416,6 @@ func TestGetUpdateInfo(t *testing.T) { }, errorFunc: assert.NoError, }, - "Enterprise - Failure 500": { - httpHandlerFunc: func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(500) - w.Header().Set("Content-Type", "application/json") - fmt.Fprint(w, "") - }, - currentUpdateInfo: CurrentUpdate{ - Provides: map[string]interface{}{ - "artifact_name": "release-1", - "device_type": "qemu"}, - }, - errorFunc: assert.Error, - }, "Open source - Success": { httpHandlerFunc: func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { @@ -448,7 +435,7 @@ func TestGetUpdateInfo(t *testing.T) { }, } - for _, test := range tests { + for name, test := range tests { // Test server that always responds with 200 code, and specific payload ts := httptest.NewTLSServer(http.HandlerFunc(test.httpHandlerFunc)) @@ -466,7 +453,7 @@ func TestGetUpdateInfo(t *testing.T) { fakeProcessUpdate := func(response *http.Response) (interface{}, error) { return nil, nil } _, err = client.getUpdateInfo(ac, fakeProcessUpdate, ts.URL, test.currentUpdateInfo) - test.errorFunc(t, err) + test.errorFunc(t, err, "Test name: %s", name) } From e4be94516952ec9b7339e7bc8fcb46e37ac855eb Mon Sep 17 00:00:00 2001 From: Alf-Rune Siqveland Date: Mon, 16 Mar 2020 09:36:52 +0100 Subject: [PATCH 07/12] Add artifact_name and device_type to POST /next parameters changelog: none Signed-off-by: Alf-Rune Siqveland --- app/mender.go | 4 ++-- client/client_update.go | 32 +++++++++++++++++++------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/mender.go b/app/mender.go index 15a96127a..8015826fa 100644 --- a/app/mender.go +++ b/app/mender.go @@ -287,7 +287,7 @@ func verifyArtifactDependencies(depends, provides map[string]interface{}) error } for key, depend := range depends { - if key == "compatible_devices" { + if key == "device_type" { // handled elsewhere continue } @@ -352,7 +352,7 @@ func (m *Mender) CheckUpdate() (*datastore.UpdateInfo, menderError) { nextServerIterator(m), reauthorize(m)), m.Config.Servers[0].ServerURL, - client.CurrentUpdate{ + &client.CurrentUpdate{ Artifact: currentArtifactName, DeviceType: deviceType, Provides: provides, diff --git a/client/client_update.go b/client/client_update.go index a46b5d977..dfc687e0f 100644 --- a/client/client_update.go +++ b/client/client_update.go @@ -33,7 +33,7 @@ const ( ) type Updater interface { - GetScheduledUpdate(api ApiRequester, server string, current CurrentUpdate) (interface{}, error) + GetScheduledUpdate(api ApiRequester, server string, current *CurrentUpdate) (interface{}, error) FetchUpdate(api ApiRequester, url string, maxWait time.Duration) (io.ReadCloser, int64, error) } @@ -60,8 +60,14 @@ type CurrentUpdate struct { Provides map[string]interface{} } +func (u *CurrentUpdate) MarshalJSON() ([]byte, error) { + u.Provides["artifact_name"] = u.Artifact + u.Provides["device_type"] = u.DeviceType + return json.Marshal(u.Provides) +} + func (u *UpdateClient) GetScheduledUpdate(api ApiRequester, server string, - current CurrentUpdate) (interface{}, error) { + current *CurrentUpdate) (interface{}, error) { return u.getUpdateInfo(api, processUpdateResponse, server, current) } @@ -72,16 +78,16 @@ func (u *UpdateClient) GetScheduledUpdate(api ApiRequester, server string, // back to the open source version with GET, and the parameters encoded in the // URL. func (u *UpdateClient) getUpdateInfo(api ApiRequester, process RequestProcessingFunc, - server string, current CurrentUpdate) (interface{}, error) { - ent_req, req, err := makeUpdateCheckRequest(server, current) + server string, current *CurrentUpdate) (interface{}, error) { + postReq, getReq, err := makeUpdateCheckRequest(server, current) if err != nil { return nil, errors.Wrapf(err, "failed to create update check request") } - r, err := api.Do(ent_req) + r, err := api.Do(postReq) if err != nil { log.Debugf("Failed sending device provides to the backend: Error: %v", err) - return nil, errors.Wrapf(err, "enterprise update check request failed") + return nil, errors.Wrapf(err, "POST update check request failed") } defer r.Body.Close() @@ -93,7 +99,7 @@ func (u *UpdateClient) getUpdateInfo(api ApiRequester, process RequestProcessing log.Debugf("device provides not accepted by the server. Response code: %d", r.StatusCode) - r, err = api.Do(req) + r, err = api.Do(getReq) if err != nil { log.Debug("Sending request error: ", err) @@ -207,7 +213,7 @@ func processUpdateResponse(response *http.Response) (interface{}, error) { } } -func makeUpdateCheckRequest(server string, current CurrentUpdate) (*http.Request, *http.Request, error) { +func makeUpdateCheckRequest(server string, current *CurrentUpdate) (*http.Request, *http.Request, error) { vals := url.Values{} if current.DeviceType != "" { vals.Add("device_type", current.DeviceType) @@ -216,7 +222,7 @@ func makeUpdateCheckRequest(server string, current CurrentUpdate) (*http.Request vals.Add("artifact_name", current.Artifact) } - providesBody, err := json.Marshal(current.Provides) + providesBody, err := json.Marshal(current) if err != nil { return nil, nil, err } @@ -227,22 +233,22 @@ func makeUpdateCheckRequest(server string, current CurrentUpdate) (*http.Request url := buildApiURL(server, ep) - ent_req, err := http.NewRequest(http.MethodPost, url, r) + postReq, err := http.NewRequest(http.MethodPost, url, r) if err != nil { return nil, nil, err } - ent_req.Header.Add("Content-Type", "application/json") + postReq.Header.Add("Content-Type", "application/json") if len(vals) != 0 { ep = ep + "?" + vals.Encode() } url = buildApiURL(server, ep) - req, err := http.NewRequest(http.MethodGet, url, nil) + getReq, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, nil, err } - return ent_req, req, nil + return postReq, getReq, nil } func makeUpdateFetchRequest(url string) (*http.Request, error) { From 04da96838af1d37b9b429db91111bf7d5387e24f Mon Sep 17 00:00:00 2001 From: Alf-Rune Siqveland Date: Mon, 16 Mar 2020 14:55:42 +0100 Subject: [PATCH 08/12] vendor: Update mender-artifact to latest master Changed all provides types map[string]interface{} -> map[string]string. changelog: none Signed-off-by: Alf-Rune Siqveland --- app/mender.go | 6 +- app/standalone.go | 4 +- app/state.go | 6 +- client/client_update.go | 2 +- datastore/datastore.go | 8 +-- datastore/standalone_data.go | 2 +- datastore/statedata.go | 4 +- device/device.go | 2 +- installer/installer.go | 2 +- .../mendersoftware/mender-artifact/LICENSE | 2 +- .../mender-artifact/areader/reader.go | 39 ++++++------ .../mender-artifact/artifact/checksum.go | 2 +- .../mender-artifact/artifact/compressor.go | 2 +- .../artifact/compressor_gzip.go | 2 +- .../artifact/compressor_lzma.go | 2 +- .../artifact/compressor_none.go | 2 +- .../mender-artifact/artifact/metadata.go | 61 +++++++++++++------ .../mender-artifact/artifact/scripter.go | 2 +- .../mender-artifact/artifact/signer.go | 2 +- .../artifact/tar_writer_file.go | 2 +- .../artifact/tar_writer_stream.go | 2 +- .../mender-artifact/artifact/update.go | 2 +- .../mender-artifact/awriter/signer.go | 2 +- .../mender-artifact/awriter/writer.go | 2 +- .../mender-artifact/handlers/common.go | 14 ++--- .../mender-artifact/handlers/module_image.go | 22 +++---- .../mender-artifact/handlers/rootfs_image.go | 14 ++--- .../mender-artifact/utils/binpath.go | 43 +++++++++++++ .../mender-artifact/utils/json.go | 37 +++++++++++ vendor/vendor.json | 30 +++++---- 30 files changed, 216 insertions(+), 106 deletions(-) create mode 100644 vendor/github.com/mendersoftware/mender-artifact/utils/binpath.go create mode 100644 vendor/github.com/mendersoftware/mender-artifact/utils/json.go diff --git a/app/mender.go b/app/mender.go index 8015826fa..e49ec2c2c 100644 --- a/app/mender.go +++ b/app/mender.go @@ -275,7 +275,9 @@ func (m *Mender) FetchUpdate(url string) (io.ReadCloser, int64, error) { return m.updater.FetchUpdate(m.api, url, m.GetRetryPollInterval()) } -func verifyArtifactDependencies(depends, provides map[string]interface{}) error { +func verifyArtifactDependencies( + depends map[string]interface{}, + provides map[string]string) error { // Generic closure for checking if element is present in slice. elemInSlice := func(elem string, slice []string) bool { for _, s := range slice { @@ -307,7 +309,7 @@ func verifyArtifactDependencies(depends, provides map[string]interface{}) error if p, ok := provides[key]; ok { switch depend.(type) { case []string: - if elemInSlice(p.(string), depend.([]string)) { + if elemInSlice(p, depend.([]string)) { continue } case string: diff --git a/app/standalone.go b/app/standalone.go index c8e4cd29f..eee35c693 100644 --- a/app/standalone.go +++ b/app/standalone.go @@ -37,7 +37,7 @@ import ( type standaloneData struct { artifactName string artifactGroup string - artifactTypeInfoProvides map[string]interface{} + artifactTypeInfoProvides map[string]string installers []installer.PayloadUpdatePerformer } @@ -134,7 +134,7 @@ func doStandaloneInstallStatesDownload(art io.ReadCloser, key []byte, } if grp, ok := standaloneData. artifactTypeInfoProvides["artifact_group"]; ok { - standaloneData.artifactGroup = grp.(string) + standaloneData.artifactGroup = grp delete(standaloneData.artifactTypeInfoProvides, "artifact_group") } diff --git a/app/state.go b/app/state.go index abf485b9f..5687dc10e 100644 --- a/app/state.go +++ b/app/state.go @@ -853,9 +853,9 @@ func (u *updateStoreState) maybeVerifyArtifactDependsAndProvides( log.Error("Failed to extract artifact dependencies from " + "header: " + err.Error()) } else if depends != nil { - var provides map[string]interface{} + var provides map[string]string // load header-info provides - provides, err = datastore.LoadProvides(ctx.Store) + provides, err := datastore.LoadProvides(ctx.Store) if err != nil { log.Error(err.Error()) return err @@ -880,7 +880,7 @@ func (u *updateStoreState) maybeVerifyArtifactDependsAndProvides( } delete(provides, "artifact_name") if grp, ok := provides["artifact_group"]; ok { - u.update.Artifact.ArtifactGroup = grp.(string) + u.update.Artifact.ArtifactGroup = grp // remove duplication delete(provides, "artifact_group") } diff --git a/client/client_update.go b/client/client_update.go index dfc687e0f..d2e309112 100644 --- a/client/client_update.go +++ b/client/client_update.go @@ -57,7 +57,7 @@ func NewUpdate() *UpdateClient { type CurrentUpdate struct { Artifact string DeviceType string - Provides map[string]interface{} + Provides map[string]string } func (u *CurrentUpdate) MarshalJSON() ([]byte, error) { diff --git a/datastore/datastore.go b/datastore/datastore.go index 8dec15480..6a32c394f 100644 --- a/datastore/datastore.go +++ b/datastore/datastore.go @@ -41,9 +41,9 @@ var ( // Loads artifact-provides (including artifact name) needed for dependency // checking before proceeding with installation of an artifact (version >= 3). -func LoadProvides(store store.Store) (map[string]interface{}, error) { +func LoadProvides(store store.Store) (map[string]string, error) { var providesBuf []byte - var provides = make(map[string]interface{}) + var provides = make(map[string]string) var err error providesBuf, err = store.ReadAll(ArtifactNameKey) @@ -51,14 +51,14 @@ func LoadProvides(store store.Store) (map[string]interface{}, error) { return nil, errors.Wrapf(err, errMsgReadingFromStoreF, "ArtifactName") } else if err == nil { - provides["artifact_name"] = interface{}(string(providesBuf)) + provides["artifact_name"] = string(providesBuf) } providesBuf, err = store.ReadAll(ArtifactGroupKey) if err != nil && !os.IsNotExist(err) { return nil, errors.Wrapf(err, errMsgReadingFromStoreF, "ArtifactGroup") } else if err == nil { - provides["artifact_group"] = interface{}(string(providesBuf)) + provides["artifact_group"] = string(providesBuf) } providesBuf, err = store.ReadAll( ArtifactTypeInfoProvidesKey) diff --git a/datastore/standalone_data.go b/datastore/standalone_data.go index fab64f7a5..4c075b623 100644 --- a/datastore/standalone_data.go +++ b/datastore/standalone_data.go @@ -19,6 +19,6 @@ type StandaloneStateData struct { Version int ArtifactName string ArtifactGroup string - ArtifactTypeInfoProvides map[string]interface{} + ArtifactTypeInfoProvides map[string]string PayloadTypes []string } diff --git a/datastore/statedata.go b/datastore/statedata.go index 0e5be0cc2..a32115216 100644 --- a/datastore/statedata.go +++ b/datastore/statedata.go @@ -238,7 +238,7 @@ type Artifact struct { ArtifactName string `json:"artifact_name"` ArtifactGroup string `json:"artifact_group"` // Holds optional provides fields in the type-info header - TypeInfoProvides map[string]interface{} `json:"artifact_provides,omitempty"` + TypeInfoProvides map[string]string `json:"artifact_provides,omitempty"` } // Info about the update in progress. @@ -279,7 +279,7 @@ func (ur *UpdateInfo) ArtifactGroup() string { return ur.Artifact.ArtifactGroup } -func (ur *UpdateInfo) ArtifactTypeInfoProvides() map[string]interface{} { +func (ur *UpdateInfo) ArtifactTypeInfoProvides() map[string]string { return ur.Artifact.TypeInfoProvides } diff --git a/device/device.go b/device/device.go index fec773a01..8075d8846 100644 --- a/device/device.go +++ b/device/device.go @@ -115,7 +115,7 @@ func GetManifestData(dataType, manifestFile string) (string, error) { } } -func (d *DeviceManager) GetProvides() (map[string]interface{}, error) { +func (d *DeviceManager) GetProvides() (map[string]string, error) { return datastore.LoadProvides(d.Store) } diff --git a/installer/installer.go b/installer/installer.go index baf903e96..52ad6f5a7 100644 --- a/installer/installer.go +++ b/installer/installer.go @@ -215,7 +215,7 @@ func (i *Installer) GetCompatibleDevices() []string { // Returns the merged artifact provides header-info and type-info fields // for artifact version >= 3. Returns nil if version < 3 -func (i *Installer) GetArtifactProvides() (map[string]interface{}, error) { +func (i *Installer) GetArtifactProvides() (map[string]string, error) { return i.ar.MergeArtifactProvides() } diff --git a/vendor/github.com/mendersoftware/mender-artifact/LICENSE b/vendor/github.com/mendersoftware/mender-artifact/LICENSE index a819abc60..131627f91 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/LICENSE +++ b/vendor/github.com/mendersoftware/mender-artifact/LICENSE @@ -1,4 +1,4 @@ -Copyright 2019 Northern.tech AS +Copyright 2020 Northern.tech AS All content in this project is licensed under the Apache License v2, unless indicated otherwise. diff --git a/vendor/github.com/mendersoftware/mender-artifact/areader/reader.go b/vendor/github.com/mendersoftware/mender-artifact/areader/reader.go index 6160fd588..f6f3b21eb 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/areader/reader.go +++ b/vendor/github.com/mendersoftware/mender-artifact/areader/reader.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import ( "github.com/mendersoftware/mender-artifact/artifact" "github.com/mendersoftware/mender-artifact/handlers" + "github.com/mendersoftware/mender-artifact/utils" "github.com/pkg/errors" ) @@ -655,12 +656,8 @@ func (ar *Reader) setInstallers(upd []artifact.UpdateType, augmented bool) error ar.installers[i] = w.NewInstance() } } else if ar.ForbidUnknownHandlers { - errstr := fmt.Sprintf("Artifact Payload type '%s' is not supported by this Mender Client", update.Type) - if update.Type == "rootfs-image" { - return errors.New(errstr + ". Ensure that the Mender Client is fully integrated and that the RootfsPartA/B configuration variables are set correctly in 'mender.conf'") - } else { - return errors.New(errstr) - } + return fmt.Errorf("Cannot load handler for unknown Payload type '%s'", + update.Type) } else { err := ar.makeInstallersForUnknownTypes(update.Type, i, augmented) if err != nil { @@ -1042,17 +1039,17 @@ func (ar *Reader) GetUpdateStorers() ([]handlers.UpdateStorer, error) { func (ar *Reader) MergeArtifactDepends() (map[string]interface{}, error) { - retMap := make(map[string]interface{}) - depends := ar.GetArtifactDepends() if depends == nil { // Artifact version < 3 return nil, nil } - retMap["artifact_name"] = depends.ArtifactName - retMap["compatible_devices"] = depends.CompatibleDevices - retMap["artifact_group"] = depends.ArtifactGroup + retMap, err := utils.MarshallStructToMap(depends) + if err != nil { + return nil, errors.Wrap(err, + "error encoding struct as type map") + } // No depends in the augmented header info @@ -1075,21 +1072,24 @@ func (ar *Reader) MergeArtifactDepends() (map[string]interface{}, error) { return retMap, nil } -func (ar *Reader) MergeArtifactProvides() (map[string]interface{}, error) { - - retMap := make(map[string]interface{}) +func (ar *Reader) MergeArtifactProvides() (map[string]string, error) { provides := ar.GetArtifactProvides() if provides == nil { // Artifact version < 3 return nil, nil } - - retMap["artifact_name"] = provides.ArtifactName - retMap["artifact_group"] = provides.ArtifactGroup + providesMap, err := utils.MarshallStructToMap(provides) + if err != nil { + return nil, errors.Wrap(err, + "error encoding struct as type map") + } + retMap := make(map[string]string) + for key, value := range providesMap { + retMap[key] = value.(string) + } // No provides in the augmented header info - for _, upd := range ar.installers { p, err := upd.GetUpdateProvides() if err != nil { @@ -1097,6 +1097,7 @@ func (ar *Reader) MergeArtifactProvides() (map[string]interface{}, error) { } else if p == nil { continue } + for key, val := range p.Map() { // Ensure there are no matching keys if _, ok := retMap[key]; ok { diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/checksum.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/checksum.go index 6c4580f3c..000ff0a18 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/checksum.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/checksum.go @@ -1,4 +1,4 @@ -// Copyright 2018 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor.go index 24ab80dcb..fa971a0c5 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor.go @@ -1,4 +1,4 @@ -// Copyright 2018 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_gzip.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_gzip.go index 52090b8ec..148b3bf94 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_gzip.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_gzip.go @@ -1,4 +1,4 @@ -// Copyright 2018 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_lzma.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_lzma.go index 87d4a3d42..6094d19eb 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_lzma.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_lzma.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_none.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_none.go index 8def16ad3..fe811be37 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_none.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/compressor_none.go @@ -1,4 +1,4 @@ -// Copyright 2018 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/metadata.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/metadata.go index 9b96f91bb..34ddea17d 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/metadata.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/metadata.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -56,6 +56,7 @@ func decode(p []byte, data WriteValidator) error { } dec := json.NewDecoder(bytes.NewReader(p)) + dec.DisallowUnknownFields() err := dec.Decode(data) if err != nil { return err @@ -295,17 +296,38 @@ func (t TypeInfoDepends) Map() map[string]interface{} { } func NewTypeInfoDepends(m interface{}) (ti TypeInfoDepends, err error) { + + const errMsgInvalidTypeFmt = "Invalid TypeInfo depends type: %T" + const errMsgInvalidTypeEntFmt = errMsgInvalidTypeFmt + ", with value %v" + ti = make(map[string]interface{}) switch m.(type) { case map[string]interface{}: m := m.(map[string]interface{}) for k, v := range m { switch v.(type) { + case string, []string: ti[k] = v - continue + + case []interface{}: + valFace := v.([]interface{}) + valStr := make([]string, len(valFace)) + for i, entFace := range v.([]interface{}) { + entStr, ok := entFace.(string) + if !ok { + return nil, fmt.Errorf( + errMsgInvalidTypeEntFmt, + v, v) + } + valStr[i] = entStr + } + ti[k] = valStr + default: - return nil, fmt.Errorf("Invalid TypeInfo type: %T", m) + return nil, fmt.Errorf( + errMsgInvalidTypeEntFmt, + v, v) } } return ti, nil @@ -322,7 +344,7 @@ func NewTypeInfoDepends(m interface{}) (ti TypeInfoDepends, err error) { } return ti, nil default: - return nil, fmt.Errorf("Invalid TypeInfo type: %T", m) + return nil, fmt.Errorf(errMsgInvalidTypeFmt, m) } } @@ -338,24 +360,29 @@ func (t *TypeInfoDepends) UnmarshalJSON(b []byte) error { return err } -type TypeInfoProvides map[string]interface{} +type TypeInfoProvides map[string]string -func (t TypeInfoProvides) Map() map[string]interface{} { - return map[string]interface{}(t) +func (t TypeInfoProvides) Map() map[string]string { + return t } func NewTypeInfoProvides(m interface{}) (ti TypeInfoProvides, err error) { - ti = make(map[string]interface{}) + + const errMsgInvalidTypeFmt = "Invalid TypeInfo provides type: %T" + const errMsgInvalidTypeEntFmt = errMsgInvalidTypeFmt + ", with value %v" + + ti = make(map[string]string) switch m.(type) { case map[string]interface{}: m := m.(map[string]interface{}) for k, v := range m { switch v.(type) { - case string, []string: - ti[k] = v + case string: + ti[k] = v.(string) continue default: - return nil, fmt.Errorf("Invalid TypeInfo type: %T", m) + return nil, fmt.Errorf(errMsgInvalidTypeEntFmt, + v, v) } } return ti, nil @@ -365,14 +392,8 @@ func NewTypeInfoProvides(m interface{}) (ti TypeInfoProvides, err error) { ti[k] = v } return ti, nil - case map[string][]string: - m := m.(map[string][]string) - for k, v := range m { - ti[k] = v - } - return ti, nil default: - return nil, fmt.Errorf("Invalid TypeInfo type: %T", m) + return nil, fmt.Errorf(errMsgInvalidTypeFmt, m) } } @@ -395,9 +416,9 @@ type TypeInfoV3 struct { Type string `json:"type"` // Checksum of the image that needs to be installed on the device in order to // apply the current update. - ArtifactDepends *TypeInfoDepends `json:"artifact_depends,omitempty"` + ArtifactDepends TypeInfoDepends `json:"artifact_depends,omitempty"` // Checksum of the image currently installed on the device. - ArtifactProvides *TypeInfoProvides `json:"artifact_provides,omitempty"` + ArtifactProvides TypeInfoProvides `json:"artifact_provides,omitempty"` } // Validate checks that the required `Type` field is set. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/scripter.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/scripter.go index 107c9a019..168632867 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/scripter.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/scripter.go @@ -1,4 +1,4 @@ -// Copyright 2017 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/signer.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/signer.go index 576d74cc5..2f8a9b807 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/signer.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/signer.go @@ -1,4 +1,4 @@ -// Copyright 2018 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/tar_writer_file.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/tar_writer_file.go index 2f1e64993..e3b0f30df 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/tar_writer_file.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/tar_writer_file.go @@ -1,4 +1,4 @@ -// Copyright 2017 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/tar_writer_stream.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/tar_writer_stream.go index 473fb2acc..62699a158 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/tar_writer_stream.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/tar_writer_stream.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/artifact/update.go b/vendor/github.com/mendersoftware/mender-artifact/artifact/update.go index 515206be6..d90af3d2c 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/artifact/update.go +++ b/vendor/github.com/mendersoftware/mender-artifact/artifact/update.go @@ -1,4 +1,4 @@ -// Copyright 2018 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/awriter/signer.go b/vendor/github.com/mendersoftware/mender-artifact/awriter/signer.go index 45db45426..362b0c6ba 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/awriter/signer.go +++ b/vendor/github.com/mendersoftware/mender-artifact/awriter/signer.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/awriter/writer.go b/vendor/github.com/mendersoftware/mender-artifact/awriter/writer.go index 243f772af..b819f1b85 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/awriter/writer.go +++ b/vendor/github.com/mendersoftware/mender-artifact/awriter/writer.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/vendor/github.com/mendersoftware/mender-artifact/handlers/common.go b/vendor/github.com/mendersoftware/mender-artifact/handlers/common.go index 83d9373e6..9ef6c84c4 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/handlers/common.go +++ b/vendor/github.com/mendersoftware/mender-artifact/handlers/common.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -61,18 +61,18 @@ type ArtifactUpdateHeaders interface { // Returns merged data of non-augmented and augmented data, where the // latter overrides the former. Returns error if they cannot be merged. - GetUpdateDepends() (*artifact.TypeInfoDepends, error) - GetUpdateProvides() (*artifact.TypeInfoProvides, error) + GetUpdateDepends() (artifact.TypeInfoDepends, error) + GetUpdateProvides() (artifact.TypeInfoProvides, error) GetUpdateMetaData() (map[string]interface{}, error) // Generic JSON // Returns non-augmented (original) data. - GetUpdateOriginalDepends() *artifact.TypeInfoDepends - GetUpdateOriginalProvides() *artifact.TypeInfoProvides + GetUpdateOriginalDepends() artifact.TypeInfoDepends + GetUpdateOriginalProvides() artifact.TypeInfoProvides GetUpdateOriginalMetaData() map[string]interface{} // Generic JSON // Returns augmented data. - GetUpdateAugmentDepends() *artifact.TypeInfoDepends - GetUpdateAugmentProvides() *artifact.TypeInfoProvides + GetUpdateAugmentDepends() artifact.TypeInfoDepends + GetUpdateAugmentProvides() artifact.TypeInfoProvides GetUpdateAugmentMetaData() map[string]interface{} // Generic JSON GetUpdateOriginalTypeInfoWriter() io.Writer diff --git a/vendor/github.com/mendersoftware/mender-artifact/handlers/module_image.go b/vendor/github.com/mendersoftware/mender-artifact/handlers/module_image.go index fa8cc680b..12921c125 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/handlers/module_image.go +++ b/vendor/github.com/mendersoftware/mender-artifact/handlers/module_image.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -134,7 +134,7 @@ func (img *ModuleImage) GetUpdateAllFiles() [](*DataFile) { return allFiles } -func (img *ModuleImage) GetUpdateOriginalDepends() *artifact.TypeInfoDepends { +func (img *ModuleImage) GetUpdateOriginalDepends() artifact.TypeInfoDepends { if img.original == nil { return img.typeInfoV3.ArtifactDepends } else { @@ -142,7 +142,7 @@ func (img *ModuleImage) GetUpdateOriginalDepends() *artifact.TypeInfoDepends { } } -func (img *ModuleImage) GetUpdateOriginalProvides() *artifact.TypeInfoProvides { +func (img *ModuleImage) GetUpdateOriginalProvides() artifact.TypeInfoProvides { if img.original == nil { return img.typeInfoV3.ArtifactProvides } else { @@ -167,19 +167,19 @@ func (img *ModuleImage) setUpdateOriginalMetaData(metaData map[string]interface{ } } -func (img *ModuleImage) GetUpdateAugmentDepends() *artifact.TypeInfoDepends { +func (img *ModuleImage) GetUpdateAugmentDepends() artifact.TypeInfoDepends { if img.original == nil { ret := make(artifact.TypeInfoDepends) - return &ret + return ret } else { return img.typeInfoV3.ArtifactDepends } } -func (img *ModuleImage) GetUpdateAugmentProvides() *artifact.TypeInfoProvides { +func (img *ModuleImage) GetUpdateAugmentProvides() artifact.TypeInfoProvides { if img.original == nil { ret := make(artifact.TypeInfoProvides) - return &ret + return ret } else { return img.typeInfoV3.ArtifactProvides } @@ -326,7 +326,7 @@ func transformGenericMapToStruct(src map[string]interface{}, dst interface{}) er return nil } -func (img *ModuleImage) GetUpdateDepends() (*artifact.TypeInfoDepends, error) { +func (img *ModuleImage) GetUpdateDepends() (artifact.TypeInfoDepends, error) { orig, err := transformStructToGenericMap(img.GetUpdateOriginalDepends()) if err != nil { return nil, err @@ -347,10 +347,10 @@ func (img *ModuleImage) GetUpdateDepends() (*artifact.TypeInfoDepends, error) { return nil, err } - return &depends, nil + return merged, nil } -func (img *ModuleImage) GetUpdateProvides() (*artifact.TypeInfoProvides, error) { +func (img *ModuleImage) GetUpdateProvides() (artifact.TypeInfoProvides, error) { orig, err := transformStructToGenericMap(img.GetUpdateOriginalProvides()) if err != nil { return nil, err @@ -371,7 +371,7 @@ func (img *ModuleImage) GetUpdateProvides() (*artifact.TypeInfoProvides, error) return nil, err } - return &provides, nil + return provides, nil } func (img *ModuleImage) GetUpdateMetaData() (map[string]interface{}, error) { diff --git a/vendor/github.com/mendersoftware/mender-artifact/handlers/rootfs_image.go b/vendor/github.com/mendersoftware/mender-artifact/handlers/rootfs_image.go index 4b0982ff2..64da17b77 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/handlers/rootfs_image.go +++ b/vendor/github.com/mendersoftware/mender-artifact/handlers/rootfs_image.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -244,11 +244,11 @@ func (rfs *Rootfs) GetUpdateOriginalType() string { return "" } -func (rfs *Rootfs) GetUpdateDepends() (*artifact.TypeInfoDepends, error) { +func (rfs *Rootfs) GetUpdateDepends() (artifact.TypeInfoDepends, error) { return rfs.GetUpdateOriginalDepends(), nil } -func (rfs *Rootfs) GetUpdateProvides() (*artifact.TypeInfoProvides, error) { +func (rfs *Rootfs) GetUpdateProvides() (artifact.TypeInfoProvides, error) { return rfs.GetUpdateOriginalProvides(), nil } @@ -274,14 +274,14 @@ func (rfs *Rootfs) setUpdateAugmentMetaData(jsonObj map[string]interface{}) erro return nil } -func (rfs *Rootfs) GetUpdateOriginalDepends() *artifact.TypeInfoDepends { +func (rfs *Rootfs) GetUpdateOriginalDepends() artifact.TypeInfoDepends { if rfs.typeInfoV3 == nil { return nil } return rfs.typeInfoV3.ArtifactDepends } -func (rfs *Rootfs) GetUpdateOriginalProvides() *artifact.TypeInfoProvides { +func (rfs *Rootfs) GetUpdateOriginalProvides() artifact.TypeInfoProvides { if rfs.typeInfoV3 == nil { return nil } @@ -296,11 +296,11 @@ func (rfs *Rootfs) GetUpdateOriginalMetaData() map[string]interface{} { } } -func (rfs *Rootfs) GetUpdateAugmentDepends() *artifact.TypeInfoDepends { +func (rfs *Rootfs) GetUpdateAugmentDepends() artifact.TypeInfoDepends { return nil } -func (rfs *Rootfs) GetUpdateAugmentProvides() *artifact.TypeInfoProvides { +func (rfs *Rootfs) GetUpdateAugmentProvides() artifact.TypeInfoProvides { return nil } diff --git a/vendor/github.com/mendersoftware/mender-artifact/utils/binpath.go b/vendor/github.com/mendersoftware/mender-artifact/utils/binpath.go new file mode 100644 index 000000000..bad148fa1 --- /dev/null +++ b/vendor/github.com/mendersoftware/mender-artifact/utils/binpath.go @@ -0,0 +1,43 @@ +// Copyright 2020 Northern.tech AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.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 utils + +import ( + "os/exec" + "path" +) + +var ( + ExternalBinaryPaths = []string{"/usr/sbin", "/sbin", "/usr/local/sbin"} +) + +func GetBinaryPath(command string) (string, error) { + // first check if command exists in PATH + p, err := exec.LookPath(command) + if err == nil { + return p, nil + } + + // maybe sbin isn't included in PATH, check there explicitly. + for _, p = range ExternalBinaryPaths { + p, err = exec.LookPath(path.Join(p, command)) + if err == nil { + return p, nil + } + } + + // not found, but oh well... + return command, err +} diff --git a/vendor/github.com/mendersoftware/mender-artifact/utils/json.go b/vendor/github.com/mendersoftware/mender-artifact/utils/json.go new file mode 100644 index 000000000..f98d4f273 --- /dev/null +++ b/vendor/github.com/mendersoftware/mender-artifact/utils/json.go @@ -0,0 +1,37 @@ +// Copyright 2020 Northern.tech AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.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 utils + +import "encoding/json" + +func AppendStructToMap(Struct interface{}, Map map[string]interface{}) error { + buf, err := json.Marshal(Struct) + if err != nil { + return err + } + if err := json.Unmarshal(buf, &Map); err != nil { + return err + } + + return nil +} + +func MarshallStructToMap(Struct interface{}) (map[string]interface{}, error) { + retMap := make(map[string]interface{}) + if err := AppendStructToMap(Struct, retMap); err != nil { + return nil, err + } + return retMap, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index f5dc967dc..2ca54c719 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -35,28 +35,34 @@ "revisionTime": "2019-11-18T11:56:43Z" }, { - "checksumSHA1": "+2/9lrKx8K9Ea4wF7sXo3L10Byg=", + "checksumSHA1": "PuNXDk0mAwaITVg6nu91ojLw4Aw=", "path": "github.com/mendersoftware/mender-artifact/areader", - "revision": "7c541c8f8348d6a11487c47e8c824ab55fbae5ae", - "revisionTime": "2019-11-18T11:56:43Z" + "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", + "revisionTime": "2020-03-13T13:38:56Z" }, { - "checksumSHA1": "8LsMfbSGddvwkqnTKFFrWXQnql0=", + "checksumSHA1": "MZXf2IJHDEzTwERfo6ar/9ipDTM=", "path": "github.com/mendersoftware/mender-artifact/artifact", - "revision": "7c541c8f8348d6a11487c47e8c824ab55fbae5ae", - "revisionTime": "2019-11-18T11:56:43Z" + "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", + "revisionTime": "2020-03-13T13:38:56Z" }, { - "checksumSHA1": "COqUP9VoKuMnKRdMwmBaurB5WJs=", + "checksumSHA1": "gc2rkb1VAmOAxY7cQ0zdipRyAQU=", "path": "github.com/mendersoftware/mender-artifact/awriter", - "revision": "7c541c8f8348d6a11487c47e8c824ab55fbae5ae", - "revisionTime": "2019-11-18T11:56:43Z" + "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", + "revisionTime": "2020-03-13T13:38:56Z" }, { - "checksumSHA1": "J1M72/QocbHblhT4TwyVpTO3gQs=", + "checksumSHA1": "CxPWbnJaSedagK6KU+pc30X0XF8=", "path": "github.com/mendersoftware/mender-artifact/handlers", - "revision": "7c541c8f8348d6a11487c47e8c824ab55fbae5ae", - "revisionTime": "2019-11-18T11:56:43Z" + "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", + "revisionTime": "2020-03-13T13:38:56Z" + }, + { + "checksumSHA1": "SRqPsG84/Ti4VBdjP/FIjRLKXGg=", + "path": "github.com/mendersoftware/mender-artifact/utils", + "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", + "revisionTime": "2020-03-13T13:38:56Z" }, { "checksumSHA1": "YGYg7j9+kmKncdC5UsI5FV237ts=", From be2325cff92c8e544b53a7e1272574cdf9ed1b6e Mon Sep 17 00:00:00 2001 From: Alf-Rune Siqveland Date: Mon, 16 Mar 2020 15:31:29 +0100 Subject: [PATCH 09/12] app: Fix verifyArtifactDependencies to handle unmarshalled depends Unmarshalled depends slices are always []interface{} type, which wasn't handled in the depends checking function. changelog: none Signed-off-by: Alf-Rune Siqveland --- app/mender.go | 51 ++++++++++++++++++++-------------------- utils/compare.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 utils/compare.go diff --git a/app/mender.go b/app/mender.go index e49ec2c2c..4d386cb04 100644 --- a/app/mender.go +++ b/app/mender.go @@ -30,6 +30,7 @@ import ( inv "github.com/mendersoftware/mender/inventory" "github.com/mendersoftware/mender/statescript" "github.com/mendersoftware/mender/store" + "github.com/mendersoftware/mender/utils" "github.com/pkg/errors" ) @@ -86,6 +87,10 @@ var ( } ) +const ( + errMsgInvalidDependsTypeF = "invalid type %T for dependency with name %s" +) + func StateStatus(m datastore.MenderState) string { status, ok := stateStatus[m] if ok { @@ -277,45 +282,41 @@ func (m *Mender) FetchUpdate(url string) (io.ReadCloser, int64, error) { func verifyArtifactDependencies( depends map[string]interface{}, - provides map[string]string) error { - // Generic closure for checking if element is present in slice. - elemInSlice := func(elem string, slice []string) bool { - for _, s := range slice { - if s == elem { - return true - } - } - return false - } + provides map[string]string, +) error { for key, depend := range depends { if key == "device_type" { // handled elsewhere continue } - switch depend := depend.(type) { - case []string: - if len(depend) == 0 { - continue - } - case string: - if depend == "" { - continue - } - default: - return errors.Errorf( - "Invalid type for dependency with name %s", key) - } if p, ok := provides[key]; ok { switch depend.(type) { + case []interface{}: + if ok, err := utils.ElemInSlice(depend, p); ok { + continue + } else if err == utils.ErrInvalidType { + return errors.Errorf( + errMsgInvalidDependsTypeF, + depend, + key, + ) + } case []string: - if elemInSlice(p, depend.([]string)) { + // No need to check type here - all deterministic + if ok, _ := utils.ElemInSlice(depend, p); ok { continue } case string: - if p == depend { + if p == depend.(string) { continue } + default: + return errors.Errorf( + errMsgInvalidDependsTypeF, + depend, + key, + ) } return errors.Errorf(errMsgDependencyNotSatisfiedF, key, depend, provides[key]) diff --git a/utils/compare.go b/utils/compare.go new file mode 100644 index 000000000..fac0d1c00 --- /dev/null +++ b/utils/compare.go @@ -0,0 +1,60 @@ +// Copyright 2020 Northern.tech AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.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 utils + +import ( + "reflect" + + "github.com/pkg/errors" +) + +var ( + ErrNotSlice = errors.New("value is not a slice") + ErrInvalidType = errors.New("invalid type") +) + +// ElemInSlice is a generic function for comparing a value with all elements +// in a slice. +func ElemInSlice(slice, elem interface{}) (bool, error) { + rSlice := reflect.ValueOf(slice) + rElem := reflect.ValueOf(elem) + // Maybe dereference arguments + if rSlice.Kind() == reflect.Ptr || rSlice.Kind() == reflect.Interface { + rSlice = rSlice.Elem() + } + if rElem.Kind() == reflect.Ptr || rElem.Kind() == reflect.Interface { + rElem = rElem.Elem() + } + if rSlice.Kind() != reflect.Slice { + return false, ErrNotSlice + } + n := rSlice.Len() + for i := 0; i < n; i++ { + sliceElem := rSlice.Index(i) + if sliceElem.Kind() == reflect.Ptr { + sliceElem = sliceElem.Elem() + } + if sliceElem.Kind() == reflect.Interface { + sliceElem = sliceElem.Elem() + } + if sliceElem.Kind() != rElem.Kind() { + return false, ErrInvalidType + } + if sliceElem.Interface() == rElem.Interface() { + return true, nil + } + } + return false, nil +} From 7fb9d0cd945c2bb3b290b2b9d2488eb082864c91 Mon Sep 17 00:00:00 2001 From: Alf-Rune Siqveland Date: Tue, 17 Mar 2020 13:08:34 +0100 Subject: [PATCH 10/12] Fix broken unit tests Fixes unit tests that went broken from the preceeding three commits. changelog: none Signed-off-by: Alf-Rune Siqveland --- LIC_FILES_CHKSUM.sha256 | 2 +- Makefile | 2 +- app/mender_test.go | 13 +++---- app/standalone_test.go | 4 +-- app/state_test.go | 8 ++--- client/client_update.go | 3 ++ client/client_update_test.go | 43 ++++++++++------------- client/test/server.go | 67 ++++++++++++++++++++++++++---------- datastore/standalone_data.go | 2 +- installer/modules_test.go | 22 ++++++------ tests/artifact_utils.go | 10 +++--- 11 files changed, 102 insertions(+), 74 deletions(-) diff --git a/LIC_FILES_CHKSUM.sha256 b/LIC_FILES_CHKSUM.sha256 index 3cb5927e7..9afb36f6a 100644 --- a/LIC_FILES_CHKSUM.sha256 +++ b/LIC_FILES_CHKSUM.sha256 @@ -3,7 +3,7 @@ beb140be4cd64599bedc691a55b2729c9cc611a4b9d6ec44e01270105daf18a2 vendor/github.com/mendersoftware/mendertesting/LICENSE ceb1b36ff073bd13d9806d4615b931707768ca9023805620acc32dd1cfc2f680 vendor/github.com/mendersoftware/log/LICENSE ceb1b36ff073bd13d9806d4615b931707768ca9023805620acc32dd1cfc2f680 vendor/github.com/mendersoftware/scopestack/LICENSE -beb140be4cd64599bedc691a55b2729c9cc611a4b9d6ec44e01270105daf18a2 vendor/github.com/mendersoftware/mender-artifact/LICENSE +32714818ad6f98ee0185a52e23a475d89122e3efd2b2c26c733781c28e798c99 vendor/github.com/mendersoftware/mender-artifact/LICENSE # # BSD 2 Clause license. 8d427fd87bc9579ea368fde3d49f9ca22eac857f91a9dec7e3004bdfab7dee86 vendor/github.com/pkg/errors/LICENSE diff --git a/Makefile b/Makefile index 738ea94d8..37394f1a1 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ V ?= PKGS = $(shell go list ./... | grep -v vendor) PKGFILES = $(shell find . \( -path ./vendor -o -path ./Godeps \) -prune \ -o -type f -name '*.go' -print) -PKGFILES_notest = $(shell echo $(PKGFILES) | tr ' ' '\n' | grep -v _test.go) +PKGFILES_notest = $(shell echo $(PKGFILES) | tr ' ' '\n' | grep -v '\(client/test\|_test.go\)' ) GOCYCLO ?= 15 CGO_ENABLED=1 diff --git a/app/mender_test.go b/app/mender_test.go index a9424a550..f4d39045a 100644 --- a/app/mender_test.go +++ b/app/mender_test.go @@ -278,7 +278,7 @@ func Test_CheckUpdateSimple(t *testing.T) { mender.ArtifactInfoFile = artifactInfo mender.DeviceTypeFile = deviceType - srv.Update.Current = client.CurrentUpdate{ + srv.Update.Current = &client.CurrentUpdate{ Artifact: "fake-id", DeviceType: "hammer", } @@ -308,8 +308,9 @@ func Test_CheckUpdateSimple(t *testing.T) { srv.Update.Has = true up, err = mender.CheckUpdate() assert.NoError(t, err) - assert.NotNil(t, up) - assert.Equal(t, *up, srv.Update.Data) + if assert.NotNil(t, up) { + assert.Equal(t, *up, srv.Update.Data) + } // pretend that we got 204 No Content from the server, i.e empty response body srv.Update.Has = false @@ -629,7 +630,7 @@ func TestAuthToken(t *testing.T) { assert.Equal(t, []byte("tokendata"), token) ts.Update.Unauthorized = true - ts.Update.Current = client.CurrentUpdate{ + ts.Update.Current = &client.CurrentUpdate{ Artifact: "fake-id", DeviceType: "foo-bar", } @@ -1005,7 +1006,7 @@ func TestReauthorization(t *testing.T) { srv.Auth.Token = []byte(`foo`) srv.Auth.Authorize = true srv.Auth.Verify = true - srv.Update.Current = client.CurrentUpdate{ + srv.Update.Current = &client.CurrentUpdate{ Artifact: "mender-image", DeviceType: "dev", } @@ -1065,7 +1066,7 @@ func TestFailoverServers(t *testing.T) { defer srv1.Close() defer srv2.Close() // Give srv2 knowledge about client artifact- and device name - srv2.Update.Current = client.CurrentUpdate{ + srv2.Update.Current = &client.CurrentUpdate{ Artifact: "mender-image", DeviceType: "dev", } diff --git a/app/standalone_test.go b/app/standalone_test.go index e44e65f76..2dde2a3ed 100644 --- a/app/standalone_test.go +++ b/app/standalone_test.go @@ -212,7 +212,7 @@ func TestDoManualUpdateArtifactV3Dependencies(t *testing.T) { ArtifactGroup: []string{"testGroup"}, CompatibleDevices: []string{"qemux86-64"}, } - typeInfoDepends := &map[string]interface{}{ + typeInfoDepends := map[string]interface{}{ "testKey": "testValue", } @@ -1039,7 +1039,7 @@ func TestStandaloneStoreAndRestore(t *testing.T) { sd: &standaloneData{ artifactName: "foobar", artifactGroup: "baz", - artifactTypeInfoProvides: map[string]interface{}{ + artifactTypeInfoProvides: map[string]string{ "bugs": "bunny", "daffy": "duck", }, diff --git a/app/state_test.go b/app/state_test.go index 33e4eb9ba..3bbdad20a 100644 --- a/app/state_test.go +++ b/app/state_test.go @@ -432,7 +432,7 @@ func TestStateUpdateCommit(t *testing.T) { defer os.RemoveAll(tempDir) DeploymentLogger = NewDeploymentLogManager(tempDir) - artifactTypeInfoProvides := map[string]interface{}{ + artifactTypeInfoProvides := map[string]string{ "test-kwrd": "test-value", } @@ -471,7 +471,7 @@ func TestStateUpdateCommit(t *testing.T) { assert.Equal(t, artifactGroup, "TestGroup") storeBuf, err = ms.ReadAll(datastore.ArtifactTypeInfoProvidesKey) assert.NoError(t, err) - var typeProvides map[string]interface{} + var typeProvides map[string]string err = json.Unmarshal(storeBuf, &typeProvides) assert.NoError(t, err) assert.Equal(t, typeProvides, artifactTypeInfoProvides) @@ -789,12 +789,12 @@ func TestStateUpdateStore(t *testing.T) { ArtifactGroup: []string{"TestGroup"}, CompatibleDevices: []string{"vexpress-qemu"}, } - artifactTypeInfoProvides := map[string]interface{}{ + artifactTypeInfoProvides := map[string]string{ "test": "moar-test", } stream, err := tests.CreateTestArtifactV3("test", "gzip", - artifactProvides, artifactDepends, &artifactTypeInfoProvides, nil) + artifactProvides, artifactDepends, artifactTypeInfoProvides, nil) require.NoError(t, err) update := &datastore.UpdateInfo{ diff --git a/client/client_update.go b/client/client_update.go index d2e309112..3aa7a54db 100644 --- a/client/client_update.go +++ b/client/client_update.go @@ -61,6 +61,9 @@ type CurrentUpdate struct { } func (u *CurrentUpdate) MarshalJSON() ([]byte, error) { + if u.Provides == nil { + u.Provides = make(map[string]string) + } u.Provides["artifact_name"] = u.Artifact u.Provides["device_type"] = u.DeviceType return json.Marshal(u.Provides) diff --git a/client/client_update_test.go b/client/client_update_test.go index 1f047de91..1a7d4fdd2 100644 --- a/client/client_update_test.go +++ b/client/client_update_test.go @@ -193,7 +193,7 @@ func Test_GetScheduledUpdate_errorParsingResponse_UpdateFailing(t *testing.T) { fakeProcessUpdate := func(response *http.Response) (interface{}, error) { return nil, errors.New("") } - _, err = client.getUpdateInfo(ac, fakeProcessUpdate, ts.URL, CurrentUpdate{}) + _, err = client.getUpdateInfo(ac, fakeProcessUpdate, ts.URL, &CurrentUpdate{}) assert.Error(t, err) } @@ -217,7 +217,7 @@ func Test_GetScheduledUpdate_responseMissingParameters_UpdateFailing(t *testing. assert.NotNil(t, client) fakeProcessUpdate := func(response *http.Response) (interface{}, error) { return nil, nil } - _, err = client.getUpdateInfo(ac, fakeProcessUpdate, ts.URL, CurrentUpdate{}) + _, err = client.getUpdateInfo(ac, fakeProcessUpdate, ts.URL, &CurrentUpdate{}) assert.NoError(t, err) } @@ -240,7 +240,7 @@ func Test_GetScheduledUpdate_ParsingResponseOK_updateSuccess(t *testing.T) { client := NewUpdate() assert.NotNil(t, client) - data, err := client.GetScheduledUpdate(ac, ts.URL, CurrentUpdate{}) + data, err := client.GetScheduledUpdate(ac, ts.URL, &CurrentUpdate{}) assert.NoError(t, err) update, ok := data.(datastore.UpdateInfo) assert.True(t, ok) @@ -321,7 +321,7 @@ func Test_UpdateApiClientError(t *testing.T) { client := NewUpdate() _, err := client.GetScheduledUpdate(NewMockApiClient(nil, errors.New("foo")), - "http://foo.bar", CurrentUpdate{}) + "http://foo.bar", &CurrentUpdate{}) assert.Error(t, err) _, _, err = client.FetchUpdate(NewMockApiClient(nil, errors.New("foo")), @@ -330,7 +330,7 @@ func Test_UpdateApiClientError(t *testing.T) { } func TestMakeUpdateCheckRequest(t *testing.T) { - ent_req, req, err := makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{}) + ent_req, req, err := makeUpdateCheckRequest("http://foo.bar", &CurrentUpdate{}) assert.NotNil(t, ent_req) assert.NotNil(t, req) assert.NoError(t, err) @@ -339,17 +339,14 @@ func TestMakeUpdateCheckRequest(t *testing.T) { req.URL.String()) t.Logf("%s\n", req.URL.String()) - ent_req, req, err = makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{ - Artifact: "foo", - Provides: map[string]interface{}{ - "artifact_name": "release-1", - }, + ent_req, req, err = makeUpdateCheckRequest("http://foo.bar", &CurrentUpdate{ + Artifact: "release-1", }) assert.NotNil(t, ent_req) assert.NotNil(t, req) assert.NoError(t, err) - assert.Equal(t, "http://foo.bar/api/devices/v1/deployments/device/deployments/next?artifact_name=foo", + assert.Equal(t, "http://foo.bar/api/devices/v1/deployments/device/deployments/next?artifact_name=release-1", req.URL.String()) t.Logf("%s\n", req.URL.String()) body, err := ioutil.ReadAll(ent_req.Body) @@ -359,13 +356,9 @@ func TestMakeUpdateCheckRequest(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "release-1", provides["artifact_name"], string(body)) - ent_req, req, err = makeUpdateCheckRequest("http://foo.bar", CurrentUpdate{ + ent_req, req, err = makeUpdateCheckRequest("http://foo.bar", &CurrentUpdate{ Artifact: "foo", DeviceType: "hammer", - Provides: map[string]interface{}{ - "artifact_name": "release-2", - "device_type": "qemu", - }, }) assert.NotNil(t, ent_req) assert.NotNil(t, req) @@ -379,15 +372,15 @@ func TestMakeUpdateCheckRequest(t *testing.T) { provides = make(map[string]interface{}) err = json.Unmarshal(body, &provides) assert.NoError(t, err) - assert.Equal(t, "release-2", provides["artifact_name"], string(body)) - assert.Equal(t, "qemu", provides["device_type"], string(body)) + assert.Equal(t, "foo", provides["artifact_name"], string(body)) + assert.Equal(t, "hammer", provides["device_type"], string(body)) } func TestGetUpdateInfo(t *testing.T) { tests := map[string]struct { httpHandlerFunc http.HandlerFunc - currentUpdateInfo CurrentUpdate + currentUpdateInfo *CurrentUpdate errorFunc func(t assert.TestingT, err error, msgAndArgs ...interface{}) bool }{ "Enterprise - Success - Update available": { @@ -396,8 +389,8 @@ func TestGetUpdateInfo(t *testing.T) { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, "") }, - currentUpdateInfo: CurrentUpdate{ - Provides: map[string]interface{}{ + currentUpdateInfo: &CurrentUpdate{ + Provides: map[string]string{ "artifact_name": "release-1", "device_type": "qemu"}, }, @@ -409,8 +402,8 @@ func TestGetUpdateInfo(t *testing.T) { w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, "") }, - currentUpdateInfo: CurrentUpdate{ - Provides: map[string]interface{}{ + currentUpdateInfo: &CurrentUpdate{ + Provides: map[string]string{ "artifact_name": "release-1", "device_type": "qemu"}, }, @@ -426,8 +419,8 @@ func TestGetUpdateInfo(t *testing.T) { fmt.Fprint(w, "") } }, - currentUpdateInfo: CurrentUpdate{ - Provides: map[string]interface{}{ + currentUpdateInfo: &CurrentUpdate{ + Provides: map[string]string{ "artifact_name": "release-1", "device_type": "qemu"}, }, diff --git a/client/test/server.go b/client/test/server.go index c591011d2..23dbf97b0 100644 --- a/client/test/server.go +++ b/client/test/server.go @@ -35,7 +35,7 @@ type updateType struct { Data datastore.UpdateInfo Unauthorized bool Called bool - Current client.CurrentUpdate + Current *client.CurrentUpdate } type updateDownloadType struct { @@ -315,32 +315,63 @@ func urlQueryToCurrentUpdate(vals url.Values) client.CurrentUpdate { } func (cts *ClientTestServer) updateReq(w http.ResponseWriter, r *http.Request) { + var ok bool + var current client.CurrentUpdate log.Infof("got update request %v", r) cts.Update.Called = true // Enterprise client device provides post is not supported yet if r.Method == "POST" { - w.WriteHeader(404) - return - } - - if !isMethod(http.MethodGet, w, r) { - return - } + if !cts.verifyAuth(w, r) { + return + } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + err = json.Unmarshal(body, ¤t) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } - log.Infof("Valid update request GET: %v", r) + if current.Artifact, ok = current. + Provides["artifact_name"]; !ok { + w.WriteHeader(400) + w.Write([]byte("artifact_name missing from payload")) + return + } + if current.DeviceType, ok = current. + Provides["device_type"]; ok { + w.WriteHeader(400) + w.Write([]byte("device_type missing from payload")) + return + } + if !reflect.DeepEqual(current, *cts.Update.Current) { + log.Errorf("incorrect current update info, got %+v, expected %+v", + current, *cts.Update.Current) + w.WriteHeader(http.StatusBadRequest) + return + } - if !cts.verifyAuth(w, r) { + } else if !isMethod(http.MethodGet, w, r) { return - } - - log.Infof("parsed URL query: %v", r.URL.Query()) + } else { + if !cts.verifyAuth(w, r) { + return + } + log.Infof("Valid update request GET: %v", r) + log.Infof("parsed URL query: %v", r.URL.Query()) + if current := urlQueryToCurrentUpdate(r.URL.Query()); !reflect.DeepEqual(current, *cts.Update.Current) { + log.Errorf("incorrect current update info, got %+v, expected %+v", + current, *cts.Update.Current) + w.WriteHeader(http.StatusBadRequest) + return + } - if current := urlQueryToCurrentUpdate(r.URL.Query()); !reflect.DeepEqual(current, cts.Update.Current) { - log.Errorf("incorrect current update info, got %+v, expected %+v", - current, cts.Update.Current) - w.WriteHeader(http.StatusBadRequest) - return } switch { diff --git a/datastore/standalone_data.go b/datastore/standalone_data.go index 4c075b623..23e5d50ca 100644 --- a/datastore/standalone_data.go +++ b/datastore/standalone_data.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/installer/modules_test.go b/installer/modules_test.go index ece9a8d37..a4e7c58a2 100644 --- a/installer/modules_test.go +++ b/installer/modules_test.go @@ -36,7 +36,7 @@ import ( ) type testStreamsTreeInfo struct { - typeInfo artifact.TypeInfoV3 + typeInfo *artifact.TypeInfoV3 } func (i *testStreamsTreeInfo) GetCurrentArtifactName() (string, error) { @@ -63,20 +63,20 @@ func (i *testStreamsTreeInfo) GetUpdateOriginalType() string { return "test-type" } -func (i *testStreamsTreeInfo) GetUpdateDepends() (*artifact.TypeInfoDepends, error) { +func (i *testStreamsTreeInfo) GetUpdateDepends() (artifact.TypeInfoDepends, error) { return i.GetUpdateOriginalDepends(), nil } -func (i *testStreamsTreeInfo) GetUpdateProvides() (*artifact.TypeInfoProvides, error) { +func (i *testStreamsTreeInfo) GetUpdateProvides() (artifact.TypeInfoProvides, error) { return i.GetUpdateOriginalProvides(), nil } func (i *testStreamsTreeInfo) GetUpdateMetaData() (map[string]interface{}, error) { return i.GetUpdateOriginalMetaData(), nil } -func (i *testStreamsTreeInfo) GetUpdateOriginalDepends() *artifact.TypeInfoDepends { +func (i *testStreamsTreeInfo) GetUpdateOriginalDepends() artifact.TypeInfoDepends { return i.typeInfo.ArtifactDepends } -func (i *testStreamsTreeInfo) GetUpdateOriginalProvides() *artifact.TypeInfoProvides { +func (i *testStreamsTreeInfo) GetUpdateOriginalProvides() artifact.TypeInfoProvides { return i.typeInfo.ArtifactProvides } func (i *testStreamsTreeInfo) GetUpdateOriginalMetaData() map[string]interface{} { @@ -85,10 +85,10 @@ func (i *testStreamsTreeInfo) GetUpdateOriginalMetaData() map[string]interface{} } } -func (i *testStreamsTreeInfo) GetUpdateAugmentDepends() *artifact.TypeInfoDepends { +func (i *testStreamsTreeInfo) GetUpdateAugmentDepends() artifact.TypeInfoDepends { return nil } -func (i *testStreamsTreeInfo) GetUpdateAugmentProvides() *artifact.TypeInfoProvides { +func (i *testStreamsTreeInfo) GetUpdateAugmentProvides() artifact.TypeInfoProvides { return nil } func (i *testStreamsTreeInfo) GetUpdateAugmentMetaData() map[string]interface{} { @@ -96,7 +96,7 @@ func (i *testStreamsTreeInfo) GetUpdateAugmentMetaData() map[string]interface{} } func (i *testStreamsTreeInfo) GetUpdateOriginalTypeInfoWriter() io.Writer { - return &i.typeInfo + return i.typeInfo } func (i *testStreamsTreeInfo) GetUpdateAugmentTypeInfoWriter() io.Writer { return nil @@ -166,12 +166,12 @@ func TestStreamsTree(t *testing.T) { }, &prov, &dep) i := testStreamsTreeInfo{ - typeInfo: artifact.TypeInfoV3{ + typeInfo: &artifact.TypeInfoV3{ Type: "test-type", - ArtifactDepends: &artifact.TypeInfoDepends{ + ArtifactDepends: artifact.TypeInfoDepends{ "test-depend-key": "test-depend-value", }, - ArtifactProvides: &artifact.TypeInfoProvides{ + ArtifactProvides: artifact.TypeInfoProvides{ "test-provide-key": "test-provide-value", }, }, diff --git a/tests/artifact_utils.go b/tests/artifact_utils.go index f199ad341..645460dda 100644 --- a/tests/artifact_utils.go +++ b/tests/artifact_utils.go @@ -1,4 +1,4 @@ -// Copyright 2019 Northern.tech AS +// Copyright 2020 Northern.tech AS // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -38,8 +38,8 @@ func (rc *artifactReadCloser) Close() error { // provided depends and provides. func CreateTestArtifactV3(data, compressAlgorithm string, artifactProvides *ArtifactProvides, artifactDepends *ArtifactDepends, - typeProvides, - typeDepends *map[string]interface{}) (*artifactReadCloser, error) { + typeProvides map[string]string, + typeDepends map[string]interface{}) (*artifactReadCloser, error) { var artifactArgs *awriter.WriteArtifactArgs if artifactProvides == nil { artifactProvides = &ArtifactProvides{ @@ -71,8 +71,8 @@ func CreateTestArtifactV3(data, compressAlgorithm string, Depends: (*artifact.ArtifactDepends)(artifactDepends), TypeInfoV3: &artifact.TypeInfoV3{ Type: "rootfs-image", - ArtifactProvides: (*artifact.TypeInfoProvides)(typeProvides), - ArtifactDepends: (*artifact.TypeInfoDepends)(typeDepends), + ArtifactProvides: (artifact.TypeInfoProvides)(typeProvides), + ArtifactDepends: (artifact.TypeInfoDepends)(typeDepends), }, Updates: &awriter.Updates{Updates: []handlers.Composer{u}}, } From 993b46363a3766f428919a25091440ca19d19490 Mon Sep 17 00:00:00 2001 From: Kristian Amlie Date: Wed, 18 Mar 2020 15:13:29 +0100 Subject: [PATCH 11/12] Add handling of TENANT_TOKEN to docker-client test container. Changelog: None Signed-off-by: Kristian Amlie --- tests/Dockerfile.daemon | 4 +++- tests/entrypoint.sh | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100755 tests/entrypoint.sh diff --git a/tests/Dockerfile.daemon b/tests/Dockerfile.daemon index eabd100a6..c794da4d4 100644 --- a/tests/Dockerfile.daemon +++ b/tests/Dockerfile.daemon @@ -30,4 +30,6 @@ COPY --from=build /mender-install/ / RUN sh -c 'mkdir -p /var/lib/mender && echo device_type=docker-client > /var/lib/mender/device_type' RUN sh -c 'echo artifact_name=original > /etc/mender/artifact_info' -CMD /etc/init.d/ssh start && mender -daemon +COPY tests/entrypoint.sh / + +CMD /entrypoint.sh diff --git a/tests/entrypoint.sh b/tests/entrypoint.sh new file mode 100755 index 000000000..a3f1d2d41 --- /dev/null +++ b/tests/entrypoint.sh @@ -0,0 +1,10 @@ +#/bin/sh + +set -e + +if [ -n "$TENANT_TOKEN" ]; then + sed -i -e "s/\"TenantToken\": *\"[^\"]*\"/\"TenantToken\": \"$TENANT_TOKEN\"/" /etc/mender/mender.conf +fi + +/etc/init.d/ssh start +mender -daemon From c2b0941e981e75de35a21651d188aa767032eeb5 Mon Sep 17 00:00:00 2001 From: Kristian Amlie Date: Fri, 27 Mar 2020 15:50:47 +0100 Subject: [PATCH 12/12] Update to latest mender-artifact dependency. Changelog: None Signed-off-by: Kristian Amlie --- .../mender-artifact/areader/reader.go | 8 ++++-- vendor/vendor.json | 28 +++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/vendor/github.com/mendersoftware/mender-artifact/areader/reader.go b/vendor/github.com/mendersoftware/mender-artifact/areader/reader.go index f6f3b21eb..906f25796 100644 --- a/vendor/github.com/mendersoftware/mender-artifact/areader/reader.go +++ b/vendor/github.com/mendersoftware/mender-artifact/areader/reader.go @@ -656,8 +656,12 @@ func (ar *Reader) setInstallers(upd []artifact.UpdateType, augmented bool) error ar.installers[i] = w.NewInstance() } } else if ar.ForbidUnknownHandlers { - return fmt.Errorf("Cannot load handler for unknown Payload type '%s'", - update.Type) + errstr := fmt.Sprintf("Artifact Payload type '%s' is not supported by this Mender Client", update.Type) + if update.Type == "rootfs-image" { + return errors.New(errstr + ". Ensure that the Mender Client is fully integrated and that the RootfsPartA/B configuration variables are set correctly in 'mender.conf'") + } else { + return errors.New(errstr) + } } else { err := ar.makeInstallersForUnknownTypes(update.Type, i, augmented) if err != nil { diff --git a/vendor/vendor.json b/vendor/vendor.json index a21f01bba..3458a8364 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -23,40 +23,40 @@ "revisionTime": "2018-08-30T19:11:22Z" }, { - "checksumSHA1": "notMSh2eSvxEagJOe+GgIFRdIeU=", + "checksumSHA1": "ce7ByqrPYpRo1lmSm3FQpqXIvys=", "path": "github.com/mendersoftware/mender-artifact", - "revision": "7c541c8f8348d6a11487c47e8c824ab55fbae5ae", - "revisionTime": "2019-11-18T11:56:43Z" + "revision": "a6d2372020522fbab2ae4d262bb9ecfa676a3cd2", + "revisionTime": "2020-01-30T09:01:47Z" }, { - "checksumSHA1": "PuNXDk0mAwaITVg6nu91ojLw4Aw=", + "checksumSHA1": "wSVd29/7OwT8qrMJe3Mna58TtJo=", "path": "github.com/mendersoftware/mender-artifact/areader", - "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", - "revisionTime": "2020-03-13T13:38:56Z" + "revision": "a6d2372020522fbab2ae4d262bb9ecfa676a3cd2", + "revisionTime": "2020-01-30T09:01:47Z" }, { "checksumSHA1": "MZXf2IJHDEzTwERfo6ar/9ipDTM=", "path": "github.com/mendersoftware/mender-artifact/artifact", - "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", - "revisionTime": "2020-03-13T13:38:56Z" + "revision": "a6d2372020522fbab2ae4d262bb9ecfa676a3cd2", + "revisionTime": "2020-01-30T09:01:47Z" }, { "checksumSHA1": "gc2rkb1VAmOAxY7cQ0zdipRyAQU=", "path": "github.com/mendersoftware/mender-artifact/awriter", - "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", - "revisionTime": "2020-03-13T13:38:56Z" + "revision": "a6d2372020522fbab2ae4d262bb9ecfa676a3cd2", + "revisionTime": "2020-01-30T09:01:47Z" }, { "checksumSHA1": "CxPWbnJaSedagK6KU+pc30X0XF8=", "path": "github.com/mendersoftware/mender-artifact/handlers", - "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", - "revisionTime": "2020-03-13T13:38:56Z" + "revision": "a6d2372020522fbab2ae4d262bb9ecfa676a3cd2", + "revisionTime": "2020-01-30T09:01:47Z" }, { "checksumSHA1": "SRqPsG84/Ti4VBdjP/FIjRLKXGg=", "path": "github.com/mendersoftware/mender-artifact/utils", - "revision": "e0f8398d0daac5e4ac5cad198842227e6a2b2d3a", - "revisionTime": "2020-03-13T13:38:56Z" + "revision": "a6d2372020522fbab2ae4d262bb9ecfa676a3cd2", + "revisionTime": "2020-01-30T09:01:47Z" }, { "checksumSHA1": "YGYg7j9+kmKncdC5UsI5FV237ts=",