From 568d37106ff752cd142a73168853476ccbe06a48 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 2 Jan 2024 16:08:50 +0100 Subject: [PATCH 1/2] Move to external go-i18n and gotext We did the work to avoid our internal/ package last cycle. Move now to it. We still need to validate each lines that we log or not. --- internal/ad/ad.go | 48 ++-- internal/ad/admxgen/admxgen.go | 44 +-- internal/ad/admxgen/common/common.go | 5 +- internal/ad/admxgen/dconf/dconf.go | 25 +- internal/ad/admxgen/files.go | 9 +- internal/ad/backends/backend.go | 4 +- internal/ad/backends/sss/sss.go | 12 +- internal/ad/backends/winbind/winbind.go | 16 +- internal/ad/common/common.go | 4 +- internal/ad/definitions.go | 4 +- internal/ad/download.go | 28 +- internal/ad/registry/registry.go | 8 +- internal/adsysservice/adsysservice.go | 9 +- internal/adsysservice/client.go | 4 +- internal/adsysservice/doc.go | 15 +- internal/adsysservice/policy.go | 12 +- internal/adsysservice/service.go | 47 ++-- internal/adsysservice/version.go | 4 +- internal/authorizer/authorizer.go | 40 +-- internal/authorizer/servercreds.go | 13 +- internal/cmdhandler/cmdhandler.go | 10 +- internal/cmdhandler/suggest.go | 11 +- internal/config/config.go | 6 +- internal/config/watchd/watchd.go | 20 +- internal/daemon/daemon.go | 25 +- internal/grpc/grpcerror/grpcerror.go | 11 +- internal/grpc/logstreamer/log.go | 7 +- internal/grpc/logstreamer/server.go | 18 +- internal/i18n/export_test.go | 21 -- internal/i18n/generate-locales.go | 256 ------------------ internal/i18n/i18n.go | 97 ------- internal/i18n/i18n_test.go | 193 ------------- internal/policies/apparmor/apparmor.go | 66 ++--- internal/policies/certificate/certificate.go | 17 +- internal/policies/dconf/dconf.go | 28 +- internal/policies/gdm/gdm.go | 6 +- internal/policies/manager.go | 31 ++- internal/policies/mount/mount.go | 58 ++-- internal/policies/policies.go | 23 +- internal/policies/privilege/privilege.go | 8 +- internal/policies/proxy/proxy.go | 8 +- internal/policies/scripts/scripts.go | 34 +-- internal/stdforward/stdforward.go | 4 +- internal/systemd/systemd.go | 16 +- internal/watchdservice/watchdservice.go | 65 +++-- internal/watchdservice/watchdservice_linux.go | 5 +- internal/watchdtui/watchdtui.go | 33 ++- internal/watcher/watcher.go | 59 ++-- 48 files changed, 464 insertions(+), 1023 deletions(-) delete mode 100644 internal/i18n/export_test.go delete mode 100644 internal/i18n/generate-locales.go delete mode 100644 internal/i18n/i18n.go delete mode 100644 internal/i18n/i18n_test.go diff --git a/internal/ad/ad.go b/internal/ad/ad.go index 3a6c8e300..37f84bfa8 100644 --- a/internal/ad/ad.go +++ b/internal/ad/ad.go @@ -20,12 +20,12 @@ import ( "sync" "time" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/ad/backends" adcommon "github.com/ubuntu/adsys/internal/ad/common" "github.com/ubuntu/adsys/internal/ad/registry" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/adsys/internal/smbsafe" @@ -117,7 +117,7 @@ var AdsysGpoListCode string // New returns an AD object to manage concurrency, with a local kr5 ticket from machine keytab. func New(ctx context.Context, configBackend backends.Backend, hostname string, opts ...Option) (ad *AD, err error) { - defer decorate.OnError(&err, i18n.G("can't create Active Directory object")) + defer decorate.OnError(&err, gotext.Get("can't create Active Directory object")) versionID, err := adcommon.GetVersionID("/") if err != nil { @@ -155,7 +155,7 @@ func New(ctx context.Context, configBackend backends.Backend, hostname string, o domain := configBackend.Domain() serverFQDN, err := configBackend.ServerFQDN(ctx) if err != nil && !errors.Is(err, backends.ErrNoActiveServer) { - return nil, fmt.Errorf(i18n.G("can't get current Server FQDN: %w"), err) + return nil, errors.New(gotext.Get("can't get current Server FQDN: %v", err)) } log.Debugf(ctx, "Backend is SSSD. AD domain: %q, server from configuration: %q", domain, serverFQDN) @@ -180,16 +180,16 @@ func New(ctx context.Context, configBackend backends.Backend, hostname string, o // The GPOs are returned from the highest priority in the hierarchy, with enforcement in reverse order // to the lowest priority. func (ad *AD) GetPolicies(ctx context.Context, objectName string, objectClass ObjectClass, userKrb5CCName string) (pols policies.Policies, err error) { - defer decorate.OnError(&err, i18n.G("can't get policies for %q"), objectName) + defer decorate.OnError(&err, gotext.Get("can't get policies for %q", objectName)) log.Debugf(ctx, "GetPolicies for %q, type %q", objectName, objectClass) if objectClass == UserObject && !strings.Contains(objectName, "@") { - return pols, fmt.Errorf(i18n.G("user name %q should be of the form %s@DOMAIN"), objectName, objectName) + return pols, errors.New(gotext.Get("user name %q should be of the form %s@DOMAIN", objectName, objectName)) } if objectClass == ComputerObject && objectName != ad.hostname { - return pols, fmt.Errorf(i18n.G("requested a type computer of %q which isn't current host %q"), objectName, ad.hostname) + return pols, errors.New(gotext.Get("requested a type computer of %q which isn't current host %q", objectName, ad.hostname)) } krb5CCPath := filepath.Join(ad.krb5CacheDir, objectName) @@ -225,7 +225,7 @@ func (ad *AD) GetPolicies(ctx context.Context, objectName string, objectClass Ob if !online { var cachedPolicies policies.Policies if cachedPolicies, err = policies.NewFromCache(ctx, filepath.Join(ad.policiesCacheDir, objectName)); err != nil { - return cachedPolicies, fmt.Errorf(i18n.G("machine is offline and policies cache is unavailable: %v"), err) + return cachedPolicies, errors.New(gotext.Get("machine is offline and policies cache is unavailable: %v", err)) } log.Infof(ctx, "Can't reach AD: machine is offline and %q policies are applied using previous online update", objectName) @@ -235,7 +235,7 @@ func (ad *AD) GetPolicies(ctx context.Context, objectName string, objectClass Ob // We need an AD DC to connect to adServerFQDN, err := ad.configBackend.ServerFQDN(ctx) if err != nil { - return policies.Policies{}, fmt.Errorf(i18n.G("can't get current Server FQDN: %w"), err) + return policies.Policies{}, errors.New(gotext.Get("can't get current Server FQDN: %v", err)) } // Otherwise, try fetching the GPO list from LDAP @@ -256,7 +256,7 @@ func (ad *AD) GetPolicies(ctx context.Context, objectName string, objectClass Ob err = cmd.Run() smbsafe.DoneExec() if err != nil { - return pols, fmt.Errorf(i18n.G("failed to retrieve the list of GPO (exited with %d): %v\n%s"), cmd.ProcessState.ExitCode(), err, stderr.String()) + return pols, errors.New(gotext.Get("failed to retrieve the list of GPO (exited with %d): %v\n%s", cmd.ProcessState.ExitCode(), err, stderr.String())) } downloadables := make(map[string]string) @@ -347,7 +347,7 @@ func (ad *AD) GetPolicies(ctx context.Context, objectName string, objectClass Ob // ListUsers returns the list of users on the system based on their cached policy information. // If active is true, the list of users is retrieved from the cached Kerberos ticket information. func (ad *AD) ListUsers(ctx context.Context, active bool) (users []string, err error) { - defer decorate.OnError(&err, i18n.G("can't list users from cache")) + defer decorate.OnError(&err, gotext.Get("can't list users from cache")) log.Debug(ctx, "ListUsers") @@ -361,7 +361,7 @@ func (ad *AD) ListUsers(ctx context.Context, active bool) (users []string, err e entries, err := os.ReadDir(cacheDir) if err != nil { - return users, fmt.Errorf(i18n.G("failed to read cache directory: %v"), err) + return users, errors.New(gotext.Get("failed to read cache directory: %v", err)) } for _, entry := range entries { @@ -390,7 +390,7 @@ func (ad *AD) ensureKrb5CCSymlink(srcKrb5CCName, dstKrb5CCName string) error { srcKrb5CCName, err := filepath.Abs(srcKrb5CCName) if err != nil { - return fmt.Errorf(i18n.G("can't get absolute path of ccname to symlink to: %v"), err) + return errors.New(gotext.Get("can't get absolute path of ccname to symlink to: %v", err)) } src, err := os.Readlink(dstKrb5CCName) @@ -401,12 +401,12 @@ func (ad *AD) ensureKrb5CCSymlink(srcKrb5CCName, dstKrb5CCName string) error { } // Delete the symlink to create a new one. if err := os.Remove(dstKrb5CCName); err != nil { - return fmt.Errorf(i18n.G("failed to remove existing symlink: %v"), err) + return errors.New(gotext.Get("failed to remove existing symlink: %v", err)) } } if err := os.MkdirAll(filepath.Dir(dstKrb5CCName), 0700); err != nil { - return fmt.Errorf(i18n.G("failed to create parent directory for symlink: %w"), err) + return errors.New(gotext.Get("failed to create parent directory for symlink: %v", err)) } if err := os.Symlink(srcKrb5CCName, dstKrb5CCName); err != nil { @@ -428,14 +428,14 @@ func (ad *AD) ensureKrb5CCCopy(krb5CCSymlink, krb5CCCopyName string) error { krb5CCSrc, err := os.Readlink(krb5CCSymlink) if err != nil { - return fmt.Errorf(i18n.G("failed to read krb5cc symlink: %w"), err) + return errors.New(gotext.Get("failed to read krb5cc symlink: %v", err)) } if copyStat, err := os.Lstat(krb5CCCopyName); err == nil && copyStat.Mode()&os.ModeSymlink == 0 { // We already have a copy of the ticket, let's check if we need to update it srcStat, err := os.Stat(krb5CCSrc) if err != nil { - return fmt.Errorf(i18n.G("failed to stat source ticket: %w"), err) + return errors.New(gotext.Get("failed to stat source ticket: %v", err)) } // The source ticket is not newer than the destination one, no need to update @@ -459,7 +459,7 @@ func (ad *AD) ensureKrb5CCCopy(krb5CCSymlink, krb5CCCopyName string) error { func safeCopyFile(src, dst string, mode os.FileMode) error { content, err := os.ReadFile(src) if err != nil { - return fmt.Errorf(i18n.G("failed to read source file: %w"), err) + return errors.New(gotext.Get("failed to read source file: %v", err)) } if err := os.WriteFile(dst+".new", content, mode); err != nil { return err @@ -522,7 +522,7 @@ func (ad *AD) parseGPOs(ctx context.Context, gpos []gpo, objectClass ObjectClass // Decode and apply policies in gpo order. First win pols, err := registry.DecodePolicy(f) if err != nil { - return fmt.Errorf(i18n.G("%s: %v"), f.Name(), err) + return errors.New(gotext.Get("%s: %v", f.Name(), err)) } // filter keys to be overridden @@ -544,7 +544,7 @@ func (ad *AD) parseGPOs(ctx context.Context, gpos []gpo, objectClass ObjectClass continue } if pol.Err != nil { - return fmt.Errorf(i18n.G("%s: %v"), f.Name(), pol.Err) + return errors.New(gotext.Get("%s: %v", f.Name(), pol.Err)) } pol.Key = strings.TrimPrefix(pol.Key, keyFilterPrefix) @@ -600,9 +600,9 @@ func (ad *AD) GetInfo(ctx context.Context) (msg string) { var online string if isOnline, err := ad.configBackend.IsOnline(); err != nil { log.Warning(ctx, err) - online = fmt.Sprint(i18n.G("**Can't check if we have an active connection**\n")) + online = fmt.Sprint(gotext.Get("**Can't check if we have an active connection**\n")) } else if !isOnline { - online = fmt.Sprint(i18n.G("**Offline mode** using cached policies\n")) + online = fmt.Sprint(gotext.Get("**Offline mode** using cached policies\n")) } domain := ad.configBackend.Domain() server, err := ad.configBackend.ServerFQDN(ctx) @@ -610,7 +610,7 @@ func (ad *AD) GetInfo(ctx context.Context) (msg string) { server = "Unknown" } - return fmt.Sprintf(i18n.G("%s\n%sDomain: %s\nServer FQDN: %s"), config, online, domain, server) + return gotext.Get("%s\n%sDomain: %s\nServer FQDN: %s", config, online, domain, server) } // NormalizeTargetName transforms the specified target to values adsys knows. @@ -648,10 +648,10 @@ func (ad *AD) NormalizeTargetName(ctx context.Context, target string, objectClas case 1: baseUser = c[0] default: - return "", fmt.Errorf(i18n.G(`only one \ is permitted in domain\username. Got: %s`), target) + return "", errors.New(gotext.Get(`only one \ is permitted in domain\username. Got: %s`, target)) } if domainSuffix == "" && ad.configBackend.DefaultDomainSuffix() == "" { - return "", fmt.Errorf(i18n.G(`no domain provided for user %q and no default domain in sssd.conf`), target) + return "", errors.New(gotext.Get(`no domain provided for user %q and no default domain in sssd.conf`, target)) } if domainSuffix == "" { domainSuffix = ad.configBackend.DefaultDomainSuffix() diff --git a/internal/ad/admxgen/admxgen.go b/internal/ad/admxgen/admxgen.go index 0ec53159a..e92ab527f 100644 --- a/internal/ad/admxgen/admxgen.go +++ b/internal/ad/admxgen/admxgen.go @@ -48,10 +48,10 @@ import ( "strings" "text/template" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/ad/admxgen/common" adcommon "github.com/ubuntu/adsys/internal/ad/common" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/decorate" "golang.org/x/text/cases" @@ -99,19 +99,19 @@ type generator struct { var ( // defaultAppendNote is the default note for append-type policies. It will be used unless a specific note is provided. - defaultAppendNote = i18n.G(` + defaultAppendNote = gotext.Get(` * Enabled: The value(s) referenced in the entry are applied on the client machine. * Disabled: The value(s) are removed from the target machine. * Not configured: Value(s) declared higher in the GPO hierarchy will be used if available.`) // defaultOverrideNote is the default note for override-type policies. It will be used unless a specific note is provided. - defaultOverrideNote = i18n.G(` + defaultOverrideNote = gotext.Get(` * Enabled: The value(s) referenced in the entry are applied on the client machine. * Disabled: The value(s) are removed from the target machine.`) ) func (g generator) generateExpandedCategories(categories []category, policies []common.ExpandedPolicy, allowMissingKeys bool) (ep []expandedCategory, err error) { - defer decorate.OnError(&err, i18n.G("can't generate expanded categories")) + defer decorate.OnError(&err, gotext.Get("can't generate expanded categories")) // noPoliciesOn is a map to attest that each release was assigned at least one property noPoliciesOn := make(map[string]struct{}) @@ -203,7 +203,7 @@ func (g generator) generateExpandedCategories(categories []category, policies [] if supportedOn == "" { if release != "all" { - supportedOn = fmt.Sprintf(i18n.G("Supported on %s %s"), g.distroID, release) + supportedOn = gotext.Get("Supported on %s %s", g.distroID, release) } } else { supportedOn = fmt.Sprintf("%s, %s", supportedOn, release) @@ -216,7 +216,7 @@ func (g generator) generateExpandedCategories(categories []category, policies [] } defaultString = p.Default - defaults = append(defaults, fmt.Sprintf(i18n.G("- Default for %s: %s"), release, p.Default)) + defaults = append(defaults, gotext.Get("- Default for %s: %s", release, p.Default)) if release > highestRelease { highestRelease = release @@ -254,10 +254,10 @@ func (g generator) generateExpandedCategories(categories []category, policies [] explainText = fmt.Sprintf("%s\n%s", explainText, strings.Join(defaults, "\n")) } else if defaultString != "" { // All defaults are the same and not empty - explainText = fmt.Sprintf("%s\n%s", explainText, fmt.Sprintf(i18n.G("- Default: %s"), defaultString)) + explainText = fmt.Sprintf("%s\n%s", explainText, gotext.Get("- Default: %s", defaultString)) } - explainText = fmt.Sprintf(i18n.G("%s\n\nNote:"), explainText) + explainText = gotext.Get("%s\n\nNote:", explainText) var note string if releasesElements["all"].Note != "" { note = releasesElements["all"].Note @@ -280,13 +280,13 @@ func (g generator) generateExpandedCategories(categories []category, policies [] // Mention if any of the policies require Ubuntu Pro // Currently this only applies to non-dconf policies if typePol != dconfPolicyType { - explainText = fmt.Sprintf("%s\n\n%s", explainText, i18n.G("An Ubuntu Pro subscription on the client is required to apply this policy.")) + explainText = fmt.Sprintf("%s\n\n%s", explainText, gotext.Get("An Ubuntu Pro subscription on the client is required to apply this policy.")) } // prepare meta for the whole policy metaEnabled, err := json.Marshal(metasEnabled) if err != nil { - return nil, errors.New(i18n.G("failed to marshal enabled meta data")) + return nil, errors.New(gotext.Get("failed to marshal enabled meta data")) } // We can’t have metaEnabled or metaDisabled being strictly equals: // some AD servers thinks they that disabled means @@ -296,7 +296,7 @@ func (g generator) generateExpandedCategories(categories []category, policies [] } metaDisabled, err := json.Marshal(metasDisabled) if err != nil { - return nil, errors.New(i18n.G("failed to marshal disabled meta data")) + return nil, errors.New(gotext.Get("failed to marshal disabled meta data")) } mergedPolicies[key] = mergedPolicy{ @@ -315,7 +315,7 @@ func (g generator) generateExpandedCategories(categories []category, policies [] for r := range noPoliciesOn { releases = append(releases, r) } - return nil, fmt.Errorf(i18n.G("some releases have no policies attached to them while being listed in categories: %v"), releases) + return nil, errors.New(gotext.Get("some releases have no policies attached to them while being listed in categories: %v", releases)) } // 2. Inflate policies in categories, keep policy order from category list @@ -325,7 +325,7 @@ func (g generator) generateExpandedCategories(categories []category, policies [] var policies []mergedPolicy if cat.DefaultPolicyClass == "" { - return expandedCategory{}, fmt.Errorf(i18n.G("%s needs a default policy class"), cat.DisplayName) + return expandedCategory{}, errors.New(gotext.Get("%s needs a default policy class", cat.DisplayName)) } defaultPolicyClass, err := common.ValidClass(cat.DefaultPolicyClass) if err != nil { @@ -340,12 +340,12 @@ func (g generator) generateExpandedCategories(categories []category, policies [] for _, p := range cat.Policies { pol, ok := mergedPolicies[p] if !ok { - msg := fmt.Sprintf(i18n.G("policy %s referenced in %q does not exist in any supported releases"), p, cat.DisplayName) + err := errors.New(gotext.Get("policy %s referenced in %q does not exist in any supported releases", p, cat.DisplayName)) if allowMissingKeys { - log.Warningf(context.Background(), msg) + log.Warning(context.Background(), err) continue } - return expandedCategory{}, errors.New(msg) + return expandedCategory{}, err } if pol.Class == "" { pol.Class = defaultPolicyClass @@ -386,7 +386,7 @@ func (g generator) generateExpandedCategories(categories []category, policies [] // Check that all policies are at least attached once if len(unattachedPolicies) > 0 { - return nil, fmt.Errorf(i18n.G("the following policies have not been assigned to a category: %v"), unattachedPolicies) + return nil, errors.New(gotext.Get("the following policies have not been assigned to a category: %v", unattachedPolicies)) } return expandedCategories, nil @@ -459,7 +459,7 @@ func (g generator) toID(key string, s ...string) string { } func (g generator) expandedCategoriesToADMX(expandedCategories []expandedCategory, dest string) (err error) { - defer decorate.OnError(&err, i18n.G("can't generate ADMX files")) + defer decorate.OnError(&err, gotext.Get("can't generate ADMX files")) var inputCategories []categoryForADMX var inputPolicies []policyForADMX @@ -476,7 +476,7 @@ func (g generator) expandedCategoriesToADMX(expandedCategories []expandedCategor }{g.distroID, inputCategories, inputPolicies} if err := os.MkdirAll(dest, 0750); err != nil { - return fmt.Errorf(i18n.G("can't create destination directory for AD policies: %v"), err) + return errors.New(gotext.Get("can't create destination directory for AD policies: %v", err)) } funcMap := template.FuncMap{ @@ -487,7 +487,7 @@ func (g generator) expandedCategoriesToADMX(expandedCategories []expandedCategor f, err := os.Create(filepath.Join(dest, g.distroID+".admx")) if err != nil { - return fmt.Errorf(i18n.G("can't create admx file: %v"), err) + return errors.New(gotext.Get("can't create admx file: %v", err)) } defer decorate.LogFuncOnError(f.Close) t := template.Must(template.New("admx.template").Funcs(funcMap).Parse(admxTemplate)) @@ -500,7 +500,7 @@ func (g generator) expandedCategoriesToADMX(expandedCategories []expandedCategor f, err = os.Create(filepath.Join(dest, g.distroID+".adml")) if err != nil { - return fmt.Errorf(i18n.G("can't create admx file: %v"), err) + return errors.New(gotext.Get("can't create admx file: %v", err)) } defer decorate.LogFuncOnError(f.Close) t = template.Must(template.New("adml.template").Funcs(funcMap).Parse(admlTemplate)) @@ -578,7 +578,7 @@ func expandedCategoriesToMD(expandedCategories []expandedCategory, rootDest stri f, err := os.Create(filepath.Join(dest, filepath.Base(polDetails.Key)) + ".md") if err != nil { - return fmt.Errorf(i18n.G("can't create md file: %v"), err) + return errors.New(gotext.Get("can't create md file: %v", err)) } defer decorate.LogFuncOnError(f.Close) t := template.Must(template.New("doc policy").Parse(docPolicyTemplate)) diff --git a/internal/ad/admxgen/common/common.go b/internal/ad/admxgen/common/common.go index aee4822a9..28d662ff7 100644 --- a/internal/ad/admxgen/common/common.go +++ b/internal/ad/admxgen/common/common.go @@ -2,9 +2,10 @@ package common import ( + "errors" "fmt" - "github.com/ubuntu/adsys/internal/i18n" + "github.com/leonelquinteros/gotext" "golang.org/x/text/cases" "golang.org/x/text/language" ) @@ -77,7 +78,7 @@ func ValidClass(class string) (string, error) { c := cases.Title(language.Und, cases.NoLower).String(class) if c != "" && c != "User" && c != "Machine" { - return "", fmt.Errorf(i18n.G("invalid class %q"), class) + return "", errors.New(gotext.Get("invalid class %q", class)) } return c, nil diff --git a/internal/ad/admxgen/dconf/dconf.go b/internal/ad/admxgen/dconf/dconf.go index a80fb7c7a..b9bddfea5 100644 --- a/internal/ad/admxgen/dconf/dconf.go +++ b/internal/ad/admxgen/dconf/dconf.go @@ -3,6 +3,7 @@ package dconf import ( "encoding/xml" + "errors" "fmt" "io" "math" @@ -12,9 +13,9 @@ import ( "strconv" "strings" + "github.com/leonelquinteros/gotext" log "github.com/sirupsen/logrus" "github.com/ubuntu/adsys/internal/ad/admxgen/common" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" "gopkg.in/ini.v1" ) @@ -53,7 +54,7 @@ var ( // Generate creates a set of expanded policies from a list of policies and // dconf schemas available on the machine. func Generate(policies []Policy, release string, root, currentSessions string) (ep []common.ExpandedPolicy, err error) { - defer decorate.OnError(&err, i18n.G("can't generate dconf expanded policies")) + defer decorate.OnError(&err, gotext.Get("can't generate dconf expanded policies")) s, d, err := loadSchemasFromDisk(filepath.Join(root, schemasPath)) if err != nil { @@ -127,7 +128,7 @@ func inflateToExpandedPolicies(policies []Policy, release, currentSessions strin Class: class, Release: release, Default: defaultVal, - Note: i18n.G(`default system value is used for "Not Configured" and enforced if "Disabled".`), + Note: gotext.Get(`default system value is used for "Not Configured" and enforced if "Disabled".`), Type: "dconf", RangeValues: s.RangeValues, Choices: s.Choices, @@ -159,11 +160,11 @@ func inflateToExpandedPolicies(policies []Policy, release, currentSessions strin min = "0" } if min == "NaN" || min == "Inf" { - return nil, fmt.Errorf(i18n.G("min value for long decimal is not a valid float: %s"), min) + return nil, errors.New(gotext.Get("min value for long decimal is not a valid float: %s", min)) } s, err := strconv.ParseFloat(min, 64) if err != nil { - return nil, fmt.Errorf(i18n.G("min value for long decimal is not a valid float: %v"), err) + return nil, errors.New(gotext.Get("min value for long decimal is not a valid float: %v", err)) } min = fmt.Sprintf("%f", math.Max(0, s)) ep.RangeValues.Min = min @@ -227,7 +228,7 @@ type schemaList struct { } func loadSchemasFromDisk(path string) (entries map[string]schemaEntry, defaultsForPath map[string]string, err error) { - defer decorate.OnError(&err, i18n.G("error while loading schemas")) + defer decorate.OnError(&err, gotext.Get("error while loading schemas")) entries = make(map[string]schemaEntry) enums := make(map[string][]string) @@ -236,24 +237,24 @@ func loadSchemasFromDisk(path string) (entries map[string]schemaEntry, defaultsF // load schemas schemas, err := filepath.Glob(filepath.Join(path, "*.xml")) if err != nil { - return nil, nil, fmt.Errorf(i18n.G("failed to read list of schemas: %w"), err) + return nil, nil, errors.New(gotext.Get("failed to read list of schemas: %v", err)) } for _, p := range schemas { f, err := os.Open(filepath.Clean(p)) if err != nil { - return nil, nil, fmt.Errorf(i18n.G("cannot open file: %w"), err) + return nil, nil, errors.New(gotext.Get("cannot open file: %v", err)) } defer decorate.LogFuncOnError(f.Close) d, err := io.ReadAll(f) if err != nil { - return nil, nil, fmt.Errorf(i18n.G("cannot read schema data: %w"), err) + return nil, nil, errors.New(gotext.Get("cannot read schema data: %v", err)) } var sl schemaList if err := xml.Unmarshal(d, &sl); err != nil { - return nil, nil, fmt.Errorf(i18n.G("%s is an invalid schema: %v"), p, err) + return nil, nil, errors.New(gotext.Get("%s is an invalid schema: %v", p, err)) } for _, s := range sl.Schema { @@ -323,7 +324,7 @@ func loadSchemasFromDisk(path string) (entries map[string]schemaEntry, defaultsF if e.enumID != "" { var ok bool if e.Choices, ok = enums[e.enumID]; !ok { - return nil, nil, fmt.Errorf(i18n.G("enum id %s referenced by %s doesn't exist in list of enums"), e.enumID, e.Schema) + return nil, nil, errors.New(gotext.Get("enum id %s referenced by %s doesn't exist in list of enums", e.enumID, e.Schema)) } e.enumID = "" entries[k] = e @@ -333,7 +334,7 @@ func loadSchemasFromDisk(path string) (entries map[string]schemaEntry, defaultsF // Load override files to override defaults overrides, err := filepath.Glob(filepath.Join(path, "*.gschema.override")) if err != nil { - return nil, nil, fmt.Errorf(i18n.G("failed to read overrides files: %w"), err) + return nil, nil, errors.New(gotext.Get("failed to read overrides files: %v", err)) } sort.Strings(overrides) diff --git a/internal/ad/admxgen/files.go b/internal/ad/admxgen/files.go index 58f7efd23..ac10f0ca9 100644 --- a/internal/ad/admxgen/files.go +++ b/internal/ad/admxgen/files.go @@ -2,16 +2,17 @@ package admxgen import ( _ "embed" + "errors" "fmt" "os" "path/filepath" "sort" "strings" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/ad/admxgen/common" "github.com/ubuntu/adsys/internal/ad/admxgen/dconf" adcommon "github.com/ubuntu/adsys/internal/ad/common" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" "golang.org/x/sync/errgroup" "gopkg.in/yaml.v3" @@ -34,12 +35,12 @@ func Expand(src, dst, root, currentSession string) error { } if _, err = os.Stat(src); err != nil { - return fmt.Errorf(i18n.G("failed to access definition files: %w"), err) + return errors.New(gotext.Get("failed to access definition files: %v", err)) } // Expand policies for all supported yaml files files, err := filepath.Glob(filepath.Join(src, "*.yaml")) if err != nil { - return fmt.Errorf(i18n.G("failed to read list of definition files: %w"), err) + return errors.New(gotext.Get("failed to read list of definition files: %v", err)) } expandedPoliciesStream := make(chan []common.ExpandedPolicy, len(files)) @@ -198,7 +199,7 @@ func GenerateDoc(categoryDefinition, src, dst string) error { } func loadDefinitions(categoryDefinition, src string) (ep []common.ExpandedPolicy, cfs categoryFileStruct, err error) { - defer decorate.OnError(&err, i18n.G("can't load category definition")) + defer decorate.OnError(&err, gotext.Get("can't load category definition")) var nilCategoryFileStruct categoryFileStruct diff --git a/internal/ad/backends/backend.go b/internal/ad/backends/backend.go index 8db0bce5e..f7c8d4282 100644 --- a/internal/ad/backends/backend.go +++ b/internal/ad/backends/backend.go @@ -5,7 +5,7 @@ import ( "context" "errors" - "github.com/ubuntu/adsys/internal/i18n" + "github.com/leonelquinteros/gotext" ) // Backend is the common interface for all backends. @@ -30,5 +30,5 @@ type Backend interface { var ( // ErrNoActiveServer is an error receive when there is no active server and no static configuration // This is received in ServerFQDN. - ErrNoActiveServer = errors.New(i18n.G("no active server found")) + ErrNoActiveServer = errors.New(gotext.Get("no active server found")) ) diff --git a/internal/ad/backends/sss/sss.go b/internal/ad/backends/sss/sss.go index 200f30045..f05088fe4 100644 --- a/internal/ad/backends/sss/sss.go +++ b/internal/ad/backends/sss/sss.go @@ -9,10 +9,10 @@ import ( "strings" "github.com/godbus/dbus/v5" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/ad/backends" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" "gopkg.in/ini.v1" ) @@ -37,7 +37,7 @@ type Config struct { // New returns a sss backend loaded from Config. func New(ctx context.Context, c Config, bus *dbus.Conn) (s SSS, err error) { - defer decorate.OnError(&err, i18n.G("can't get domain configuration from %+v"), c) + defer decorate.OnError(&err, gotext.Get("can't get domain configuration from %+v", c)) log.Debug(ctx, "Loading SSS configuration for AD backend") @@ -57,11 +57,11 @@ func New(ctx context.Context, c Config, bus *dbus.Conn) (s SSS, err error) { // Take first domain as domain for machine and all users sssdDomain := strings.Split(cfg.Section("sssd").Key("domains").String(), ",")[0] if sssdDomain == "" { - return SSS{}, errors.New(i18n.G("failed to find default sssd domain in sssd.conf")) + return SSS{}, errors.New(gotext.Get("failed to find default sssd domain in sssd.conf")) } domain := cfg.Section(fmt.Sprintf("domain/%s", sssdDomain)).Key("ad_domain").String() if domain == "" { - return SSS{}, fmt.Errorf(i18n.G("could not find AD domain name corresponding to %q"), sssdDomain) + return SSS{}, errors.New(gotext.Get("could not find AD domain name corresponding to %q", sssdDomain)) } if defaultDomainSuffix == "" { @@ -102,7 +102,7 @@ func (sss SSS) Domain() string { // If the dynamic lookup worked, but there is still no server FQDN found (for instance, backend // if offline), the error raised is of type ErrorNoActiveServer. func (sss SSS) ServerFQDN(ctx context.Context) (serverFQDN string, err error) { - defer decorate.OnError(&err, i18n.G("error while trying to look up AD server address on SSSD for %q"), sss.domain) + defer decorate.OnError(&err, gotext.Get("error while trying to look up AD server address on SSSD for %q", sss.domain)) if sss.staticServerFQDN != "" { return sss.staticServerFQDN, nil @@ -134,7 +134,7 @@ func (sss SSS) DefaultDomainSuffix() string { func (sss SSS) IsOnline() (bool, error) { var online bool if err := sss.domainDbus.Call(consts.SSSDDbusInterface+".IsOnline", 0).Store(&online); err != nil { - return false, fmt.Errorf(i18n.G("failed to retrieve offline state from SSSD: %v"), err) + return false, errors.New(gotext.Get("failed to retrieve offline state from SSSD: %v", err)) } return online, nil } diff --git a/internal/ad/backends/winbind/winbind.go b/internal/ad/backends/winbind/winbind.go index db968ed14..b9fd371d4 100644 --- a/internal/ad/backends/winbind/winbind.go +++ b/internal/ad/backends/winbind/winbind.go @@ -61,8 +61,8 @@ import ( "strings" "unsafe" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/smbsafe" "github.com/ubuntu/decorate" ) @@ -93,7 +93,7 @@ type options struct { // New returns a winbind backend loaded from Config. func New(ctx context.Context, c Config, hostname string, opts ...Option) (w Winbind, err error) { - defer decorate.OnError(&err, i18n.G("can't get domain configuration from %+v"), c) + defer decorate.OnError(&err, gotext.Get("can't get domain configuration from %+v", c)) // defaults args := options{ @@ -144,8 +144,8 @@ func (w Winbind) HostKrb5CCName() (string, error) { smbsafe.WaitExec() defer smbsafe.DoneExec() if cmd, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).CombinedOutput(); err != nil { - return "", fmt.Errorf(i18n.G(`could not get krb5 cached ticket for %q: %w: -%s`), principal, err, string(cmd)) + return "", errors.New(gotext.Get(`could not get krb5 cached ticket for %q: %v: +%s`, principal, err, string(cmd))) } return target, nil @@ -160,7 +160,7 @@ func (w Winbind) DefaultDomainSuffix() string { // It returns first any static configuration. If nothing is found, it will fetch // the active server from winbind. func (w Winbind) ServerFQDN(ctx context.Context) (serverFQDN string, err error) { - defer decorate.OnError(&err, i18n.G("error while trying to look up AD server address on winbind")) + defer decorate.OnError(&err, gotext.Get("error while trying to look up AD server address on winbind")) if w.staticServerFQDN != "" { return strings.TrimPrefix(w.staticServerFQDN, "ldap://"), nil @@ -187,7 +187,7 @@ func (w Winbind) IsOnline() (bool, error) { defer C.free(unsafe.Pointer(cDomain)) online, err := C.is_online(cDomain) if err != nil { - err = fmt.Errorf(i18n.G("could not get online status for domain %q: status code %d"), w.domain, err) + err = errors.New(gotext.Get("could not get online status for domain %q: status code %d", w.domain, err)) } return bool(online), err } @@ -195,7 +195,7 @@ func (w Winbind) IsOnline() (bool, error) { func domainName() (string, error) { dc := C.get_domain_name() if dc == nil { - return "", errors.New(i18n.G("could not get domain name")) + return "", errors.New(gotext.Get("could not get domain name")) } defer C.free(unsafe.Pointer(dc)) return C.GoString(dc), nil @@ -206,7 +206,7 @@ func dcName(domain string) (string, error) { defer C.free(unsafe.Pointer(cDomain)) dc := C.get_dc_name(cDomain) if dc == nil { - return "", fmt.Errorf(i18n.G("could not get domain controller name for domain %q"), domain) + return "", errors.New(gotext.Get("could not get domain controller name for domain %q", domain)) } defer C.free(unsafe.Pointer(dc)) return C.GoString(dc), nil diff --git a/internal/ad/common/common.go b/internal/ad/common/common.go index 856268346..c45cae505 100644 --- a/internal/ad/common/common.go +++ b/internal/ad/common/common.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "github.com/ubuntu/adsys/internal/i18n" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/decorate" ) @@ -17,7 +17,7 @@ const KeyPrefix = "Software/Policies" // GetVersionID returns from root a the VERSION_ID field of os-release. func GetVersionID(root string) (versionID string, err error) { - defer decorate.OnError(&err, i18n.G("cannot get versionID")) + defer decorate.OnError(&err, gotext.Get("cannot get versionID")) releaseFile := filepath.Join(root, "etc/os-release") diff --git a/internal/ad/definitions.go b/internal/ad/definitions.go index 9ff53955f..e5d82063a 100644 --- a/internal/ad/definitions.go +++ b/internal/ad/definitions.go @@ -4,15 +4,15 @@ import ( "context" "fmt" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" policydefinitions "github.com/ubuntu/adsys/policies" "github.com/ubuntu/decorate" ) // GetPolicyDefinitions returns admx and adml content for the given type t of policies. func GetPolicyDefinitions(ctx context.Context, format, distroID string) (admx string, adml string, err error) { - decorate.OnError(&err, i18n.G("can't get policy definition file")) + decorate.OnError(&err, gotext.Get("can't get policy definition file")) log.Debugf(ctx, "GetPolicyDefinitions for %q (%q)", distroID, format) diff --git a/internal/ad/download.go b/internal/ad/download.go index e11856db4..c31d40d35 100644 --- a/internal/ad/download.go +++ b/internal/ad/download.go @@ -43,9 +43,9 @@ import ( "strings" "sync" + "github.com/leonelquinteros/gotext" "github.com/mvo5/libsmbclient-go" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/smbsafe" "github.com/ubuntu/decorate" "golang.org/x/sync/errgroup" @@ -62,7 +62,7 @@ This should not be called concurrently. It returns if the assets were refreshed or not. */ func (ad *AD) fetch(ctx context.Context, krb5Ticket string, downloadables map[string]string) (assetsWereRefreshed bool, err error) { - defer decorate.OnError(&err, i18n.G("can't download all gpos and assets")) + defer decorate.OnError(&err, gotext.Get("can't download all gpos and assets")) // protect env variable and map creation ad.fetchMu.Lock() @@ -104,7 +104,7 @@ func (ad *AD) fetch(ctx context.Context, krb5Ticket string, downloadables map[st g = ad.downloadables[name] } errg.Go(func() (err error) { - defer decorate.OnError(&err, i18n.G("can't download %q"), g.name) + defer decorate.OnError(&err, gotext.Get("can't download %q", g.name)) smbsafe.WaitSmb() defer smbsafe.DoneSmb() @@ -135,9 +135,9 @@ func (ad *AD) fetch(ctx context.Context, krb5Ticket string, downloadables map[st if !shouldDownload { if g.isAssets { - log.Infof(ctx, i18n.G("Assets directory is already up to date")) + log.Info(ctx, gotext.Get("Assets directory is already up to date")) } else { - log.Infof(ctx, i18n.G("GPO %q is already up to date"), g.name) + log.Info(ctx, gotext.Get("GPO %q is already up to date", g.name)) } return nil @@ -167,7 +167,7 @@ var errNoGPTINI = errors.New("no GPT.INI file") // needsDownload returns if the downloadable should be refreshed. // This is done by comparing GPT.INI Version= content. func needsDownload(ctx context.Context, client *libsmbclient.Client, g *downloadable, localPath string) (updateNeeded bool, err error) { - defer decorate.OnError(&err, i18n.G("can't check if %s needs refreshing"), g.name) + defer decorate.OnError(&err, gotext.Get("can't check if %s needs refreshing", g.name)) g.mu.RLock() defer g.mu.RUnlock() @@ -204,7 +204,7 @@ func needsDownload(ctx context.Context, client *libsmbclient.Client, g *download } func getGPOVersion(ctx context.Context, r io.Reader, downloadableName string) (version int, err error) { - defer decorate.OnError(&err, i18n.G("invalid remote GPT.INI")) + defer decorate.OnError(&err, gotext.Get("invalid remote GPT.INI")) buf, err := io.ReadAll(r) if err != nil { @@ -219,7 +219,7 @@ func getGPOVersion(ctx context.Context, r io.Reader, downloadableName string) (v // If the file exists but doesn't contain a Version key, we log a message and return 0 // This is the case for some Default Domain Policy GPOs if !cfg.Section("General").HasKey("Version") { - log.Infof(ctx, i18n.G("No version key found in GPT.INI for %s, assuming 0"), downloadableName) + log.Info(ctx, gotext.Get("No version key found in GPT.INI for %s, assuming 0", downloadableName)) return 0, nil } @@ -232,7 +232,7 @@ func getGPOVersion(ctx context.Context, r io.Reader, downloadableName string) (v // downloadDir will dl in a temporary directory and only commit it if fully downloaded without any errors. func downloadDir(ctx context.Context, client *libsmbclient.Client, url, dest string) (err error) { - defer decorate.OnError(&err, i18n.G("download %q failed"), url) + defer decorate.OnError(&err, gotext.Get("download %q failed", url)) smbsafe.WaitSmb() defer smbsafe.DoneSmb() @@ -245,7 +245,7 @@ func downloadDir(ctx context.Context, client *libsmbclient.Client, url, dest str // It is a directory: recursive download if err := d.Closedir(); err != nil { - return fmt.Errorf(i18n.G("could not close directory: %v"), err) + return errors.New(gotext.Get("could not close directory: %v", err)) } tmpdest, err := os.MkdirTemp(filepath.Dir(dest), fmt.Sprintf("%s.*", filepath.Base(dest))) @@ -255,7 +255,7 @@ func downloadDir(ctx context.Context, client *libsmbclient.Client, url, dest str // Always to try remove temporary directory, so that in case of any failures, it’s not left behind defer func() { if err := os.RemoveAll(tmpdest); err != nil { - log.Info(ctx, i18n.G("Could not clean up temporary directory:"), err) + log.Info(ctx, gotext.Get("Could not clean up temporary directory:"), err) } }() if err := downloadRecursive(ctx, client, url, tmpdest); err != nil { @@ -305,7 +305,7 @@ func downloadRecursive(ctx context.Context, client *libsmbclient.Client, url, de switch dirent.Type { case libsmbclient.SmbcFile: - log.Debugf(ctx, i18n.G("Downloading %s"), entityURL) + log.Debug(ctx, gotext.Get("Downloading %s", entityURL)) f, err := client.Open(entityURL, 0, 0) if err != nil { return err @@ -343,7 +343,7 @@ func findLocalGPTIni(path string) (string, error) { entries, err := os.ReadDir(path) if err != nil { - return "", fmt.Errorf(i18n.G("could not read directory %q: %w"), path, err) + return "", errors.New(gotext.Get("could not read directory %q: %v", path, err)) } for _, entry := range entries { @@ -352,5 +352,5 @@ func findLocalGPTIni(path string) (string, error) { } } - return "", fmt.Errorf(i18n.G("could not find GPT.INI in %q"), path) + return "", errors.New(gotext.Get("could not find GPT.INI in %q", path)) } diff --git a/internal/ad/registry/registry.go b/internal/ad/registry/registry.go index 8a957895a..89993a710 100644 --- a/internal/ad/registry/registry.go +++ b/internal/ad/registry/registry.go @@ -15,7 +15,7 @@ import ( "strings" "unicode/utf16" - "github.com/ubuntu/adsys/internal/i18n" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/decorate" ) @@ -52,7 +52,7 @@ type meta struct { // DecodePolicy parses a policy stream in registry file format and returns a slice of entries. func DecodePolicy(r io.Reader) (entries []entry.Entry, err error) { - defer decorate.OnError(&err, i18n.G("can't parse policy")) + defer decorate.OnError(&err, gotext.Get("can't parse policy")) ent, err := readPolicy(r) if err != nil { @@ -180,7 +180,7 @@ type policyFileHeader struct { } func readPolicy(r io.Reader) (entries []policyRawEntry, err error) { - defer decorate.OnError(&err, i18n.G("invalid policy")) + defer decorate.OnError(&err, gotext.Get("invalid policy")) validPolicyFileHeader := policyFileHeader{ Signature: 0x67655250, @@ -303,7 +303,7 @@ func decodeUtf16(b []byte) (string, error) { // getMetaValues returns meta values (including empty value) for options. func getMetaValues(data []byte, keypath string) (metaValues map[string]meta, err error) { - defer decorate.OnError(&err, i18n.G("can't decode meta value for %s: %v"), keypath, err) + defer decorate.OnError(&err, gotext.Get("can't decode meta value for %s: %v", keypath, err)) metaValues = make(map[string]meta) v, err := decodeUtf16(data) diff --git a/internal/adsysservice/adsysservice.go b/internal/adsysservice/adsysservice.go index 8c37725e4..557b9fa57 100644 --- a/internal/adsysservice/adsysservice.go +++ b/internal/adsysservice/adsysservice.go @@ -3,12 +3,14 @@ package adsysservice import ( "context" + "errors" "fmt" "os" "strings" "time" "github.com/godbus/dbus/v5" + "github.com/leonelquinteros/gotext" "github.com/sirupsen/logrus" "github.com/ubuntu/adsys" "github.com/ubuntu/adsys/internal/ad" @@ -22,7 +24,6 @@ import ( "github.com/ubuntu/adsys/internal/grpc/interceptorschain" "github.com/ubuntu/adsys/internal/grpc/logconnections" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies" "github.com/ubuntu/decorate" "google.golang.org/grpc" @@ -189,7 +190,7 @@ func WithWinbindConfig(c winbind.Config) func(o *options) error { // If url or domain is empty, we load the missing parameters from sssd.conf, taking first // domain in the list if not provided. func New(ctx context.Context, opts ...option) (s *Service, err error) { - defer decorate.OnError(&err, i18n.G("couldn't create adsys service")) + defer decorate.OnError(&err, gotext.Get("couldn't create adsys service")) // defaults args := options{} @@ -261,7 +262,7 @@ func New(ctx context.Context, opts ...option) (s *Service, err error) { adBackend, err = winbind.New(ctx, args.winbindConfig, hostname) } if err != nil { - return nil, fmt.Errorf(i18n.G("could not initialize AD backend: %v"), err) + return nil, errors.New(gotext.Get("could not initialize AD backend: %v", err)) } adc, err := ad.New(ctx, adBackend, hostname, adOptions...) @@ -354,7 +355,7 @@ func (s *Service) RegisterGRPCServer(d *daemon.Daemon) *grpc.Server { // Quit cleans every ressources than the service was using. func (s *Service) Quit(ctx context.Context) { if err := s.bus.Close(); err != nil { - log.Warningf(ctx, i18n.G("Can't disconnect system dbus: %v"), err) + log.Warning(ctx, gotext.Get("Can't disconnect system dbus: %v", err)) } } diff --git a/internal/adsysservice/client.go b/internal/adsysservice/client.go index 4200484d5..edce4db44 100644 --- a/internal/adsysservice/client.go +++ b/internal/adsysservice/client.go @@ -4,12 +4,12 @@ import ( "fmt" "time" + "github.com/leonelquinteros/gotext" "github.com/sirupsen/logrus" "github.com/ubuntu/adsys" "github.com/ubuntu/adsys/internal/grpc/contextidler" "github.com/ubuntu/adsys/internal/grpc/interceptorschain" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -23,7 +23,7 @@ type AdSysClient struct { // NewClient connect to the socket and returns a new AdSysClient. func NewClient(socket string, timeout time.Duration) (c *AdSysClient, err error) { - defer decorate.OnError(&err, i18n.G("can't create client for service")) + defer decorate.OnError(&err, gotext.Get("can't create client for service")) conn, err := grpc.Dial(fmt.Sprintf("unix:%s", socket), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStreamInterceptor(interceptorschain.StreamClient( diff --git a/internal/adsysservice/doc.go b/internal/adsysservice/doc.go index 1c60da2ec..d14a2c102 100644 --- a/internal/adsysservice/doc.go +++ b/internal/adsysservice/doc.go @@ -3,22 +3,23 @@ package adsysservice import ( "bufio" "embed" + "errors" "fmt" "path/filepath" "regexp" "strings" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys" "github.com/ubuntu/adsys/docs" "github.com/ubuntu/adsys/internal/authorizer" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" ) // GetDoc returns a chapter documentation from server. func (s *Service) GetDoc(r *adsys.GetDocRequest, stream adsys.Service_GetDocServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while getting documentation")) + defer decorate.OnError(&err, gotext.Get("error while getting documentation")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil { return err @@ -27,7 +28,7 @@ func (s *Service) GetDoc(r *adsys.GetDocRequest, stream adsys.Service_GetDocServ // Get all documentation metadata _, chaptersToFiles, filesToTitle, err := docStructure(docs.Dir, "index.md", "") if err != nil { - return fmt.Errorf(i18n.G("could not list documentation directory: %v"), err) + return errors.New(gotext.Get("could not list documentation directory: %v", err)) } // Find a match, removing trailing / for directory folder. @@ -40,7 +41,7 @@ func (s *Service) GetDoc(r *adsys.GetDocRequest, stream adsys.Service_GetDocServ out, err := renderDocumentationPage(p, filesToTitle) if err != nil { - return fmt.Errorf(i18n.G("could not read chapter %q: %v"), chapter, err) + return errors.New(gotext.Get("could not read chapter %q: %v", chapter, err)) } if err := stream.Send(&adsys.StringResponse{ @@ -53,7 +54,7 @@ func (s *Service) GetDoc(r *adsys.GetDocRequest, stream adsys.Service_GetDocServ // ListDoc returns a list of all documentation from server. func (s *Service) ListDoc(_ *adsys.Empty, stream adsys.Service_ListDocServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while listing documentation")) + defer decorate.OnError(&err, gotext.Get("error while listing documentation")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil { return err @@ -61,7 +62,7 @@ func (s *Service) ListDoc(_ *adsys.Empty, stream adsys.Service_ListDocServer) (e chapters, _, _, err := docStructure(docs.Dir, "index.md", "") if err != nil { - return fmt.Errorf(i18n.G("could not list documentation directory: %v"), err) + return errors.New(gotext.Get("could not list documentation directory: %v", err)) } if err := stream.Send(&adsys.ListDocReponse{ @@ -213,7 +214,7 @@ func renderDocumentationPage(p string, filesToTitle map[string]string) (string, f, err := docs.Dir.Open(p) if err != nil { - return "", fmt.Errorf(i18n.G("no file %q found in documentation"), p) + return "", errors.New(gotext.Get("no file %q found in documentation", p)) } defer f.Close() diff --git a/internal/adsysservice/policy.go b/internal/adsysservice/policy.go index f30e0ca46..6a5190b7c 100644 --- a/internal/adsysservice/policy.go +++ b/internal/adsysservice/policy.go @@ -4,12 +4,12 @@ import ( "context" "fmt" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys" "github.com/ubuntu/adsys/internal/ad" "github.com/ubuntu/adsys/internal/adsysservice/actions" "github.com/ubuntu/adsys/internal/authorizer" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies" "github.com/ubuntu/adsys/internal/policies/certificate" "github.com/ubuntu/decorate" @@ -19,7 +19,7 @@ import ( // UpdatePolicy refreshes or creates a policy for current user or user given as argument. // It can purge the policy instead of updating it if requested. func (s *Service) UpdatePolicy(r *adsys.UpdatePolicyRequest, stream adsys.Service_UpdatePolicyServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while updating policy")) + defer decorate.OnError(&err, gotext.Get("error while updating policy")) objectClass := ad.UserObject if r.GetIsComputer() || r.GetAll() { @@ -84,7 +84,7 @@ func (s *Service) updatePolicyFor(ctx context.Context, isComputer bool, target s // DumpPolicies displays all applied policies for a given user. func (s *Service) DumpPolicies(r *adsys.DumpPoliciesRequest, stream adsys.Service_DumpPoliciesServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while displaying applied policies")) + defer decorate.OnError(&err, gotext.Get("error while displaying applied policies")) objectClass := ad.UserObject if r.GetIsComputer() { @@ -119,7 +119,7 @@ func (s *Service) DumpPolicies(r *adsys.DumpPoliciesRequest, stream adsys.Servic // DumpPoliciesDefinitions dumps requested policy definitions stored in daemon at build time. func (s *Service) DumpPoliciesDefinitions(r *adsys.DumpPolicyDefinitionsRequest, stream adsys.Service_DumpPoliciesDefinitionsServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while dumping policy definitions")) + defer decorate.OnError(&err, gotext.Get("error while dumping policy definitions")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil { return err @@ -142,7 +142,7 @@ func (s *Service) DumpPoliciesDefinitions(r *adsys.DumpPolicyDefinitionsRequest, // GPOListScript returns the embedded GPO python list script. func (s *Service) GPOListScript(_ *adsys.Empty, stream adsys.Service_GPOListScriptServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while getting gpo list script")) + defer decorate.OnError(&err, gotext.Get("error while getting gpo list script")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil { return err @@ -159,7 +159,7 @@ func (s *Service) GPOListScript(_ *adsys.Empty, stream adsys.Service_GPOListScri // CertAutoEnrollScript returns the embedded certificate autoenrollment python script. func (s *Service) CertAutoEnrollScript(_ *adsys.Empty, stream adsys.Service_CertAutoEnrollScriptServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while getting certificate autoenrollment script")) + defer decorate.OnError(&err, gotext.Get("error while getting certificate autoenrollment script")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil { return err diff --git a/internal/adsysservice/service.go b/internal/adsysservice/service.go index cebb5e1d0..3e13d32d3 100644 --- a/internal/adsysservice/service.go +++ b/internal/adsysservice/service.go @@ -8,12 +8,12 @@ import ( "time" "github.com/godbus/dbus/v5" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys" "github.com/ubuntu/adsys/internal/adsysservice/actions" "github.com/ubuntu/adsys/internal/authorizer" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies" "github.com/ubuntu/adsys/internal/stdforward" "github.com/ubuntu/decorate" @@ -24,7 +24,7 @@ import ( // Anything logged by the server on stdout, stderr or via the standard logger. // Only one call at a time can be performed here. func (s *Service) Cat(_ *adsys.Empty, stream adsys.Service_CatServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while trying to display daemon output")) + defer decorate.OnError(&err, gotext.Get("error while trying to display daemon output")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), actions.ActionServiceManage); err != nil { return err @@ -60,7 +60,7 @@ func (ss streamWriter) Write(b []byte) (n int, err error) { // Status returns internal daemon status to the client. func (s *Service) Status(_ *adsys.Empty, stream adsys.Service_StatusServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while getting daemon status")) + defer decorate.OnError(&err, gotext.Get("error while getting daemon status")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil { return err @@ -82,8 +82,8 @@ func (s *Service) Status(_ *adsys.Empty, stream adsys.Service_StatusServer) (err state.apparmorDir = consts.DefaultApparmorDir } - timeout := i18n.G("unknown") - socket := i18n.G("unknown") + timeout := gotext.Get("unknown") + socket := gotext.Get("unknown") if s.daemon != nil { timeout = s.daemon.Timeout().String() sock := s.daemon.GetSocketAddr() @@ -96,47 +96,48 @@ func (s *Service) Status(_ *adsys.Empty, stream adsys.Service_StatusServer) (err timeLayout := "Mon Jan 2 15:04" - nextRefresh := i18n.G("unknown") + nextRefresh := gotext.Get("unknown") if next, err := s.nextRefreshTime(); err == nil { nextRefresh = next.Format(timeLayout) } else { log.Warning(stream.Context(), err) } - updateFmt := i18n.G("%s, updated on %s") - updateMachine := i18n.G("Machine, no gpo applied found") + // FIXME: gotext.Get needs to have the arguments parsed. + updateFmt := "%s" + gotext.Get(", updated on ") + "%s" + updateMachine := gotext.Get("Machine, no gpo applied found") t, err := s.policyManager.LastUpdateFor(stream.Context(), "", true) if err == nil { - updateMachine = fmt.Sprintf(updateFmt, i18n.G("Machine"), t.Format(timeLayout)) + updateMachine = fmt.Sprintf(updateFmt, gotext.Get("Machine"), t.Format(timeLayout)) } - updateUsers := fmt.Sprint(i18n.G("Can't get connected users")) + updateUsers := fmt.Sprint(gotext.Get("Can't get connected users")) users, err := s.adc.ListUsers(stream.Context(), true) if err == nil { - updateUsers = fmt.Sprint(i18n.G("Connected users:")) + updateUsers = fmt.Sprint(gotext.Get("Connected users:")) for _, u := range users { if t, err := s.policyManager.LastUpdateFor(stream.Context(), u, false); err == nil { updateUsers = updateUsers + "\n " + fmt.Sprintf(updateFmt, u, t.Format(timeLayout)) } else { - updateUsers = updateUsers + "\n " + fmt.Sprintf(i18n.G("%s, no gpo applied found"), u) + updateUsers = updateUsers + "\n " + gotext.Get("%s, no gpo applied found", u) } } if len(users) == 0 { - updateUsers = updateUsers + "\n " + i18n.G("None") + updateUsers = updateUsers + "\n " + gotext.Get("None") } } - ubuntuProStatus := i18n.G("Ubuntu Pro subscription is not active on this machine. Rules belonging to the following policy types will not be applied:\n") + ubuntuProStatus := gotext.Get("Ubuntu Pro subscription is not active on this machine. Rules belonging to the following policy types will not be applied:\n") proOnlyRules := slices.Clone(policies.ProOnlyRules) slices.Sort(proOnlyRules) ubuntuProStatus = ubuntuProStatus + " - " + strings.Join(proOnlyRules, "\n - ") subscriptionEnabled := s.policyManager.GetSubscriptionState(stream.Context()) if subscriptionEnabled { - ubuntuProStatus = i18n.G("Ubuntu Pro subscription active.") + ubuntuProStatus = gotext.Get("Ubuntu Pro subscription active.") } - status := fmt.Sprintf(i18n.G(`%s + status := gotext.Get(`%s %s Next Refresh: %s @@ -153,7 +154,7 @@ Daemon: Dconf path: %s Sudoers path: %s PolicyKit path: %s - Apparmor path: %s`), updateMachine, updateUsers, nextRefresh, + Apparmor path: %s`, updateMachine, updateUsers, nextRefresh, ubuntuProStatus, strings.Join(strings.Split(adInfo, "\n"), "\n "), timeout, socket, state.cacheDir, state.runDir, state.dconfDir, @@ -171,7 +172,7 @@ Daemon: // Stop requests to stop the service once all connections are done. Force will shut it down immediately and drop // existing connections. func (s *Service) Stop(r *adsys.StopRequest, stream adsys.Service_StopServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while trying to stop daemon")) + defer decorate.OnError(&err, gotext.Get("error while trying to stop daemon")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), actions.ActionServiceManage); err != nil { return err @@ -183,7 +184,7 @@ func (s *Service) Stop(r *adsys.StopRequest, stream adsys.Service_StopServer) (e // ListUsers returns the list of currently active users. func (s *Service) ListUsers(r *adsys.ListUsersRequest, stream adsys.Service_ListUsersServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while trying to get the list of active users")) + defer decorate.OnError(&err, gotext.Get("error while trying to get the list of active users")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil { return err @@ -204,10 +205,10 @@ func (s *Service) ListUsers(r *adsys.ListUsersRequest, stream adsys.Service_List // nextRefreshTime returns next adsys schedule refresh call. func (s Service) nextRefreshTime() (next *time.Time, err error) { - defer decorate.OnError(&err, i18n.G("error while trying to determine next refresh time")) + defer decorate.OnError(&err, gotext.Get("error while trying to determine next refresh time")) if s.initSystemTime == nil { - return nil, errors.New(i18n.G("no boot system time found")) + return nil, errors.New(gotext.Get("no boot system time found")) } const unit = "adsys-gpo-refresh.timer" @@ -218,11 +219,11 @@ func (s Service) nextRefreshTime() (next *time.Time, err error) { strings.ReplaceAll(strings.ReplaceAll(unit, ".", "_2e"), "-", "_2d")))) val, err := timerUnit.GetProperty(fmt.Sprintf("%s.NextElapseUSecMonotonic", consts.SystemdDbusTimerInterface)) if err != nil { - return nil, fmt.Errorf(i18n.G("could not find %s unit on systemd bus: no GPO refresh scheduled? %v"), unit, err) + return nil, errors.New(gotext.Get("could not find %s unit on systemd bus: no GPO refresh scheduled? %v", unit, err)) } nextRaw, ok := val.Value().(uint64) if !ok { - return nil, fmt.Errorf(i18n.G("invalid next GPO refresh value: %v"), val.Value(), err) + return nil, errors.New(gotext.Get("invalid next GPO refresh value for %v: %v", val.Value(), err)) } nextRefresh := s.initSystemTime.Add(time.Duration(nextRaw) * time.Microsecond / time.Nanosecond) diff --git a/internal/adsysservice/version.go b/internal/adsysservice/version.go index 6e42256e3..aa6f85427 100644 --- a/internal/adsysservice/version.go +++ b/internal/adsysservice/version.go @@ -1,17 +1,17 @@ package adsysservice import ( + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys" "github.com/ubuntu/adsys/internal/authorizer" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" ) // Version returns version from server. func (s *Service) Version(_ *adsys.Empty, stream adsys.Service_VersionServer) (err error) { - defer decorate.OnError(&err, i18n.G("error while getting daemon version")) + defer decorate.OnError(&err, gotext.Get("error while getting daemon version")) if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil { return err diff --git a/internal/authorizer/authorizer.go b/internal/authorizer/authorizer.go index 615e40a04..441c5b419 100644 --- a/internal/authorizer/authorizer.go +++ b/internal/authorizer/authorizer.go @@ -15,8 +15,8 @@ import ( "strings" "github.com/godbus/dbus/v5" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" "google.golang.org/grpc/peer" ) @@ -53,7 +53,7 @@ func withRoot(root string) func(*Authorizer) { // New returns a new authorizer. func New(bus *dbus.Conn, options ...func(*Authorizer)) (auth *Authorizer, err error) { - defer decorate.OnError(&err, i18n.G("can't create new authorizer")) + defer decorate.OnError(&err, gotext.Get("can't create new authorizer")) authority := bus.Object("org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority") @@ -108,17 +108,17 @@ type authResult struct { // IsAllowedFromContext returns nil if the user is allowed to perform an operation. // The pid and uid are extracted from peerCredsInfo grpc context. func (a Authorizer) IsAllowedFromContext(ctx context.Context, action Action) (err error) { - log.Debug(ctx, i18n.G("Check if grpc request peer is authorized")) + log.Debug(ctx, gotext.Get("Check if grpc request peer is authorized")) - defer decorate.OnError(&err, i18n.G("permission denied")) + defer decorate.OnError(&err, gotext.Get("permission denied")) p, ok := peer.FromContext(ctx) if !ok { - return errors.New(i18n.G("context request doesn't have grpc peer creds informations.")) + return errors.New(gotext.Get("context request doesn't have grpc peer creds informations.")) } pci, ok := p.AuthInfo.(peerCredsInfo) if !ok { - return errors.New(i18n.G("context request grpc peer creeds information is not a peerCredsInfo.")) + return errors.New(gotext.Get("context request grpc peer creeds information is not a peerCredsInfo.")) } // Is it an action needing user checking? @@ -126,15 +126,15 @@ func (a Authorizer) IsAllowedFromContext(ctx context.Context, action Action) (er if action.SelfID != "" { userName, ok := ctx.Value(OnUserKey).(string) if !ok { - return errors.New(i18n.G("request to act on user action should have a user name attached")) + return errors.New(gotext.Get("request to act on user action should have a user name attached")) } user, err := a.userLookup(userName) if err != nil { - return fmt.Errorf(i18n.G("couldn't retrieve user for %q: %v"), userName, err) + return errors.New(gotext.Get("couldn't retrieve user for %q: %v", userName, err)) } uid, err := strconv.Atoi(user.Uid) if err != nil { - return fmt.Errorf(i18n.G("couldn't convert %q to a valid uid for %q"), user.Uid, userName) + return errors.New(gotext.Get("couldn't convert %q to a valid uid for %q", user.Uid, userName)) } actionUID = uint32(uid) } @@ -147,10 +147,10 @@ func (a Authorizer) IsAllowedFromContext(ctx context.Context, action Action) (er // (self or others). func (a Authorizer) isAllowed(ctx context.Context, action Action, pid int32, uid uint32, actionUID uint32) error { if uid == 0 { - log.Debug(ctx, i18n.G("Authorized as being administrator")) + log.Debug(ctx, gotext.Get("Authorized as being administrator")) return nil } else if action == ActionAlwaysAllowed { - log.Debug(ctx, i18n.G("Any user always authorized")) + log.Debug(ctx, gotext.Get("Any user always authorized")) return nil } else if action.SelfID != "" { action.ID = action.OtherID @@ -161,7 +161,7 @@ func (a Authorizer) isAllowed(ctx context.Context, action Action, pid int32, uid f, err := os.Open(filepath.Join(a.root, fmt.Sprintf("proc/%d/stat", pid))) if err != nil { - return fmt.Errorf(i18n.G("couldn't open stat file for process: %v"), err) + return errors.New(gotext.Get("couldn't open stat file for process: %v", err)) } defer decorate.LogFuncOnErrorContext(ctx, f.Close) @@ -185,13 +185,13 @@ func (a Authorizer) isAllowed(ctx context.Context, action Action, pid int32, uid "org.freedesktop.PolicyKit1.Authority.CheckAuthorization", dbus.FlagAllowInteractiveAuthorization, subject, action.ID, details, checkAllowInteraction, "").Store(&result) if err != nil { - return fmt.Errorf(i18n.G("call to polkit failed: %v"), err) + return errors.New(gotext.Get("call to polkit failed: %v", err)) } - log.Debugf(ctx, i18n.G("Polkit call result, authorized: %t"), result.IsAuthorized) + log.Debug(ctx, gotext.Get("Polkit call result, authorized: %t", result.IsAuthorized)) if !result.IsAuthorized { - return errors.New(i18n.G("polkit denied access")) + return errors.New(gotext.Get("polkit denied access")) } return nil } @@ -202,7 +202,7 @@ func (a Authorizer) isAllowed(ctx context.Context, action Action, pid int32, uid // // https://cgit.freedesktop.org/polkit/tree/src/polkit/polkitunixprocess.c func getStartTimeFromReader(r io.Reader) (t uint64, err error) { - defer decorate.OnError(&err, i18n.G("can't determine start time of client process")) + defer decorate.OnError(&err, gotext.Get("can't determine start time of client process")) data, err := io.ReadAll(r) if err != nil { @@ -220,19 +220,19 @@ func getStartTimeFromReader(r io.Reader) (t uint64, err error) { // starttime field. idx := strings.IndexByte(contents, ')') if idx < 0 { - return 0, errors.New(i18n.G("parsing error: missing )")) + return 0, errors.New(gotext.Get("parsing error: missing )")) } idx += 2 // skip ") " if idx > len(contents) { - return 0, errors.New(i18n.G("parsing error: ) at the end")) + return 0, errors.New(gotext.Get("parsing error: ) at the end")) } tokens := strings.Split(contents[idx:], " ") if len(tokens) < 20 { - return 0, errors.New(i18n.G("parsing error: less fields than required")) + return 0, errors.New(gotext.Get("parsing error: less fields than required")) } v, err := strconv.ParseUint(tokens[19], 10, 64) if err != nil { - return 0, fmt.Errorf(i18n.G("parsing error: %v"), err) + return 0, errors.New(gotext.Get("parsing error: %v", err)) } return v, nil } diff --git a/internal/authorizer/servercreds.go b/internal/authorizer/servercreds.go index 67edc3b1b..c5e8ca89c 100644 --- a/internal/authorizer/servercreds.go +++ b/internal/authorizer/servercreds.go @@ -2,10 +2,11 @@ package authorizer import ( "context" + "errors" "fmt" "net" - "github.com/ubuntu/adsys/internal/i18n" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/decorate" "golang.org/x/sys/unix" "google.golang.org/grpc" @@ -21,20 +22,20 @@ func WithUnixPeerCreds() grpc.ServerOption { type serverPeerCreds struct{} func (serverPeerCreds) ServerHandshake(conn net.Conn) (n net.Conn, c credentials.AuthInfo, err error) { - defer decorate.OnError(&err, i18n.G("server handshake failed")) + defer decorate.OnError(&err, gotext.Get("server handshake failed")) var cred *unix.Ucred // net.Conn is an interface. Expect only *net.UnixConn types uc, ok := conn.(*net.UnixConn) if !ok { - return conn, nil, fmt.Errorf(i18n.G("unexpected socket type")) + return conn, nil, fmt.Errorf(gotext.Get("unexpected socket type")) } // Fetches raw network connection from UnixConn raw, err := uc.SyscallConn() if err != nil { - return conn, nil, fmt.Errorf(i18n.G("error opening raw connection: %s"), err) + return conn, nil, errors.New(gotext.Get("error opening raw connection: %s", err)) } // The raw.Control() callback does not return an error directly. @@ -47,10 +48,10 @@ func (serverPeerCreds) ServerHandshake(conn net.Conn) (n net.Conn, c credentials unix.SO_PEERCRED) }) if err != nil { - return conn, nil, fmt.Errorf(i18n.G("GetsockoptUcred() error: %s"), err) + return conn, nil, errors.New(gotext.Get("GetsockoptUcred() error: %s", err)) } if err2 != nil { - return conn, nil, fmt.Errorf(i18n.G("Control() error: %s"), err2) + return conn, nil, errors.New(gotext.Get("Control() error: %s", err2)) } return conn, peerCredsInfo{uid: cred.Uid, pid: cred.Pid}, nil diff --git a/internal/cmdhandler/cmdhandler.go b/internal/cmdhandler/cmdhandler.go index cd94afa9f..1cf3044a2 100644 --- a/internal/cmdhandler/cmdhandler.go +++ b/internal/cmdhandler/cmdhandler.go @@ -5,9 +5,9 @@ import ( "fmt" "strings" + "github.com/leonelquinteros/gotext" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" ) @@ -35,7 +35,7 @@ func NoValidArgs(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellC // README and manpage refers to them in each subsection (parents are differents, but only one is kept if we use the same object). func RegisterAlias(cmd, parent *cobra.Command) { alias := *cmd - t := fmt.Sprintf(i18n.G("Alias of %q"), cmd.CommandPath()) + t := gotext.Get("Alias of %q", cmd.CommandPath()) if alias.Long != "" { t = fmt.Sprintf("%s (%s)", alias.Long, t) } @@ -45,14 +45,14 @@ func RegisterAlias(cmd, parent *cobra.Command) { // InstallVerboseFlag adds the -v and -vv options and returns the reference to it. func InstallVerboseFlag(cmd *cobra.Command, viper *viper.Viper) *int { - r := cmd.PersistentFlags().CountP("verbose", "v", i18n.G("issue INFO (-v), DEBUG (-vv) or DEBUG with caller (-vvv) output")) + r := cmd.PersistentFlags().CountP("verbose", "v", gotext.Get("issue INFO (-v), DEBUG (-vv) or DEBUG with caller (-vvv) output")) decorate.LogOnError(viper.BindPFlag("verbose", cmd.PersistentFlags().Lookup("verbose"))) return r } // InstallSocketFlag adds the -s and --sockets options and returns the reference to it. func InstallSocketFlag(cmd *cobra.Command, viper *viper.Viper, defaultPath string) *string { - s := cmd.PersistentFlags().StringP("socket", "s", defaultPath, i18n.G("socket path to use between daemon and client. Can be overridden by systemd socket activation.")) + s := cmd.PersistentFlags().StringP("socket", "s", defaultPath, gotext.Get("socket path to use between daemon and client. Can be overridden by systemd socket activation.")) decorate.LogOnError(viper.BindPFlag("socket", cmd.PersistentFlags().Lookup("socket"))) return s } @@ -63,7 +63,7 @@ func InstallConfigFlag(cmd *cobra.Command, persistent bool) *string { if persistent { target = cmd.PersistentFlags() } - return target.StringP("config", "c", "", i18n.G("use a specific configuration file")) + return target.StringP("config", "c", "", gotext.Get("use a specific configuration file")) } // CalledCmd returns the actual command called by the user inferred from the arguments. diff --git a/internal/cmdhandler/suggest.go b/internal/cmdhandler/suggest.go index 220c93f63..2f2474dca 100644 --- a/internal/cmdhandler/suggest.go +++ b/internal/cmdhandler/suggest.go @@ -4,14 +4,15 @@ import ( "fmt" "strings" + "github.com/leonelquinteros/gotext" "github.com/spf13/cobra" - "github.com/ubuntu/adsys/internal/i18n" ) // SubcommandsRequiredWithSuggestions will ensure we have a subcommand provided by the user and augments it with // suggestion for commands, alias and help on root command. func SubcommandsRequiredWithSuggestions(cmd *cobra.Command, args []string) error { - requireMsg := i18n.G("%s requires a valid subcommand") + // FIXME: bypass Get not supported only string without the command itself unfortunately + requireMsg := "%s " + gotext.Get("requires a valid subcommand") // This will be triggered if cobra didn't find any subcommands. // Find some suggestions. var suggestions []string @@ -48,14 +49,14 @@ func SubcommandsRequiredWithSuggestions(cmd *cobra.Command, args []string) error var suggestionsMsg string if len(suggestions) > 0 { - suggestionsMsg += i18n.G("Did you mean this?\n") + suggestionsMsg += gotext.Get("Did you mean this?\n") for _, s := range suggestions { - suggestionsMsg += fmt.Sprintf(i18n.G("\t%v\n"), s) + suggestionsMsg += gotext.Get("\t%v\n", s) } } if suggestionsMsg != "" { - requireMsg = fmt.Sprintf(i18n.G("%s. %s"), requireMsg, suggestionsMsg) + requireMsg = gotext.Get("%s. %s", requireMsg, suggestionsMsg) } return fmt.Errorf(requireMsg, cmd.Name()) diff --git a/internal/config/config.go b/internal/config/config.go index 0eeed0c6e..5f7f80891 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,12 +9,12 @@ import ( "path/filepath" "github.com/fsnotify/fsnotify" + "github.com/leonelquinteros/gotext" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" ) @@ -40,7 +40,7 @@ func SetVerboseMode(level int) { // to let you deserialize the initial configuration and returns any errors. // Then, it automatically watches any configuration changes and will call configChanged with refresh set to true. func Init(name string, cmd cobra.Command, vip *viper.Viper, configChanged func(refreshed bool) error) (err error) { - defer decorate.OnError(&err, i18n.G("can't load configuration")) + defer decorate.OnError(&err, gotext.Get("can't load configuration")) // Force a visit of the local flags so persistent flags for all parents are merged. cmd.LocalFlags() @@ -62,7 +62,7 @@ func Init(name string, cmd cobra.Command, vip *viper.Viper, configChanged func(r vip.AddConfigPath("/etc/") // Add the executable path to the config search path. if binPath, err := os.Executable(); err != nil { - log.Warningf(context.Background(), i18n.G("Failed to get current executable path, not adding it as a config dir: %v"), err) + log.Warning(context.Background(), gotext.Get("Failed to get current executable path, not adding it as a config dir: %v", err)) } else { vip.AddConfigPath(filepath.Dir(binPath)) } diff --git a/internal/config/watchd/watchd.go b/internal/config/watchd/watchd.go index 2c019dfcc..5b265227c 100644 --- a/internal/config/watchd/watchd.go +++ b/internal/config/watchd/watchd.go @@ -9,8 +9,8 @@ import ( "path/filepath" "strings" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" "gopkg.in/yaml.v3" ) @@ -31,12 +31,12 @@ func DirsFromConfigFile(ctx context.Context, configFile string) []string { var dirs []string config, err := os.ReadFile(configFile) if err != nil { - log.Debugf(ctx, i18n.G("Could not read config file: %v"), err) + log.Debug(ctx, gotext.Get("Could not read config file: %v", err)) return dirs } cfg := AppConfig{} if err := yaml.Unmarshal(config, &cfg); err != nil { - log.Debugf(ctx, i18n.G("Could not unmarshal config YAML: %v"), err) + log.Debug(ctx, gotext.Get("Could not unmarshal config YAML: %v", err)) return dirs } dirs = cfg.Dirs @@ -48,28 +48,28 @@ func DirsFromConfigFile(ctx context.Context, configFile string) []string { // directories that are passed in actually exist. It receives a config file and // a slice of absolute sorted paths. func WriteConfig(confFile string, dirs []string) (err error) { - defer decorate.OnError(&err, i18n.G("can't write config")) + defer decorate.OnError(&err, gotext.Get("can't write config")) if len(dirs) == 0 { - return fmt.Errorf(i18n.G("needs at least one directory to watch")) + return errors.New(gotext.Get("needs at least one directory to watch")) } // Make sure all directories exist for _, dir := range dirs { if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { - return fmt.Errorf(i18n.G("directory %q does not exist"), dir) + return errors.New(gotext.Get("directory %q does not exist", dir)) } } // Make sure the directory structure exists for the config file if err := os.MkdirAll(filepath.Dir(confFile), 0750); err != nil { - return fmt.Errorf(i18n.G("unable to create config directory: %v"), err) + return errors.New(gotext.Get("unable to create config directory: %v", err)) } cfg := AppConfig{Dirs: dirs, Verbose: 0} data, err := yaml.Marshal(&cfg) if err != nil { - return fmt.Errorf(i18n.G("unable to marshal: %v"), err) + return errors.New(gotext.Get("unable to marshal: %v", err)) } if err := os.WriteFile(confFile, data, 0600); err != nil { @@ -86,7 +86,7 @@ func WriteConfig(confFile string, dirs []string) (err error) { // only covers the cases used by the service installer, which should be good // enough for us. func ConfigFileFromArgs(args string) (string, error) { - err := fmt.Errorf(i18n.G("missing config file in CLI arguments")) + err := fmt.Errorf(gotext.Get("missing config file in CLI arguments")) _, configFile, found := strings.Cut(args, "-c") if !found { @@ -107,7 +107,7 @@ func ConfigFileFromArgs(args string) (string, error) { func DefaultConfigPath() string { binPath, err := os.Executable() if err != nil { - log.Warningf(context.Background(), i18n.G("failed to get executable path, using relative path for default config: %v"), err) + log.Warning(context.Background(), gotext.Get("failed to get executable path, using relative path for default config: %v", err)) } return filepath.Join(filepath.Dir(binPath), fmt.Sprintf("%s.yaml", CmdName)) } diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index eaa6ce7cd..144a5a822 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -4,6 +4,7 @@ package daemon import ( "context" + "errors" "fmt" "net" "os" @@ -12,8 +13,8 @@ import ( "github.com/coreos/go-systemd/v22/activation" "github.com/coreos/go-systemd/v22/daemon" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" "google.golang.org/grpc" ) @@ -69,7 +70,7 @@ func WithServerQuit(f func(context.Context)) func(o *options) error { // New returns an new, initialized daemon server, which handles systemd activation. // If systemd activation is used, it will override any socket passed here. func New(registerGRPCServer GRPCServerRegisterer, socket string, opts ...option) (d *Daemon, err error) { - defer decorate.OnError(&err, i18n.G("can't create daemon")) + defer decorate.OnError(&err, gotext.Get("can't create daemon")) // defaults args := options{ @@ -110,7 +111,7 @@ func New(registerGRPCServer GRPCServerRegisterer, socket string, opts ...option) d.useSocketActivation = true d.lis <- listeners[0] default: - return nil, fmt.Errorf(i18n.G("unexpected number of systemd socket activation (%d != 1)"), len(listeners)) + return nil, errors.New(gotext.Get("unexpected number of systemd socket activation (%d != 1)", len(listeners))) } d.grpcserver = d.registerGRPCServer(d) @@ -128,7 +129,7 @@ func (d *Daemon) UseSocket(socket string) (err error) { return nil } - defer decorate.OnError(&err, i18n.G("can't listen on new socket %q"), socket) + defer decorate.OnError(&err, gotext.Get("can't listen on new socket %q", socket)) lis, err := net.Listen("unix", socket) if err != nil { @@ -155,12 +156,12 @@ func (d *Daemon) UseSocket(socket string) (err error) { // When the server stop listening, the socket is removed automatically. // Configuration can be reloaded and we will then listen on the new socket. func (d *Daemon) Listen() (err error) { - defer decorate.OnError(&err, i18n.G("can't serve")) + defer decorate.OnError(&err, gotext.Get("can't serve")) if sent, err := d.systemdSdNotifier(false, "READY=1"); err != nil { - return fmt.Errorf(i18n.G("couldn't send ready notification to systemd: %v"), err) + return errors.New(gotext.Get("couldn't send ready notification to systemd: %v", err)) } else if sent { - log.Debug(context.Background(), i18n.G("Ready state sent to systemd")) + log.Debug(context.Background(), gotext.Get("Ready state sent to systemd")) } lis := <-d.lis @@ -170,7 +171,7 @@ func (d *Daemon) Listen() (err error) { // handle socket configuration reloading for { - log.Infof(context.Background(), i18n.G("Serving on %s"), lis.Addr().String()) + log.Info(context.Background(), gotext.Get("Serving on %s", lis.Addr().String())) if err := (d.grpcserver.Serve(lis)); err != nil { return fmt.Errorf("unable to start GRPC server: %w", err) } @@ -188,7 +189,7 @@ func (d *Daemon) Listen() (err error) { d.socketMu.Unlock() d.grpcserver = d.registerGRPCServer(d) } - log.Debug(context.Background(), i18n.G("Quitting")) + log.Debug(context.Background(), gotext.Get("Quitting")) d.serverQuit(context.Background()) return nil @@ -210,15 +211,15 @@ func (d *Daemon) Quit(force bool) { // stop gracefully stops the grpc server unless force is true. func (d *Daemon) stop(force bool) { - log.Info(context.Background(), i18n.G("Stopping daemon requested.")) + log.Info(context.Background(), gotext.Get("Stopping daemon requested.")) if force { d.grpcserver.Stop() return } - log.Info(context.Background(), i18n.G("Wait for active requests to close.")) + log.Info(context.Background(), gotext.Get("Wait for active requests to close.")) d.grpcserver.GracefulStop() - log.Debug(context.Background(), i18n.G("All connections have now ended.")) + log.Debug(context.Background(), gotext.Get("All connections have now ended.")) } // GetSocketAddr returns currently used socket address by daemon. diff --git a/internal/grpc/grpcerror/grpcerror.go b/internal/grpc/grpcerror/grpcerror.go index 3a8c0cbc1..eb7e3af2c 100644 --- a/internal/grpc/grpcerror/grpcerror.go +++ b/internal/grpc/grpcerror/grpcerror.go @@ -3,9 +3,8 @@ package grpcerror import ( "errors" - "fmt" - "github.com/ubuntu/adsys/internal/i18n" + "github.com/leonelquinteros/gotext" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -26,16 +25,16 @@ func Format(err error, daemonName string) error { switch st.Code() { // no daemon case codes.Unavailable: - err = fmt.Errorf(i18n.G("Couldn't connect to %s daemon: %v"), daemonName, st.Message()) + err = errors.New(gotext.Get("Couldn't connect to %s daemon: %v", daemonName, st.Message())) // timeout case codes.DeadlineExceeded: - err = errors.New(i18n.G("Service took too long to respond. Disconnecting client.")) + err = errors.New(gotext.Get("Service took too long to respond. Disconnecting client.")) // regular error without annotation case codes.Unknown: - err = fmt.Errorf(i18n.G("Error from server: %v"), st.Message()) + err = errors.New(gotext.Get("Error from server: %v", st.Message())) // grpc error, just format it default: - err = fmt.Errorf(i18n.G("Error %s from server: %v"), st.Code(), st.Message()) + err = errors.New(gotext.Get("Error %s from server: %v", st.Code(), st.Message())) } return err } diff --git a/internal/grpc/logstreamer/log.go b/internal/grpc/logstreamer/log.go index 95e868fbc..8df30245a 100644 --- a/internal/grpc/logstreamer/log.go +++ b/internal/grpc/logstreamer/log.go @@ -9,12 +9,13 @@ package log import ( "context" + "errors" "fmt" "strings" "sync" + "github.com/leonelquinteros/gotext" "github.com/sirupsen/logrus" - "github.com/ubuntu/adsys/internal/i18n" ) const ( @@ -173,7 +174,7 @@ func log(ctx context.Context, level logrus.Level, args ...interface{}) { } if err := logLocallyMaybeRemote(level, caller, msg, localLogger, idRequest, sendStream); err != nil { - localLogger.Warningf(localLogFormatWithID, idRequest, i18n.G("couldn't send logs to client")) + localLogger.Warningf(localLogFormatWithID, idRequest, gotext.Get("couldn't send logs to client")) } } @@ -181,7 +182,7 @@ func logLocallyMaybeRemote(level logrus.Level, caller, msg string, localLogger * // decorate depends on logstreamer: we can’t use it here defer func() { if err != nil { - err = fmt.Errorf(i18n.G("can't send logs to client: %v"), err) + err = errors.New(gotext.Get("can't send logs to client: %v", err)) } }() diff --git a/internal/grpc/logstreamer/server.go b/internal/grpc/logstreamer/server.go index 340537c00..d4447324e 100644 --- a/internal/grpc/logstreamer/server.go +++ b/internal/grpc/logstreamer/server.go @@ -8,8 +8,8 @@ import ( "math/big" "strconv" + "github.com/leonelquinteros/gotext" "github.com/sirupsen/logrus" - "github.com/ubuntu/adsys/internal/i18n" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) @@ -40,10 +40,10 @@ func StreamServerInterceptor(localLogger *logrus.Logger) func(srv interface{}, s // create and log request ID idRequest := fmt.Sprintf("%s:%s", clientID, createID()) - if err := ssLogs.sendLogs(logrus.DebugLevel.String(), "", fmt.Sprintf(i18n.G("Connecting as [[%s]]"), idRequest)); err != nil { - localLogger.Warningf(localLogFormatWithID, idRequest, i18n.G("Couldn't send initial connection log to client")) + if err := ssLogs.sendLogs(logrus.DebugLevel.String(), "", gotext.Get("Connecting as [[%s]]", idRequest)); err != nil { + localLogger.Warningf(localLogFormatWithID, idRequest, gotext.Get("Couldn't send initial connection log to client")) } - Infof(context.Background(), i18n.G("New connection from client [[%s]]"), idRequest) + Info(context.Background(), gotext.Get("New connection from client [[%s]]", idRequest)) // attach stream logger options to context so that we can log locally and remotely from context ssLogs.ctx = context.WithValue(ss.Context(), logContextKey, logContext{ @@ -85,14 +85,14 @@ func extractMetaFromContext(ctx context.Context) (clientID string, withCaller bo // decorate depends on logstreamer: we can’t use it here defer func() { if err != nil { - err = fmt.Errorf(i18n.G("invalid metdata from client: %v\n. Please use the StreamClientInterceptor: %v"), err) + err = errors.New(gotext.Get("invalid metdata from client: %v\n. Please use the StreamClientInterceptor: %v", clientID, err)) } }() // extract logs metadata from the client md, ok := metadata.FromIncomingContext(ctx) if !ok { - return "", false, errors.New(i18n.G("missing client metadata")) + return "", false, errors.New(gotext.Get("missing client metadata")) } clientID, err = validUniqueMdEntry(md, clientIDKey) if err != nil { @@ -104,7 +104,7 @@ func extractMetaFromContext(ctx context.Context) (clientID string, withCaller bo } withCaller, err = strconv.ParseBool(withCallerRaw) if err != nil { - return "", false, fmt.Errorf(i18n.G("%s isn't a boolean: %v"), clientWantCallerKey, err) + return "", false, errors.New(gotext.Get("%s isn't a boolean: %v", clientWantCallerKey, err)) } return clientID, withCaller, nil @@ -113,10 +113,10 @@ func extractMetaFromContext(ctx context.Context) (clientID string, withCaller bo func validUniqueMdEntry(md metadata.MD, key string) (string, error) { v := md.Get(key) if len(v) == 0 { - return "", fmt.Errorf(i18n.G("missing metadata %s for incoming request"), key) + return "", errors.New(gotext.Get("missing metadata %s for incoming request", key)) } if len(v) != 1 { - return "", fmt.Errorf(i18n.G("invalid metadata %s for incoming request: %q"), key, v) + return "", errors.New(gotext.Get("invalid metadata %s for incoming request: %q", key, v)) } return v[0], nil } diff --git a/internal/i18n/export_test.go b/internal/i18n/export_test.go deleted file mode 100644 index c70b8f9c9..000000000 --- a/internal/i18n/export_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package i18n - -// WithLocaleDir enables overriding locale directory in tests. -func WithLocaleDir(path string) func(l *i18n) { - return func(l *i18n) { - l.localeDir = path - } -} - -// WithLoc enables overriding loc settings in tests. -func WithLoc(loc string) func(l *i18n) { - return func(l *i18n) { - l.loc = loc - } -} - -// ResetGlobals resets G and GN to their empty func. -func ResetGlobals() { - G = func(msgid string) string { return msgid } - NG = func(msgid string, msgidPlural string, n uint32) string { return msgid } -} diff --git a/internal/i18n/generate-locales.go b/internal/i18n/generate-locales.go deleted file mode 100644 index 1fa98dfc4..000000000 --- a/internal/i18n/generate-locales.go +++ /dev/null @@ -1,256 +0,0 @@ -//go:build tools -// +build tools - -package main - -import ( - "bufio" - "fmt" - "io/fs" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/ubuntu/adsys/internal/generators" -) - -const usage = `Usage of %s: - - create-po POT DIRECTORY LOC [LOC...] - Create new LOC.po file(s) in DIRECTORY from an existing pot file. - update-po POT DIRECTORY - Create/Update a pot file and refresh any existing po files in DIRECTORY. - generate-mo DOMAIN PODIR DIRECTORY - Create .mo files for any .po in PODIR in an structured hierarchy in DIRECTORY. -` - -func main() { - if len(os.Args) < 2 { - log.Fatalf(usage, os.Args[0]) - } - - switch os.Args[1] { - case "create-po": - if len(os.Args) < 5 { - log.Fatalf(usage, os.Args[0]) - } - if generators.InstallOnlyMode() { - return - } - if err := createPo(os.Args[2], os.Args[3], os.Args[4:]); err != nil { - log.Fatalf("Error when creating po files: %v", err) - } - - case "update-po": - if len(os.Args) != 4 { - log.Fatalf(usage, os.Args[0]) - } - if generators.InstallOnlyMode() { - return - } - if err := updatePo(os.Args[2], os.Args[3]); err != nil { - log.Fatalf("Error when updating po files: %v", err) - } - - case "generate-mo": - if len(os.Args) != 5 { - log.Fatalf(usage, os.Args[0]) - } - if err := generateMo(os.Args[2], os.Args[3], filepath.Join(generators.DestDirectory(os.Args[4]), "usr", "share")); err != nil { - log.Fatalf("Error when generating mo files: %v", err) - } - default: - log.Fatalf(usage, os.Args[0]) - } -} - -// createPo creates new po files. -func createPo(potfile, localeDir string, locs []string) error { - if _, err := os.Stat(potfile); err != nil { - return fmt.Errorf("%q can't be read: %v", potfile, err) - } - - for _, loc := range locs { - pofile := filepath.Join(localeDir, loc+".po") - if _, err := os.Stat(pofile); err == nil { - log.Printf("Skipping %q as already exists. Please use update-po to refresh it or delete it first.", loc) - continue - } - - if out, err := exec.Command("msginit", - "--input="+potfile, "--locale="+loc+".UTF-8", "--no-translator", "--output="+pofile).CombinedOutput(); err != nil { - return fmt.Errorf("couldn't create %q: %v.\nCommand output: %s", pofile, err, out) - } - } - - return nil -} - -// updatePo creates pot files and update any existing .po ones. -func updatePo(potfile, localeDir string) error { - if err := os.MkdirAll(localeDir, 0755); err != nil { - return fmt.Errorf("couldn't create directory for %q: %v", localeDir, err) - } - - // Create pot file - var files []string - root := filepath.Dir(localeDir) - err := filepath.WalkDir(root, func(p string, de fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("fail to access %q: %v", p, err) - } - // Only deal with files - if de.IsDir() { - return nil - } - - if !strings.HasSuffix(p, ".go") && !strings.HasSuffix(p, ".go.template") { - return nil - } - files = append(files, strings.TrimPrefix(p, root+"/")) - - return nil - }) - - if err != nil { - return err - } - - var potcreation string - // if already existed: extract POT creation date to keep it (xgettext always updates it) - if _, err := os.Stat(potfile); err == nil { - if potcreation, err = getPOTCreationDate(potfile); err != nil { - log.Fatal(err) - } - } - args := append([]string{ - "--keyword=G", "--keyword=GN", "--add-comments", "--sort-output", "--package-name=" + strings.TrimSuffix(filepath.Base(potfile), ".pot"), - "-D", root, "--output=" + potfile}, files...) - if out, err := exec.Command("xgettext", args...).CombinedOutput(); err != nil { - return fmt.Errorf("couldn't compile pot file: %v\nCommand output: %s", err, out) - } - if potcreation != "" { - if err := rewritePOTCreationDate(potcreation, potfile); err != nil { - log.Fatalf("couldn't change POT Creation file: %v", err) - } - } - - // Merge existing po files - poCandidates, err := os.ReadDir(localeDir) - if err != nil { - log.Fatalf("couldn't list content of %q: %v", localeDir, err) - } - for _, f := range poCandidates { - if !strings.HasSuffix(f.Name(), ".po") { - continue - } - - pofile := filepath.Join(localeDir, f.Name()) - - // extract POT creation date to keep it (msgmerge always updates it) - potcreation, err := getPOTCreationDate(pofile) - if err != nil { - log.Fatal(err) - } - - if out, err := exec.Command("msgmerge", "--update", "--backup=none", pofile, potfile).CombinedOutput(); err != nil { - return fmt.Errorf("couldn't refresh %q: %v.\nCommand output: %s", pofile, err, out) - } - - if err := rewritePOTCreationDate(potcreation, pofile); err != nil { - log.Fatalf("couldn't change POT Creation file: %v", err) - } - } - - return nil -} - -// generateMo generates a locale directory structure with a mo for each po in localeDir. -func generateMo(domain, in, out string) error { - baseLocaleDir := filepath.Join(out, "locale") - if err := generators.CleanDirectory(baseLocaleDir); err != nil { - log.Fatalln(err) - } - - poCandidates, err := os.ReadDir(in) - if err != nil { - log.Fatalf("couldn't list content of %q: %v", in, err) - } - for _, f := range poCandidates { - if !strings.HasSuffix(f.Name(), ".po") { - continue - } - - candidate := filepath.Join(in, f.Name()) - outDir := filepath.Join(baseLocaleDir, strings.TrimSuffix(f.Name(), ".po"), "LC_MESSAGES") - if err := os.MkdirAll(outDir, 0755); err != nil { - return fmt.Errorf("couldn't create %q: %v", out, err) - } - if out, err := exec.Command("msgfmt", - "--output-file="+filepath.Join(outDir, domain+".mo"), - candidate).CombinedOutput(); err != nil { - return fmt.Errorf("couldn't compile mo file from %q: %v.\nCommand output: %s", candidate, err, out) - } - } - return nil -} - -const potCreationDatePrefix = `"POT-Creation-Date:` - -func getPOTCreationDate(p string) (string, error) { - f, err := os.Open(p) - if err != nil { - return "", fmt.Errorf("couldn't open %q: %v", p, err) - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - if strings.HasPrefix(scanner.Text(), potCreationDatePrefix) { - return scanner.Text(), nil - } - } - - if err := scanner.Err(); err != nil { - return "", fmt.Errorf("error while reading %q: %v", p, err) - } - - return "", fmt.Errorf("didn't find %q in %q", potCreationDatePrefix, p) -} - -func rewritePOTCreationDate(potcreation, p string) error { - f, err := os.Open(p) - if err != nil { - return fmt.Errorf("couldn't open %q: %v", p, err) - } - defer f.Close() - out, err := os.Create(p + ".new") - if err != nil { - return fmt.Errorf("couldn't open %q: %v", p+".new", err) - } - defer out.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - t := scanner.Text() - if strings.HasPrefix(t, potCreationDatePrefix) { - t = potcreation - } - if _, err := out.WriteString(t + "\n"); err != nil { - return fmt.Errorf("couldn't write to %q: %v", p+".new", err) - } - } - - if err := scanner.Err(); err != nil { - return fmt.Errorf("error while reading %q: %v", p, err) - } - f.Close() - out.Close() - - if err := os.Rename(p+".new", p); err != nil { - return fmt.Errorf("couldn't rename %q to %q: %v", p+".new", p, err) - } - return nil -} diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go deleted file mode 100644 index da745d3b6..000000000 --- a/internal/i18n/i18n.go +++ /dev/null @@ -1,97 +0,0 @@ -// Package i18n is responsible for internationalization/translation handling and generation. -package i18n - -/* - * This package is inspired from https://github.com/snapcore/snapd/blob/master/i18n, with other snap dependecies removed - * and adapted to follow common go best practices. - */ - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/snapcore/go-gettext" -) - -type i18n struct { - domain string - localeDir string - loc string - - gettext.Catalog - translations gettext.Translations -} - -var ( - locale i18n - - // G is the shorthand for Gettext. - G = func(msgid string) string { return msgid } - // NG is the shorthand for NGettext. - NG = func(msgid string, msgidPlural string, n uint32) string { return msgid } -) - -// InitI18nDomain calls bind + set locale to system values. -func InitI18nDomain(domain string, options ...func(l *i18n)) { - locale = i18n{ - domain: domain, - localeDir: "/usr/share/locale", - } - for _, option := range options { - option(&locale) - } - - locale.bindTextDomain(locale.domain, locale.localeDir) - locale.setLocale(locale.loc) - - G = locale.Gettext - NG = locale.NGettext -} - -// langpackResolver tries to fetch locale mo file path. -// It first checks for the real locale (e.g. de_DE) and then -// tries to simplify the locale (e.g. de_DE -> de). -func langpackResolver(root string, locale string, domain string) string { - for _, locale := range []string{locale, strings.SplitN(locale, "_", 2)[0]} { - r := filepath.Join(locale, "LC_MESSAGES", fmt.Sprintf("%s.mo", domain)) - - // look into the generated mo files path first for translations, then the system - var candidateDirs []string - // Ubuntu uses /usr/share/locale-langpack and patches the glibc gettext implementation - candidateDirs = append(candidateDirs, filepath.Join(root, "..", "locale-langpack")) - candidateDirs = append(candidateDirs, root) - - for _, dir := range candidateDirs { - candidateMo := filepath.Join(dir, r) - // Only load valid candidates, if we can't access it or have perm issues, ignore - if _, err := os.Stat(candidateMo); err != nil { - continue - } - return candidateMo - } - } - - return "" -} - -func (l *i18n) bindTextDomain(domain, dir string) { - l.translations = gettext.NewTranslations(dir, domain, langpackResolver) -} - -// setLocale initializes the locale name and simplify it. -// If empty, it defaults to system ones set in LC_MESSAGES and LANG. -func (l *i18n) setLocale(loc string) { - if loc == "" { - loc = os.Getenv("LC_MESSAGES") - if loc == "" { - loc = os.Getenv("LANG") - } - } - // de_DE.UTF-8, de_DE@euro all need to get simplified - loc = strings.Split(loc, "@")[0] - loc = strings.Split(loc, ".")[0] - - l.Catalog = l.translations.Locale(loc) -} diff --git a/internal/i18n/i18n_test.go b/internal/i18n/i18n_test.go deleted file mode 100644 index 94239bda6..000000000 --- a/internal/i18n/i18n_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package i18n_test - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/ubuntu/adsys/internal/i18n" -) - -const ( - defaultDomain = "adsys-test" - defaultLoc = "en_DK" - secondaryLoc = "en" -) - -var ( - defaultPo = fmt.Sprintf(` -msgid "" -msgstr "" -"Project-Id-Version: %s\n" -"Report-Msgid-Bugs-To: adsys-devel@lists.ubuntu.com\n" -"POT-Creation-Date: 2013-10-05 14:08+0200\n" -"Language: %s\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=n != 1;>\n" -msgid "plural_1" -msgid_plural "plural_2" -msgstr[0] "translated plural_1" -msgstr[1] "translated plural_2" -msgid "singular" -msgstr "translated singular" -`, defaultDomain, defaultLoc) - - localePo = map[string]string{ - defaultLoc: defaultPo, - secondaryLoc: strings.ReplaceAll( - strings.ReplaceAll(defaultPo, defaultLoc, secondaryLoc), - "translated singular", "secondary translated singular"), - } -) - -func TestTranslations(t *testing.T) { - t.Skip("WILL BE REMOVED") - defaultLocaleDir := filepath.Join(t.TempDir(), "locale") - compileMoFiles(t, defaultLocaleDir) - - tests := map[string]struct { - // default is singular/translated singular - text []string - want string - - localeDir string - lcmessages string // lcmessages can be set to "-" to ensure it's empty - lang string - domain string - loc string // loc can be set to "-" to ensure it's empty - - rename map[string]string - noinit bool - }{ - "One text elem, prefer en_DK over en": {}, - "Multiple text elems": {text: []string{"plural_1", "plural_2"}, want: "translated plural_1"}, - - // Locale preferences - "en_DK@ is en_DK": {loc: defaultLoc + "@foo"}, - "en_DK. is en_DK": {loc: defaultLoc + ".foo"}, - "Fallback to en if en_DK isn't present": { - want: "secondary translated singular", - rename: map[string]string{filepath.Join(defaultLocaleDir, "en_DK"): filepath.Join(defaultLocaleDir, "other")}, - }, - "Prefer locale-langpack to locale": { - want: "secondary translated singular", - rename: map[string]string{ - filepath.Join(defaultLocaleDir, "en"): filepath.Join(strings.ReplaceAll(defaultLocaleDir, "locale", "locale-langpack"), "en_DK"), - }, - }, - - "No loc prefers LC_MESSAGES first": {lcmessages: "en_DK", loc: "-"}, - "No loc fallbacks to LANG if no LC_MESSAGES": {lang: "en_DK", loc: "-", lcmessages: "-"}, - - "Untranslated elem": {text: []string{"untranslated"}, want: "untranslated"}, - "Missing locale": {loc: "doesntexists", want: "singular"}, - "Missing domain": {domain: "doesntexists", want: "singular"}, - "Invalid locale directory": {localeDir: "/doesntexists", want: "singular"}, - "Init wasn't ran": {noinit: true, want: "singular"}, - } - - for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { - // We can't run those subtests in parallel as we want defer of global functions to end once all subtests are. - - // As we can't run those tests in parallel, we are doing the file switches and env changes to test priorities - // in subtests here and reset globals. - defer i18n.ResetGlobals() - if tc.text == nil { - tc.text = []string{"singular"} - } - if tc.want == "" { - tc.want = "translated singular" - } - if tc.localeDir == "" { - tc.localeDir = defaultLocaleDir - } - if tc.lcmessages == "" { - tc.lcmessages = "FR_fr" - } else if tc.lcmessages == "-" { - tc.lcmessages = "" - } - t.Setenv("LC_MESSAGES", tc.lcmessages) - if tc.lang == "" { - tc.lang = "FR_fr" - } - t.Setenv("LANG", tc.lang) - if tc.loc == "" { - tc.loc = defaultLoc - } else if tc.loc == "-" { - tc.loc = "" - } - if tc.domain == "" { - tc.domain = defaultDomain - } - if tc.rename != nil { - for old, new := range tc.rename { - renameElem(t, old, new) - } - } - - if !tc.noinit { - i18n.InitI18nDomain(tc.domain, i18n.WithLocaleDir(tc.localeDir), i18n.WithLoc(tc.loc)) - } - switch len(tc.text) { - case 1: - assert.Equal(t, tc.want, i18n.G(tc.text[0])) - case 2: - assert.Equal(t, tc.want, i18n.NG(tc.text[0], tc.text[1], 1)) - default: - t.Fatalf("unexpected case: %v", tc.text) - } - }) - } -} - -func compileMoFiles(t *testing.T, localeDir string) { - t.Helper() - - for loc, poContent := range localePo { - fullLocaleDir := filepath.Join(localeDir, loc, "LC_MESSAGES") - if err := os.MkdirAll(fullLocaleDir, 0750); err != nil { - t.Fatalf("couldn't create temporary directory %q: %v", fullLocaleDir, err) - } - - po := filepath.Join(localeDir, defaultDomain+".po") - mo := filepath.Join(fullLocaleDir, defaultDomain+".mo") - - if err := os.WriteFile(po, []byte(poContent), 0600); err != nil { - t.Fatalf("couldn't write po file: %v", err) - } - - // nolint:gosec // G204 false positive, the binary is hardcoded - cmd := exec.Command("msgfmt", po, "--output-file", mo) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - t.Fatalf("couldn't compile %q to %q: %v", po, mo, err) - } - } -} - -// renameElem rename old file to new. -// The rename is reverted when the test ends. -func renameElem(t *testing.T, old, new string) { - t.Helper() - - if err := os.MkdirAll(filepath.Dir(new), 0750); err != nil { - t.Fatalf("couldn't create parent directory %q to be renamed: %v", new, err) - } - if err := os.Rename(old, new); err != nil { - t.Fatalf("couldn't rename %q to %q: %v", old, new, err) - } - t.Cleanup(func() { - if err := os.Rename(new, old); err != nil { - t.Fatalf("couldn't restore %q to %q: %v", new, old, err) - } - }) -} diff --git a/internal/policies/apparmor/apparmor.go b/internal/policies/apparmor/apparmor.go index 48bf50911..3d47e3884 100644 --- a/internal/policies/apparmor/apparmor.go +++ b/internal/policies/apparmor/apparmor.go @@ -34,9 +34,9 @@ import ( "sync" "time" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/adsys/internal/smbsafe" "github.com/ubuntu/decorate" @@ -111,7 +111,7 @@ type AssetsDumper func(ctx context.Context, relSrc, dest string, uid int, gid in // 7a. If apparmor_parser fails, move /etc/apparmor.d/adsys/.old to /etc/apparmor.d/adsys/ // 7b. If apparmor_parser succeeds, remove /etc/apparmor.d/adsys/.old. func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer bool, entries []entry.Entry, assetsDumper AssetsDumper) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply apparmor policy to %s"), objectName) + defer decorate.OnError(&err, gotext.Get("can't apply apparmor policy to %s", objectName)) objectDir := "machine" if !isComputer { @@ -131,7 +131,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer return err } // Otherwise, just let the user know - log.Warningf(ctx, i18n.G("Apparmor is not available on this system: %v"), err) + log.Warning(ctx, gotext.Get("Apparmor is not available on this system: %v", err)) return nil } m.apparmorParserCmd[0] = absPath @@ -139,13 +139,13 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer // If we have no entries, attempt to unload them and remove the apparmor directory idx := slices.IndexFunc(entries, func(e entry.Entry) bool { return e.Key == fmt.Sprintf("apparmor-%s", objectDir) }) if idx == -1 || entries[idx].Disabled { - log.Debugf(ctx, fmt.Sprintf(i18n.G("No entries found for the apparmor %s policy"), objectDir)) + log.Debug(ctx, gotext.Get("No entries found for the apparmor %s policy", objectDir)) return m.unloadAllRules(ctx, objectName, isComputer) } - log.Debugf(ctx, i18n.G("Applying apparmor %s policy to %s"), objectDir, objectName) + log.Debug(ctx, gotext.Get("Applying apparmor %s policy to %s", objectDir, objectName)) if err := os.MkdirAll(apparmorPath, 0750); err != nil { - return fmt.Errorf(i18n.G("can't create apparmor directory %q: %v"), apparmorPath, err) + return errors.New(gotext.Get("can't create apparmor directory %q: %v", apparmorPath, err)) } switch objectDir { @@ -160,7 +160,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer // applyUserPolicy applies apparmor policies for the machine object. func (m *Manager) applyMachinePolicy(ctx context.Context, e entry.Entry, apparmorPath string, assetsDumper AssetsDumper) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply machine policy")) + defer decorate.OnError(&err, gotext.Get("can't apply machine policy")) existingProfiles, err := filesInDir(apparmorPath) if err != nil { @@ -183,11 +183,11 @@ func (m *Manager) applyMachinePolicy(ctx context.Context, e entry.Entry, apparmo // Remove any existing stale directories oldApparmorPath := apparmorPath + ".old" if err := os.RemoveAll(oldApparmorPath); err != nil { - return fmt.Errorf(i18n.G("can't remove old apparmor directory %q: %v"), oldApparmorPath, err) + return errors.New(gotext.Get("can't remove old apparmor directory %q: %v", oldApparmorPath, err)) } newApparmorPath := apparmorPath + ".new" if err := os.RemoveAll(newApparmorPath); err != nil { - return fmt.Errorf(i18n.G("can't remove new apparmor directory %q: %v"), newApparmorPath, err) + return errors.New(gotext.Get("can't remove new apparmor directory %q: %v", newApparmorPath, err)) } // Dump assets to the adsys/machine.new/ subdirectory with correct @@ -199,13 +199,13 @@ func (m *Manager) applyMachinePolicy(ctx context.Context, e entry.Entry, apparmo // Rename existing apparmor policy to .old if err := os.Rename(apparmorPath, oldApparmorPath); err != nil { - return fmt.Errorf(i18n.G("can't rename apparmor directory %q to %q: %v"), apparmorPath, oldApparmorPath, err) + return errors.New(gotext.Get("can't rename apparmor directory %q to %q: %v", apparmorPath, oldApparmorPath, err)) } defer cleanupOldApparmorDir(ctx, oldApparmorPath, apparmorPath) // Rename new apparmor policy to current if err := os.Rename(newApparmorPath, apparmorPath); err != nil { - return fmt.Errorf(i18n.G("can't rename apparmor directory %q to %q: %v"), newApparmorPath, apparmorPath, err) + return errors.New(gotext.Get("can't rename apparmor directory %q to %q: %v", newApparmorPath, apparmorPath, err)) } // Get the list of files to run apparmor_parser on @@ -244,20 +244,20 @@ func (m *Manager) applyMachinePolicy(ctx context.Context, e entry.Entry, apparmo out, err := cmd.CombinedOutput() smbsafe.DoneExec() if err != nil { - return fmt.Errorf(i18n.G("failed to load apparmor rules: %w\n%s"), err, string(out)) + return errors.New(gotext.Get("failed to load apparmor rules: %v\n%s", err, string(out))) } } // Loading rules succeeded, remove old apparmor policy dir if err := os.RemoveAll(oldApparmorPath); err != nil { - return fmt.Errorf(i18n.G("can't remove old apparmor directory %q: %v"), oldApparmorPath, err) + return errors.New(gotext.Get("can't remove old apparmor directory %q: %v", oldApparmorPath, err)) } return nil } // applyUserPolicy applies apparmor policies for the user object. func (m *Manager) applyUserPolicy(ctx context.Context, e entry.Entry, apparmorPath string, username string, assetsDumper AssetsDumper) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply user policy")) + defer decorate.OnError(&err, gotext.Get("can't apply user policy")) // Create a temporary filepath to be used by the assets dumper and dump all // assets in order to get our user policy @@ -273,7 +273,7 @@ func (m *Manager) applyUserPolicy(ctx context.Context, e entry.Entry, apparmorPa // The user policy is always a single file if len(profilePaths) != 1 { - return fmt.Errorf(i18n.G("expected exactly one profile, got %d"), len(profilePaths)) + return errors.New(gotext.Get("expected exactly one profile, got %d", len(profilePaths))) } profilePath := profilePaths[0] profileContents, err := os.ReadFile(profilePath) @@ -301,7 +301,7 @@ func (m *Manager) applyUserPolicy(ctx context.Context, e entry.Entry, apparmorPa // Reload apparmor machine profiles to ensure that updates to the user policy are applied existingProfiles, err := filesInDir(filepath.Join(m.apparmorDir, "machine")) if errors.Is(err, os.ErrNotExist) { - log.Warningf(ctx, i18n.G("No apparmor machine profiles configured for this machine, skipping reload")) + log.Warningf(ctx, gotext.Get("No apparmor machine profiles configured for this machine, skipping reload")) return nil } if err != nil { @@ -325,11 +325,11 @@ func (m *Manager) applyUserPolicy(ctx context.Context, e entry.Entry, apparmorPa restoreErr = os.WriteFile(filepath.Join(apparmorPath, username), oldContent, 0600) } if restoreErr != nil { - log.Warningf(ctx, i18n.G("Failed to restore old apparmor user profile: %v"), restoreErr) + log.Warning(ctx, gotext.Get("Failed to restore old apparmor user profile: %v", restoreErr)) } // Return the execution error - return fmt.Errorf(i18n.G("failed to load apparmor rules: %w\n%s"), err, string(out)) + return errors.New(gotext.Get("failed to load apparmor rules: %v\n%s", err, string(out))) } return nil } @@ -340,7 +340,7 @@ func (m *Manager) applyUserPolicy(ctx context.Context, e entry.Entry, apparmorPa // If isComputer is true, only rules pertaining to the given user are unloaded. // No action is taken if the directory doesn't exist. func (m *Manager) unloadAllRules(ctx context.Context, objectName string, isComputer bool) (err error) { - defer decorate.OnError(&err, i18n.G("can't unload apparmor rules")) + defer decorate.OnError(&err, gotext.Get("can't unload apparmor rules")) machinePoliciesPath := filepath.Join(m.apparmorDir, "machine") pathToRemove := machinePoliciesPath @@ -417,12 +417,12 @@ func (m *Manager) policiesFromFiles(ctx context.Context, profiles []string) (pol err = cmd.Run() smbsafe.DoneExec() if err != nil { - return nil, fmt.Errorf(i18n.G("failed to get apparmor policies: %w\n%s"), err, errb.String()) + return nil, errors.New(gotext.Get("failed to get apparmor policies: %v\n%s", err, errb.String())) } // Execution succeeded but we still got something on stderr, let the user know if errb.Len() > 0 { - log.Warningf(ctx, i18n.G(`Got stderr output from apparmor_parser: -%s`), errb.String()) + log.Warning(ctx, gotext.Get(`Got stderr output from apparmor_parser: +%s`, errb.String())) } for _, line := range strings.Split(outb.String(), "\n") { @@ -443,7 +443,7 @@ func (m *Manager) unloadPolicies(ctx context.Context, policies []string) error { return nil } - log.Debugf(ctx, i18n.G("Unloading %d apparmor policies: %v"), len(policies), policies) + log.Debug(ctx, gotext.Get("Unloading %d apparmor policies: %v", len(policies), policies)) apparmorParserCmd := append(m.apparmorParserCmd, "-R") // #nosec G204 - We are in control of the arguments cmd := exec.CommandContext(ctx, apparmorParserCmd[0], apparmorParserCmd[1:]...) @@ -464,7 +464,7 @@ func (m *Manager) unloadPolicies(ctx context.Context, policies []string) error { } // For each policy, declare an empty block to remove it. if _, err := io.WriteString(stdin, fmt.Sprintf("profile %s {}\n", policy)); err != nil { - log.Warningf(ctx, i18n.G("Couldn't write to apparmor parser stdin: %v"), err) + log.Warning(ctx, gotext.Get("Couldn't write to apparmor parser stdin: %v", err)) } } }() @@ -472,7 +472,7 @@ func (m *Manager) unloadPolicies(ctx context.Context, policies []string) error { smbsafe.WaitExec() defer smbsafe.DoneExec() if out, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf(i18n.G("failed to unload apparmor policies: %w\n%s"), err, string(out)) + return errors.New(gotext.Get("failed to unload apparmor policies: %v\n%s", err, string(out))) } return nil } @@ -480,11 +480,11 @@ func (m *Manager) unloadPolicies(ctx context.Context, policies []string) error { // loadedPolicies parses the given system policies file and returns the list of // loaded apparmor policies. func (m *Manager) loadedPolicies() (policies []string, err error) { - defer decorate.OnError(&err, i18n.G("can't parse loaded apparmor policies")) + defer decorate.OnError(&err, gotext.Get("can't parse loaded apparmor policies")) file, err := os.Open(m.loadedPoliciesFile) if err != nil { - return nil, fmt.Errorf(i18n.G("failed to open %q: %w"), m.loadedPoliciesFile, err) + return nil, errors.New(gotext.Get("failed to open %q: %v", m.loadedPoliciesFile, err)) } defer file.Close() @@ -516,11 +516,11 @@ func cleanupOldApparmorDir(ctx context.Context, oldApparmorPath, apparmorPath st // Otherwise, we need to restore the old apparmor directory if err := os.RemoveAll(apparmorPath); err != nil { - log.Warningf(ctx, i18n.G("Couldn't remove new apparmor directory: %v"), err) + log.Warning(ctx, gotext.Get("Couldn't remove new apparmor directory: %v", err)) } if err := os.Rename(oldApparmorPath, apparmorPath); err != nil { - log.Warningf(ctx, i18n.G("Couldn't restore previous apparmor directory: %v"), err) + log.Warning(ctx, gotext.Get("Couldn't restore previous apparmor directory: %v", err)) } } @@ -537,10 +537,10 @@ func filesFromEntry(e entry.Entry, apparmorPath string) ([]string, error) { profileFilePath := filepath.Join(apparmorPath, profile) info, err := os.Stat(profileFilePath) if err != nil { - return nil, fmt.Errorf(i18n.G("apparmor profile %q is not accessible: %w"), err) + return nil, errors.New(gotext.Get("apparmor profile %q is not accessible: %v", profile, err)) } if info.IsDir() { - return nil, fmt.Errorf(i18n.G("apparmor profile %q is a directory and not a file"), profile) + return nil, errors.New(gotext.Get("apparmor profile %q is a directory and not a file", profile)) } // Clean and deduplicate the profile file paths @@ -556,7 +556,7 @@ func filesFromEntry(e entry.Entry, apparmorPath string) ([]string, error) { // removeUnusedAssets removes all files/directories in the given directory that // are not in the given list of files. func removeUnusedAssets(apparmorPath string, filesToKeep []string) (e error) { - defer decorate.OnError(&e, i18n.G("can't remove unused apparmor assets")) + defer decorate.OnError(&e, gotext.Get("can't remove unused apparmor assets")) return filepath.WalkDir(apparmorPath, func(path string, d os.DirEntry, err error) error { if err != nil { @@ -640,7 +640,7 @@ func intersection(a, b []string) []string { // writeIfChanged will only write to path if content is different from current content. func writeIfChanged(path string, content string) (oldContent []byte, changed bool, err error) { - defer decorate.OnError(&err, i18n.G("can't save %s"), path) + defer decorate.OnError(&err, gotext.Get("can't save %s", path)) oldContent, err = os.ReadFile(path) if err == nil && string(oldContent) == content { diff --git a/internal/policies/certificate/certificate.go b/internal/policies/certificate/certificate.go index a5bf87257..ced672509 100644 --- a/internal/policies/certificate/certificate.go +++ b/internal/policies/certificate/certificate.go @@ -19,6 +19,7 @@ import ( "context" _ "embed" // embed cert enroll python script "encoding/json" + "errors" "fmt" "os" "os/exec" @@ -29,9 +30,9 @@ import ( "sync" "time" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/adsys/internal/smbsafe" "github.com/ubuntu/decorate" @@ -150,7 +151,7 @@ func New(domain string, opts ...Option) *Manager { // ApplyPolicy runs the certificate autoenrollment script to enroll or un-enroll the machine. func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer, isOnline bool, entries []entry.Entry) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply certificate policy")) + defer decorate.OnError(&err, gotext.Get("can't apply certificate policy")) m.mu.Lock() defer m.mu.Unlock() @@ -161,7 +162,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer } if !isOnline { - log.Debug(ctx, i18n.G("AD backend is offline, skipping certificate policy")) + log.Debug(ctx, gotext.Get("AD backend is offline, skipping certificate policy")) return nil } @@ -185,7 +186,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer entry := entries[idx] value, err := strconv.Atoi(entry.Value) if err != nil { - return fmt.Errorf(i18n.G("failed to parse certificate policy entry value: %w"), err) + return errors.New(gotext.Get("failed to parse certificate policy entry value: %v", err)) } if value&disabledFlag == disabledFlag { @@ -206,7 +207,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer valuename := keyparts[len(keyparts)-1] gpoData, err := gpoData(entry.Value, valuename) if err != nil { - return fmt.Errorf(i18n.G("failed to parse policy entry value: %w"), err) + return errors.New(gotext.Get("failed to parse policy entry value: %v", err)) } polSrvRegistryEntries = append(polSrvRegistryEntries, gpoEntry{keyname, valuename, gpoData, gpoType(valuename)}) @@ -222,7 +223,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer jsonGPOData, err := json.Marshal(polSrvRegistryEntries) if err != nil { - return fmt.Errorf(i18n.G("failed to marshal policy server registry entries: %v"), err) + return errors.New(gotext.Get("failed to marshal policy server registry entries: %v", err)) } if err := m.runScript(ctx, action, objectName, "--policy_servers_json", string(jsonGPOData)); err != nil { @@ -251,9 +252,9 @@ func (m *Manager) runScript(ctx context.Context, action, objectName string, extr output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf(i18n.G("failed to run certificate autoenrollment script (exited with %d): %v\n%s"), cmd.ProcessState.ExitCode(), err, string(output)) + return errors.New(gotext.Get("failed to run certificate autoenrollment script (exited with %d): %v\n%s", cmd.ProcessState.ExitCode(), err, string(output))) } - log.Infof(ctx, i18n.G("Certificate autoenrollment script ran successfully\n%s"), string(output)) + log.Info(ctx, gotext.Get("Certificate autoenrollment script ran successfully\n%s", string(output))) return nil } diff --git a/internal/policies/dconf/dconf.go b/internal/policies/dconf/dconf.go index c8f2a485c..c5ee738db 100644 --- a/internal/policies/dconf/dconf.go +++ b/internal/policies/dconf/dconf.go @@ -40,9 +40,9 @@ import ( "sync" "github.com/godbus/dbus/v5" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/adsys/internal/smbsafe" "github.com/ubuntu/decorate" @@ -66,7 +66,7 @@ func NewWithDconfDir(dir string) *Manager { // ApplyPolicy generates a dconf computer or user policy based on a list of entries. func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer bool, entries []entry.Entry) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply dconf policy to %s"), objectName) + defer decorate.OnError(&err, gotext.Get("can't apply dconf policy to %s", objectName)) dconfDir := m.dconfDir if dconfDir == "" { @@ -97,7 +97,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer if !isComputer && len(entries) > 0 { if _, err := os.Stat(filepath.Join(dbsPath, "machine.d", "locks", "adsys")); err != nil { - return fmt.Errorf(i18n.G("machine dconf database is required before generating a policy for an user. This one returns: %v"), err) + return errors.New(gotext.Get("machine dconf database is required before generating a policy for an user. This one returns: %v", err)) } } @@ -105,13 +105,13 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer // We don't clean up the machine database because we don't know if there's any user GPO depending on it. if !isComputer && len(entries) == 0 { if err := os.RemoveAll(dbPath); err != nil { - return fmt.Errorf(i18n.G("can't remove user dconf database directory: %v"), err) + return errors.New(gotext.Get("can't remove user dconf database directory: %v", err)) } if er := os.RemoveAll(filepath.Join(dbsPath, objectName)); er != nil { - return fmt.Errorf(i18n.G("can't remove user dconf binary database: %v"), er) + return errors.New(gotext.Get("can't remove user dconf binary database: %v", err)) } if err := os.RemoveAll(filepath.Join(profilesPath, objectName)); err != nil { - return fmt.Errorf(i18n.G("can't remove user dconf profile: %v"), err) + return errors.New(gotext.Get("can't remove user dconf profile: %v", err)) } return nil } @@ -141,7 +141,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer // normalize common user error cases and check gsettings schema signature match. e.Value = normalizeValue(e.Meta, e.Value) if err := checkSignature(e.Meta, e.Value); err != nil { - errMsgs = append(errMsgs, fmt.Sprintf(i18n.G("- error on %s: %v"), e.Key, err)) + errMsgs = append(errMsgs, gotext.Get("- error on %s: %v", e.Key, err)) continue } @@ -210,7 +210,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer m.dconfUpdateMu.Unlock() smbsafe.DoneExec() if errExec != nil { - err = fmt.Errorf(i18n.G("dconf update failed: %v"), out) + err = errors.New(gotext.Get("dconf update failed: %v", out)) } return nil @@ -218,7 +218,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer // writeIfChanged will only write to path if content is different from current content. func writeIfChanged(path string, content string) (done bool, err error) { - defer decorate.OnError(&err, i18n.G("can't save %s"), path) + defer decorate.OnError(&err, gotext.Get("can't save %s", path)) if oldContent, err := os.ReadFile(path); err == nil && string(oldContent) == content { return false, nil @@ -239,7 +239,7 @@ func writeIfChanged(path string, content string) (done bool, err error) { // The adsys system-db should always be the first system-db in the file to enforce their values // (upper system-db in the profile wins). func writeProfile(ctx context.Context, user, profilesPath string) (err error) { - defer decorate.OnError(&err, i18n.G("can't update user profile %s"), profilesPath) + defer decorate.OnError(&err, gotext.Get("can't update user profile %s", profilesPath)) profilePath := filepath.Join(profilesPath, user) log.Debugf(ctx, "Update user profile %s", profilePath) @@ -430,19 +430,19 @@ func splitOnNonEscaped(v, sep string) []string { // checkSignature returns an error if the value doesn't match the expected variant signature. func checkSignature(meta, value string) (err error) { - defer decorate.OnError(&err, i18n.G("error while checking signature")) + defer decorate.OnError(&err, gotext.Get("error while checking signature")) if meta == "" { - return fmt.Errorf(i18n.G("empty signature for %v"), meta) + return errors.New(gotext.Get("empty signature for %v", meta)) } sig, err := dbus.ParseSignature(meta) if err != nil { - return fmt.Errorf(i18n.G("%s is not a valid gsettings signature: %v"), meta, err) + return errors.New(gotext.Get("%s is not a valid gsettings signature: %v", meta, err)) } _, err = dbus.ParseVariant(value, sig) if err != nil { - return fmt.Errorf(i18n.G("can't parse %q as %q: %v"), value, meta, err) + return errors.New(gotext.Get("can't parse %q as %q: %v", value, meta, err)) } return nil diff --git a/internal/policies/gdm/gdm.go b/internal/policies/gdm/gdm.go index 1792dec7b..a36125420 100644 --- a/internal/policies/gdm/gdm.go +++ b/internal/policies/gdm/gdm.go @@ -9,8 +9,8 @@ import ( "context" "strings" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/dconf" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/decorate" @@ -37,7 +37,7 @@ func WithDconf(m *dconf.Manager) func(o *options) error { // New returns a new manager for gdm policy handlers. func New(opts ...option) (m *Manager, err error) { - defer decorate.OnError(&err, i18n.G("can't create a new gdm handler manager")) + defer decorate.OnError(&err, gotext.Get("can't create a new gdm handler manager")) // defaults args := options{ @@ -57,7 +57,7 @@ func New(opts ...option) (m *Manager, err error) { // ApplyPolicy generates a dconf computer or user policy based on a list of entries. func (m *Manager) ApplyPolicy(ctx context.Context, entries []entry.Entry) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply gdm policy")) + defer decorate.OnError(&err, gotext.Get("can't apply gdm policy")) log.Debug(ctx, "ApplyPolicy gdm policy") diff --git a/internal/policies/manager.go b/internal/policies/manager.go index c587c34b5..c331e73f5 100644 --- a/internal/policies/manager.go +++ b/internal/policies/manager.go @@ -25,6 +25,7 @@ package policies import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -34,10 +35,10 @@ import ( "time" "github.com/godbus/dbus/v5" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/ad/backends" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/apparmor" "github.com/ubuntu/adsys/internal/policies/certificate" "github.com/ubuntu/adsys/internal/policies/dconf" @@ -238,7 +239,7 @@ func WithCertAutoenrollCmd(cmd []string) Option { // NewManager returns a new manager with all default policy handlers. func NewManager(bus *dbus.Conn, hostname string, backend backends.Backend, opts ...Option) (m *Manager, err error) { - defer decorate.OnError(&err, i18n.G("can't create a new policy handlers manager")) + defer decorate.OnError(&err, gotext.Get("can't create a new policy handlers manager")) defaultSystemdCaller, err := systemd.New(bus) if err != nil { @@ -351,7 +352,7 @@ func NewManager(bus *dbus.Conn, hostname string, backend backends.Backend, opts // ApplyPolicies generates a computer or user policy based on a list of entries // retrieved from a directory service. func (m *Manager) ApplyPolicies(ctx context.Context, objectName string, isComputer bool, pols *Policies) (err error) { - defer decorate.OnError(&err, i18n.G("failed to apply policy to %q"), objectName) + defer decorate.OnError(&err, gotext.Get("failed to apply policy to %q", objectName)) // We have a lock per objectName to prevent multiple instances of ApplyPolicies for the same object. m.muMu.Lock() @@ -363,11 +364,11 @@ func (m *Manager) ApplyPolicies(ctx context.Context, objectName string, isComput m.muMu.Unlock() rules := pols.GetUniqueRules() - action := i18n.G("Applying") + action := gotext.Get("Applying") if len(rules) == 0 { - action = i18n.G("Unloading") + action = gotext.Get("Unloading") } - log.Infof(ctx, i18n.G("%s policies for %s (machine: %v)"), action, objectName, isComputer) + log.Info(ctx, gotext.Get("%s policies for %s (machine: %v)", action, objectName, isComputer)) var g errgroup.Group // Applying dconf policies take a while to complete, so it's better to start applying them before @@ -377,7 +378,7 @@ func (m *Manager) ApplyPolicies(ctx context.Context, objectName string, isComput }) if !m.GetSubscriptionState(ctx) { if filteredRules := filterRules(ctx, rules); len(filteredRules) > 0 { - log.Warningf(ctx, i18n.G("Rules from the following policy types will be filtered out as the machine is not enrolled to Ubuntu Pro: %s"), strings.Join(filteredRules, ", ")) + log.Warning(ctx, gotext.Get("Rules from the following policy types will be filtered out as the machine is not enrolled to Ubuntu Pro: %s", strings.Join(filteredRules, ", "))) } } @@ -419,7 +420,7 @@ func (m *Manager) ApplyPolicies(ctx context.Context, objectName string, isComput // DumpPolicies displays the currently applied policies and rules (since last update) for objectName. // It can in addition show the rules and overridden content. func (m *Manager) DumpPolicies(ctx context.Context, objectName string, computerOnly, withRules, withOverridden bool) (msg string, err error) { - defer decorate.OnError(&err, i18n.G("failed to dump policies for %q"), objectName) + defer decorate.OnError(&err, gotext.Get("failed to dump policies for %q", objectName)) log.Infof(ctx, "Dumping policies for %s", objectName) @@ -427,22 +428,22 @@ func (m *Manager) DumpPolicies(ctx context.Context, objectName string, computerO var alreadyProcessedRules map[string]struct{} if !computerOnly { - fmt.Fprintln(&out, i18n.G("Policies from machine configuration:")) + fmt.Fprintln(&out, gotext.Get("Policies from machine configuration:")) policiesHost, err := NewFromCache(ctx, filepath.Join(m.policiesCacheDir, m.hostname)) if err != nil { - return "", fmt.Errorf(i18n.G("no policy applied for %q: %v"), m.hostname, err) + return "", errors.New(gotext.Get("no policy applied for %q: %v", m.hostname, err)) } for _, g := range policiesHost.GPOs { alreadyProcessedRules = g.Format(&out, withRules, withOverridden, alreadyProcessedRules) } - fmt.Fprintln(&out, i18n.G("Policies from user configuration:")) + fmt.Fprintln(&out, gotext.Get("Policies from user configuration:")) } // Load target policies policiesTarget, err := NewFromCache(ctx, filepath.Join(m.policiesCacheDir, objectName)) if err != nil { - log.Infof(ctx, i18n.G("User %q not found on cache."), objectName) - return "", fmt.Errorf(i18n.G("no policy applied for %q: %v"), objectName, err) + log.Info(ctx, gotext.Get("User %q not found on cache.", objectName)) + return "", errors.New(gotext.Get("no policy applied for %q: %v", objectName, err)) } for _, g := range policiesTarget.GPOs { alreadyProcessedRules = g.Format(&out, withRules, withOverridden, alreadyProcessedRules) @@ -453,7 +454,7 @@ func (m *Manager) DumpPolicies(ctx context.Context, objectName string, computerO // LastUpdateFor returns the last update time for object or current machine. func (m *Manager) LastUpdateFor(ctx context.Context, objectName string, isMachine bool) (t time.Time, err error) { - defer decorate.OnError(&err, i18n.G("failed to get policy last update time %q (machine: %q)"), objectName, isMachine) + defer decorate.OnError(&err, gotext.Get("failed to get policy last update time %q (machine: %v)", objectName, isMachine)) log.Infof(ctx, "Get policies last update time %q (machine: %t)", objectName, isMachine) @@ -463,7 +464,7 @@ func (m *Manager) LastUpdateFor(ctx context.Context, objectName string, isMachin info, err := os.Stat(filepath.Join(m.policiesCacheDir, objectName)) if err != nil { - return time.Time{}, fmt.Errorf(i18n.G("policies were not applied for %q: %v"), objectName, err) + return time.Time{}, errors.New(gotext.Get("policies were not applied for %q: %v", objectName, err)) } return info.ModTime(), nil } diff --git a/internal/policies/mount/mount.go b/internal/policies/mount/mount.go index 29711a95a..91c496941 100644 --- a/internal/policies/mount/mount.go +++ b/internal/policies/mount/mount.go @@ -26,8 +26,8 @@ import ( "strings" "github.com/coreos/go-systemd/v22/unit" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/decorate" ) @@ -65,7 +65,7 @@ type systemdCaller interface { // New creates a Manager to handle mount policies. func New(runDir string, systemUnitDir string, systemdCaller systemdCaller, opts ...Option) (m *Manager, err error) { - defer decorate.OnError(&err, i18n.G("failed to create new mount manager")) + defer decorate.OnError(&err, gotext.Get("failed to create new mount manager")) o := options{ userLookup: user.Lookup, @@ -102,7 +102,7 @@ func New(runDir string, systemUnitDir string, systemdCaller systemdCaller, opts // ApplyPolicy generates mount policies based on a list of entries. func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer bool, entries []entry.Entry) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply mount policy to %s"), objectName) + defer decorate.OnError(&err, gotext.Get("can't apply mount policy to %s", objectName)) log.Debugf(ctx, "Applying mount policy to %s", objectName) @@ -120,12 +120,12 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer }) if i == -1 { - log.Debugf(ctx, i18n.G("The provided entries are not supported by the %s mount manager: %v"), key, entries) + log.Debug(ctx, gotext.Get("The provided entries are not supported by the %s mount manager: %v", key, entries)) return m.cleanup(ctx, objectName, isComputer) } if entries[i].Disabled { - log.Debugf(ctx, i18n.G("The entry %q is disabled and will be skipped"), entries[i].Key) + log.Debug(ctx, gotext.Get("The entry %q is disabled and will be skipped", entries[i].Key)) return m.cleanup(ctx, objectName, isComputer) } @@ -136,20 +136,20 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer } func (m *Manager) applyUserMountsPolicy(ctx context.Context, username string, entry entry.Entry) (err error) { - defer decorate.OnError(&err, i18n.G("failed to apply policy for user %q"), username) + defer decorate.OnError(&err, gotext.Get("failed to apply policy for user %q", username)) log.Debugf(ctx, "Applying mount policy to user %q", username) var uid, gid int u, err := m.userLookup(username) if err != nil { - return fmt.Errorf(i18n.G("could not retrieve user for %q: %w"), err) + return errors.New(gotext.Get("could not retrieve user for %q: %v", username, err)) } if uid, err = strconv.Atoi(u.Uid); err != nil { - return fmt.Errorf(i18n.G("couldn't convert %q to a valid uid for %q"), u.Uid, username) + return errors.New(gotext.Get("couldn't convert %q to a valid uid for %q", u.Uid, username)) } if gid, err = strconv.Atoi(u.Gid); err != nil { - return fmt.Errorf(i18n.G("couldn't convert %q to a valid gid for %q"), u.Gid, username) + return errors.New(gotext.Get("couldn't convert %q to a valid gid for %q", u.Gid, username)) } objectPath := filepath.Join(m.runDir, "users", u.Uid) @@ -157,7 +157,7 @@ func (m *Manager) applyUserMountsPolicy(ctx context.Context, username string, en // This creates the user directory and set its ownership to the current user. if err := mkdirAllWithUIDGID(objectPath, uid, gid); err != nil { - return fmt.Errorf(i18n.G("can't create user directory %q for %q: %w"), objectPath, username, err) + return errors.New(gotext.Get("can't create user directory %q for %q: %v", objectPath, username, err)) } parsedValues, err := parseEntryValues(ctx, entry) @@ -181,9 +181,9 @@ func (m *Manager) applyUserMountsPolicy(ctx context.Context, username string, en } func (m *Manager) applySystemMountsPolicy(ctx context.Context, machineName string, entry entry.Entry) (err error) { - defer decorate.OnError(&err, i18n.G("error when applying mount policy to machine %q"), machineName) + defer decorate.OnError(&err, gotext.Get("error when applying mount policy to machine %q", machineName)) - log.Debugf(ctx, i18n.G("Applying mount policy to machine %q"), machineName) + log.Debug(ctx, gotext.Get("Applying mount policy to machine %q", machineName)) parsedValues, err := parseEntryValues(ctx, entry) if err != nil { @@ -237,7 +237,7 @@ func (m *Manager) applySystemMountsPolicy(ctx context.Context, machineName strin return err } if err := m.systemdCaller.StartUnit(ctx, name); err != nil { - log.Warningf(ctx, i18n.G("failed to start unit %q: %v"), name, err) + log.Warning(ctx, gotext.Get("failed to start unit %q: %v", name, err)) } } @@ -347,10 +347,10 @@ func whatStringFromInfo(mi mountInfo) string { // parseEntryValues parses the entry value, trimming whitespaces and removing duplicates. func parseEntryValues(ctx context.Context, e entry.Entry) (p []string, err error) { - defer decorate.OnError(&err, i18n.G("failed to parse entry values")) + defer decorate.OnError(&err, gotext.Get("failed to parse entry values")) if e.Err != nil { - return nil, fmt.Errorf(i18n.G("entry is errored: %w"), e.Err) + return nil, errors.New(gotext.Get("entry is errored: %v", e.Err)) } seen := make(map[string]string) @@ -364,9 +364,9 @@ func parseEntryValues(ctx context.Context, e entry.Entry) (p []string, err error tmp := strings.TrimPrefix(v, krbTag) if prev, ok := seen[tmp]; ok { if prev == v { - log.Debugf(ctx, i18n.G("Value %q is duplicated."), v) + log.Debug(ctx, gotext.Get("Value %q is duplicated.", v)) } else { - log.Warningf(ctx, i18n.G("The location %q was already set up to be mounted with different options or authentication. The first provided value %q will be used instead."), v, prev) + log.Warning(ctx, gotext.Get("The location %q was already set up to be mounted with different options or authentication. The first provided value %q will be used instead.", v, prev)) } continue } @@ -389,7 +389,7 @@ func checkValue(value string) error { // Value left: protocol:/// if _, hostnameAndPath, found := strings.Cut(tmp, ":"); !found || !strings.HasPrefix(hostnameAndPath, "//") { - return fmt.Errorf(i18n.G("entry %q is badly formatted"), value) + return errors.New(gotext.Get("entry %q is badly formatted", value)) } return nil @@ -397,7 +397,7 @@ func checkValue(value string) error { // writeIfChanged will only write to path if content is different from current content. func writeIfChanged(path string, content string) (done bool, err error) { - defer decorate.OnError(&err, i18n.G("can't save %s"), path) + defer decorate.OnError(&err, gotext.Get("can't save %s", path)) if oldContent, err := os.ReadFile(path); err == nil && string(oldContent) == content { return false, nil @@ -416,7 +416,7 @@ func writeIfChanged(path string, content string) (done bool, err error) { // writeFileWithUIDGID writes the content into the specified path and changes its ownership to the specified uid/gid. func writeFileWithUIDGID(path string, uid, gid int, content string) (err error) { - defer decorate.OnError(&err, i18n.G("failed when writing file %s"), path) + defer decorate.OnError(&err, gotext.Get("failed when writing file %s", path)) // #nosec G306. This should be world-readable. if err = os.WriteFile(path+".new", []byte(content+"\n"), 0600); err != nil { @@ -438,7 +438,7 @@ func writeFileWithUIDGID(path string, uid, gid int, content string) (err error) // mkdirAllWithUIDGID create a directory and sets its ownership to the specified uid and gid. func mkdirAllWithUIDGID(p string, uid, gid int) error { if err := os.MkdirAll(p, 0750); err != nil { - return fmt.Errorf(i18n.G("can't create directory %q: %v"), p, err) + return fmt.Errorf(gotext.Get("can't create directory %q: %v", p, err)) } return chown(p, nil, uid, gid) @@ -447,7 +447,7 @@ func mkdirAllWithUIDGID(p string, uid, gid int) error { // chown either chown the file descriptor attached, or the path if this one is null to uid and gid. // It will know if we should skip chown for tests. func chown(p string, f *os.File, uid, gid int) (err error) { - defer decorate.OnError(&err, i18n.G("can't chown %q"), p) + defer decorate.OnError(&err, gotext.Get("can't chown %q", p)) if os.Getenv("ADSYS_SKIP_ROOT_CALLS") != "" { uid = -1 @@ -464,9 +464,9 @@ func chown(p string, f *os.File, uid, gid int) (err error) { // cleanup removes the files generated when applying the mount policy to an object. func (m *Manager) cleanup(ctx context.Context, objectName string, isComputer bool) (err error) { - defer decorate.OnError(&err, i18n.G("failed to clean up mount policy files for %q"), objectName) + defer decorate.OnError(&err, gotext.Get("failed to clean up mount policy files for %q", objectName)) - log.Debugf(ctx, i18n.G("Cleaning up mount policy files for %q"), objectName) + log.Debugf(ctx, gotext.Get("Cleaning up mount policy files for %q", objectName)) if !isComputer { var u *user.User @@ -485,9 +485,9 @@ func (m *Manager) cleanup(ctx context.Context, objectName string, isComputer boo // cleanupMountsFile removes the mounts file, if there is any, created for the user with the specified uid. func (m *Manager) cleanupMountsFile(ctx context.Context, uid string) (err error) { - defer decorate.OnError(&err, i18n.G("failed to clean up mounts file")) + defer decorate.OnError(&err, gotext.Get("failed to clean up mounts file")) - log.Debugf(ctx, i18n.G("Cleaning up mounts file for user with uid %q"), uid) + log.Debug(ctx, gotext.Get("Cleaning up mounts file for user with uid %q", uid)) p := filepath.Join(m.runDir, "users", uid, "mounts") @@ -501,12 +501,12 @@ func (m *Manager) cleanupMountsFile(ctx context.Context, uid string) (err error) // cleanupMountUnits removes all the mount units generated by adsys for the current system. func (m *Manager) cleanupMountUnits(ctx context.Context, units []string) (err error) { - defer decorate.OnError(&err, i18n.G("failed to clean up the mount units")) + defer decorate.OnError(&err, gotext.Get("failed to clean up the mount units")) for _, unit := range units { // Tries to stop the unit before disabling and removing it. if err := m.systemdCaller.StopUnit(ctx, unit); err != nil { - log.Warningf(ctx, i18n.G("Failed to stop unit %q: %v"), unit, err) + log.Warning(ctx, gotext.Get("Failed to stop unit %q: %v", unit, err)) } // Disables the unit before removing it. @@ -515,7 +515,7 @@ func (m *Manager) cleanupMountUnits(ctx context.Context, units []string) (err er } if err := os.Remove(filepath.Join(m.systemUnitDir, unit)); err != nil { - return fmt.Errorf(i18n.G("could not remove file %q: %w"), unit, err) + return errors.New(gotext.Get("could not remove file %q: %v", unit, err)) } } diff --git a/internal/policies/policies.go b/internal/policies/policies.go index 5bb5ad3ae..0e42d0fe2 100644 --- a/internal/policies/policies.go +++ b/internal/policies/policies.go @@ -8,7 +8,6 @@ import ( "archive/zip" "context" "errors" - "fmt" "io" "io/fs" "os" @@ -16,8 +15,8 @@ import ( "sort" "strings" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/decorate" "golang.org/x/exp/mmap" @@ -47,7 +46,7 @@ type Policies struct { // We pass directly the compressed DB and don’t save immediately in cache as we will do it // once the gpos are all treated. func New(ctx context.Context, gpos []GPO, assetsDBPath string) (pols Policies, err error) { - defer decorate.OnError(&err, i18n.G("can't created new policies")) + defer decorate.OnError(&err, gotext.Get("can't created new policies")) log.Debugf(ctx, "Creating new policies") @@ -67,7 +66,7 @@ func New(ctx context.Context, gpos []GPO, assetsDBPath string) (pols Policies, e // NewFromCache returns cached policies loaded from the p cache directory. func NewFromCache(ctx context.Context, p string) (pols Policies, err error) { - defer decorate.OnError(&err, i18n.G("can't get cached policies from %s"), p) + defer decorate.OnError(&err, gotext.Get("can't get cached policies from %s", p)) log.Debugf(ctx, "Loading policies from cache using %s", p) @@ -107,7 +106,7 @@ func openAssetsInMemory(assetsDB string) (assets *assetsFromMMAP, err error) { r, err := zip.NewReader(f, int64(f.Len())) if err != nil { - return nil, fmt.Errorf(i18n.G("invalid zip db archive: %w"), err) + return nil, errors.New(gotext.Get("invalid zip db archive: %v", err)) } return &assetsFromMMAP{ @@ -120,7 +119,7 @@ func openAssetsInMemory(assetsDB string) (assets *assetsFromMMAP, err error) { // Save serializes in p policies. // Do not save again if p is already the origin. We don’t allow modifying GPOs or assets on the object. func (pols *Policies) Save(p string) (err error) { - defer decorate.OnError(&err, i18n.G("can't save policies to %s"), p) + defer decorate.OnError(&err, gotext.Get("can't save policies to %s", p)) if err := os.MkdirAll(p, 0700); err != nil { return err @@ -218,17 +217,17 @@ func (r *readerAtToReader) Read(p []byte) (n int, err error) { // The destination directory or file should not exists. // A uid or gid different from -1 means that every directories and files will be chown to that user and group. func (pols *Policies) SaveAssetsTo(ctx context.Context, relSrc, dest string, uid, gid int) (err error) { - defer decorate.OnError(&err, i18n.G("can't save assets to %s"), dest) + defer decorate.OnError(&err, gotext.Get("can't save assets to %s", dest)) log.Debugf(ctx, "export assets %q to %q", relSrc, dest) if pols.assets == nil { - return errors.New(i18n.G("no assets attached")) + return errors.New(gotext.Get("no assets attached")) } // error out if dest exists if _, err := os.Stat(dest); !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf(i18n.G("destination %q already exists"), dest) + return errors.New(gotext.Get("destination %q already exists", dest)) } baseDir := strings.TrimSuffix(relSrc, "/") @@ -240,7 +239,7 @@ func (pols *Policies) saveAssetsRecursively(relSrc, dest, baseDir string, uid, g relSrc = strings.TrimSuffix(relSrc, "/") if relSrc == "" { - return errors.New(i18n.G("no relSrc provided to look into database archive")) + return errors.New(gotext.Get("no relSrc provided to look into database archive")) } dstPath := filepath.Join(dest, strings.TrimPrefix(relSrc, baseDir)) @@ -300,7 +299,7 @@ func (pols *Policies) saveAssetsRecursively(relSrc, dest, baseDir string, uid, g // CompressAssets allow compressing all assets from SYSVOL in a single zip file. func CompressAssets(ctx context.Context, p string) (err error) { - defer decorate.OnError(&err, i18n.G("can't compress assets from %s"), p) + defer decorate.OnError(&err, gotext.Get("can't compress assets from %s", p)) log.Debugf(ctx, "compress assets from %q", p) @@ -431,7 +430,7 @@ func (pols Policies) GetUniqueRules() map[string][]entry.Entry { // chown either chown the file descriptor attached, or the path if this one is null to uid and gid. // It will know if we should skip chown for tests. func chown(p string, f *os.File, uid, gid int) (err error) { - defer decorate.OnError(&err, i18n.G("can't chown %q"), p) + defer decorate.OnError(&err, gotext.Get("can't chown %q", p)) if os.Getenv("ADSYS_SKIP_ROOT_CALLS") != "" { uid = -1 diff --git a/internal/policies/privilege/privilege.go b/internal/policies/privilege/privilege.go index 6c671a5a9..17631e568 100644 --- a/internal/policies/privilege/privilege.go +++ b/internal/policies/privilege/privilege.go @@ -25,9 +25,9 @@ import ( "sort" "strings" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/decorate" "gopkg.in/ini.v1" @@ -66,7 +66,7 @@ func NewWithDirs(sudoersDir, policyKitDir string) *Manager { // ApplyPolicy generates a privilege policy based on a list of entries. func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer bool, entries []entry.Entry) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply privilege policy to %s"), objectName) + defer decorate.OnError(&err, gotext.Get("can't apply privilege policy to %s", objectName)) // We only have privilege escalation on computers. if !isComputer { @@ -247,7 +247,7 @@ func splitAndNormalizeUsersAndGroups(ctx context.Context, v string) []string { // It lists /etc/polkit-1/localauthority.conf.d and take the highest file in ascii order to match // from the [configuration] section AdminIdentities value. func getSystemPolkitAdminIdentities(ctx context.Context, policyKitDir string) (adminIdentities string, err error) { - defer decorate.OnError(&err, i18n.G("can't get existing system polkit administrators in %s"), policyKitDir) + defer decorate.OnError(&err, gotext.Get("can't get existing system polkit administrators in %s", policyKitDir)) polkitConfFiles, err := filepath.Glob(filepath.Join(policyKitDir, "localauthority.conf.d", "*.conf")) if err != nil { @@ -260,7 +260,7 @@ func getSystemPolkitAdminIdentities(ctx context.Context, policyKitDir string) (a return "", err } if fi.IsDir() { - log.Warningf(ctx, i18n.G("%s is a directory. Ignoring."), p) + log.Warning(ctx, gotext.Get("%s is a directory. Ignoring.", p)) continue } diff --git a/internal/policies/proxy/proxy.go b/internal/policies/proxy/proxy.go index b0db2ef5a..7324af98c 100644 --- a/internal/policies/proxy/proxy.go +++ b/internal/policies/proxy/proxy.go @@ -19,8 +19,8 @@ import ( "strings" "github.com/godbus/dbus/v5" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/decorate" ) @@ -76,7 +76,7 @@ func New(bus *dbus.Conn, args ...Option) *Manager { // ApplyPolicy applies the system proxy policy (via a D-Bus call to ubuntu-proxy-manager). func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer bool, entries []entry.Entry) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply proxy policy")) + defer decorate.OnError(&err, gotext.Get("can't apply proxy policy")) // Proxy policies are currently only supported on computers if !isComputer { @@ -92,7 +92,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer for _, e := range entries { key := e.Key[strings.LastIndex(e.Key, "/")+1:] if !slices.Contains(supportedKeys, key) { - log.Warningf(ctx, i18n.G("Encountered unsupported key '%s' while parsing proxy entries, skipping it"), key) + log.Warning(ctx, gotext.Get("Encountered unsupported key '%s' while parsing proxy entries, skipping it", key)) } args[key] = e.Value } @@ -111,7 +111,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer args["auto"]).Err; err != nil { var dbusErr dbus.Error if errors.As(err, &dbusErr) && dbusErr.Name == errDBusServiceUnknownName { - log.Warningf(ctx, i18n.G("Not applying proxy settings as ubuntu-proxy-manager is not installed: %s"), dbusErr.Error()) + log.Warning(ctx, gotext.Get("Not applying proxy settings as ubuntu-proxy-manager is not installed: %s", dbusErr.Error())) return nil } return err diff --git a/internal/policies/scripts/scripts.go b/internal/policies/scripts/scripts.go index 675eab84d..b53ab4265 100644 --- a/internal/policies/scripts/scripts.go +++ b/internal/policies/scripts/scripts.go @@ -25,9 +25,9 @@ import ( "strconv" "strings" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/consts" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/policies/entry" "github.com/ubuntu/decorate" ) @@ -59,7 +59,7 @@ type Option func(*options) // New creates a manager with a specific scripts directory. func New(runDir string, unitStarter unitStarter, opts ...Option) (m *Manager, err error) { - defer decorate.OnError(&err, i18n.G("can't create scripts manager")) + defer decorate.OnError(&err, gotext.Get("can't create scripts manager")) // defaults args := options{ @@ -93,7 +93,7 @@ type AssetsDumper func(ctx context.Context, relSrc, dest string, uid int, gid in // ApplyPolicy generates a privilege policy based on a list of entries. func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer bool, entries []entry.Entry, assetsDumper AssetsDumper) (err error) { - defer decorate.OnError(&err, i18n.G("can't apply scripts policy to %s"), objectName) + defer decorate.OnError(&err, gotext.Get("can't apply scripts policy to %s", objectName)) log.Debugf(ctx, "Applying scripts policy to %s", objectName) @@ -102,13 +102,13 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer if !isComputer { user, err := m.userLookup(objectName) if err != nil { - return fmt.Errorf(i18n.G("couldn't retrieve user for %q: %v"), objectName, err) + return errors.New(gotext.Get("couldn't retrieve user for %q: %v", objectName, err)) } if uid, err = strconv.Atoi(user.Uid); err != nil { - return fmt.Errorf(i18n.G("couldn't convert %q to a valid uid for %q"), user.Uid, objectName) + return errors.New(gotext.Get("couldn't convert %q to a valid uid for %q", user.Uid, objectName)) } if gid, err = strconv.Atoi(user.Gid); err != nil { - return fmt.Errorf(i18n.G("couldn't convert %q to a valid gid for %q"), user.Gid, objectName) + return errors.New(gotext.Get("couldn't convert %q to a valid gid for %q", user.Gid, objectName)) } objectDir = filepath.Join("users", user.Uid) @@ -137,10 +137,10 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer // This creates objectDirPath and scriptsDir directory. // We chown objectDirPath and scripts (user specific) to uid:gid of the user. Nothing is done for the machine if err := mkdirAllWithUIDGid(objectPath, uid, gid); err != nil { - return fmt.Errorf(i18n.G("can't create object directory %q: %v"), objectPath, err) + return errors.New(gotext.Get("can't create object directory %q: %v", objectPath, err)) } if err := mkdirAllWithUIDGid(scriptsPath, uid, gid); err != nil { - return fmt.Errorf(i18n.G("can't create scripts directory %q: %v"), scriptsPath, err) + return errors.New(gotext.Get("can't create scripts directory %q: %v", scriptsPath, err)) } // Dump assets to scripts/scripts/ subdirectory with correct ownership. If no assets is present while entries != nil, we want to return an error. @@ -165,14 +165,14 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer log.Debugf(ctx, "%q: found %q. Marking as executable %q", e.Key, script, scriptFilePath) info, err := os.Stat(scriptFilePath) if errors.Is(err, os.ErrNotExist) { - return fmt.Errorf(i18n.G("script %q doesn't exist in SYSVOL scripts/ subdirectory"), script) + return errors.New(gotext.Get("script %q doesn't exist in SYSVOL scripts/ subdirectory", script)) } if info.IsDir() { - return fmt.Errorf(i18n.G("script %q is a directory and not a file to execute"), script) + return errors.New(gotext.Get("script %q is a directory and not a file to execute", script)) } // nolint:gosec // G302 - scripts need rx permissions if err := os.Chmod(scriptFilePath, 0550); err != nil { - return fmt.Errorf(i18n.G("can't change mode of script %qto %o: %v"), scriptFilePath, 0550, err) + return errors.New(gotext.Get("can't change mode of script %qto %o: %v", scriptFilePath, 0550, err)) } // append it to the list of our scripts @@ -225,7 +225,7 @@ func (m *Manager) ApplyPolicy(ctx context.Context, objectName string, isComputer // RunScripts executes all scripts in directory if ready and not already executed. // allowOrderMissing will not require order to exists if we are ready to execute. func RunScripts(ctx context.Context, order string, allowOrderMissing bool) (err error) { - defer decorate.OnError(&err, i18n.G("can't run scripts listed in %s"), order) + defer decorate.OnError(&err, gotext.Get("can't run scripts listed in %s", order)) log.Infof(ctx, "Calling RunScripts on %q", order) @@ -233,7 +233,7 @@ func RunScripts(ctx context.Context, order string, allowOrderMissing bool) (err // Ensure we are ready to execute if _, err := os.Stat(filepath.Join(baseDir, readyFlag)); err != nil { - return fmt.Errorf(i18n.G("%q is not ready to execute scripts"), order) + return errors.New(gotext.Get("%q is not ready to execute scripts", order)) } // create running flag for the user or machine @@ -273,7 +273,7 @@ func RunScripts(ctx context.Context, order string, allowOrderMissing bool) (err return err } if info.IsDir() { - return fmt.Errorf(i18n.G("%q is a directory and not a file"), order) + return errors.New(gotext.Get("%q is a directory and not a file", order)) } scanner := bufio.NewScanner(f) @@ -300,14 +300,14 @@ func RunScripts(ctx context.Context, order string, allowOrderMissing bool) (err func mkdirAllWithUIDGid(p string, uid, gid int) error { if err := os.MkdirAll(p, 0750); err != nil { - return fmt.Errorf(i18n.G("can't create scripts directory %q: %v"), p, err) + return fmt.Errorf(gotext.Get("can't create scripts directory %q: %v", p, err)) } return chown(p, nil, uid, gid) } func createFlagFile(ctx context.Context, path string, uid, gid int) (err error) { - defer decorate.OnError(&err, i18n.G("can't create flag file %q"), path) + defer decorate.OnError(&err, gotext.Get("can't create flag file %q", path)) log.Debugf(ctx, "Create script flag %q", path) f, err := os.Create(path) @@ -325,7 +325,7 @@ func createFlagFile(ctx context.Context, path string, uid, gid int) (err error) // chown either chown the file descriptor attached, or the path if this one is null to uid and gid. // It will know if we should skip chown for tests. func chown(p string, f *os.File, uid, gid int) (err error) { - defer decorate.OnError(&err, i18n.G("can't chown %q"), p) + defer decorate.OnError(&err, gotext.Get("can't chown %q", p)) if os.Getenv("ADSYS_SKIP_ROOT_CALLS") != "" { uid = -1 diff --git a/internal/stdforward/stdforward.go b/internal/stdforward/stdforward.go index 531f12c82..315e73648 100644 --- a/internal/stdforward/stdforward.go +++ b/internal/stdforward/stdforward.go @@ -8,8 +8,8 @@ import ( "os" "sync" + "github.com/leonelquinteros/gotext" log "github.com/sirupsen/logrus" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" ) @@ -66,7 +66,7 @@ func AddStderrWriter(w io.Writer) (remove func(), err error) { } func addWriter(dest *forwarder, std **os.File, w io.Writer) (f func(), err error) { - defer decorate.OnError(&err, i18n.G("can't redirect output")) + defer decorate.OnError(&err, gotext.Get("can't redirect output")) // Initialize our forwarder var onceErr error diff --git a/internal/systemd/systemd.go b/internal/systemd/systemd.go index 767c8b0bb..2c557d3e9 100644 --- a/internal/systemd/systemd.go +++ b/internal/systemd/systemd.go @@ -8,7 +8,7 @@ import ( systemdDbus "github.com/coreos/go-systemd/v22/dbus" "github.com/godbus/dbus/v5" - "github.com/ubuntu/adsys/internal/i18n" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/decorate" ) @@ -32,7 +32,7 @@ func New(bus *dbus.Conn) (*DefaultCaller, error) { // StartUnit starts the given unit. func (s DefaultCaller) StartUnit(ctx context.Context, unit string) (err error) { - defer decorate.OnError(&err, i18n.G("failed to start unit %s"), unit) + defer decorate.OnError(&err, gotext.Get("failed to start unit %s", unit)) reschan := make(chan string) if _, err = s.conn.StartUnitContext(ctx, unit, "replace", reschan); err != nil { @@ -40,14 +40,14 @@ func (s DefaultCaller) StartUnit(ctx context.Context, unit string) (err error) { } if job := <-reschan; job != jobDone { - return errors.New(i18n.G("start job failed")) + return errors.New(gotext.Get("start job failed")) } return nil } // StopUnit stops the given unit. func (s DefaultCaller) StopUnit(ctx context.Context, unit string) (err error) { - defer decorate.OnError(&err, i18n.G("failed to stop unit %s"), unit) + defer decorate.OnError(&err, gotext.Get("failed to stop unit %s", unit)) reschan := make(chan string) if _, err = s.conn.StopUnitContext(ctx, unit, "replace", reschan); err != nil { @@ -55,14 +55,14 @@ func (s DefaultCaller) StopUnit(ctx context.Context, unit string) (err error) { } if job := <-reschan; job != jobDone { - return errors.New(i18n.G("stop job failed")) + return errors.New(gotext.Get("stop job failed")) } return nil } // EnableUnit enables the given unit. func (s DefaultCaller) EnableUnit(ctx context.Context, unit string) (err error) { - defer decorate.OnError(&err, i18n.G("failed to enable unit %s"), unit) + defer decorate.OnError(&err, gotext.Get("failed to enable unit %s", unit)) if _, _, err := s.conn.EnableUnitFilesContext(ctx, []string{unit}, false, true); err != nil { return err @@ -72,7 +72,7 @@ func (s DefaultCaller) EnableUnit(ctx context.Context, unit string) (err error) // DisableUnit disables the given unit. func (s DefaultCaller) DisableUnit(ctx context.Context, unit string) (err error) { - defer decorate.OnError(&err, i18n.G("failed to disable unit %s"), unit) + defer decorate.OnError(&err, gotext.Get("failed to disable unit %s", unit)) if _, err := s.conn.DisableUnitFilesContext(ctx, []string{unit}, false); err != nil { return err @@ -82,7 +82,7 @@ func (s DefaultCaller) DisableUnit(ctx context.Context, unit string) (err error) // DaemonReload scans and reloads unit files. This is an equivalent to systemctl daemon-reload. func (s DefaultCaller) DaemonReload(ctx context.Context) (err error) { - defer decorate.OnError(&err, i18n.G("failed to reload units")) + defer decorate.OnError(&err, gotext.Get("failed to reload units")) return s.conn.ReloadContext(ctx) } diff --git a/internal/watchdservice/watchdservice.go b/internal/watchdservice/watchdservice.go index 160a5f765..52581fbb7 100644 --- a/internal/watchdservice/watchdservice.go +++ b/internal/watchdservice/watchdservice.go @@ -4,15 +4,14 @@ package watchdservice import ( "context" "errors" - "fmt" "os" "strings" "time" "github.com/kardianos/service" + "github.com/leonelquinteros/gotext" watchdconfig "github.com/ubuntu/adsys/internal/config/watchd" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/loghooks" "github.com/ubuntu/adsys/internal/watcher" "github.com/ubuntu/decorate" @@ -124,8 +123,8 @@ func New(ctx context.Context, opts ...option) (*WatchdService, error) { // UpdateDirs updates the watcher with the new directories. func (s *WatchdService) UpdateDirs(ctx context.Context, dirs []string) (err error) { - defer decorate.OnError(&err, i18n.G("failed to change directories to watch")) - log.Info(ctx, i18n.G("Updating directories to watch")) + defer decorate.OnError(&err, gotext.Get("failed to change directories to watch")) + log.Info(ctx, gotext.Get("Updating directories to watch")) if err := s.watcher.UpdateDirs(ctx, dirs); err != nil { return err @@ -138,8 +137,8 @@ func (s *WatchdService) UpdateDirs(ctx context.Context, dirs []string) (err erro // Start starts the watcher service. func (s *WatchdService) Start(ctx context.Context) (err error) { - defer decorate.OnError(&err, i18n.G("failed to start service")) - log.Info(ctx, i18n.G("Starting service")) + defer decorate.OnError(&err, gotext.Get("failed to start service")) + log.Info(ctx, gotext.Get("Starting service")) stat, err := s.service.Status() if err != nil { @@ -160,8 +159,8 @@ func (s *WatchdService) Start(ctx context.Context) (err error) { // Stop stops the watcher service. func (s *WatchdService) Stop(ctx context.Context) (err error) { - defer decorate.OnError(&err, i18n.G("failed to stop service")) - log.Info(ctx, i18n.G("Stopping service")) + defer decorate.OnError(&err, gotext.Get("failed to stop service")) + log.Info(ctx, gotext.Get("Stopping service")) stat, err := s.service.Status() if err != nil { @@ -204,8 +203,8 @@ func (s *WatchdService) waitForStatus(ctx context.Context, status service.Status // Restart restarts the watcher service. func (s *WatchdService) Restart(ctx context.Context) (err error) { - defer decorate.OnError(&err, i18n.G("failed to restart service")) - log.Info(ctx, i18n.G("Restarting service")) + defer decorate.OnError(&err, gotext.Get("failed to restart service")) + log.Info(ctx, gotext.Get("Restarting service")) stat, err := s.service.Status() if err != nil { @@ -228,8 +227,8 @@ func (s *WatchdService) Restart(ctx context.Context) (err error) { // Status provides a status of the watcher service in a pretty format. func (s *WatchdService) Status(ctx context.Context) (status string, err error) { - defer decorate.OnError(&err, i18n.G("failed to retrieve status for service")) - log.Debug(ctx, i18n.G("Getting status from service")) + defer decorate.OnError(&err, gotext.Get("failed to retrieve status for service")) + log.Debug(ctx, gotext.Get("Getting status from service")) uninstalledState := service.Status(42) stat, err := s.service.Status() @@ -242,19 +241,19 @@ func (s *WatchdService) Status(ctx context.Context) (status string, err error) { var serviceStatus string switch stat { case service.StatusRunning: - serviceStatus = i18n.G("running") + serviceStatus = gotext.Get("running") case service.StatusStopped: - serviceStatus = i18n.G("stopped") + serviceStatus = gotext.Get("stopped") case service.StatusUnknown: - serviceStatus = i18n.G("unknown") + serviceStatus = gotext.Get("unknown") case uninstalledState: - serviceStatus = i18n.G("not installed") + serviceStatus = gotext.Get("not installed") default: - serviceStatus = i18n.G("undefined") + serviceStatus = gotext.Get("undefined") } var statStr strings.Builder - statStr.WriteString(fmt.Sprintf(i18n.G("Service status: %s"), serviceStatus)) + statStr.WriteString(gotext.Get("Service status: %s", serviceStatus)) // If the service is installed, attempt to figure out the configured // directories and the binary path. @@ -272,7 +271,7 @@ func (s *WatchdService) Status(ctx context.Context) (status string, err error) { exePath, err = os.Executable() if err != nil { - log.Warningf(ctx, i18n.G("Failed to get current executable path: %v"), err) + log.Warning(ctx, gotext.Get("Failed to get current executable path: %v", err)) } if exePath != svcInfo.binPath && svcInfo.binPath != "" { @@ -281,21 +280,21 @@ func (s *WatchdService) Status(ctx context.Context) (status string, err error) { } statStr.WriteString("\n\n") - statStr.WriteString(fmt.Sprintf(i18n.G("Config file: %s\n"), svcInfo.configFile)) - statStr.WriteString(i18n.G("Watched directories: ")) + statStr.WriteString(gotext.Get("Config file: %s\n", svcInfo.configFile)) + statStr.WriteString(gotext.Get("Watched directories: ")) if len(svcInfo.dirs) == 0 { - statStr.WriteString(i18n.G("no configured directories")) + statStr.WriteString(gotext.Get("no configured directories")) } for _, dir := range svcInfo.dirs { - statStr.WriteString(fmt.Sprintf(i18n.G("\n - %s"), dir)) + statStr.WriteString(gotext.Get("\n - %s", dir)) } if pathMismatch { - log.Warningf(ctx, i18n.G(`Service binary path does not match executable path + log.Warning(ctx, gotext.Get(`Service binary path does not match executable path Service binary path: %s -Current executable path: %s`), svcInfo.binPath, exePath) +Current executable path: %s`, svcInfo.binPath, exePath)) } status = statStr.String() @@ -322,7 +321,7 @@ func (s *WatchdService) ConfigFile(ctx context.Context) (string, error) { // args returns the service configuration extracted from the // service arguments. func (s *WatchdService) args(ctx context.Context) (svcInfo serviceInfo, err error) { - defer decorate.OnError(&err, i18n.G("failed to get service info from arguments")) + defer decorate.OnError(&err, gotext.Get("failed to get service info from arguments")) svcInfo = serviceInfo{} binPath, args, err := s.serviceArgs() @@ -344,8 +343,8 @@ func (s *WatchdService) args(ctx context.Context) (svcInfo serviceInfo, err erro // Install installs the watcher service and starts it if it doesn't // automatically start in due time. func (s *WatchdService) Install(ctx context.Context) (err error) { - defer decorate.OnError(&err, i18n.G("failed to install service")) - log.Info(ctx, i18n.G("Installing watcher service")) + defer decorate.OnError(&err, gotext.Get("failed to install service")) + log.Info(ctx, gotext.Get("Installing watcher service")) if err := s.service.Install(); err != nil { return err } @@ -362,12 +361,12 @@ func (s *WatchdService) Install(ctx context.Context) (err error) { // If the service is not installed it logs a message and returns. // If the service is running it attempts to stop it first. func (s *WatchdService) Uninstall(ctx context.Context) (err error) { - defer decorate.OnError(&err, i18n.G("failed to uninstall service")) - log.Info(ctx, i18n.G("Uninstalling watcher service")) + defer decorate.OnError(&err, gotext.Get("failed to uninstall service")) + log.Info(ctx, gotext.Get("Uninstalling watcher service")) stat, err := s.service.Status() if errors.Is(err, service.ErrNotInstalled) { - log.Info(ctx, i18n.G("Service is not installed")) + log.Info(ctx, gotext.Get("Service is not installed")) return nil } @@ -405,8 +404,8 @@ func (s *WatchdService) Uninstall(ctx context.Context) (err error) { // Run runs the watcher service. func (s *WatchdService) Run(ctx context.Context) (err error) { - defer decorate.OnError(&err, i18n.G("failed to run service")) + defer decorate.OnError(&err, gotext.Get("failed to run service")) - log.Info(ctx, i18n.G("Running watcher service")) + log.Info(ctx, gotext.Get("Running watcher service")) return s.service.Run() } diff --git a/internal/watchdservice/watchdservice_linux.go b/internal/watchdservice/watchdservice_linux.go index 55de64edd..787f3892b 100644 --- a/internal/watchdservice/watchdservice_linux.go +++ b/internal/watchdservice/watchdservice_linux.go @@ -1,13 +1,14 @@ package watchdservice import ( + "errors" "fmt" "slices" "strings" "github.com/godbus/dbus/v5" + "github.com/leonelquinteros/gotext" "github.com/ubuntu/adsys/internal/consts" - "github.com/ubuntu/adsys/internal/i18n" ) // execStart represents the ExecStart option of a systemd service. It maps to @@ -44,7 +45,7 @@ func (s *WatchdService) serviceArgs() (string, string, error) { var execStarts []execStart err = svcUnit.StoreProperty(fmt.Sprintf("%s.ExecStart", consts.SystemdDbusServiceInterface), &execStarts) if err != nil || len(execStarts) == 0 { - return "", "", fmt.Errorf(i18n.G("could not find %s unit on systemd bus: no service installed? %v"), s.Name(), err) + return "", "", errors.New(gotext.Get("could not find %s unit on systemd bus: no service installed? %v", s.Name(), err)) } binPath := execStarts[0].BinPath diff --git a/internal/watchdtui/watchdtui.go b/internal/watchdtui/watchdtui.go index 05b65dea8..7a02f2dd1 100644 --- a/internal/watchdtui/watchdtui.go +++ b/internal/watchdtui/watchdtui.go @@ -18,7 +18,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/leonelquinteros/gotext" watchdconfig "github.com/ubuntu/adsys/internal/config/watchd" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/adsys/internal/watchdservice" ) @@ -165,8 +164,8 @@ func initialModel(configFile string, prevConfigFile string, isDefaultConfig bool switch i { case 0: - t.Placeholder = fmt.Sprintf(i18n.G("Config file location (leave blank for default: %s)"), m.defaultConfig) - t.Prompt = i18n.G("Config file: ") + t.Placeholder = gotext.Get("Config file location (leave blank for default: %s)", m.defaultConfig) + t.Prompt = gotext.Get("Config file: ") t.PromptStyle = boldStyle t.Focus() @@ -176,7 +175,7 @@ func initialModel(configFile string, prevConfigFile string, isDefaultConfig bool t.SetValue(configFile) } case 1: - t.Placeholder = i18n.G("Directory to watch (one per line)") + t.Placeholder = gotext.Get("Directory to watch (one per line)") } m.inputs[i] = t @@ -411,7 +410,7 @@ func updateConfigInputError(input *textinput.Model) { if errors.Is(err, os.ErrNotExist) { input.Err = nil if !filepath.IsAbs(value) { - input.Err = fmt.Errorf(i18n.G("%s will be the absolute path"), absPath) + input.Err = errors.New(gotext.Get("%s will be the absolute path", absPath)) } return } @@ -423,12 +422,12 @@ func updateConfigInputError(input *textinput.Model) { } if stat.IsDir() { - input.Err = fmt.Errorf(i18n.G("%s is a directory; will create %s.yaml inside"), absPath, watchdconfig.CmdName) + input.Err = errors.New(gotext.Get("%s is a directory; will create %s.yaml inside", absPath, watchdconfig.CmdName)) return } if stat.Mode().IsRegular() { - input.Err = fmt.Errorf(i18n.G("%s: file already exists and will be overwritten"), absPath) + input.Err = errors.New(gotext.Get("%s: file already exists and will be overwritten", absPath)) return } @@ -442,7 +441,7 @@ func (m *model) updateDirInputErrorAndStyle(i int) { if m.inputs[i].Value() == "" { m.inputs[i].Err = nil if len(m.inputs) == 2 { - m.inputs[i].Err = errors.New(i18n.G("please enter at least one directory")) + m.inputs[i].Err = errors.New(gotext.Get("please enter at least one directory")) } return } @@ -454,18 +453,18 @@ func (m *model) updateDirInputErrorAndStyle(i int) { m.inputs[i].TextStyle = successStyle if stat, err := os.Stat(absPath); err != nil { - m.inputs[i].Err = fmt.Errorf(i18n.G("%s: directory does not exist, please enter a valid path"), absPath) + m.inputs[i].Err = errors.New(gotext.Get("%s: directory does not exist, please enter a valid path", absPath)) m.inputs[i].TextStyle = noStyle } else if !stat.IsDir() { - m.inputs[i].Err = fmt.Errorf(i18n.G("%s: is not a directory"), absPath) + m.inputs[i].Err = errors.New(gotext.Get("%s: is not a directory", absPath)) m.inputs[i].TextStyle = noStyle } } func (m model) submitText() string { - text := i18n.G("Install") + text := gotext.Get("Install") if m.prevConfig != "" { - text = i18n.G("Update") + text = gotext.Get("Update") } return text } @@ -473,20 +472,20 @@ func (m model) submitText() string { // View renders the UI based on the data in the model. func (m model) View() string { if m.loading { - return fmt.Sprintf(i18n.G("%s installing service... please wait."), m.spinner.View()) + return gotext.Get("%s installing service... please wait.", m.spinner.View()) } if err := m.err; err != nil { - return fmt.Sprintf(i18n.G("Could not install service: %v\n"), err) + return gotext.Get("Could not install service: %v\n", err) } if !m.typing { - return fmt.Sprintln(i18n.G("Service adwatchd was successfully installed and is now running.")) + return fmt.Sprintln(gotext.Get("Service adwatchd was successfully installed and is now running.")) } var b strings.Builder - b.WriteString(titleStyle.Render(i18n.G("Ubuntu AD Watch Daemon Installer"))) + b.WriteString(titleStyle.Render(gotext.Get("Ubuntu AD Watch Daemon Installer"))) b.WriteString("\n\n") // Display config input and hint @@ -501,7 +500,7 @@ func (m model) View() string { b.WriteString("\n\n") - directoriesMsg := i18n.G("Directories:") + directoriesMsg := gotext.Get("Directories:") if m.focusIndex > 0 && m.focusIndex < len(m.inputs) { b.WriteString(focusedStyle.Render(directoriesMsg)) } else { diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 96a9684a8..425aa40c8 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -6,7 +6,6 @@ package watcher import ( "context" "errors" - "fmt" "os" "path/filepath" "strconv" @@ -15,8 +14,8 @@ import ( "github.com/fsnotify/fsnotify" "github.com/kardianos/service" + "github.com/leonelquinteros/gotext" log "github.com/ubuntu/adsys/internal/grpc/logstreamer" - "github.com/ubuntu/adsys/internal/i18n" "github.com/ubuntu/decorate" "gopkg.in/ini.v1" ) @@ -58,7 +57,7 @@ func init() { // New returns a new Watcher instance. func New(ctx context.Context, initialDirs []string, opts ...option) (*Watcher, error) { if len(initialDirs) == 0 { - return nil, errors.New(i18n.G("no directories to watch")) + return nil, errors.New(gotext.Get("no directories to watch")) } // Set default options @@ -104,7 +103,7 @@ func New(ctx context.Context, initialDirs []string, opts ...option) (*Watcher, e go func() { defer close(watching) if errWatching := w.watch(ctx, dirs, initError); errWatching != nil { - log.Warningf(ctx, i18n.G("Watch failed: %v"), errWatching) + log.Warning(ctx, gotext.Get("Watch failed: %v", errWatching)) } }() err := <-initError @@ -112,7 +111,7 @@ func New(ctx context.Context, initialDirs []string, opts ...option) (*Watcher, e case stopCmd: if watching == nil { - cmdErr <- errors.New(i18n.G("the service is already stopping or not running")) + cmdErr <- errors.New(gotext.Get("the service is already stopping or not running")) break } @@ -140,7 +139,7 @@ func New(ctx context.Context, initialDirs []string, opts ...option) (*Watcher, e // asynchronously. When our function exits, the service manager registers a // signal handler that calls Stop when a signal is received. func (w *Watcher) Start(_ service.Service) (err error) { - defer decorate.OnError(&err, i18n.G("can't start service")) + defer decorate.OnError(&err, gotext.Get("can't start service")) return w.send(nil, startCmd, nil) } @@ -149,7 +148,7 @@ func (w *Watcher) Start(_ service.Service) (err error) { // Documentation states that the function should not take more than a few // seconds to execute. func (w *Watcher) Stop(_ service.Service) (err error) { - defer decorate.OnError(&err, i18n.G("can't stop service")) + defer decorate.OnError(&err, gotext.Get("can't stop service")) return w.send(nil, stopCmd, nil) } @@ -167,21 +166,21 @@ func (w *Watcher) send(ctx *context.Context, action int, dirs []string) error { // UpdateDirs restarts watch loop with new directories. No action is taken if // one or more directories do not exist. func (w *Watcher) UpdateDirs(ctx context.Context, dirs []string) (err error) { - defer decorate.OnError(&err, i18n.G("can't update directories to watch")) - log.Debugf(ctx, i18n.G("Updating directories to %v"), dirs) + defer decorate.OnError(&err, gotext.Get("can't update directories to watch")) + log.Debug(ctx, gotext.Get("Updating directories to %v", dirs)) if len(dirs) == 0 { - return errors.New(i18n.G("need at least one directory to watch")) + return errors.New(gotext.Get("need at least one directory to watch")) } for _, dir := range dirs { if _, err := os.Stat(dir); os.IsNotExist(err) { - return fmt.Errorf(i18n.G("directory %q does not exist"), dir) + return errors.New(gotext.Get("directory %q does not exist", dir)) } } if err := w.send(&ctx, stopCmd, nil); err != nil { - log.Warningf(ctx, i18n.G("Error stopping watcher: %v"), err) + log.Warning(ctx, gotext.Get("Error stopping watcher: %v", err)) } return w.send(&ctx, startCmd, dirs) @@ -189,18 +188,18 @@ func (w *Watcher) UpdateDirs(ctx context.Context, dirs []string) (err error) { // watch is the main watch loop. func (w *Watcher) watch(ctx context.Context, dirs []string, initError chan<- error) (err error) { - defer decorate.OnError(&err, i18n.G("can't watch over %v"), dirs) + defer decorate.OnError(&err, gotext.Get("can't watch over %v", dirs)) fsWatcher, err := fsnotify.NewWatcher() if err != nil { - initError <- fmt.Errorf(i18n.G("could not initialize fsnotify watcher: %v"), err) + initError <- errors.New(gotext.Get("could not initialize fsnotify watcher: %v", err)) } defer fsWatcher.Close() // Collect directories to watch. for _, dir := range dirs { if err := watchSubDirs(ctx, fsWatcher, dir); err != nil { - initError <- fmt.Errorf(i18n.G("failed to watch directory %q: %v"), dir, err) + initError <- errors.New(gotext.Get("failed to watch directory %q: %v", dir, err)) } } @@ -218,7 +217,7 @@ func (w *Watcher) watch(ctx context.Context, dirs []string, initError chan<- err if !ok || event.Name == "" { continue } - log.Debugf(ctx, i18n.G("Got event: %v"), event) + log.Debug(ctx, gotext.Get("Got event: %v", event)) // If the modified file is our own change, ignore it. if strings.EqualFold(filepath.Base(event.Name), gptFileName) { @@ -228,18 +227,18 @@ func (w *Watcher) watch(ctx context.Context, dirs []string, initError chan<- err if event.Has(fsnotify.Create) { fileInfo, err := os.Stat(event.Name) if err != nil { - log.Warningf(ctx, i18n.G("Failed to stat: %s"), err) + log.Warning(ctx, gotext.Get("Failed to stat: %s", err)) continue } // Add new detected files and directories to the watch list. if fileInfo.IsDir() { if err := watchSubDirs(ctx, fsWatcher, event.Name); err != nil { - log.Warningf(ctx, i18n.G("Failed to watch: %s"), err) + log.Warning(ctx, gotext.Get("Failed to watch: %s", err)) } } else if fileInfo.Mode().IsRegular() { if err := fsWatcher.Add(event.Name); err != nil { - log.Warningf(ctx, i18n.G("Failed add watcher on %q: %s"), event.Name, err) + log.Warning(ctx, gotext.Get("Failed add watcher on %q: %s", event.Name, err)) } } } @@ -247,7 +246,7 @@ func (w *Watcher) watch(ctx context.Context, dirs []string, initError chan<- err // Remove deleted or renamed files/directories from the watch list. if event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) { if err := fsWatcher.Remove(event.Name); err != nil { - log.Debugf(ctx, i18n.G("Failed to remove watcher on %q: %s"), event.Name, err) + log.Debug(ctx, gotext.Get("Failed to remove watcher on %q: %s", event.Name, err)) } } @@ -289,7 +288,7 @@ func (w *Watcher) watch(ctx context.Context, dirs []string, initError chan<- err case err, ok := <-fsWatcher.Errors: if ok { - log.Warningf(ctx, i18n.G("Got event error: %v"), err) + log.Warning(ctx, gotext.Get("Got event error: %v", err)) } continue @@ -298,7 +297,7 @@ func (w *Watcher) watch(ctx context.Context, dirs []string, initError chan<- err updateVersions(ctx, modifiedRootDirs) case <-ctx.Done(): - log.Infof(ctx, i18n.G("Watcher stopped")) + log.Infof(ctx, gotext.Get("Watcher stopped")) // Check if there was a timer in progress to not miss an update before exiting. if refreshTimer.Stop() { updateVersions(ctx, modifiedRootDirs) @@ -310,14 +309,14 @@ func (w *Watcher) watch(ctx context.Context, dirs []string, initError chan<- err // watchSubDirs walks a given directory and adds all subdirectories to the watch list. func watchSubDirs(ctx context.Context, fsWatcher *fsnotify.Watcher, path string) (err error) { - defer decorate.OnError(&err, i18n.G("can't watch directory and children of %s"), path) - log.Debugf(ctx, i18n.G("Watching %s and children"), path) + defer decorate.OnError(&err, gotext.Get("can't watch directory and children of %s", path)) + log.Debug(ctx, gotext.Get("Watching %s and children", path)) err = filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { if err != nil { return err } - log.Debugf(ctx, i18n.G("Watching: %v"), p) + log.Debug(ctx, gotext.Get("Watching: %v", p)) return fsWatcher.Add(p) }) return err @@ -344,7 +343,7 @@ func getRootDir(path string, rootDirs []string) (string, error) { } } if rootDir == "" { - return "", fmt.Errorf(i18n.G("no root directory matching %s found"), path) + return "", errors.New(gotext.Get("no root directory matching %s found", path)) } return rootDir, nil @@ -355,21 +354,21 @@ func updateVersions(ctx context.Context, modifiedRootDirs []string) { for _, dir := range modifiedRootDirs { gptIniPath := filepath.Join(dir, gptFileName) if err := bumpVersion(ctx, gptIniPath); err != nil { - log.Warningf(ctx, i18n.G("Failed to bump %s version: %s"), gptIniPath, err) + log.Warning(ctx, gotext.Get("Failed to bump %s version: %s", gptIniPath, err)) } } } // bumpVersion does the actual bumping of the version in the given GPT.ini file. func bumpVersion(ctx context.Context, path string) (err error) { - defer decorate.OnError(&err, i18n.G("can't bump version for %s"), path) - log.Infof(ctx, i18n.G("Bumping version for %s"), path) + defer decorate.OnError(&err, gotext.Get("can't bump version for %s", path)) + log.Info(ctx, gotext.Get("Bumping version for %s", path)) cfg, err := ini.Load(path) // If the file doesn't exist, create it and initialize the key to be updated. if err != nil { - log.Infof(ctx, i18n.G("error loading ini contents: %v, creating a new file"), err) + log.Info(ctx, gotext.Get("error loading ini contents: %v, creating a new file", err)) cfg = ini.Empty() if _, err := cfg.Section("General").NewKey("Version", "0"); err != nil { return err From 06b6526e0de1317bfabd8e087de9cc2a0cd83e64 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 2 Jan 2024 16:10:00 +0100 Subject: [PATCH 2/2] Update go i18n dependencies --- go.mod | 3 +-- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3e27daee9..5b62ab2e8 100644 --- a/go.mod +++ b/go.mod @@ -23,14 +23,13 @@ require ( github.com/mvo5/libsmbclient-go v0.0.0-20220607104205-b69795f58cd0 github.com/pkg/sftp v1.13.6 github.com/sirupsen/logrus v1.9.3 - github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae github.com/ubuntu/decorate v0.0.0-20230125165522-2d5b0a9bb117 - github.com/ubuntu/go-i18n v0.0.0-20230830081132-d6654b958899 + github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.17.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/net v0.19.0 diff --git a/go.sum b/go.sum index 404c4c478..0f9eeec2e 100644 --- a/go.sum +++ b/go.sum @@ -244,8 +244,6 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 h1:PaunR+BhraKSLxt2awQ42zofkP+NKh/VjQ0PjIMk/y4= -github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= @@ -275,8 +273,8 @@ github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= github.com/ubuntu/decorate v0.0.0-20230125165522-2d5b0a9bb117 h1:XQpsQG5lqRJlx4mUVHcJvyyc1rdTI9nHvwrdfcuy8aM= github.com/ubuntu/decorate v0.0.0-20230125165522-2d5b0a9bb117/go.mod h1:mx0TjbqsaDD9DUT5gA1s3hw47U6RIbbIBfvGzR85K0g= -github.com/ubuntu/go-i18n v0.0.0-20230830081132-d6654b958899 h1:FquZaRc8X0+ImXSHKrqb4hdzcvlCHhXDNLjxzkwit6Y= -github.com/ubuntu/go-i18n v0.0.0-20230830081132-d6654b958899/go.mod h1:ZRhdDyx6YkKz/YiMWi0gS3uMCltgdaKz9IpkiNf/GRg= +github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= +github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47/go.mod h1:ZRhdDyx6YkKz/YiMWi0gS3uMCltgdaKz9IpkiNf/GRg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=