Skip to content

Commit aa0aa95

Browse files
⭐ Allow overriding provider registry url (#5943)
* ⭐ Allow overriding provider registry url You can override the provider registry by either setting ``` registry_url: https://releases.mondoo.com/providers ``` in your mondoo config or by setting the `MONDOO_REGISTRY_URL` environment variable Future Improvements: - We should cache the provider manifest for some period of time - We can use etags to check if the provider manifest has actually changed * rename registry_url to providers_url * fix lint * fix issue with the providers_url passed through --config not being used * add providers url to status * fix incorrect check * fix lint * fix(lint): handle errcheck by assigning viper.BindEnv results to _ Signed-off-by: Gary Bright <gary@mondoo.com> * remove failing ci test * don't keep retrying on econnrefused * default providers registry url * Fix early config parsing --------- Signed-off-by: Gary Bright <gary@mondoo.com> Co-authored-by: Gary Bright <gary@mondoo.com>
1 parent 62dc683 commit aa0aa95

File tree

7 files changed

+387
-84
lines changed

7 files changed

+387
-84
lines changed

apps/cnquery/cmd/root.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ func init() {
120120
viper.BindPFlag("log-level", rootCmd.PersistentFlags().Lookup("log-level"))
121121
viper.BindPFlag("api_proxy", rootCmd.PersistentFlags().Lookup("api-proxy"))
122122
viper.BindPFlag("auto_update", rootCmd.PersistentFlags().Lookup("auto-update"))
123-
viper.BindEnv("features")
123+
_ = viper.BindEnv("features")
124+
_ = viper.BindEnv("providers_url")
124125

125126
config.Init(rootCmd)
126127
}

apps/cnquery/cmd/status.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"encoding/json"
99
"os"
1010
"strings"
11+
"syscall"
1112
"time"
1213

1314
"github.com/cockroachdb/errors"
@@ -142,6 +143,12 @@ func checkStatus() (Status, error) {
142143
}
143144
}
144145

146+
if opts.ProvidersURL != "" {
147+
s.Client.ProvidersURL = opts.ProvidersURL
148+
} else {
149+
s.Client.ProvidersURL = providers.DefaultProviderRegistryURL
150+
}
151+
145152
return s, nil
146153
}
147154

@@ -164,6 +171,7 @@ type ClientStatus struct {
164171
Hostname string `json:"hostname,omitempty"`
165172
Registered bool `json:"registered,omitempty"`
166173
PingPongError error `json:"pingPongError,omitempty"`
174+
ProvidersURL string `json:"providersUrl,omitempty"`
167175
}
168176

169177
func (s Status) RenderCliStatus() {
@@ -188,6 +196,8 @@ func (s Status) RenderCliStatus() {
188196
}
189197
}
190198

199+
log.Info().Msg("Providers URL:\t" + s.Client.ProvidersURL)
200+
191201
installed, outdated, err := getProviders()
192202
if err != nil {
193203
log.Warn().Err(err).Msg("failed to get provider info")
@@ -260,16 +270,24 @@ func getProviders() ([]string, []string, error) {
260270
if err != nil {
261271
return nil, nil, err
262272
}
273+
var errCircuitBreaker error
263274
for _, provider := range allProviders {
264275
installed = append(installed, provider.Name)
265-
latestVersion, err := providers.LatestVersion(provider.Name)
266-
if err != nil {
267-
continue
268-
}
269-
if latestVersion != provider.Version && provider.Name != "core" {
270-
outdated = append(outdated, provider.Name)
276+
if errCircuitBreaker == nil {
277+
latestVersion, err := providers.LatestVersion(context.Background(), provider.Name)
278+
if err != nil {
279+
// If we get a connection refused, we assume this will happen for all providers
280+
// so we will return early
281+
if errors.Is(err, syscall.ECONNREFUSED) {
282+
errCircuitBreaker = err
283+
}
284+
continue
285+
}
286+
if latestVersion != provider.Version && provider.Name != "core" {
287+
outdated = append(outdated, provider.Name)
288+
}
271289
}
272290
}
273291

274-
return installed, outdated, nil
292+
return installed, outdated, errCircuitBreaker
275293
}

cli/config/config.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/cockroachdb/errors"
1313
"github.com/rs/zerolog/log"
1414
"github.com/spf13/cobra"
15+
"github.com/spf13/pflag"
1516
"github.com/spf13/viper"
1617
"go.mondoo.com/cnquery/v12"
1718
"go.mondoo.com/cnquery/v12/logger"
@@ -49,6 +50,19 @@ func Init(rootCmd *cobra.Command) {
4950
})
5051
// persistent flags are global for the application
5152
rootCmd.PersistentFlags().StringVar(&UserProvidedPath, "config", "", "Set config file path (default $HOME/.config/mondoo/mondoo.yml)")
53+
54+
// We need to parse the flags really early in the process, so that
55+
// the config path is set before we initialize viper. This is because
56+
// the providers configuration needs to be available before the rootCmd
57+
// is executed as it does things like tries to download a provider if its missing
58+
// See AttachCLIs in cli/providers/providers.go
59+
flags := pflag.NewFlagSet("set", pflag.ContinueOnError)
60+
flags.ParseErrorsAllowlist.UnknownFlags = true
61+
flags.StringVar(&UserProvidedPath, "config", "", "")
62+
flags.BoolP("help", "h", false, "")
63+
if err := flags.Parse(os.Args); err != nil {
64+
log.Debug().Err(err).Msg("could not parse flags")
65+
}
5266
}
5367

5468
func InitViperConfig() {
@@ -227,6 +241,11 @@ type CommonOpts struct {
227241

228242
// annotations that will be applied to all assets
229243
Annotations map[string]string `json:"annotations,omitempty" mapstructure:"annotations"`
244+
245+
// ProvidersURL is the URL where providers are downloaded from
246+
// if not set, the default Mondoo provider URL is used
247+
// This can be a custom URL for an internal provider registry
248+
ProvidersURL string `json:"providers_url,omitempty" mapstructure:"providers_url"`
230249
}
231250

232251
// Workload Identity Federation

cli/providers/providers.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,17 @@ func AttachCLIs(rootCmd *cobra.Command, commands ...*Command) error {
5353
return nil
5454
}
5555

56-
func detectConnectorName(args []string, rootCmd *cobra.Command, commands []*Command, providers providers.Providers) (string, bool) {
56+
func detectConnectorName(args []string, rootCmd *cobra.Command, commands []*Command, existing providers.Providers) (string, bool) {
5757
autoUpdate := true
5858

5959
config.InitViperConfig()
60+
61+
if viper.IsSet("providers_url") {
62+
if providersURL := viper.GetString("providers_url"); providersURL != "" {
63+
providers.SetProviderRegistry(providers.NewMondooProviderRegistry(providers.WithBaseURL(providersURL)))
64+
}
65+
}
66+
6067
if viper.IsSet("auto_update") {
6168
autoUpdate = viper.GetBool("auto_update")
6269
}
@@ -84,8 +91,8 @@ func detectConnectorName(args []string, rootCmd *cobra.Command, commands []*Comm
8491
attachPFlags(flags, cmd.Command.Flags())
8592
}
8693

87-
for i := range providers {
88-
provider := providers[i]
94+
for i := range existing {
95+
provider := existing[i]
8996
for j := range provider.Connectors {
9097
conn := provider.Connectors[j]
9198
for k := range conn.Flags {

providers/providers.go

Lines changed: 15 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package providers
66
import (
77
"archive/tar"
88
"bytes"
9+
"context"
910
"encoding/json"
1011
"io"
1112
"net"
@@ -407,9 +408,10 @@ func EnsureProvider(search ProviderLookup, autoUpdate bool, existing Providers)
407408
}
408409

409410
func Install(name string, version string) (*Provider, error) {
411+
ctx := context.Background()
410412
if version == "" {
411413
// if no version is specified, we default to installing the latest one
412-
latestVersion, err := LatestVersion(name)
414+
latestVersion, err := registry.GetLatestVersion(ctx, name)
413415
if err != nil {
414416
return nil, err
415417
}
@@ -419,65 +421,42 @@ func Install(name string, version string) (*Provider, error) {
419421
log.Info().
420422
Str("version", version).
421423
Msg("installing provider '" + name + "'")
422-
return installVersion(name, version)
424+
return installVersion(ctx, name, version)
423425
}
424426

425-
// This is the default installation source for core providers.
426-
const upstreamURL = "https://releases.mondoo.com/providers/{NAME}/{VERSION}/{NAME}_{VERSION}_{OS}_{ARCH}.tar.xz"
427+
func installVersion(ctx context.Context, name string, version string) (*Provider, error) {
428+
logCtx := log.With().Str("provider", name).Str("version", version).Logger()
427429

428-
func installVersion(name string, version string) (*Provider, error) {
429-
url := upstreamURL
430-
url = strings.ReplaceAll(url, "{NAME}", name)
431-
url = strings.ReplaceAll(url, "{VERSION}", version)
432-
url = strings.ReplaceAll(url, "{OS}", runtime.GOOS)
433-
url = strings.ReplaceAll(url, "{ARCH}", runtime.GOARCH)
434-
435-
log.Debug().Str("url", url).Msg("installing provider from URL")
436-
client, err := httpClientWithRetry()
430+
res, err := registry.DownloadProvider(ctx, name, version, runtime.GOOS, runtime.GOARCH)
437431
if err != nil {
438432
return nil, err
439433
}
440-
441-
res, err := client.Get(url)
442-
if err != nil {
443-
log.Debug().Str("url", url).Msg("failed to install from URL (get request)")
444-
return nil, errors.Wrap(err, "failed to install "+name+"-"+version)
445-
}
446-
447-
if res.StatusCode == http.StatusNotFound {
448-
return nil, errors.New("cannot find provider " + name + "-" + version + " under url " + url)
449-
} else if res.StatusCode != http.StatusOK {
450-
log.Debug().Str("url", url).Int("status", res.StatusCode).Msg("failed to install from URL (status code)")
451-
return nil, errors.New("failed to install " + name + "-" + version + ", received status code: " + res.Status)
452-
}
453-
454-
log.Debug().Str("url", url).Msg("request complete, installing provider")
434+
defer res.Close()
455435

456436
// we have to create new Reader to get rid of the timeouts imposed by the http client
457437
var tar []byte
458-
if tar, err = io.ReadAll(res.Body); err != nil {
459-
log.Debug().Str("url", url).Msg("failed to install from URL (read body)")
438+
if tar, err = io.ReadAll(res); err != nil {
439+
logCtx.Debug().Msg("failed to read body of provider download")
460440
return nil, errors.Wrap(err, "failed to install "+name+"-"+version+", failed to read body")
461441
}
462442

463443
reader := io.NopCloser(
464444
bytes.NewReader(tar),
465445
)
466446

467-
// else we know we got a 200 response, we can safely install
468447
installed, err := InstallIO(reader, InstallConf{
469448
Dst: DefaultPath,
470449
})
471450
if err != nil {
472-
log.Debug().Str("url", url).Msg("failed to install from URL (download)")
451+
logCtx.Debug().Msg("failed to install provider")
473452
return nil, errors.Wrap(err, "failed to install "+name+"-"+version)
474453
}
475454

476455
if len(installed) == 0 {
477456
return nil, errors.New("couldn't find installed provider")
478457
}
479458
if len(installed) > 1 {
480-
log.Warn().Msg("too many providers were installed")
459+
logCtx.Warn().Msg("too many providers were installed")
481460
}
482461
if installed[0].Version != version {
483462
return nil, errors.New("version for provider didn't match expected install version: expected " + version + ", installed: " + installed[0].Version)
@@ -524,44 +503,6 @@ func installDependencies(provider *Provider, existing Providers) error {
524503
return nil
525504
}
526505

527-
func LatestVersion(name string) (string, error) {
528-
client, err := httpClientWithRetry()
529-
if err != nil {
530-
return "", err
531-
}
532-
533-
res, err := client.Get("https://releases.mondoo.com/providers/latest.json")
534-
if err != nil {
535-
return "", err
536-
}
537-
538-
data, err := io.ReadAll(res.Body)
539-
if err != nil {
540-
log.Debug().Err(err).Msg("reading latest.json failed")
541-
return "", errors.New("failed to read response from upstream provider versions")
542-
}
543-
544-
var upstreamVersions ProviderVersions
545-
err = json.Unmarshal(data, &upstreamVersions)
546-
if err != nil {
547-
log.Debug().Err(err).Msg("parsing latest.json failed")
548-
return "", errors.New("failed to parse response from upstream provider versions")
549-
}
550-
551-
var latestVersion string
552-
for i := range upstreamVersions.Providers {
553-
if upstreamVersions.Providers[i].Name == name {
554-
latestVersion = upstreamVersions.Providers[i].Version
555-
break
556-
}
557-
}
558-
559-
if latestVersion == "" {
560-
return "", errors.New("cannot determine latest version of provider '" + name + "'")
561-
}
562-
return latestVersion, nil
563-
}
564-
565506
func PrintInstallResults(providers []*Provider) {
566507
for i := range providers {
567508
provider := providers[i]
@@ -886,6 +827,7 @@ func readProviderDir(pdir string) (*Provider, error) {
886827
}
887828

888829
func TryProviderUpdate(provider *Provider, update UpdateProvidersConfig) (*Provider, error) {
830+
ctx := context.Background()
889831
if provider.Path == "" {
890832
return nil, errors.New("cannot determine installation path for provider")
891833
}
@@ -909,7 +851,7 @@ func TryProviderUpdate(provider *Provider, update UpdateProvidersConfig) (*Provi
909851
}
910852
}
911853

912-
latest, err := LatestVersion(provider.Name)
854+
latest, err := registry.GetLatestVersion(ctx, provider.Name)
913855
if err != nil {
914856
log.Warn().Msg(err.Error())
915857
// we can just continue with the existing provider, no need to error up,
@@ -937,7 +879,7 @@ func TryProviderUpdate(provider *Provider, update UpdateProvidersConfig) (*Provi
937879
Str("installed", provider.Version).
938880
Str("latest", latest).
939881
Msg("found a new version for '" + provider.Name + "' provider")
940-
provider, err = installVersion(provider.Name, latest)
882+
provider, err = installVersion(ctx, provider.Name, latest)
941883
if err != nil {
942884
return nil, err
943885
}

0 commit comments

Comments
 (0)