diff --git a/Tiltfile b/Tiltfile index dd67c866f681..1c4b8ff13295 100644 --- a/Tiltfile +++ b/Tiltfile @@ -364,45 +364,11 @@ def deploy_observability(): ) def prepare_all(): - allow_k8s_arg = "" - if settings.get("allowed_contexts"): - if type(settings.get("allowed_contexts")) == "string": - allow_k8s_arg = "--allow-k8s-contexts={} ".format(settings.get("allowed_contexts")) - if type(settings.get("allowed_contexts")) == "list": - for context in settings.get("allowed_contexts"): - allow_k8s_arg = allow_k8s_arg + "--allow-k8s-contexts={} ".format(context) - tools_arg = "--tools kustomize,envsubst " - cert_manager_arg = "" - if settings.get("deploy_cert_manager"): - cert_manager_arg = "--cert-manager " - - # Note: we are creating clusterctl CRDs using kustomize (vs using clusterctl) because we want to create - # a dependency between these resources and provider resources. - kustomize_build_arg = "--kustomize-builds clusterctl.crd:./cmd/clusterctl/config/crd/ " - for tool in settings.get("deploy_observability", []): - kustomize_build_arg = kustomize_build_arg + "--kustomize-builds {tool}.observability:./hack/observability/{tool}/ ".format(tool = tool) - providers_arg = "" - for name in get_providers(): - p = providers.get(name) - if p == None: - fail("Provider with name " + name + " not found") - if p.get("kustomize_config", True): - context = p.get("context") - debug = "" - providers_arg = providers_arg + "--providers {name}:{context} ".format( - name = name, - context = context, - ) - tilt_settings_file_arg = "--tilt-settings-file " + tilt_file - cmd = "make -B tilt-prepare && ./hack/tools/bin/tilt-prepare {allow_k8s_arg}{tools_arg}{cert_manager_arg}{kustomize_build_arg}{providers_arg}{tilt_settings_file_arg}".format( - allow_k8s_arg = allow_k8s_arg, + cmd = "make -B tilt-prepare && ./hack/tools/bin/tilt-prepare {tools_arg}{tilt_settings_file_arg}".format( tools_arg = tools_arg, - cert_manager_arg = cert_manager_arg, - kustomize_build_arg = kustomize_build_arg, - providers_arg = providers_arg, tilt_settings_file_arg = tilt_settings_file_arg, ) local(cmd, env = settings.get("kustomize_substitutions", {})) diff --git a/hack/tools/tilt-prepare/main.go b/hack/tools/tilt-prepare/main.go index 5c5eac29a86e..a1f143410671 100644 --- a/hack/tools/tilt-prepare/main.go +++ b/hack/tools/tilt-prepare/main.go @@ -57,30 +57,29 @@ import ( /* Example call for tilt up: --tools kustomize,envsubst - --cert-manager - --kustomize-builds clusterctl.crd:./cmd/clusterctl/config/crd/ - --kustomize-builds observability.tools:./hack/observability/ - --providers core:. - --providers kubeadm-bootstrap:./bootstrap/kubeadm - --providers kubeadm-control-plane:./controlplane/kubeadm - --providers docker:./test/infrastructure/docker */ var ( rootPath string tiltBuildPath string - toolsFlag = pflag.StringSlice("tools", []string{}, "list of tools to be created; each value should correspond to a make target") - certManagerFlag = pflag.Bool("cert-manager", false, "prepare cert-manager") - kustomizeBuildsFlag = pflag.StringSlice("kustomize-builds", []string{}, "list of kustomize build to be run; each value should be in the form name:path") - providersBuildsFlag = pflag.StringSlice("providers", []string{}, "list of providers to be installed; each value should be in the form name:path") - allowK8SContextsFlag = pflag.StringSlice("allow-k8s-contexts", []string{}, "Specifies that Tilt is allowed to run against the specified k8s context name; Kind is automatically allowed") tiltSettingsFileFlag = pflag.String("tilt-settings-file", "./tilt-settings.yaml", "Path to a tilt-settings.(json|yaml) file") + toolsFlag = pflag.StringSlice("tools", []string{}, "list of tools to be created; each value should correspond to a make target") ) type tiltSettings struct { - Debug map[string]debugConfig `json:"debug,omitempty"` - ExtraArgs map[string]extraArgs `json:"extra_args,omitempty"` + Debug map[string]debugConfig `json:"debug,omitempty"` + ExtraArgs map[string]extraArgs `json:"extra_args,omitempty"` + DeployCertManager bool `json:"deploy_cert_manager,omitempty"` + DeployObservability []string `json:"deploy_observability,omitempty"` + EnableProviders []string `json:"enable_providers,omitempty"` + AllowedContexts []string `json:"allowed_contexts,omitempty"` + ProviderRepos []string `json:"provider_repos,omitempty"` +} + +type providerSettings struct { + Name string `json:"name,omitempty"` + Context *string `json:"context"` } type debugConfig struct { @@ -118,9 +117,6 @@ func main() { start := time.Now() pflag.Parse() - if err := allowK8sConfig(); err != nil { - klog.Exit(fmt.Sprintf("[main] tilt-prepare can't start: %v", err)) - } ctx := ctrl.SetupSignalHandler() @@ -129,6 +125,10 @@ func main() { klog.Exit(fmt.Sprintf("[main] failed to read tilt settings: %v", err)) } + if err := allowK8sConfig(ts); err != nil { + klog.Exit(fmt.Sprintf("[main] tilt-prepare can't start: %v", err)) + } + // Execute a first group of tilt prepare tasks, building all the tools required in subsequent steps/by tilt. if err := tiltTools(ctx); err != nil { klog.Exit(fmt.Sprintf("[main] failed to prepare tilt tools: %v", err)) @@ -187,7 +187,7 @@ func setDebugDefaults(ts *tiltSettings) { } // allowK8sConfig mimics allow_k8s_contexts; only kind is enabled by default but more can be added. -func allowK8sConfig() error { +func allowK8sConfig(ts *tiltSettings) error { config, err := clientcmd.NewDefaultClientConfigLoadingRules().Load() if err != nil { return errors.Wrap(err, "failed to load Kubeconfig") @@ -204,7 +204,7 @@ func allowK8sConfig() error { return nil } - allowed := sets.NewString(*allowK8SContextsFlag...) + allowed := sets.NewString(ts.AllowedContexts...) if !allowed.Has(config.CurrentContext) { return errors.Errorf("context %s is not allowed", config.CurrentContext) } @@ -227,41 +227,123 @@ func tiltTools(ctx context.Context) error { func tiltResources(ctx context.Context, ts *tiltSettings) error { tasks := map[string]taskFunction{} + // Note: we are creating clusterctl CRDs using kustomize (vs using clusterctl) because we want to create + // a dependency between these resources and provider resources. + tasks["clusterctl.crd"] = kustomizeTask("./cmd/clusterctl/config/crd/", "clusterctl.crd.yaml") + // If required, all the task to install cert manager. // NOTE: strictly speaking cert-manager is not a resource, however it is a dependency for most of the actual resources // and running this is the same task group of the kustomize/provider tasks gives the maximum benefits in terms of reducing the total elapsed time. - if *certManagerFlag { + if ts.DeployCertManager { tasks["cert-manager-cainjector"] = preLoadImageTask(fmt.Sprintf("quay.io/jetstack/cert-manager-cainjector:%s", config.CertManagerDefaultVersion)) tasks["cert-manager-webhook"] = preLoadImageTask(fmt.Sprintf("quay.io/jetstack/cert-manager-webhook:%s", config.CertManagerDefaultVersion)) tasks["cert-manager-controller"] = preLoadImageTask(fmt.Sprintf("quay.io/jetstack/cert-manager-controller:%s", config.CertManagerDefaultVersion)) tasks["cert-manager"] = certManagerTask() } - // Add a kustomize task for each name/path defined using the --kustomize-build flag. - for _, k := range *kustomizeBuildsFlag { - values := strings.Split(k, ":") - if len(values) != 2 { - return errors.Errorf("[resources] failed to parse --kustomize-build flag %s: value should be in the form of name:path", k) - } - name := values[0] - path := values[1] + // Add a kustomize task for each tool configured via deploy_observability. + for _, tool := range ts.DeployObservability { + name := fmt.Sprintf("%s.observability", tool) + path := fmt.Sprintf("./hack/observability/%s/", tool) tasks[name] = kustomizeTask(path, fmt.Sprintf("%s.yaml", name)) } - // Add a provider task for each name/path defined using the --provider flag. - for _, p := range *providersBuildsFlag { - pValues := strings.Split(p, ":") - if len(pValues) != 2 { - return errors.Errorf("[resources] failed to parse --provider flag %s: value should be in the form of name:path", p) + providerPaths := map[string]string{"core": ".", + "kubeadm-bootstrap": "bootstrap/kubeadm", + "kubeadm-control-plane": "controlplane/kubeadm", + "docker": "test/infrastructure/docker", + } + + // Add all the provider paths to the providerpaths map + for _, p := range ts.ProviderRepos { + providerContexts, err := loadProviders(p) + if err != nil { + return err + } + + for name, path := range providerContexts { + providerPaths[name] = path + } + } + + enableCore := false + for _, providerName := range ts.EnableProviders { + if providerName == "core" { + enableCore = true + } + } + if !enableCore { + ts.EnableProviders = append(ts.EnableProviders, "core") + } + + // Add the provider task for each of the enabled provider + for _, providerName := range ts.EnableProviders { + path, ok := providerPaths[providerName] + if !ok { + return errors.Errorf("failed to obtain path for the provider %s", providerName) } - name := pValues[0] - path := pValues[1] - tasks[name] = providerTask(name, fmt.Sprintf("%s/config/default", path), ts) + tasks[providerName] = providerTask(providerName, fmt.Sprintf("%s/config/default", path), ts) } return runTaskGroup(ctx, "resources", tasks) } +func loadProviders(r string) (map[string]string, error) { + var contextPath string + providerData, err := readProviderSettings(r) + if err != nil { + return nil, err + } + + providerContexts := map[string]string{} + for _, p := range providerData { + if p.Context != nil { + contextPath = r + "/" + *p.Context + } else { + contextPath = r + } + providerContexts[p.Name] = contextPath + } + return providerContexts, nil +} + +func readProviderSettings(path string) ([]providerSettings, error) { + path, err := checkProviderFileFormat(path) + if err != nil { + return nil, err + } + + content, err := os.ReadFile(path) //nolint:gosec + if err != nil { + return nil, err + } + + ps := []providerSettings{} + // providerSettings file can be an array and this is done to detect arrays + if strings.HasPrefix(string(content), "[") || strings.HasPrefix(string(content), "-") { + if err := yaml.Unmarshal(content, &ps); err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to read provider path %s tilt file", path)) + } + } else { + p := providerSettings{} + if err := yaml.Unmarshal(content, &p); err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to read provider path %s tilt file", path)) + } + ps = append(ps, p) + } + return ps, nil +} + +func checkProviderFileFormat(path string) (string, error) { + if _, err := os.Stat(path + "/tilt-provider.yaml"); err == nil { + return path + "/tilt-provider.yaml", nil + } + if _, err := os.Stat(path + "/tilt-provider.json"); err == nil { + return path + "/tilt-provider.json", nil + } + return "", fmt.Errorf("unable to find a tilt settings file under %s", path) +} + type taskFunction func(ctx context.Context, prefix string, errors chan error) // runTaskGroup executes a group of task in parallel handling an error channel.