From a2d1823feed36ff8e08a293164c48735633aabcb Mon Sep 17 00:00:00 2001 From: Pau Capdevila Date: Fri, 14 Mar 2025 16:54:23 +0100 Subject: [PATCH 1/5] fix(versions): log release versions when config is missing When no fab.yaml is found, the versions command shows the release versions instead of an error If there are overrides in fab.yaml, they are shown Signed-off-by: Pau Capdevila --- pkg/hhfab/cmdconfig.go | 18 ------ pkg/hhfab/cmdversions.go | 133 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 pkg/hhfab/cmdversions.go diff --git a/pkg/hhfab/cmdconfig.go b/pkg/hhfab/cmdconfig.go index b5c34f51c..dd6271a70 100644 --- a/pkg/hhfab/cmdconfig.go +++ b/pkg/hhfab/cmdconfig.go @@ -267,24 +267,6 @@ func Validate(ctx context.Context, workDir, cacheDir string, hMode HydrateMode) return nil } -func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode) error { - cfg, err := load(ctx, workDir, cacheDir, true, hMode, "") - if err != nil { - return err - } - - slog.Info("Printing versions of all components") - - data, err := kyaml.Marshal(cfg.Fab.Status.Versions) - if err != nil { - return fmt.Errorf("marshalling versions: %w", err) - } - - fmt.Println(string(data)) - - return nil -} - func load(ctx context.Context, workDir, cacheDir string, wiringAndHydration bool, mode HydrateMode, setJoinToken string) (*Config, error) { if err := checkWorkCacheDir(workDir, cacheDir); err != nil { return nil, err diff --git a/pkg/hhfab/cmdversions.go b/pkg/hhfab/cmdversions.go new file mode 100644 index 000000000..440d6df99 --- /dev/null +++ b/pkg/hhfab/cmdversions.go @@ -0,0 +1,133 @@ +// Copyright 2024 Hedgehog +// SPDX-License-Identifier: Apache-2.0 + +package hhfab + +import ( + "context" + "errors" + "fmt" + "log/slog" + "os" + "path/filepath" + + fabapi "go.githedgehog.com/fabricator/api/fabricator/v1beta1" + "go.githedgehog.com/fabricator/pkg/fab" + "sigs.k8s.io/yaml" +) + +func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode) error { + configPath := filepath.Join(workDir, FabConfigFile) + if _, err := os.Stat(configPath); err != nil && errors.Is(err, os.ErrNotExist) { + slog.Info("No configuration found", "file", FabConfigFile, "action", "Showing release versions") + freshFab := fabapi.Fabricator{} + if err := freshFab.CalculateVersions(fab.Versions); err != nil { + return fmt.Errorf("calculating default versions: %w", err) + } + data, err := yaml.Marshal(freshFab.Status.Versions) + if err != nil { + return fmt.Errorf("marshalling versions: %w", err) + } + fmt.Println(string(data)) + + return nil + } + + cfg, err := load(ctx, workDir, cacheDir, true, hMode, "") + if err != nil { + return err + } + + freshFab := fabapi.Fabricator{} + if err := freshFab.CalculateVersions(fab.Versions); err != nil { + return fmt.Errorf("calculating default versions: %w", err) + } + releaseData, err := yaml.Marshal(freshFab.Status.Versions) + if err != nil { + return fmt.Errorf("marshalling release versions: %w", err) + } + var release map[string]interface{} + if err := yaml.Unmarshal(releaseData, &release); err != nil { + return fmt.Errorf("unmarshalling release versions: %w", err) + } + + overridesRaw, err := yaml.Marshal(cfg.Fab.Spec.Overrides.Versions) + if err != nil { + slog.Warn("Failed to marshal overrides", "error", err) + fmt.Println(string(releaseData)) + + return nil + } + + var overrides map[string]interface{} + if err := yaml.Unmarshal(overridesRaw, &overrides); err != nil { + slog.Warn("Failed to unmarshal overrides", "error", err) + fmt.Println(string(releaseData)) + + return nil + } + + if len(overrides) == 0 { + slog.Info("Printing versions of all components") + fmt.Println(string(releaseData)) + + return nil + } + + slog.Info("Printing versions of all components (overridden←→release)") + merged := make(map[string]interface{}) + + for category, value := range release { + merged[category] = processVersionCategory(category, value, overrides) + } + + mergedData, err := yaml.Marshal(merged) + if err != nil { + return fmt.Errorf("marshalling merged versions: %w", err) + } + fmt.Println(string(mergedData)) + + return nil +} + +func processVersionCategory(category string, releaseValue interface{}, overrides map[string]interface{}) interface{} { + releaseCat, isMapRelease := releaseValue.(map[string]interface{}) + if !isMapRelease { + return releaseValue + } + + result := make(map[string]interface{}) + + overrideCat, overrideExists := overrides[category] + if !overrideExists { + return releaseCat + } + + overrideMap, isMapOverride := overrideCat.(map[string]interface{}) + if !isMapOverride { + return releaseCat + } + + for compName, releaseVer := range releaseCat { + releaseVerStr, isString := releaseVer.(string) + if !isString { + nestedResult := processVersionCategory(compName, releaseVer, overrideMap) + result[compName] = nestedResult + + continue + } + + if overrideComp, exists := overrideMap[compName]; exists { + overrideVerStr, isOverrideString := overrideComp.(string) + if isOverrideString { + result[compName] = fmt.Sprintf("%s←→%s", overrideVerStr, releaseVerStr) + } else { + result[compName] = releaseVer + } + } else { + result[compName] = releaseVer + } + } + + return result +} From 46bb8dc7425c7c4debad35d9bc47e6981dd4b924 Mon Sep 17 00:00:00 2001 From: Pau Capdevila Date: Tue, 8 Jul 2025 18:46:01 +0200 Subject: [PATCH 2/5] chore: add version override query from the API hhfab versions --live Signed-off-by: Pau Capdevila --- cmd/hhfab/main.go | 15 ++++--- pkg/hhfab/cmdversions.go | 97 +++++++++++++++++++++++++++++++++++----- 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/cmd/hhfab/main.go b/cmd/hhfab/main.go index 5e7675279..d3f354254 100644 --- a/cmd/hhfab/main.go +++ b/cmd/hhfab/main.go @@ -629,12 +629,17 @@ func Run(ctx context.Context) error { }, }, { - Name: "versions", - Usage: "print versions of all components", - Flags: flatten(defaultFlags, hModeFlags), + Name: "versions", + Usage: "print versions of all components", + Flags: flatten(defaultFlags, hModeFlags, []cli.Flag{ + &cli.BoolFlag{ + Name: "live", + Usage: "load versions from running API instead of the config file (fab.yaml)", + }, + }), Before: before(false), - Action: func(_ *cli.Context) error { - if err := hhfab.Versions(ctx, workDir, cacheDir, hhfab.HydrateMode(hydrateMode)); err != nil { + Action: func(c *cli.Context) error { + if err := hhfab.Versions(ctx, workDir, cacheDir, hhfab.HydrateMode(hydrateMode), c.Bool("live")); err != nil { return fmt.Errorf("printing versions: %w", err) } diff --git a/pkg/hhfab/cmdversions.go b/pkg/hhfab/cmdversions.go index 440d6df99..d9a759ff2 100644 --- a/pkg/hhfab/cmdversions.go +++ b/pkg/hhfab/cmdversions.go @@ -11,12 +11,23 @@ import ( "os" "path/filepath" + "go.githedgehog.com/fabric/pkg/util/kubeutil" fabapi "go.githedgehog.com/fabricator/api/fabricator/v1beta1" "go.githedgehog.com/fabricator/pkg/fab" - "sigs.k8s.io/yaml" + kclient "sigs.k8s.io/controller-runtime/pkg/client" + kyaml "sigs.k8s.io/yaml" ) -func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode) error { +func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode, live bool) error { + if live { + cfg := &Config{ + WorkDir: workDir, + CacheDir: cacheDir, + } + + return getVersionsFromCluster(ctx, cfg) + } + configPath := filepath.Join(workDir, FabConfigFile) if _, err := os.Stat(configPath); err != nil && errors.Is(err, os.ErrNotExist) { slog.Info("No configuration found", "file", FabConfigFile, "action", "Showing release versions") @@ -24,7 +35,7 @@ func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode) if err := freshFab.CalculateVersions(fab.Versions); err != nil { return fmt.Errorf("calculating default versions: %w", err) } - data, err := yaml.Marshal(freshFab.Status.Versions) + data, err := kyaml.Marshal(freshFab.Status.Versions) if err != nil { return fmt.Errorf("marshalling versions: %w", err) } @@ -42,16 +53,74 @@ func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode) if err := freshFab.CalculateVersions(fab.Versions); err != nil { return fmt.Errorf("calculating default versions: %w", err) } - releaseData, err := yaml.Marshal(freshFab.Status.Versions) + releaseData, err := kyaml.Marshal(freshFab.Status.Versions) if err != nil { return fmt.Errorf("marshalling release versions: %w", err) } var release map[string]interface{} - if err := yaml.Unmarshal(releaseData, &release); err != nil { + if err := kyaml.Unmarshal(releaseData, &release); err != nil { return fmt.Errorf("unmarshalling release versions: %w", err) } - overridesRaw, err := yaml.Marshal(cfg.Fab.Spec.Overrides.Versions) + overridesRaw, err := kyaml.Marshal(cfg.Fab.Spec.Overrides.Versions) + if err != nil { + slog.Warn("Failed to marshal overrides", "error", err) + fmt.Println(string(releaseData)) + + return nil + } + + var overrides map[string]interface{} + if err := kyaml.Unmarshal(overridesRaw, &overrides); err != nil { + slog.Warn("Failed to unmarshal overrides", "error", err) + fmt.Println(string(releaseData)) + + return nil + } + + if len(overrides) == 0 { + slog.Info("Printing versions of all components") + fmt.Println(string(releaseData)) + + return nil + } + + slog.Info("Printing versions of all components (release → override)") + merged := make(map[string]interface{}) + + for category, value := range release { + merged[category] = processVersionCategory(category, value, overrides) + } + + mergedData, err := kyaml.Marshal(merged) + if err != nil { + return fmt.Errorf("marshalling merged versions: %w", err) + } + fmt.Println(string(mergedData)) + + return nil +} + +func getVersionsFromCluster(ctx context.Context, c *Config) error { + kubeconfig := filepath.Join(c.WorkDir, VLABDir, VLABKubeConfig) + kube, err := kubeutil.NewClient(ctx, kubeconfig, fabapi.SchemeBuilder) + if err != nil { + return fmt.Errorf("creating kube client: %w", err) + } + + fab := &fabapi.Fabricator{} + if err := kube.Get(ctx, kclient.ObjectKey{Name: "default", Namespace: "fab"}, fab); err != nil { + return fmt.Errorf("getting fabricator object: %w", err) + } + + slog.Info("Printing versions from live cluster") + + releaseData, err := kyaml.Marshal(fab.Status.Versions) + if err != nil { + return fmt.Errorf("marshalling versions: %w", err) + } + + overridesRaw, err := kyaml.Marshal(fab.Spec.Overrides.Versions) if err != nil { slog.Warn("Failed to marshal overrides", "error", err) fmt.Println(string(releaseData)) @@ -60,7 +129,7 @@ func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode) } var overrides map[string]interface{} - if err := yaml.Unmarshal(overridesRaw, &overrides); err != nil { + if err := kyaml.Unmarshal(overridesRaw, &overrides); err != nil { slog.Warn("Failed to unmarshal overrides", "error", err) fmt.Println(string(releaseData)) @@ -74,14 +143,22 @@ func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode) return nil } - slog.Info("Printing versions of all components (overridden←→release)") + var release map[string]interface{} + if err := kyaml.Unmarshal(releaseData, &release); err != nil { + slog.Warn("Failed to unmarshal release versions", "error", err) + fmt.Println(string(releaseData)) + + return nil + } + + slog.Info("Printing versions of all components (release → override)") merged := make(map[string]interface{}) for category, value := range release { merged[category] = processVersionCategory(category, value, overrides) } - mergedData, err := yaml.Marshal(merged) + mergedData, err := kyaml.Marshal(merged) if err != nil { return fmt.Errorf("marshalling merged versions: %w", err) } @@ -120,7 +197,7 @@ func processVersionCategory(category string, releaseValue interface{}, overrides if overrideComp, exists := overrideMap[compName]; exists { overrideVerStr, isOverrideString := overrideComp.(string) if isOverrideString { - result[compName] = fmt.Sprintf("%s←→%s", overrideVerStr, releaseVerStr) + result[compName] = fmt.Sprintf("%s → %s", releaseVerStr, overrideVerStr) } else { result[compName] = releaseVer } From a593c1d8162a41c39fa1794b014bb28641ebd59a Mon Sep 17 00:00:00 2001 From: Pau Capdevila Date: Tue, 8 Jul 2025 20:15:02 +0000 Subject: [PATCH 3/5] ci: show hhfab versions Signed-off-by: Pau Capdevila --- .github/workflows/ci.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 752007458..1bc22857c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -318,6 +318,7 @@ jobs: HHFAB_VLAB_COLLECT: true if: ${{ env.run_release_test == 'true' }} run: | + bin/hhfab versions bin/hhfab init -v --dev -m ${{ matrix.fabricmode }} --include-onie=${{ matrix.includeonie }} --gateway=${{ matrix.gateway }} bin/hhfab vlab gen -v export HHFAB_JOIN_TOKEN=$(openssl rand -base64 24) @@ -337,6 +338,7 @@ jobs: HHFAB_VLAB_COLLECT: true if: ${{ env.run_release_test != 'true' }} run: | + bin/hhfab versions bin/hhfab init -v --dev -m ${{ matrix.fabricmode }} --include-onie=${{ matrix.includeonie }} --gateway=${{ matrix.gateway }} bin/hhfab vlab gen -v bin/hhfab diagram -f mermaid @@ -434,6 +436,7 @@ jobs: mkdir old curl -fsSL https://i.hhdev.io/hhfab | USE_SUDO=false INSTALL_DIR=./old VERSION="${{ matrix.fromversion }}" bash + bin/hhfab versions old/hhfab init -v --dev -m ${{ matrix.fabricmode }} --include-onie=true old/hhfab vlab gen -v old/hhfab vlab up -v --ready setup-vpcs --ready test-connectivity --ready exit ${{ matrix.fromflags }} @@ -454,6 +457,7 @@ jobs: HHFAB_REG_REPO: 127.0.0.1:30000 HHFAB_VLAB_COLLECT: true run: | + bin/hhfab versions bin/hhfab vlab up -v --ready inspect --ready setup-vpcs --ready test-connectivity --ready exit --upgrade - name: Upload show-tech artifacts @@ -468,6 +472,7 @@ jobs: HHFAB_REG_REPO: 127.0.0.1:30000 HHFAB_VLAB_COLLECT: true run: | + bin/hhfab versions bin/hhfab vlab up -v --ready wait --ready exit -m=manual - name: Upload show-tech artifacts @@ -574,6 +579,7 @@ jobs: if: ${{ env.run_release_test == 'true' }} run: | source "./lab-ci/envs/$KUBE_NODE/source.sh" + bin/hhfab versions bin/hhfab init -v --dev --include-onie=${{ matrix.includeonie }} -w "./lab-ci/envs/$KUBE_NODE/wiring.yaml" # TODO: make controls restricted again when we figure out how to get NTP upstream working for isolated VMs @@ -594,6 +600,7 @@ jobs: if: ${{ env.run_release_test != 'true' }} run: | source "./lab-ci/envs/$KUBE_NODE/source.sh" + bin/hhfab versions bin/hhfab init -v --dev --include-onie=${{ matrix.includeonie }} -w "./lab-ci/envs/$KUBE_NODE/wiring.yaml" bin/hhfab diagram -f mermaid From 8d2a76b6686958164b1e3463d36c609a226a0f4a Mon Sep 17 00:00:00 2001 From: Pau Capdevila Date: Wed, 9 Jul 2025 12:41:41 +0200 Subject: [PATCH 4/5] fix: use CalculateVersions from fabricator API Signed-off-by: Pau Capdevila --- pkg/hhfab/cmdversions.go | 163 +++++++++++++++------------------------ 1 file changed, 62 insertions(+), 101 deletions(-) diff --git a/pkg/hhfab/cmdversions.go b/pkg/hhfab/cmdversions.go index d9a759ff2..5d5595547 100644 --- a/pkg/hhfab/cmdversions.go +++ b/pkg/hhfab/cmdversions.go @@ -53,50 +53,13 @@ func Versions(ctx context.Context, workDir, cacheDir string, hMode HydrateMode, if err := freshFab.CalculateVersions(fab.Versions); err != nil { return fmt.Errorf("calculating default versions: %w", err) } - releaseData, err := kyaml.Marshal(freshFab.Status.Versions) - if err != nil { - return fmt.Errorf("marshalling release versions: %w", err) - } - var release map[string]interface{} - if err := kyaml.Unmarshal(releaseData, &release); err != nil { - return fmt.Errorf("unmarshalling release versions: %w", err) - } - overridesRaw, err := kyaml.Marshal(cfg.Fab.Spec.Overrides.Versions) + versionsData, err := formatVersions(freshFab.Status.Versions, cfg.Fab.Status.Versions) if err != nil { - slog.Warn("Failed to marshal overrides", "error", err) - fmt.Println(string(releaseData)) - - return nil + return fmt.Errorf("formatting versions: %w", err) } - var overrides map[string]interface{} - if err := kyaml.Unmarshal(overridesRaw, &overrides); err != nil { - slog.Warn("Failed to unmarshal overrides", "error", err) - fmt.Println(string(releaseData)) - - return nil - } - - if len(overrides) == 0 { - slog.Info("Printing versions of all components") - fmt.Println(string(releaseData)) - - return nil - } - - slog.Info("Printing versions of all components (release → override)") - merged := make(map[string]interface{}) - - for category, value := range release { - merged[category] = processVersionCategory(category, value, overrides) - } - - mergedData, err := kyaml.Marshal(merged) - if err != nil { - return fmt.Errorf("marshalling merged versions: %w", err) - } - fmt.Println(string(mergedData)) + fmt.Println(versionsData) return nil } @@ -108,101 +71,99 @@ func getVersionsFromCluster(ctx context.Context, c *Config) error { return fmt.Errorf("creating kube client: %w", err) } - fab := &fabapi.Fabricator{} - if err := kube.Get(ctx, kclient.ObjectKey{Name: "default", Namespace: "fab"}, fab); err != nil { + fabObj := &fabapi.Fabricator{} + if err := kube.Get(ctx, kclient.ObjectKey{Name: "default", Namespace: "fab"}, fabObj); err != nil { return fmt.Errorf("getting fabricator object: %w", err) } slog.Info("Printing versions from live cluster") - releaseData, err := kyaml.Marshal(fab.Status.Versions) - if err != nil { - return fmt.Errorf("marshalling versions: %w", err) + freshFab := fabapi.Fabricator{} + if err := freshFab.CalculateVersions(fab.Versions); err != nil { + return fmt.Errorf("calculating default versions: %w", err) } - overridesRaw, err := kyaml.Marshal(fab.Spec.Overrides.Versions) + versionsData, err := formatVersions(freshFab.Status.Versions, fabObj.Status.Versions) if err != nil { - slog.Warn("Failed to marshal overrides", "error", err) - fmt.Println(string(releaseData)) - - return nil + return fmt.Errorf("formatting versions: %w", err) } - var overrides map[string]interface{} - if err := kyaml.Unmarshal(overridesRaw, &overrides); err != nil { - slog.Warn("Failed to unmarshal overrides", "error", err) - fmt.Println(string(releaseData)) + fmt.Println(versionsData) - return nil - } - - if len(overrides) == 0 { - slog.Info("Printing versions of all components") - fmt.Println(string(releaseData)) + return nil +} - return nil +func formatVersions(releaseVersions, overriddenVersions fabapi.Versions) (string, error) { + releaseMap, err := convertToMap(releaseVersions) + if err != nil { + return "", fmt.Errorf("converting release versions to map: %w", err) } - var release map[string]interface{} - if err := kyaml.Unmarshal(releaseData, &release); err != nil { - slog.Warn("Failed to unmarshal release versions", "error", err) - fmt.Println(string(releaseData)) + overriddenMap, err := convertToMap(overriddenVersions) + if err != nil { + slog.Warn("Failed to convert overridden versions", "error", err) + data, _ := kyaml.Marshal(releaseVersions) - return nil + return string(data), nil } slog.Info("Printing versions of all components (release → override)") - merged := make(map[string]interface{}) - for category, value := range release { - merged[category] = processVersionCategory(category, value, overrides) - } + result := compareVersionMaps(releaseMap, overriddenMap) - mergedData, err := kyaml.Marshal(merged) + resultData, err := kyaml.Marshal(result) if err != nil { - return fmt.Errorf("marshalling merged versions: %w", err) + return "", fmt.Errorf("marshalling result: %w", err) } - fmt.Println(string(mergedData)) - return nil + return string(resultData), nil } -func processVersionCategory(category string, releaseValue interface{}, overrides map[string]interface{}) interface{} { - releaseCat, isMapRelease := releaseValue.(map[string]interface{}) - if !isMapRelease { - return releaseValue - } - - result := make(map[string]interface{}) - - overrideCat, overrideExists := overrides[category] - if !overrideExists { - return releaseCat +func convertToMap(v interface{}) (map[string]interface{}, error) { + data, err := kyaml.Marshal(v) + if err != nil { + return nil, fmt.Errorf("marshalling: %w", err) } - overrideMap, isMapOverride := overrideCat.(map[string]interface{}) - if !isMapOverride { - return releaseCat + var result map[string]interface{} + if err := kyaml.Unmarshal(data, &result); err != nil { + return nil, fmt.Errorf("unmarshalling: %w", err) } - for compName, releaseVer := range releaseCat { - releaseVerStr, isString := releaseVer.(string) - if !isString { - nestedResult := processVersionCategory(compName, releaseVer, overrideMap) - result[compName] = nestedResult + return result, nil +} - continue - } +func compareVersionMaps(releases, overridden map[string]interface{}) map[string]interface{} { + result := make(map[string]interface{}) - if overrideComp, exists := overrideMap[compName]; exists { - overrideVerStr, isOverrideString := overrideComp.(string) - if isOverrideString { - result[compName] = fmt.Sprintf("%s → %s", releaseVerStr, overrideVerStr) + for key, releaseValue := range releases { + releaseMap, isMap := releaseValue.(map[string]interface{}) + if isMap { + overriddenMap, hasOverridden := overridden[key].(map[string]interface{}) + if hasOverridden { + result[key] = compareVersionMaps(releaseMap, overriddenMap) } else { - result[compName] = releaseVer + result[key] = releaseMap } } else { - result[compName] = releaseVer + releaseStr, isString := releaseValue.(string) + if !isString { + result[key] = releaseValue + + continue + } + + overriddenValue, hasOverridden := overridden[key] + if hasOverridden { + overriddenStr, isString := overriddenValue.(string) + if isString && overriddenStr != "" && overriddenStr != releaseStr { + result[key] = fmt.Sprintf("%s → %s", releaseStr, overriddenStr) + } else { + result[key] = releaseValue + } + } else { + result[key] = releaseValue + } } } From fc412087bed51a240d4150346fc75bfaa52a08f6 Mon Sep 17 00:00:00 2001 From: Pau Capdevila Date: Wed, 9 Jul 2025 12:43:44 +0200 Subject: [PATCH 5/5] ci: fix hhfab versions in vlab upgrade jobs Signed-off-by: Pau Capdevila --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1bc22857c..97811fcb6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -436,8 +436,8 @@ jobs: mkdir old curl -fsSL https://i.hhdev.io/hhfab | USE_SUDO=false INSTALL_DIR=./old VERSION="${{ matrix.fromversion }}" bash - bin/hhfab versions old/hhfab init -v --dev -m ${{ matrix.fabricmode }} --include-onie=true + old/hhfab versions old/hhfab vlab gen -v old/hhfab vlab up -v --ready setup-vpcs --ready test-connectivity --ready exit ${{ matrix.fromflags }}