diff --git a/cmd/container-suseconnect/main.go b/cmd/container-suseconnect/main.go index 02f815c..e59a23e 100644 --- a/cmd/container-suseconnect/main.go +++ b/cmd/container-suseconnect/main.go @@ -37,8 +37,7 @@ func main() { app.Name = "container-suseconnect" app.Version = cs.Version app.Usage = "Access zypper repositories from within containers" - app.UsageText = - `This application can be used to retrieve basic metadata about SLES + app.UsageText = `This application can be used to retrieve basic metadata about SLES related products and module extensions. Please use the 'list-products' subcommand for listing all currently @@ -55,6 +54,7 @@ func main() { // Switch the default application behavior in relation to the basename defaultUsageAdditionZypp := "" defaultUsageAdditionListProducts := "" + switch filepath.Base(os.Args[0]) { case "container-suseconnect-zypp": app.Action = runZypperPlugin @@ -108,24 +108,29 @@ func requestProducts() ([]cs.Product, error) { // config from "mounted" files if the service is not available if err := regionsrv.ServerReachable(); err == nil { log.Printf("containerbuild-regionsrv reachable, reading config\n") + cloudCfg, err := regionsrv.ReadConfigFromServer() if err != nil { return nil, err } + credentials.Username = cloudCfg.Username credentials.Password = cloudCfg.Password credentials.InstanceData = cloudCfg.InstanceData + suseConnectData.SccURL = "https://" + cloudCfg.ServerFqdn suseConnectData.Insecure = false if cloudCfg.Ca != "" { regionsrv.SafeCAFile(cloudCfg.Ca) } + regionsrv.UpdateHostsFile(cloudCfg.ServerFqdn, cloudCfg.ServerIP) } else { if err := cs.ReadConfiguration(&credentials); err != nil { return nil, err } + if err := cs.ReadConfiguration(&suseConnectData); err != nil { return nil, err } @@ -135,6 +140,7 @@ func requestProducts() ([]cs.Product, error) { if err != nil { return nil, err } + log.Printf("Installed product: %v\n", installedProduct) log.Printf("Registration server set to %v\n", suseConnectData.SccURL) @@ -188,6 +194,7 @@ func runListModules(_ *cli.Context) error { fmt.Printf("All available modules:\n\n") cs.ListModules(os.Stdout, products) + return nil } @@ -200,5 +207,6 @@ func runListProducts(_ *cli.Context) error { fmt.Printf("All available products:\n\n") cs.ListProducts(os.Stdout, products, "none") + return nil } diff --git a/internal/configuration.go b/internal/configuration.go index 3f6b596..09c25c9 100644 --- a/internal/configuration.go +++ b/internal/configuration.go @@ -56,6 +56,7 @@ func getLocationPath(locations []string) string { return path } } + return "" } @@ -68,6 +69,7 @@ func ReadConfiguration(config Configuration) error { if config.onLocationsNotFound() { return nil } + return loggedError(GetCredentialsError, "Warning: SUSE credentials not found: %v - automatic handling of repositories not done.", config.locations()) } @@ -83,18 +85,21 @@ func ReadConfiguration(config Configuration) error { // Parses the contents given by the reader and updated the given configuration. func parse(config Configuration, reader io.Reader) error { scanner := bufio.NewScanner(reader) + for scanner.Scan() { // Comments & empty lines. if strings.IndexAny(scanner.Text(), "#-") == 0 { continue } + if scanner.Text() == "" { continue } - // Each line should be constructed as 'key' 'separator' 'value'. line := scanner.Text() + // Each line should be constructed as 'key' 'separator' 'value'. parts := strings.SplitN(line, string(config.separator()), 2) + if len(parts) != 2 { return loggedError(GetCredentialsError, "Can't parse line: %v", line) } @@ -108,8 +113,10 @@ func parse(config Configuration, reader io.Reader) error { if err := scanner.Err(); err != nil { return loggedError(GetCredentialsError, "Error when scanning configuration: %v", err) } + if err := config.afterParseCheck(); err != nil { return err } + return nil } diff --git a/internal/configuration_test.go b/internal/configuration_test.go index cd75aa4..ffce459 100644 --- a/internal/configuration_test.go +++ b/internal/configuration_test.go @@ -31,6 +31,7 @@ func TestGetLocationPath(t *testing.T) { "does/not/exist", "testdata/products-sle12.json", } + path = getLocationPath(strs) if path != "testdata/products-sle12.json" { t.Fatalf("Wrong location path: %v", path) @@ -62,10 +63,12 @@ func TestNotFound(t *testing.T) { var cfg NotFoundConfiguration prepareLogger() + err := ReadConfiguration(&cfg) if err == nil || err.Error() != "Warning: SUSE credentials not found: [] - automatic handling of repositories not done." { t.Fatalf("Wrong error: %v", err) } + shouldHaveLogged(t, "Warning: SUSE credentials not found: [] - automatic handling of repositories not done.") } @@ -94,11 +97,13 @@ func TestNotAllowed(t *testing.T) { var cfg NotAllowedConfiguration prepareLogger() + err := ReadConfiguration(&cfg) msg := "Can't open /etc/shadow file: open /etc/shadow: permission denied" if err == nil || err.Error() != msg { t.Fatal("Wrong error") } + shouldHaveLogged(t, msg) } @@ -110,12 +115,15 @@ func TestParseInvalid(t *testing.T) { file.Close() t.Fatal("There should be an error here") } + prepareLogger() + err = parse(cfg, file) msg := "Error when scanning configuration: invalid argument" if err == nil || err.Error() != msg { t.Fatal("Wrong error") } + shouldHaveLogged(t, msg) } @@ -154,11 +162,14 @@ func TestParseFailNoSeparator(t *testing.T) { var cfg ErrorAfterParseConfiguration str := strings.NewReader("keywithoutvalue") + prepareLogger() + err := parse(cfg, str) msg := "Can't parse line: keywithoutvalue" if err == nil || err.Error() != msg { t.Fatal("Wrong error") } + shouldHaveLogged(t, msg) } diff --git a/internal/credentials.go b/internal/credentials.go index 8ff42a5..d5f78d5 100644 --- a/internal/credentials.go +++ b/internal/credentials.go @@ -43,7 +43,6 @@ func (cr *Credentials) locations() []string { } func (cr *Credentials) onLocationsNotFound() bool { - env_user := os.Getenv("SCC_CREDENTIAL_USERNAME") env_pass := os.Getenv("SCC_CREDENTIAL_PASSWORD") @@ -73,8 +72,10 @@ func (cr *Credentials) afterParseCheck() error { if cr.Username == "" { return loggedError(GetCredentialsError, "Can't find username") } + if cr.Password == "" { return loggedError(GetCredentialsError, "Can't find password") } + return nil } diff --git a/internal/credentials_test.go b/internal/credentials_test.go index 7b62295..347790a 100644 --- a/internal/credentials_test.go +++ b/internal/credentials_test.go @@ -27,6 +27,7 @@ func TestCredentials(t *testing.T) { if cr.separator() != '=' { t.Fatal("Wrong separator") } + prepareLogger() err := cr.afterParseCheck() msg := "Can't find username" @@ -107,15 +108,19 @@ func TestIntegrationCredentials(t *testing.T) { if err != nil { t.Fatal("This should've been a successful run") } + if mock.cr.Username != "SCC_a6994b1d3ae14b35agc7cef46b4fff9a" { t.Fatal("Unexpected name value") } + if mock.cr.Password != "10yb1x6bd159g741ad420fd5aa5083e4" { t.Fatal("Unexpected password value") } + if mock.cr.SystemToken != "36531d07-a283-441b-a02a-1cd9a88b0d5d" { t.Fatal("Unexpected system_token value") } + if mock.cr.onLocationsNotFound() { t.Fatalf("It should've been false") } diff --git a/internal/installed_product.go b/internal/installed_product.go index 4912d62..5dc9d64 100644 --- a/internal/installed_product.go +++ b/internal/installed_product.go @@ -60,9 +60,9 @@ func parseInstalledProduct(reader io.Reader) (InstalledProduct, error) { var p InstalledProduct err := xml.Unmarshal(xmlData, &p) if err != nil { - return InstalledProduct{}, - loggedError(InstalledProductError, "Can't parse base product file: %v", err.Error()) + return InstalledProduct{}, loggedError(InstalledProductError, "Can't parse base product file: %v", err.Error()) } + return p, nil } @@ -74,8 +74,7 @@ func readInstalledProduct(provider ProductProvider) (InstalledProduct, error) { xmlFile, err := os.Open(provider.Location()) if err != nil { - return InstalledProduct{}, - loggedError(InstalledProductError, "Can't open base product file: %v", err.Error()) + return InstalledProduct{}, loggedError(InstalledProductError, "Can't open base product file: %v", err.Error()) } defer xmlFile.Close() diff --git a/internal/installed_product_test.go b/internal/installed_product_test.go index 708e8de..b28c71d 100644 --- a/internal/installed_product_test.go +++ b/internal/installed_product_test.go @@ -32,6 +32,7 @@ func TestFailNonExistantProduct(t *testing.T) { if err == nil { t.Fatal("This file should not exist...") } + if err.Error() != "No base product detected" { t.Fatal("Wrong error message") } @@ -50,6 +51,7 @@ func TestFailNotAllowedProduct(t *testing.T) { if err == nil { t.Fatal("This file should not be available...") } + if err.Error() != "Can't open base product file: open /etc/shadow: permission denied" { t.Fatal("Wrong error message") } @@ -68,6 +70,7 @@ func TestFailBadFormattedProduct(t *testing.T) { if err == nil { t.Fatal("This file should have a bad format") } + if err.Error() != "Can't parse base product file: EOF" { t.Fatal("Wrong error message") } @@ -86,15 +89,19 @@ func TestMockProvider(t *testing.T) { if err != nil { t.Fatal("It should've read it just fine") } + if p.Identifier != "SLES" { t.Fatal("Wrong product name") } + if p.Version != "12" { t.Fatal("Wrong product version") } + if p.Arch != "x86_64" { t.Fatal("Wrong product arch") } + if p.String() != "SLES-12-x86_64" { t.Fatal("Wrong product string") } @@ -109,6 +116,7 @@ func TestSUSE(t *testing.T) { if err == nil { t.Fatal("It should fail") } + return } diff --git a/internal/logger.go b/internal/logger.go index a996ba8..cd5929b 100644 --- a/internal/logger.go +++ b/internal/logger.go @@ -47,8 +47,7 @@ func getLogWritter(path string) (io.WriteCloser, error) { return nil, fmt.Errorf("log path is not absulute: %s", path) } - lf, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640) - + lf, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o640) if err != nil { return nil, err } @@ -61,7 +60,6 @@ func getLogWritter(path string) (io.WriteCloser, error) { } _, err = lf.WriteString(fmt.Sprintf("container-suseconnect %s\n", Version)) - if err != nil { lf.Close() return nil, err diff --git a/internal/products.go b/internal/products.go index d92a3ae..f9bc883 100644 --- a/internal/products.go +++ b/internal/products.go @@ -58,10 +58,12 @@ func fixRepoUrlsForRMT(p *Product) error { loggedError(RepositoryError, "Unable to parse repository URL: %s - %v", p.Repositories[i].URL, err) return err } + params := repourl.Query() if params.Get("credentials") == "" { params["credentials"] = []string{"SCCcredentials"} } + repourl.RawQuery = params.Encode() p.Repositories[i].URL = repourl.String() } @@ -72,6 +74,7 @@ func fixRepoUrlsForRMT(p *Product) error { return err } } + return nil } @@ -104,10 +107,12 @@ func parseProducts(reader io.Reader) ([]Product, error) { products = append(products, product) } } + if err != nil { return products, loggedError(RepositoryError, "Can't read product information: %v - %s", err.Error(), data) } + return products, nil } @@ -117,19 +122,14 @@ func parseProducts(reader io.Reader) ([]Product, error) { // be requested. // This function relies on [/connect/subscriptions/products](https://github.com/SUSE/connect/wiki/SCC-API-%28Implemented%29#product) API. func requestProductsFromRegCodeOrSystem(data SUSEConnectData, regCode string, - credentials Credentials, installed InstalledProduct) ([]Product, error) { + credentials Credentials, installed InstalledProduct, +) ([]Product, error) { var products []Product var err error - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: data.Insecure}, - Proxy: http.ProxyFromEnvironment, - } - client := &http.Client{Transport: tr} req, err := http.NewRequest("GET", data.SccURL, nil) if err != nil { - return products, - loggedError(NetworkError, "Could not connect with registration server: %v\n", err) + return products, loggedError(NetworkError, "Could not connect with registration server: %v\n", err) } values := req.URL.Query() @@ -137,6 +137,7 @@ func requestProductsFromRegCodeOrSystem(data SUSEConnectData, regCode string, values.Add("version", installed.Version) values.Add("arch", installed.Arch) req.URL.RawQuery = values.Encode() + if len(regCode) > 0 { req.Header.Add("Authorization", `Token token=`+regCode) req.URL.Path = "/connect/subscriptions/products" @@ -146,25 +147,37 @@ func requestProductsFromRegCodeOrSystem(data SUSEConnectData, regCode string, req.URL.Path = "/connect/systems/products" auth := url.UserPassword(credentials.Username, credentials.Password) req.URL.User = auth + if credentials.SystemToken != "" { req.Header.Add("System-Token", credentials.SystemToken) } } + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: data.Insecure, + }, + Proxy: http.ProxyFromEnvironment, + }, + } + resp, err := client.Do(req) if err != nil { return products, err } + if resp.StatusCode != 200 { - dec := json.NewDecoder(resp.Body) var payload map[string]interface{} + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&payload); err == nil { if err, ok := payload["error"]; ok { log.Println(err) } } - return products, - loggedError(SubscriptionServerError, "Unexpected error while retrieving products with regCode %s: %s", regCode, resp.Status) + + return products, loggedError(SubscriptionServerError, "Unexpected error while retrieving products with regCode %s: %s", regCode, resp.Status) } return parseProducts(resp.Body) @@ -175,7 +188,8 @@ func requestProductsFromRegCodeOrSystem(data SUSEConnectData, regCode string, // connection with the registration server. The `installed` parameter contains // the product to be requested. func RequestProducts(data SUSEConnectData, credentials Credentials, - installed InstalledProduct) ([]Product, error) { + installed InstalledProduct, +) ([]Product, error) { var products []Product var regCodes []string var err error @@ -191,6 +205,7 @@ func RequestProducts(data SUSEConnectData, credentials Credentials, err = _err continue } + products = append(products, p...) } diff --git a/internal/products_test.go b/internal/products_test.go index 8049003..cbca2dc 100644 --- a/internal/products_test.go +++ b/internal/products_test.go @@ -41,12 +41,15 @@ func productHelper(t *testing.T, product Product, expectedVersion string) { func productHelperSLE12(t *testing.T, product Product) { productHelper(t, product, "12") + if len(product.Repositories) != 4 { t.Fatalf("Wrong number of repos %v", len(product.Repositories)) } + if product.Repositories[3].Name != "SLES12-Debuginfo-Pool" { t.Fatal("Unexpected value") } + expectedURL := "https://smt.test.lan/repo/SUSE/Products/SLE-SERVER/12/x86_64/product_debug" if string(product.Repositories[3].URL) != expectedURL { t.Fatalf("Unexpected repository URL: %s", product.Repositories[3].URL) @@ -55,9 +58,11 @@ func productHelperSLE12(t *testing.T, product Product) { func productHelperSLE15RMT(t *testing.T, product Product) { productHelper(t, product, "15.1") + if len(product.Repositories) != 6 { t.Fatal("Wrong number of repos") } + if product.Extensions[0].Repositories[2].Name != "SLE-Module-Basesystem15-SP1-Pool" { t.Fatalf("Unexpected Extension Name: %v", product.Extensions[0].Repositories[2].Name) } @@ -105,9 +110,11 @@ func TestValidProduct(t *testing.T) { if err != nil { t.Fatal("Unexpected error when reading a valid JSON file") } + if len(products) != 1 { t.Fatalf("Unexpected number of products found. Got %d, expected %d", len(products), 1) } + productHelperSLE12(t, products[0]) } @@ -181,9 +188,11 @@ func TestValidRequestForProduct(t *testing.T) { if err != nil { t.Fatal("It should've run just fine...") } + if len(products) != 1 { t.Fatalf("Unexpected number of products found. Got %d, expected %d", len(products), 1) } + productHelperSLE12(t, products[0]) } @@ -194,6 +203,7 @@ func TestValidRequestForProductUsingRMT(t *testing.T) { if r.URL.Path == "/connect/systems/subscriptions" { http.Error(w, "", http.StatusNotFound) } + // The result also looks slightly different resFile := "testdata/products-sle15-rmt.json" file, err := os.Open(resFile) @@ -214,8 +224,10 @@ func TestValidRequestForProductUsingRMT(t *testing.T) { if err != nil { t.Fatal("It should've run just fine...") } + if len(products) != 1 { t.Fatalf("Unexpected number of products found. Got %d, expected %d", len(products), 1) } + productHelperSLE15RMT(t, products[0]) } diff --git a/internal/regionsrv/ca.go b/internal/regionsrv/ca.go index c161114..381bb4c 100644 --- a/internal/regionsrv/ca.go +++ b/internal/regionsrv/ca.go @@ -44,12 +44,11 @@ func updateNeeded(contents string) bool { if err != nil { return true } - sum := strings.TrimSpace(string(data)) hash := md5.New() io.WriteString(hash, contents) - return sum != string(hash.Sum(nil)) + return strings.TrimSpace(string(data)) != string(hash.Sum(nil)) } // safeCAFile implements `SafeCAFile` by assuming a `commander` type will be @@ -60,12 +59,11 @@ func safeCAFile(cmd commander, contents string) error { } // Nuke everything before populating things back again. - - _ = os.Remove(hashFilePath) - _ = os.Remove(caFilePath) + os.Remove(hashFilePath) + os.Remove(caFilePath) // Safe the file - err := os.WriteFile(caFilePath, []byte(contents), 0644) + err := os.WriteFile(caFilePath, []byte(contents), 0o644) if err != nil { return err } @@ -78,7 +76,7 @@ func safeCAFile(cmd commander, contents string) error { // Safe the new checksum hash := md5.New() io.WriteString(hash, contents) - _ = os.WriteFile(hashFilePath, hash.Sum(nil), 0644) + os.WriteFile(hashFilePath, hash.Sum(nil), 0o644) return nil } diff --git a/internal/regionsrv/ca_test.go b/internal/regionsrv/ca_test.go index 03dbb8e..db86b84 100644 --- a/internal/regionsrv/ca_test.go +++ b/internal/regionsrv/ca_test.go @@ -59,6 +59,7 @@ func TestNoUpdateIsNeeded(t *testing.T) { if updateNeeded("valid") { t.Fatal("Should not be needed") } + if !updateNeeded("nope") { t.Fatal("Should be needed") } @@ -92,8 +93,8 @@ func TestSafeCAFileBadWrite(t *testing.T) { cmd := testCommand{shouldFail: false} err := safeCAFile(cmd, "valid") - _ = os.Remove(hashFilePath) - _ = os.Remove(caFilePath) + os.Remove(hashFilePath) + os.Remove(caFilePath) if err == nil { t.Fatal("Should've failed") @@ -108,12 +109,13 @@ func TestSafeCAFileBadCommand(t *testing.T) { cmd := testCommand{shouldFail: true} err := safeCAFile(cmd, "valid") - _ = os.Remove(hashFilePath) - _ = os.Remove(caFilePath) + os.Remove(hashFilePath) + os.Remove(caFilePath) if err == nil { t.Fatal("Expected error to be non-nil\n") } + if err.Error() != "I AM ERROR" { t.Fatalf("Expected another error, got: %v\n", err) } @@ -127,12 +129,12 @@ func TestSafeCAFileSuccess(t *testing.T) { err := safeCAFile(cmd, "valid") if err != nil { - _ = os.Remove(hashFilePath) + os.Remove(hashFilePath) t.Fatalf("Expected error to be nil: %v\n", err) } b, _ := os.ReadFile(hashFilePath) - _ = os.Remove(hashFilePath) + os.Remove(hashFilePath) hash := md5.New() io.WriteString(hash, "valid") diff --git a/internal/regionsrv/hostsfile.go b/internal/regionsrv/hostsfile.go index 74962d4..08914aa 100644 --- a/internal/regionsrv/hostsfile.go +++ b/internal/regionsrv/hostsfile.go @@ -34,6 +34,7 @@ func UpdateHostsFile(hostname string, ip string) error { newcontent := "" hostChecked := false shorthost := strings.Split(hostname, ".")[0] + for _, line := range lines { fields := strings.Fields(line) if len(fields) >= 2 && fields[1] == hostname { @@ -41,8 +42,10 @@ func UpdateHostsFile(hostname string, ip string) error { log.Printf("updating hosts entry for %s", hostname) line = fmt.Sprintf("%s %s %s\n", ip, hostname, shorthost) } + hostChecked = true } + newcontent += line + "\n" } @@ -50,7 +53,7 @@ func UpdateHostsFile(hostname string, ip string) error { newcontent += fmt.Sprintf("%s %s %s\n", ip, hostname, shorthost) } - err = os.WriteFile(hostsFile, []byte(newcontent), 0644) + err = os.WriteFile(hostsFile, []byte(newcontent), 0o644) if err != nil { return fmt.Errorf("can't write %s file: %v", hostsFile, err.Error()) } diff --git a/internal/regionsrv/hostsfile_test.go b/internal/regionsrv/hostsfile_test.go index 4916023..17b3e14 100644 --- a/internal/regionsrv/hostsfile_test.go +++ b/internal/regionsrv/hostsfile_test.go @@ -55,7 +55,7 @@ func TestUpdateHostsFileCouldNotRead(t *testing.T) { } func TestUpdateHostsFileCouldNotWrite(t *testing.T) { - hostsFile = copyHostFileToTemp(0400) + hostsFile = copyHostFileToTemp(0o400) if hostsFile == "" { t.Fatalf("Failed to initialize hosts file") } @@ -69,7 +69,7 @@ func TestUpdateHostsFileCouldNotWrite(t *testing.T) { } func TestUpdateHostsFileSuccessful(t *testing.T) { - hostsFile = copyHostFileToTemp(0644) + hostsFile = copyHostFileToTemp(0o644) if hostsFile == "" { t.Fatalf("Failed to initialize hosts file") } @@ -98,7 +98,7 @@ func TestUpdateHostsFileSuccessful(t *testing.T) { } func TestUpdateHostsFileUpdateExistingEntry(t *testing.T) { - hostsFile = copyHostFileToTemp(0644) + hostsFile = copyHostFileToTemp(0o644) if hostsFile == "" { t.Fatalf("Failed to initialize hosts file") } diff --git a/internal/regionsrv/server.go b/internal/regionsrv/server.go index c3e75d9..1cbcf93 100644 --- a/internal/regionsrv/server.go +++ b/internal/regionsrv/server.go @@ -67,7 +67,8 @@ func ServerReachable() error { return err } - _ = conn.Close() + conn.Close() + return nil } @@ -98,9 +99,9 @@ func ReadConfigFromServer() (*ContainerBuildConfig, error) { // If something is really bad on the server side, it may return an empty // response. Catch this error here. - if data.InstanceData == "" && data.ServerFqdn == "" && - data.ServerIP == "" && data.Ca == "" { + if data.InstanceData == "" && data.ServerFqdn == "" && data.ServerIP == "" && data.Ca == "" { return nil, errors.New("empty response from the server") } + return data, nil } diff --git a/internal/regionsrv/server_test.go b/internal/regionsrv/server_test.go index c4f3dd8..178f6a5 100644 --- a/internal/regionsrv/server_test.go +++ b/internal/regionsrv/server_test.go @@ -40,20 +40,24 @@ func (ts *testServer) run() (err error) { if err != nil { err = fmt.Errorf("could not start test server: %v", err) ts.bootstrapped <- true + return } for { ts.bootstrapped <- true + conn, err := ts.server.Accept() if err != nil { err = errors.New("could not accept connection") break } + if conn == nil { err = errors.New("could not create connection") break } + if ts.badResponse { io.WriteString(conn, "{") } @@ -62,8 +66,8 @@ func (ts *testServer) run() (err error) { if err != nil { break } - io.WriteString(conn, string(b)) + io.WriteString(conn, string(b)) conn.Close() } @@ -75,6 +79,7 @@ func (ts *testServer) close() error { if ts.server == nil { return nil } + return ts.server.Close() } @@ -149,6 +154,7 @@ func TestValidResponse(t *testing.T) { if err != nil { t.Fatalf("should be nil but got: %v", err) } + if cfg.InstanceData != ts.response.InstanceData { t.Fatalf("Expected '%v', got '%v'", cfg.InstanceData, ts.response.InstanceData) } diff --git a/internal/regionsrv/zypper.go b/internal/regionsrv/zypper.go index f1963fd..328f450 100644 --- a/internal/regionsrv/zypper.go +++ b/internal/regionsrv/zypper.go @@ -44,6 +44,7 @@ func ParseStdin() (map[string]string, error) { // are just : header lines and don't contain a body. sr := bytes.NewReader(msg) scanner := bufio.NewScanner(sr) + for scanner.Scan() { if first { first = false @@ -80,6 +81,7 @@ func PrintResponse(params map[string]string) error { } printFromConfiguration(params["path"], cfg) + return nil } @@ -99,5 +101,4 @@ func printFromConfiguration(path string, cfg *ContainerBuildConfig) { fmt.Printf("X-Instance-Data:%s\n\n", cfg.InstanceData) // Message needs to be NUL-terminated fmt.Printf("%s\000", u.String()) - } diff --git a/internal/regionsrv/zypper_test.go b/internal/regionsrv/zypper_test.go index e537e16..cde7ea7 100644 --- a/internal/regionsrv/zypper_test.go +++ b/internal/regionsrv/zypper_test.go @@ -53,12 +53,15 @@ func TestParseStdinSuccessful(t *testing.T) { if err != nil { t.Fatalf("ParseStdin returned an error: %v", err) } + if len(params) != 2 { t.Fatalf("There should be two entries, %v instead", len(params)) } + if params["key"] != "value1" { t.Fatalf("Expected 'key' to contain 'value1', got '%v' instead", params["key"]) } + if params["another"] != "value2" { t.Fatalf("Expected 'key' to contain 'value2', got '%v' instead", params["another"]) } @@ -139,9 +142,11 @@ func TestPrintFromConfiguration(t *testing.T) { "", "https://banjo:kazooie@test.fqdn.com/path\x00", } + if len(lines) != len(expected) { t.Fatalf("Expected %v lines, got %v", len(expected), len(lines)) } + for k, v := range expected { if lines[k] != v { t.Fatalf("Expected '%v', got '%v'", v, lines[k]) diff --git a/internal/service.go b/internal/service.go index 5c2c722..10b7e72 100644 --- a/internal/service.go +++ b/internal/service.go @@ -34,7 +34,6 @@ func boolToInt(b bool) int { // DumpRepositories dumps the repositories of the given product to the given // writer. func DumpRepositories(w io.Writer, product Product) { - fmt.Fprintf(w, "# generated by container-suseconnect\n") fmt.Fprintf(w, "\n") @@ -89,6 +88,7 @@ func moduleEnabledInEnv(identifier string) bool { return true } } + return false } @@ -99,18 +99,21 @@ func moduleEnabledInProductFiles(identifier string) bool { if err != nil { return false } + for _, file := range files { ext := filepath.Ext(file.Name()) info, err := file.Info() if err != nil { continue } + if ext == ".prod" && identifier == strings.TrimSuffix(file.Name(), ext) && info.Mode()&os.ModeSymlink == 0 { return true } } + return false } diff --git a/internal/setup_test.go b/internal/setup_test.go index 12a7ef7..4b1af84 100644 --- a/internal/setup_test.go +++ b/internal/setup_test.go @@ -57,7 +57,6 @@ func captureStderr(t *testing.T, fn func()) (string, error) { // redirect Stderr to capture the log written orig := os.Stderr r, w, err := os.Pipe() - if err != nil { return "", err } @@ -71,7 +70,6 @@ func captureStderr(t *testing.T, fn func()) (string, error) { w.Close() data, err := io.ReadAll(r) - if err != nil { return "", err } diff --git a/internal/subscriptions.go b/internal/subscriptions.go index 5ff772c..7fc6563 100644 --- a/internal/subscriptions.go +++ b/internal/subscriptions.go @@ -37,24 +37,27 @@ type Subscription struct { // This function uses SCC's "/connect/systems/subscriptions" API func requestRegcodes(data SUSEConnectData, credentials Credentials) ([]string, error) { var codes []string - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: data.Insecure}, - Proxy: http.ProxyFromEnvironment, - } - client := &http.Client{Transport: tr} + req, err := http.NewRequest("GET", data.SccURL, nil) if err != nil { - return codes, - loggedError(NetworkError, "Could not connect with registration server: %v\n", err) + return codes, loggedError(NetworkError, "Could not connect with registration server: %v\n", err) } req.URL.Path = "/connect/systems/subscriptions" + req.URL.User = url.UserPassword(credentials.Username, credentials.Password) - auth := url.UserPassword(credentials.Username, credentials.Password) if credentials.SystemToken != "" { req.Header.Add("System-Token", credentials.SystemToken) } - req.URL.User = auth + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: data.Insecure, + }, + Proxy: http.ProxyFromEnvironment, + }, + } resp, err := client.Do(req) if err != nil { @@ -66,12 +69,12 @@ func requestRegcodes(data SUSEConnectData, credentials Credentials) ([]string, e // has this API. Just return a empty string log.Println("Cannot fetch regcodes. Assuming it is SMT server") codes = append(codes, "") + return codes, nil } if resp.StatusCode != 200 { - return codes, - loggedError(SubscriptionServerError, "Unexpected error while retrieving regcode: %s", resp.Status) + return codes, loggedError(SubscriptionServerError, "Unexpected error while retrieving regcode: %s", resp.Status) } subscriptions, err := parseSubscriptions(resp.Body) @@ -86,6 +89,7 @@ func requestRegcodes(data SUSEConnectData, credentials Credentials) ([]string, e loggedError(SubscriptionServerError, "Skipping regCode: %s -- expired.", subscription.RegCode) } } + return codes, err } @@ -103,8 +107,10 @@ func parseSubscriptions(reader io.Reader) ([]Subscription, error) { if err != nil { return subscriptions, loggedError(SubscriptionError, "Can't read subscription: %v", err.Error()) } + if len(subscriptions) == 0 { return subscriptions, loggedError(SubscriptionError, "Got 0 subscriptions") } + return subscriptions, nil } diff --git a/internal/subscriptions_test.go b/internal/subscriptions_test.go index 68b5d79..c499dca 100644 --- a/internal/subscriptions_test.go +++ b/internal/subscriptions_test.go @@ -83,9 +83,11 @@ func TestValidSubscriptions(t *testing.T) { if err != nil { t.Fatal("Unexpected error when reading a valid JSON file") } + if len(subscriptions) != 2 { t.Fatalf("Unexpected number of subscriptions found. Got %d, expected %d", len(subscriptions), 1) } + subscriptionHelper(t, subscriptions[0]) } @@ -147,10 +149,12 @@ func TestValidRequestForRegcodes(t *testing.T) { if err != nil { t.Fatal("It should've run just fine...") } + // This also tests that we're not including expired regcodes if len(codes) != 1 { t.Fatalf("Unexpected number of products found. Got %d, expected %d", len(codes), 1) } + if codes[0] != "35098ff7" { t.Fatalf("Got the wrong registration code: %v", codes[0]) } @@ -176,6 +180,7 @@ func TestRequestEmptyRegcodes(t *testing.T) { if err == nil || err.Error() != "Got 0 subscriptions" { t.Fatal("Unexpected error when reading a valid JSON file") } + if len(codes) != 0 { t.Fatalf("It should be 0") } diff --git a/internal/suseconnect.go b/internal/suseconnect.go index 4d3f89b..6554979 100644 --- a/internal/suseconnect.go +++ b/internal/suseconnect.go @@ -55,5 +55,6 @@ func (data *SUSEConnectData) afterParseCheck() error { if data.SccURL == "" { data.SccURL = sccURLStr } + return nil } diff --git a/internal/suseconnect_test.go b/internal/suseconnect_test.go index 2695e71..8e9dac0 100644 --- a/internal/suseconnect_test.go +++ b/internal/suseconnect_test.go @@ -28,26 +28,31 @@ func TestSUSEConnectData(t *testing.T) { if data.separator() != ':' { t.Fatal("Wrong separator") } + err := data.afterParseCheck() if err != nil { t.Fatal("There should not be an error") } + if data.SccURL != sccURLStr { t.Fatal("The URL should be the one from sccURLstr") } locs := data.locations() + if locs[0] != "/etc/SUSEConnect" { t.Fatal("Wrong location") } + if locs[1] != "/run/secrets/SUSEConnect" { t.Fatal("Wrong location") } - // It should log a proper warning. buffer := bytes.NewBuffer([]byte{}) log.SetOutput(buffer) data.setValues("unknown", "value") + + // It should log a proper warning. if !strings.Contains(buffer.String(), "Warning: Unknown key 'unknown'") { t.Fatal("Wrong warning!") } @@ -68,6 +73,7 @@ func (mock *SUSEConnectDataMock) locations() []string { if locationShouldBeFound { return []string{"testdata/suseconnect.txt"} } + return []string{"testdata/notfound.txt"} } @@ -96,9 +102,11 @@ func TestIntegrationSUSEConnectData(t *testing.T) { if err != nil { t.Fatal("This should've been a successful run") } + if mock.data.SccURL != "https://smt.test.lan" { t.Fatal("Unexpected URL value") } + if !mock.data.Insecure { t.Fatal("Unexpected Insecure value") } @@ -113,6 +121,7 @@ func TestLocationsNotFound(t *testing.T) { if err != nil { t.Fatal("This should've been a successful run") } + if mock.data.SccURL != "https://scc.suse.com" { t.Fatal("It should've been scc.suse.com") }