From d2c1cfd2d5ac565669a58fbd7c75755bc9f27383 Mon Sep 17 00:00:00 2001 From: Felix Schnizlein Date: Mon, 7 Oct 2024 14:20:17 +0200 Subject: [PATCH 1/8] Add --set-labels to register command to set labels at registration time on SCC --- cmd/suseconnect/connectUsage.txt | 4 +++ cmd/suseconnect/suseconnect.go | 26 +++++++++++++------ internal/connect/api.go | 14 +++++++++++ internal/connect/api_test.go | 43 ++++++++++++++++++++++++++++++++ internal/connect/labels.go | 27 ++++++++++++++++++++ internal/connect/labels_test.go | 39 +++++++++++++++++++++++++++++ testdata/set_labels.json | 10 ++++++++ 7 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 internal/connect/labels.go create mode 100644 internal/connect/labels_test.go create mode 100644 testdata/set_labels.json diff --git a/cmd/suseconnect/connectUsage.txt b/cmd/suseconnect/connectUsage.txt index 8e58dbda..4e34bda8 100644 --- a/cmd/suseconnect/connectUsage.txt +++ b/cmd/suseconnect/connectUsage.txt @@ -15,6 +15,10 @@ Manage subscriptions at https://scc.suse.com be registered. Relates that product to the specified subscription, and enables software repositories for that product. + --set-labels [LABELS] + Set labels in SCC when the product is registered. + Multiple labels can be specified providing a comma + seperated -d, --de-register De-registers the system and base product, or in conjunction with --product, a single extension, and removes all its services installed by SUSEConnect. diff --git a/cmd/suseconnect/suseconnect.go b/cmd/suseconnect/suseconnect.go index cdbcfcdd..29518490 100644 --- a/cmd/suseconnect/suseconnect.go +++ b/cmd/suseconnect/suseconnect.go @@ -68,6 +68,7 @@ func main() { fsRoot string namespace string token string + labels string product singleStringFlag instanceDataFile string listExtensions bool @@ -103,6 +104,7 @@ func main() { flag.StringVar(&namespace, "namespace", "", "") flag.StringVar(&token, "regcode", "", "") flag.StringVar(&token, "r", "", "") + flag.StringVar(&labels, "set-labels", "", "") flag.StringVar(&instanceDataFile, "instance-data", "", "") flag.StringVar(&email, "email", "", "") flag.StringVar(&email, "e", "", "") @@ -273,13 +275,23 @@ func main() { } err := connect.Register(jsonFlag) - if jsonFlag && err != nil { - out := connect.RegisterOut{Success: false, Message: err.Error()} - str, _ := json.Marshal(&out) - fmt.Println(string(str)) - os.Exit(1) - } else { - exitOnError(err) + if err != nil { + if jsonFlag { + out := connect.RegisterOut{Success: false, Message: err.Error()} + str, _ := json.Marshal(&out) + fmt.Println(string(str)) + os.Exit(1) + } else { + exitOnError(err) + } + } + + // After successful registration we try to set labels + if len(labels) > 0 { + err := connect.AssignAndCrateLabels(strings.Split(labels, ",")) + if err != nil && !jsonFlag { + fmt.Printf("Problem setting labels for this system: %s\n", err) + } } } } diff --git a/internal/connect/api.go b/internal/connect/api.go index 87c9cdd8..148b6a05 100644 --- a/internal/connect/api.go +++ b/internal/connect/api.go @@ -339,3 +339,17 @@ func installerUpdates(product Product) ([]zypper.Repository, error) { } return repos, nil } + +func setLabels(labels []Label) error { + var payload struct { + Labels []Label `json:"labels"` + } + payload.Labels = labels + body, err := json.Marshal(payload) + + if err != nil { + return err + } + _, err = callHTTP("POST", "/connect/systems/labels", body, nil, authSystem) + return err +} diff --git a/internal/connect/api_test.go b/internal/connect/api_test.go index 37af7ff9..d7a7ec08 100644 --- a/internal/connect/api_test.go +++ b/internal/connect/api_test.go @@ -375,3 +375,46 @@ func TestMakeSysInfoBody(t *testing.T) { assert.NoError(err) assert.Equal(expectedBody, string(body)) } + +func TestSetLabelsOk(t *testing.T) { + assert := assert.New(t) + + testLabels := []Label{ + Label{Name: "label1"}, + Label{Name: "label2"}, + } + + setRootToTmp() + credentials.CreateTestCredentials("", "", CFG.FsRoot, t) + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(util.ReadTestFile("set_labels.json", t)) + })) + defer ts.Close() + + CFG.BaseURL = ts.URL + + err := setLabels(testLabels) + assert.NoError(err) +} + +func TestSetLabelsError(t *testing.T) { + assert := assert.New(t) + testLabels := []Label{ + Label{Name: "label1"}, + Label{Name: "label2"}, + } + + setRootToTmp() + credentials.CreateTestCredentials("", "", CFG.FsRoot, t) + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + errMsg := "{\"status\":422,\"error\":\"Cannot set more than 10 labels on system: test-system\",\"type\":\"error\",\"localized_error\":\"Es können nicht mehr als 10 labels per System gesetzt werden: test-system\"}" + http.Error(w, errMsg, http.StatusUnprocessableEntity) + })) + defer ts.Close() + CFG.BaseURL = ts.URL + + err := setLabels(testLabels) + assert.ErrorContains(err, "Es können") +} diff --git a/internal/connect/labels.go b/internal/connect/labels.go new file mode 100644 index 00000000..89685049 --- /dev/null +++ b/internal/connect/labels.go @@ -0,0 +1,27 @@ +package connect + +import ( + "github.com/SUSE/connect-ng/internal/util" + "strings" +) + +var ( + localSetLabels = setLabels +) + +type Label struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +func AssignAndCrateLabels(labels []string) error { + collection := []Label{} + + for _, name := range labels { + name = strings.TrimSpace(name) + collection = append(collection, Label{Name: name}) + } + + util.Debug.Printf(util.Bold("Setting Labels %s"), strings.Join(labels, ",")) + return localSetLabels(collection) +} diff --git a/internal/connect/labels_test.go b/internal/connect/labels_test.go new file mode 100644 index 00000000..d4e1db54 --- /dev/null +++ b/internal/connect/labels_test.go @@ -0,0 +1,39 @@ +package connect + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func mockSetLabelsApiCall(t *testing.T, expectedLabels []Label) { + localSetLabels = func(labels []Label) error { + assert.ElementsMatch(t, expectedLabels, labels, "setLabels: provided labels do not match expectedLabels") + return nil + } +} + +func TestAssignAndCreateLabelsOk(t *testing.T) { + assert := assert.New(t) + expectedLabels := []Label{ + Label{Name: "label1"}, + Label{Name: "label2"}, + } + + mockSetLabelsApiCall(t, expectedLabels) + + err := AssignAndCrateLabels([]string{"label1", "label2"}) + assert.NoError(err) +} + +func TestAssignAndCreateLabelsError(t *testing.T) { + assert := assert.New(t) + + localSetLabels = func([]Label) error { + return fmt.Errorf("Cannot set more than 10 labels on system: test-system") + } + + err := AssignAndCrateLabels([]string{"label1", "label2"}) + assert.ErrorContains(err, "Cannot set more than") +} diff --git a/testdata/set_labels.json b/testdata/set_labels.json new file mode 100644 index 00000000..07cb9dc2 --- /dev/null +++ b/testdata/set_labels.json @@ -0,0 +1,10 @@ +{ + "labels": [ + { + "name": "label1" + }, + { + "name": "label2" + } + ] +} From a64a538227647e4a1876fb921b09a1bc6603ce8a Mon Sep 17 00:00:00 2001 From: Felix Schnizlein Date: Tue, 8 Oct 2024 09:22:22 +0200 Subject: [PATCH 2/8] Update cmd/suseconnect/connectUsage.txt Co-authored-by: Alex P. --- cmd/suseconnect/connectUsage.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/suseconnect/connectUsage.txt b/cmd/suseconnect/connectUsage.txt index 2dc17734..b3b1d046 100644 --- a/cmd/suseconnect/connectUsage.txt +++ b/cmd/suseconnect/connectUsage.txt @@ -17,8 +17,7 @@ Manage subscriptions at https://scc.suse.com and enables software repositories for that product. --set-labels [LABELS] Set labels in SCC when the product is registered. - Multiple labels can be specified providing a comma - seperated + To add multiple labels, separate them with commas. -d, --de-register De-registers the system and base product, or in conjunction with --product, a single extension, and removes all its services installed by SUSEConnect. From d229ca07d7a6ce08187aa68c6788d13518406982 Mon Sep 17 00:00:00 2001 From: Felix Schnizlein Date: Wed, 16 Oct 2024 09:07:54 +0200 Subject: [PATCH 3/8] Fixed spelling mistake --- cmd/suseconnect/suseconnect.go | 2 +- internal/connect/labels.go | 2 +- internal/connect/labels_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/suseconnect/suseconnect.go b/cmd/suseconnect/suseconnect.go index 08494449..04016ff1 100644 --- a/cmd/suseconnect/suseconnect.go +++ b/cmd/suseconnect/suseconnect.go @@ -300,7 +300,7 @@ func main() { // After successful registration we try to set labels if len(labels) > 0 { - err := connect.AssignAndCrateLabels(strings.Split(labels, ",")) + err := connect.AssignAndCreateLabels(strings.Split(labels, ",")) if err != nil && !jsonFlag { fmt.Printf("Problem setting labels for this system: %s\n", err) } diff --git a/internal/connect/labels.go b/internal/connect/labels.go index 89685049..da0db5dc 100644 --- a/internal/connect/labels.go +++ b/internal/connect/labels.go @@ -14,7 +14,7 @@ type Label struct { Description string `json:"description,omitempty"` } -func AssignAndCrateLabels(labels []string) error { +func AssignAndCreateLabels(labels []string) error { collection := []Label{} for _, name := range labels { diff --git a/internal/connect/labels_test.go b/internal/connect/labels_test.go index d4e1db54..b1f58e6a 100644 --- a/internal/connect/labels_test.go +++ b/internal/connect/labels_test.go @@ -23,7 +23,7 @@ func TestAssignAndCreateLabelsOk(t *testing.T) { mockSetLabelsApiCall(t, expectedLabels) - err := AssignAndCrateLabels([]string{"label1", "label2"}) + err := AssignAndCreateLabels([]string{"label1", "label2"}) assert.NoError(err) } @@ -34,6 +34,6 @@ func TestAssignAndCreateLabelsError(t *testing.T) { return fmt.Errorf("Cannot set more than 10 labels on system: test-system") } - err := AssignAndCrateLabels([]string{"label1", "label2"}) + err := AssignAndCreateLabels([]string{"label1", "label2"}) assert.ErrorContains(err, "Cannot set more than") } From 93a73cbc14788d0863bda717dc8ab42498731a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Sabat=C3=A9=20Sol=C3=A0?= Date: Thu, 3 Oct 2024 09:40:23 +0200 Subject: [PATCH 4/8] internal: Add a ServerType to the configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the internal configuration of SUSEConnect to understand on which kind of server it's trying to connect. This is relevant in case we want to perform some operations on SCC and some others on RMT. Signed-off-by: Miquel Sabaté Solà --- cmd/suseconnect/suseconnect.go | 6 +++--- internal/connect/config.go | 31 +++++++++++++++++++++++++++---- internal/connect/config_test.go | 2 ++ internal/connect/connection.go | 12 ++++++++++++ 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/cmd/suseconnect/suseconnect.go b/cmd/suseconnect/suseconnect.go index 04016ff1..a1b9537b 100644 --- a/cmd/suseconnect/suseconnect.go +++ b/cmd/suseconnect/suseconnect.go @@ -261,11 +261,11 @@ func main() { fmt.Print(string(out)) } else { - if instanceDataFile != "" && connect.URLDefault() { + if instanceDataFile != "" && connect.CFG.IsScc() { fmt.Print("Please use --instance-data only in combination ") fmt.Print("with --url pointing to your RMT or SMT server\n") os.Exit(1) - } else if connect.URLDefault() && token == "" && product.value == "" { + } else if connect.CFG.IsScc() && token == "" && product.value == "" { flag.Usage() os.Exit(1) } else if isSumaManaged() { @@ -316,7 +316,7 @@ func main() { } func maybeBrokenSMTError() error { - if !connect.URLDefault() && !connect.UpToDate() { + if !connect.CFG.IsScc() && !connect.UpToDate() { return fmt.Errorf("Your Registration Proxy server doesn't support this function. " + "Please update it and try again.") } diff --git a/internal/connect/config.go b/internal/connect/config.go index 53517039..9cafb257 100644 --- a/internal/connect/config.go +++ b/internal/connect/config.go @@ -26,6 +26,15 @@ const ( defaultEnableSystemUptimeTracking = false ) +// Kinds of servers which are supported by SUSEConnect. +type ServerType uint64 + +const ( + Unknown ServerType = iota + Scc + Rmt +) + // Config holds the config! type Config struct { Path string @@ -40,10 +49,10 @@ type Config struct { Email string `json:"email"` AutoAgreeEULA bool EnableSystemUptimeTracking bool - - NoZypperRefresh bool - AutoImportRepoKeys bool - SkipServiceInstall bool + ServerType ServerType + NoZypperRefresh bool + AutoImportRepoKeys bool + SkipServiceInstall bool } // NewConfig returns a Config with defaults @@ -123,6 +132,11 @@ func parseConfig(r io.Reader, c *Config) { util.Debug.Printf("Cannot parse line \"%s\" from %s", line, c.Path) } } + + // Set the server type depending on what we parsed from the configuration. + if c.BaseURL == defaultBaseURL { + c.ServerType = Scc + } } // MergeJSON merges attributes of jsn that match Config fields @@ -131,3 +145,12 @@ func (c *Config) MergeJSON(jsn string) error { util.Debug.Printf("Merged options: %+v", c) return err } + +// Returns true if we detected that the configuration points to SCC. +// +// NOTE: this will be reliable if the configuration file already pointed to SCC, +// but it might need to be filled in upon HTTP requests to further guess if it's +// a Glue instance running on localhost or similar developer-only scenarios. +func (c *Config) IsScc() bool { + return c.ServerType == Scc +} diff --git a/internal/connect/config_test.go b/internal/connect/config_test.go index a01ddcf5..ae91fd86 100644 --- a/internal/connect/config_test.go +++ b/internal/connect/config_test.go @@ -57,12 +57,14 @@ func TestSaveLoad(t *testing.T) { c1 := NewConfig() c1.Path = path c1.AutoAgreeEULA = true + c1.ServerType = Unknown if err := c1.Save(); err != nil { t.Fatalf("Unable to write config: %s", err) } c2 := NewConfig() c2.Path = path c2.Load() + c2.ServerType = Unknown if !reflect.DeepEqual(c1, c2) { t.Errorf("got %+v, expected %+v", c2, c1) } diff --git a/internal/connect/connection.go b/internal/connect/connection.go index 615158a0..188288e3 100644 --- a/internal/connect/connection.go +++ b/internal/connect/connection.go @@ -155,6 +155,18 @@ func callHTTP(verb, path string, body []byte, query map[string]string, auth auth } defer resp.Body.Close() + // If we failed to detect which server type was being used when loading the + // configuration, we can actually further inspect it via some of the headers + // that are returned by Glue vs RMT. Hence, if the server type is unknown, + // make an educated guess now. + if CFG.ServerType == Unknown { + if api := resp.Header.Get("Scc-Api-Version"); api == sccAPIVersion { + CFG.ServerType = Scc + } else { + CFG.ServerType = Rmt + } + } + // For each request SCC might update the System token for a given system. // This will be given through the `System-Token` header, so we have to grab // this here and store it for the next request. From 77d734fa4693bf68580e0087b252f8ba9f0605d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Sabat=C3=A9=20Sol=C3=A0?= Date: Thu, 3 Oct 2024 09:41:32 +0200 Subject: [PATCH 5/8] internal: Skip the docker auth generation on RMT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes bsc#1231185 Signed-off-by: Miquel Sabaté Solà --- cmd/suseconnect/suseconnect.go | 2 +- internal/connect/client.go | 17 +++++++++-------- internal/connect/config.go | 32 +++++++++++++++++++++++++++----- internal/connect/config_test.go | 4 ++-- internal/connect/connection.go | 6 +++--- 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/cmd/suseconnect/suseconnect.go b/cmd/suseconnect/suseconnect.go index a1b9537b..537b291d 100644 --- a/cmd/suseconnect/suseconnect.go +++ b/cmd/suseconnect/suseconnect.go @@ -135,7 +135,7 @@ func main() { fmt.Printf("URL \"%s\" not valid: %s\n", baseURL, err) os.Exit(1) } - connect.CFG.BaseURL = baseURL + connect.CFG.ChangeBaseURL(baseURL) writeConfig = true } if fsRoot != "" { diff --git a/internal/connect/client.go b/internal/connect/client.go index 356c7e20..eb1ea2bb 100644 --- a/internal/connect/client.go +++ b/internal/connect/client.go @@ -387,8 +387,14 @@ func announceOrUpdate(quiet bool) error { } if err = cred.CreateCredentials(login, password, "", cred.SystemCredentialsPath(CFG.FsRoot)); err == nil { - util.Debug.Print("\nAdding SUSE registry system authentication configuration ...") - setupRegistryAuthentication(login, password) + // If the user is authenticated against the SCC, then setup the Docker + // Registry configuration for the system. Otherwise, if the system is + // behind a proxy (e.g. RMT), this step might fail and it's best to + // avoid it (see bsc#1231185). + if CFG.IsScc() { + util.Debug.Print("\nAdding SUSE registry system authentication configuration ...") + setupRegistryAuthentication(login, password) + } } return err } @@ -405,14 +411,9 @@ func UpToDate() bool { return upToDate() } -// URLDefault returns true if using https://scc.suse.com -func URLDefault() bool { - return CFG.BaseURL == defaultBaseURL -} - func printInformation(action string, jsonOutput bool) { var server string - if URLDefault() { + if CFG.IsScc() { server = "SUSE Customer Center" } else { server = "registration proxy " + CFG.BaseURL diff --git a/internal/connect/config.go b/internal/connect/config.go index 9cafb257..0840e896 100644 --- a/internal/connect/config.go +++ b/internal/connect/config.go @@ -30,9 +30,9 @@ const ( type ServerType uint64 const ( - Unknown ServerType = iota - Scc - Rmt + UnknownProvider ServerType = iota + SccProvider + RmtProvider ) // Config holds the config! @@ -93,6 +93,12 @@ func (c Config) Save() error { func (c *Config) Load() { f, err := os.Open(c.Path) if err != nil { + // If we failed at parsing the configuration, we can make further + // assumptions based on the base URL being used. + if c.BaseURL == defaultBaseURL { + c.ServerType = SccProvider + } + util.Debug.Println(err) return } @@ -101,6 +107,22 @@ func (c *Config) Load() { util.Debug.Printf("Config after parsing: %+v", c) } +// Change the base url to be used when talking to the server to the one being +// provided. +func (c *Config) ChangeBaseURL(baseUrl string) { + c.BaseURL = baseUrl + + // When making an explicit change of the URL, we can further detect which + // kind of server we are dealing with. For now, let's keep it simple, and if + // it's the defaultBaseURL then we assume it to be SccProvider, otherwise + // RmtProvider. + if c.BaseURL == defaultBaseURL { + c.ServerType = SccProvider + } else { + c.ServerType = RmtProvider + } +} + func parseConfig(r io.Reader, c *Config) { scanner := bufio.NewScanner(r) for scanner.Scan() { @@ -135,7 +157,7 @@ func parseConfig(r io.Reader, c *Config) { // Set the server type depending on what we parsed from the configuration. if c.BaseURL == defaultBaseURL { - c.ServerType = Scc + c.ServerType = SccProvider } } @@ -152,5 +174,5 @@ func (c *Config) MergeJSON(jsn string) error { // but it might need to be filled in upon HTTP requests to further guess if it's // a Glue instance running on localhost or similar developer-only scenarios. func (c *Config) IsScc() bool { - return c.ServerType == Scc + return c.ServerType == SccProvider } diff --git a/internal/connect/config_test.go b/internal/connect/config_test.go index ae91fd86..4a311fe5 100644 --- a/internal/connect/config_test.go +++ b/internal/connect/config_test.go @@ -57,14 +57,14 @@ func TestSaveLoad(t *testing.T) { c1 := NewConfig() c1.Path = path c1.AutoAgreeEULA = true - c1.ServerType = Unknown + c1.ServerType = UnknownProvider if err := c1.Save(); err != nil { t.Fatalf("Unable to write config: %s", err) } c2 := NewConfig() c2.Path = path c2.Load() - c2.ServerType = Unknown + c2.ServerType = UnknownProvider if !reflect.DeepEqual(c1, c2) { t.Errorf("got %+v, expected %+v", c2, c1) } diff --git a/internal/connect/connection.go b/internal/connect/connection.go index 188288e3..03ad0231 100644 --- a/internal/connect/connection.go +++ b/internal/connect/connection.go @@ -159,11 +159,11 @@ func callHTTP(verb, path string, body []byte, query map[string]string, auth auth // configuration, we can actually further inspect it via some of the headers // that are returned by Glue vs RMT. Hence, if the server type is unknown, // make an educated guess now. - if CFG.ServerType == Unknown { + if CFG.ServerType == UnknownProvider { if api := resp.Header.Get("Scc-Api-Version"); api == sccAPIVersion { - CFG.ServerType = Scc + CFG.ServerType = SccProvider } else { - CFG.ServerType = Rmt + CFG.ServerType = RmtProvider } } From 21e14d007a264cf60ec9a9f3fbbf1438571ec668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Sabat=C3=A9=20Sol=C3=A0?= Date: Mon, 28 Oct 2024 09:55:37 +0100 Subject: [PATCH 6/8] Only send labels if targetting SCC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miquel Sabaté Solà --- cmd/suseconnect/suseconnect.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/suseconnect/suseconnect.go b/cmd/suseconnect/suseconnect.go index 537b291d..0d21157e 100644 --- a/cmd/suseconnect/suseconnect.go +++ b/cmd/suseconnect/suseconnect.go @@ -298,8 +298,9 @@ func main() { } } - // After successful registration we try to set labels - if len(labels) > 0 { + // After successful registration we try to set labels if we are + // targetting SCC. + if connect.CFG.IsScc() && len(labels) > 0 { err := connect.AssignAndCreateLabels(strings.Split(labels, ",")) if err != nil && !jsonFlag { fmt.Printf("Problem setting labels for this system: %s\n", err) From 90f7981b69ac550f51398440c1341c65089aa331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Sabat=C3=A9=20Sol=C3=A0?= Date: Fri, 25 Oct 2024 16:24:12 +0200 Subject: [PATCH 7/8] Honor auto-import-gpg-keys flag on migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parsing of CLI arguments and how these are passed through the internal SCC client, zypper and other tools is not ideal and needs to be re-worked quite urgently. This is why other silly bugs have appeared in which we are not passing the correct arguments to our backend code. Until this rework is not done, let's simply apply this quick fix which simply passes the `--gpg-auto-import-keys` flag to the zypper backend when finding product packages. Fixes bsc#1231328 Signed-off-by: Miquel Sabaté Solà --- cmd/zypper-migration/migration.go | 10 +++++----- internal/zypper/zypper.go | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/zypper-migration/migration.go b/cmd/zypper-migration/migration.go index d70d4659..5f77bd6e 100644 --- a/cmd/zypper-migration/migration.go +++ b/cmd/zypper-migration/migration.go @@ -497,8 +497,8 @@ func compareEditions(left, right string) int { return 0 } -func cleanupProductRepos(p connect.Product, force bool) error { - productPackages, err := zypper.FindProductPackages(p.Name) +func cleanupProductRepos(p connect.Product, force, autoImportRepoKeys bool) error { + productPackages, err := zypper.FindProductPackages(p.Name, autoImportRepoKeys) if err != nil { return err } @@ -564,7 +564,7 @@ func isSUSEService(service zypper.ZypperService) bool { // adds/removes services to match target state // disables obsolete repos // returns base product version string -func migrateSystem(migration connect.MigrationPath, forceDisableRepos bool) (string, error) { +func migrateSystem(migration connect.MigrationPath, forceDisableRepos, autoImportRepoKeys bool) (string, error) { var baseProductVersion string systemServices, _ := zypper.InstalledServices() @@ -587,7 +587,7 @@ func migrateSystem(migration connect.MigrationPath, forceDisableRepos bool) (str } } - if err := cleanupProductRepos(p, forceDisableRepos); err != nil { + if err := cleanupProductRepos(p, forceDisableRepos, autoImportRepoKeys); err != nil { return baseProductVersion, err } @@ -678,7 +678,7 @@ func applyMigration(migration connect.MigrationPath, systemProducts []connect.Pr } } - baseProductVersion, err := migrateSystem(migration, nonInteractive || forceDisableRepos) + baseProductVersion, err := migrateSystem(migration, nonInteractive || forceDisableRepos, autoImportRepoKeys) if err != nil { return fsInconsistent, err } diff --git a/internal/zypper/zypper.go b/internal/zypper/zypper.go index c7cea3d5..e5ea6589 100644 --- a/internal/zypper/zypper.go +++ b/internal/zypper/zypper.go @@ -358,9 +358,13 @@ func parseSearchResultXML(xmlDoc []byte) ([]Package, error) { } // FindProductPackages returns list of product packages for given product -func FindProductPackages(identifier string) ([]Package, error) { +func FindProductPackages(identifier string, autoImportRepoKeys bool) ([]Package, error) { args := []string{"--xmlout", "--no-refresh", "--non-interactive", "search", "-s", "--match-exact", "-t", "product", identifier} + if autoImportRepoKeys { + args = append([]string{"--gpg-auto-import-keys"}, args...) + } + // Don't fail when zypper exits with 104 (no product found) or 6 (no repositories) output, err := zypperRun(args, []int{zypperOK, zypperErrNoRepos, zypperInfoCapNotFound}) if err != nil { From 2c7426ce1035f42c1e6605ef464659b39ae0a893 Mon Sep 17 00:00:00 2001 From: Will Stephenson Date: Fri, 8 Nov 2024 17:23:16 +0100 Subject: [PATCH 8/8] Write diagnostics/error messages to stderr Like a good POSIX program --- cmd/suseconnect/suseconnect.go | 62 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/cmd/suseconnect/suseconnect.go b/cmd/suseconnect/suseconnect.go index 0d21157e..23d1f1c2 100644 --- a/cmd/suseconnect/suseconnect.go +++ b/cmd/suseconnect/suseconnect.go @@ -132,7 +132,7 @@ func main() { connect.CFG.Load() if baseURL != "" { if err := validateURL(baseURL); err != nil { - fmt.Printf("URL \"%s\" not valid: %s\n", baseURL, err) + fmt.Fprintf(os.Stderr, "URL \"%s\" not valid: %s\n", baseURL, err) os.Exit(1) } connect.CFG.ChangeBaseURL(baseURL) @@ -140,7 +140,7 @@ func main() { } if fsRoot != "" { if fsRoot[0] != '/' { - fmt.Println("The path specified in the --root option must be absolute.") + fmt.Fprintln(os.Stderr, "The path specified in the --root option must be absolute.") os.Exit(1) } connect.CFG.FsRoot = fsRoot @@ -155,9 +155,9 @@ func main() { } if product.isSet { if p, err := connect.SplitTriplet(product.value); err != nil { - fmt.Print("Please provide the product identifier in this format: ") - fmt.Print("//. You can find ") - fmt.Print("these values by calling: 'SUSEConnect --list-extensions'\n") + fmt.Fprint(os.Stderr, "Please provide the product identifier in this format: ") + fmt.Fprint(os.Stderr, "//. You can find ") + fmt.Fprint(os.Stderr, "these values by calling: 'SUSEConnect --list-extensions'\n") os.Exit(1) } else { connect.CFG.Product = p @@ -262,14 +262,14 @@ func main() { fmt.Print(string(out)) } else { if instanceDataFile != "" && connect.CFG.IsScc() { - fmt.Print("Please use --instance-data only in combination ") - fmt.Print("with --url pointing to your RMT or SMT server\n") + fmt.Fprint(os.Stderr, "Please use --instance-data only in combination ") + fmt.Fprint(os.Stderr, "with --url pointing to your RMT or SMT server\n") os.Exit(1) } else if connect.CFG.IsScc() && token == "" && product.value == "" { flag.Usage() os.Exit(1) } else if isSumaManaged() { - fmt.Println("This system is managed by SUSE Manager / Uyuni, do not use SUSEconnect.") + fmt.Fprintln(os.Stderr, "This system is managed by SUSE Manager / Uyuni, do not use SUSEconnect.") os.Exit(1) } else { @@ -303,14 +303,14 @@ func main() { if connect.CFG.IsScc() && len(labels) > 0 { err := connect.AssignAndCreateLabels(strings.Split(labels, ",")) if err != nil && !jsonFlag { - fmt.Printf("Problem setting labels for this system: %s\n", err) + fmt.Fprintf(os.Stderr, "Problem setting labels for this system: %s\n", err) } } } } if writeConfig { if err := connect.CFG.Save(); err != nil { - fmt.Printf("Problem writing configuration: %s\n", err) + fmt.Fprintf(os.Stderr, "Problem writing configuration: %s\n", err) os.Exit(1) } } @@ -329,55 +329,55 @@ func exitOnError(err error) { return } if ze, ok := err.(zypper.ZypperError); ok { - fmt.Println(ze) + fmt.Fprintln(os.Stderr, ze) os.Exit(ze.ExitCode) } if ue, ok := err.(*url.Error); ok && errors.Is(ue, syscall.ECONNREFUSED) { - fmt.Println("Error:", err) + fmt.Fprintln(os.Stderr, "Error:", err) os.Exit(64) } if je, ok := err.(connect.JSONError); ok { if err := maybeBrokenSMTError(); err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) } else { - fmt.Print("Error: Cannot parse response from server\n") - fmt.Println(je) + fmt.Fprint(os.Stderr, "Error: Cannot parse response from server\n") + fmt.Fprintln(os.Stderr, je) } os.Exit(66) } if ae, ok := err.(connect.APIError); ok { if ae.Code == http.StatusUnauthorized && connect.IsRegistered() { - fmt.Print("Error: Invalid system credentials, probably because the ") - fmt.Print("registered system was deleted in SUSE Customer Center. ") - fmt.Print("Check ", connect.CFG.BaseURL, " whether your system appears there. ") - fmt.Print("If it does not, please call SUSEConnect --cleanup and re-register this system.\n") + fmt.Fprint(os.Stderr, "Error: Invalid system credentials, probably because the ") + fmt.Fprint(os.Stderr, "registered system was deleted in SUSE Customer Center. ") + fmt.Fprint(os.Stderr, "Check ", connect.CFG.BaseURL, " whether your system appears there. ") + fmt.Fprint(os.Stderr, "If it does not, please call SUSEConnect --cleanup and re-register this system.\n") } else if err := maybeBrokenSMTError(); err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) } else { - fmt.Println(ae) + fmt.Fprintln(os.Stderr, ae) } os.Exit(67) } switch err { case connect.ErrSystemNotRegistered: - fmt.Print("Deregistration failed. Check if the system has been ") - fmt.Print("registered using the --status-text option or use the ") - fmt.Print("--regcode parameter to register it.\n") + fmt.Fprint(os.Stderr, "Deregistration failed. Check if the system has been ") + fmt.Fprint(os.Stderr, "registered using the --status-text option or use the ") + fmt.Fprint(os.Stderr, "--regcode parameter to register it.\n") os.Exit(69) case connect.ErrListExtensionsUnregistered: - fmt.Print("To list extensions, you must first register the base product, ") - fmt.Print("using: SUSEConnect -r \n") + fmt.Fprint(os.Stderr, "To list extensions, you must first register the base product, ") + fmt.Fprint(os.Stderr, "using: SUSEConnect -r \n") os.Exit(1) case connect.ErrBaseProductDeactivation: - fmt.Print("Can not deregister base product. Use SUSEConnect -d to deactivate ") - fmt.Print("the whole system.\n") + fmt.Fprint(os.Stderr, "Can not deregister base product. Use SUSEConnect -d to deactivate ") + fmt.Fprint(os.Stderr, "the whole system.\n") os.Exit(70) case connect.ErrPingFromUnregistered: - fmt.Print("Error sending keepalive: ") - fmt.Print("System is not registered. Use the --regcode parameter to register it.\n") + fmt.Fprint(os.Stderr, "Error sending keepalive: ") + fmt.Fprint(os.Stderr, "System is not registered. Use the --regcode parameter to register it.\n") os.Exit(71) default: - fmt.Printf("SUSEConnect error: %s\n", err) + fmt.Fprintf(os.Stderr, "SUSEConnect error: %s\n", err) os.Exit(1) } }