more experimental feature updates (#486)

* updates for experimental features and renamed delete to remove
* added examples back for experimental features
* update stability warning message

Co-authored-by: Camryn Carter <camryn.carter@ranchergovernment.com>
Signed-off-by: Zack Brady <zackbrady123@gmail.com>

* fixed more tests to use ghcr for hauler
* updated test data workflow

---------

Signed-off-by: Zack Brady <zackbrady123@gmail.com>
Co-authored-by: Camryn Carter <camryn.carter@ranchergovernment.com>
This commit is contained in:
Zack Brady
2026-01-08 14:57:52 -05:00
committed by GitHub
parent c54065f316
commit ff3cece87f
12 changed files with 107 additions and 61 deletions

View File

@@ -24,13 +24,14 @@ jobs:
hauler login ghcr.io --username ${{ github.repository_owner }} --password ${{ secrets.GITHUB_TOKEN }} hauler login ghcr.io --username ${{ github.repository_owner }} --password ${{ secrets.GITHUB_TOKEN }}
hauler login docker.io --username ${{ secrets.DOCKERHUB_USERNAME }} --password ${{ secrets.DOCKERHUB_TOKEN }} hauler login docker.io --username ${{ secrets.DOCKERHUB_USERNAME }} --password ${{ secrets.DOCKERHUB_TOKEN }}
- name: Process Testdata Manifests - name: Process Images for Tests
run: | run: |
for manifest in testdata/*.yaml; do hauler store add image nginx:1.25-alpine
echo "Processing $manifest..." hauler store add image nginx:1.26-alpine
name=$(basename "$manifest" .yaml) hauler store add image busybox
hauler store sync --filename "$manifest" hauler store add image busybox:stable
done hauler store add image gcr.io/distroless/base
hauler store add image gcr.io/distroless/base@sha256:7fa7445dfbebae4f4b7ab0e6ef99276e96075ae42584af6286ba080750d6dfe5
- name: Push Store Contents to Hauler-Dev GitHub Container Registry - name: Push Store Contents to Hauler-Dev GitHub Container Registry
run: | run: |

View File

@@ -354,12 +354,12 @@ jobs:
# verify fileserver directory structure # verify fileserver directory structure
tree -hC fileserver tree -hC fileserver
- name: Verify - hauler store delete-artifact (image) - name: Verify - hauler store remove (image)
run: | run: |
hauler store delete-artifact --help hauler store remove --help
# add test images # add test images
hauler store add image docker.io/library/nginx:1.25-alpine hauler store add image ghcr.io/hauler-dev/library/nginx:1.25-alpine
hauler store add image docker.io/library/nginx:1.26-alpine hauler store add image ghcr.io/hauler-dev/library/nginx:1.26-alpine
# confirm artifacts # confirm artifacts
hauler store info | grep 'nginx:1.25' hauler store info | grep 'nginx:1.25'
hauler store info | grep 'nginx:1.26' hauler store info | grep 'nginx:1.26'
@@ -367,7 +367,7 @@ jobs:
BLOBS_BEFORE=$(find store/blobs/sha256 -type f | wc -l | xargs) BLOBS_BEFORE=$(find store/blobs/sha256 -type f | wc -l | xargs)
echo "blobs before deletion: $BLOBS_BEFORE" echo "blobs before deletion: $BLOBS_BEFORE"
# delete one artifact # delete one artifact
hauler store delete-artifact nginx:1.25 --force hauler store remove nginx:1.25 --force
# verify artifact removed # verify artifact removed
! hauler store info | grep -q "nginx:1.25" ! hauler store info | grep -q "nginx:1.25"
# non-deleted artifact exists # non-deleted artifact exists
@@ -385,11 +385,11 @@ jobs:
exit 1 exit 1
fi fi
# verify remaining image not missing layers # verify remaining image not missing layers
hauler store extract docker.io/library/nginx:1.26-alpine hauler store extract ghcr.io/hauler-dev/library/nginx:1.26-alpine
- name: Verify - hauler store delete-artifact (chart) - name: Verify - hauler store remove (chart)
run: | run: |
hauler store delete-artifact --help hauler store remove --help
# add test images # add test images
hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable --version 2.8.4 hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable --version 2.8.4
hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable --version 2.8.5 hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable --version 2.8.5
@@ -400,7 +400,7 @@ jobs:
BLOBS_BEFORE=$(find store/blobs/sha256 -type f | wc -l | xargs) BLOBS_BEFORE=$(find store/blobs/sha256 -type f | wc -l | xargs)
echo "blobs before deletion: $BLOBS_BEFORE" echo "blobs before deletion: $BLOBS_BEFORE"
# delete one artifact # delete one artifact
hauler store delete-artifact 2.8.4 --force hauler store remove 2.8.4 --force
# verify artifact removed # verify artifact removed
! hauler store info | grep -q "2.8.4" ! hauler store info | grep -q "2.8.4"
# non-deleted artifact exists # non-deleted artifact exists
@@ -420,9 +420,9 @@ jobs:
# verify remaining image not missing layers # verify remaining image not missing layers
hauler store extract hauler/rancher:2.8.5 hauler store extract hauler/rancher:2.8.5
- name: Verify - hauler store delete-artifact (file) - name: Verify - hauler store remove (file)
run: | run: |
hauler store delete-artifact --help hauler store remove --help
# add test images # add test images
hauler store add file https://get.hauler.dev hauler store add file https://get.hauler.dev
hauler store add file https://get.rke2.io/install.sh hauler store add file https://get.rke2.io/install.sh
@@ -433,7 +433,7 @@ jobs:
BLOBS_BEFORE=$(find store/blobs/sha256 -type f | wc -l | xargs) BLOBS_BEFORE=$(find store/blobs/sha256 -type f | wc -l | xargs)
echo "blobs before deletion: $BLOBS_BEFORE" echo "blobs before deletion: $BLOBS_BEFORE"
# delete one artifact # delete one artifact
hauler store delete-artifact get.hauler.dev --force hauler store remove get.hauler.dev --force
# verify artifact removed # verify artifact removed
! hauler store info | grep -q "get.hauler.dev" ! hauler store info | grep -q "get.hauler.dev"
# non-deleted artifact exists # non-deleted artifact exists

View File

@@ -12,12 +12,19 @@ For more information, please review the **[Hauler Documentation](https://hauler.
## Recent Changes ## Recent Changes
### In Hauler v1.4.0...
- Added a notice to `hauler store sync --products/--product-registry` to warn users the default registry will be updated in a future release.
- Users will see logging notices when using the `--products/--product-registry` such as...
- `!!! WARNING !!! [--products] will be updating its default registry in a future release...`
- `!!! WARNING !!! [--product-registry] will be updating its default registry in a future release...`
### In Hauler v1.2.0... ### In Hauler v1.2.0...
- Upgraded the `apiVersion` to `v1` from `v1alpha1` - Upgraded the `apiVersion` to `v1` from `v1alpha1`
- Users are able to use `v1` and `v1alpha1`, but `v1alpha1` is now deprecated and will be removed in a future release. We will update the community when we fully deprecate and remove the functionality of `v1alpha1` - Users are able to use `v1` and `v1alpha1`, but `v1alpha1` is now deprecated and will be removed in a future release. We will update the community when we fully deprecate and remove the functionality of `v1alpha1`
- Users will see logging notices when using the old `apiVersion` such as... - Users will see logging notices when using the old `apiVersion` such as...
- `!!! DEPRECATION WARNING !!! apiVersion [v1alpha1] will be removed in a future release !!! DEPRECATION WARNING !!!` - `!!! DEPRECATION WARNING !!! apiVersion [v1alpha1] will be removed in a future release...`
--- ---
- Updated the behavior of `hauler store load` to default to loading a `haul` with the name of `haul.tar.zst` and requires the flag of `--filename/-f` to load a `haul` with a different name - Updated the behavior of `hauler store load` to default to loading a `haul` with the name of `haul.tar.zst` and requires the flag of `--filename/-f` to load a `haul` with a different name
- Users can load multiple `hauls` by specifying multiple flags of `--filename/-f` - Users can load multiple `hauls` by specifying multiple flags of `--filename/-f`

View File

@@ -14,7 +14,7 @@ func New(ctx context.Context, ro *flags.CliRootOpts) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "hauler", Use: "hauler",
Short: "Airgap Swiss Army Knife", Short: "Airgap Swiss Army Knife",
Example: " View the Docs: https://docs.hauler.dev\n Environment Variables: " + consts.HaulerDir + " | " + consts.HaulerTempDir + " | " + consts.HaulerStoreDir + " | " + consts.HaulerIgnoreErrors, Example: " View the Docs: https://docs.hauler.dev\n Environment Variables: " + consts.HaulerDir + " | " + consts.HaulerTempDir + " | " + consts.HaulerStoreDir + " | " + consts.HaulerIgnoreErrors + "\n Warnings: Hauler commands and flags marked with (EXPERIMENTAL) are not yet stable and may change in the future.",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
l := log.FromContext(ctx) l := log.FromContext(ctx)
l.SetLevel(ro.LogLevel) l.SetLevel(ro.LogLevel)

View File

@@ -8,6 +8,7 @@ import (
"hauler.dev/go/hauler/cmd/hauler/cli/store" "hauler.dev/go/hauler/cmd/hauler/cli/store"
"hauler.dev/go/hauler/internal/flags" "hauler.dev/go/hauler/internal/flags"
"hauler.dev/go/hauler/pkg/log"
) )
func addStore(parent *cobra.Command, ro *flags.CliRootOpts) { func addStore(parent *cobra.Command, ro *flags.CliRootOpts) {
@@ -32,7 +33,7 @@ func addStore(parent *cobra.Command, ro *flags.CliRootOpts) {
addStoreInfo(rso, ro), addStoreInfo(rso, ro),
addStoreCopy(rso, ro), addStoreCopy(rso, ro),
addStoreAdd(rso, ro), addStoreAdd(rso, ro),
addStoreDeleteArtifact(rso, ro), addStoreRemove(rso, ro),
) )
parent.AddCommand(cmd) parent.AddCommand(cmd)
@@ -70,9 +71,16 @@ func addStoreSync(rso *flags.StoreRootOpts, ro *flags.CliRootOpts) *cobra.Comman
Short: "Sync content to the content store", Short: "Sync content to the content store",
Args: cobra.ExactArgs(0), Args: cobra.ExactArgs(0),
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
// Check if the products flag was passed // warn if products or product-registry flag is used by the user
if cmd.Flags().Changed("products") {
log.FromContext(cmd.Context()).Warnf("!!! WARNING !!! [--products] will be updating its default registry in a future release.")
}
if cmd.Flags().Changed("product-registry") {
log.FromContext(cmd.Context()).Warnf("!!! WARNING !!! [--product-registry] will be updating its default registry in a future release.")
}
// check if the products flag was passed
if len(o.Products) > 0 { if len(o.Products) > 0 {
// Only clear the default if the user did NOT explicitly set --filename // only clear the default if the user did not explicitly set it
if !cmd.Flags().Changed("filename") { if !cmd.Flags().Changed("filename") {
o.FileName = []string{} o.FileName = []string{}
} }
@@ -328,7 +336,10 @@ hauler store add image gcr.io/distroless/base@sha256:7fa7445dfbebae4f4b7ab0e6ef9
# fetch image with full image reference, specific platform, and signature verification # fetch image with full image reference, specific platform, and signature verification
curl -sfOL https://raw.githubusercontent.com/rancherfederal/carbide-releases/main/carbide-key.pub curl -sfOL https://raw.githubusercontent.com/rancherfederal/carbide-releases/main/carbide-key.pub
hauler store add image rgcrprod.azurecr.us/rancher/rke2-runtime:v1.31.5-rke2r1 --platform linux/amd64 --key carbide-key.pub`, hauler store add image rgcrprod.azurecr.us/rancher/rke2-runtime:v1.31.5-rke2r1 --platform linux/amd64 --key carbide-key.pub
# fetch image and rewrite path
hauler store add image busybox --rewrite custom-path/busybox:latest`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context() ctx := cmd.Context()
@@ -368,7 +379,10 @@ hauler store add chart hauler-helm --repo oci://ghcr.io/hauler-dev --version 1.2
hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable hauler store add chart rancher --repo https://releases.rancher.com/server-charts/stable
# fetch remote helm chart with specific version # fetch remote helm chart with specific version
hauler store add chart rancher --repo https://releases.rancher.com/server-charts/latest --version 2.10.1`, hauler store add chart rancher --repo https://releases.rancher.com/server-charts/latest --version 2.10.1
# fetch remote helm chart and rewrite path
hauler store add chart hauler-helm --repo oci://ghcr.io/hauler-dev --rewrite custom-path/hauler-chart:latest`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context() ctx := cmd.Context()
@@ -386,12 +400,31 @@ hauler store add chart rancher --repo https://releases.rancher.com/server-charts
return cmd return cmd
} }
func addStoreDeleteArtifact(rso *flags.StoreRootOpts, ro *flags.CliRootOpts) *cobra.Command { func addStoreRemove(rso *flags.StoreRootOpts, ro *flags.CliRootOpts) *cobra.Command {
o := &flags.DeleteArtifactOpts{} o := &flags.RemoveOpts{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "delete-artifact <artifact-ref>", Use: "remove <artifact-ref>",
Short: "Delete an artifact from the content store (experimental)", Short: "Remove an artifact from the content store (experimental)",
Aliases: []string{"del"}, Example: `# remove an image using full store reference
hauler store info
hauler store remove index.docker.io/library/busybox:stable
# remove a chart using full store reference
hauler store info
hauler store remove hauler/rancher:2.8.4
# remove a file using full store reference
hauler store info
hauler store remove hauler/rke2-install.sh
# remove any artifact with the latest tag
hauler store remove :latest
# remove any artifact with 'busybox' in the reference
hauler store remove busybox
# force remove without verification
hauler store remove busybox:latest --force`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context() ctx := cmd.Context()
@@ -401,7 +434,7 @@ func addStoreDeleteArtifact(rso *flags.StoreRootOpts, ro *flags.CliRootOpts) *co
return err return err
} }
return store.DeleteArtifactCmd(ctx, o, s, args[0]) return store.RemoveCmd(ctx, o, s, args[0])
}, },
} }
o.AddFlags(cmd) o.AddFlags(cmd)

View File

@@ -12,7 +12,7 @@ import (
"hauler.dev/go/hauler/pkg/store" "hauler.dev/go/hauler/pkg/store"
) )
func DeleteArtifactCmd(ctx context.Context, o *flags.DeleteArtifactOpts, s *store.Layout, ref string) error { func RemoveCmd(ctx context.Context, o *flags.RemoveOpts, s *store.Layout, ref string) error {
l := log.FromContext(ctx) l := log.FromContext(ctx)
// collect matching artifacts // collect matching artifacts
@@ -67,13 +67,13 @@ func DeleteArtifactCmd(ctx context.Context, o *flags.DeleteArtifactOpts, s *stor
} }
} }
//delete artifact(s) //remove artifact(s)
for _, m := range matches { for _, m := range matches {
if err := s.DeleteArtifact(ctx, m.reference, m.desc); err != nil { if err := s.RemoveArtifact(ctx, m.reference, m.desc); err != nil {
return fmt.Errorf("failed to delete artifact %s: %w", m.reference, err) return fmt.Errorf("failed to remove artifact %s: %w", m.reference, err)
} }
l.Infof("deleted [%s] of type %s with digest [%s]", m.reference, m.desc.MediaType, m.desc.Digest.String()) l.Infof("removed [%s] of type %s with digest [%s]", m.reference, m.desc.MediaType, m.desc.Digest.String())
} }
// clean up unreferenced blobs // clean up unreferenced blobs

View File

@@ -169,7 +169,7 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
case consts.FilesContentKind: case consts.FilesContentKind:
switch gvk.Version { switch gvk.Version {
case "v1alpha1": case "v1alpha1":
l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release !!! DEPRECATION WARNING !!!", gvk.Version) l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release...", gvk.Version)
var alphaCfg v1alpha1.Files var alphaCfg v1alpha1.Files
if err := yaml.Unmarshal(doc, &alphaCfg); err != nil { if err := yaml.Unmarshal(doc, &alphaCfg); err != nil {
@@ -203,7 +203,7 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
case consts.ImagesContentKind: case consts.ImagesContentKind:
switch gvk.Version { switch gvk.Version {
case "v1alpha1": case "v1alpha1":
l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release !!! DEPRECATION WARNING !!!", gvk.Version) l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release...", gvk.Version)
var alphaCfg v1alpha1.Images var alphaCfg v1alpha1.Images
if err := yaml.Unmarshal(doc, &alphaCfg); err != nil { if err := yaml.Unmarshal(doc, &alphaCfg); err != nil {
@@ -496,7 +496,7 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
case consts.ChartsContentKind: case consts.ChartsContentKind:
switch gvk.Version { switch gvk.Version {
case "v1alpha1": case "v1alpha1":
l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release !!! DEPRECATION WARNING !!!", gvk.Version) l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release...", gvk.Version)
var alphaCfg v1alpha1.Charts var alphaCfg v1alpha1.Charts
if err := yaml.Unmarshal(doc, &alphaCfg); err != nil { if err := yaml.Unmarshal(doc, &alphaCfg); err != nil {
@@ -530,7 +530,7 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
case consts.ChartsCollectionKind: case consts.ChartsCollectionKind:
switch gvk.Version { switch gvk.Version {
case "v1alpha1": case "v1alpha1":
l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release !!! DEPRECATION WARNING !!!", gvk.Version) l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release...", gvk.Version)
var alphaCfg v1alpha1.ThickCharts var alphaCfg v1alpha1.ThickCharts
if err := yaml.Unmarshal(doc, &alphaCfg); err != nil { if err := yaml.Unmarshal(doc, &alphaCfg); err != nil {
@@ -578,7 +578,7 @@ func processContent(ctx context.Context, fi *os.File, o *flags.SyncOpts, s *stor
case consts.ImageTxtsContentKind: case consts.ImageTxtsContentKind:
switch gvk.Version { switch gvk.Version {
case "v1alpha1": case "v1alpha1":
l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release !!! DEPRECATION WARNING !!!", gvk.Version) l.Warnf("!!! DEPRECATION WARNING !!! apiVersion [%s] will be removed in a future release...", gvk.Version)
var alphaCfg v1alpha1.ImageTxts var alphaCfg v1alpha1.ImageTxts
if err := yaml.Unmarshal(doc, &alphaCfg); err != nil { if err := yaml.Unmarshal(doc, &alphaCfg); err != nil {

View File

@@ -61,5 +61,8 @@ func (o *AddChartOpts) AddFlags(cmd *cobra.Command) {
f.StringVar(&o.ChartOpts.KeyFile, "key-file", "", "(Optional) Location of the TLS Key to use for client authenication") f.StringVar(&o.ChartOpts.KeyFile, "key-file", "", "(Optional) Location of the TLS Key to use for client authenication")
f.BoolVar(&o.ChartOpts.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "(Optional) Skip TLS certificate verification") f.BoolVar(&o.ChartOpts.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "(Optional) Skip TLS certificate verification")
f.StringVar(&o.ChartOpts.CaFile, "ca-file", "", "(Optional) Location of CA Bundle to enable certification verification") f.StringVar(&o.ChartOpts.CaFile, "ca-file", "", "(Optional) Location of CA Bundle to enable certification verification")
f.StringVar(&o.Rewrite, "rewrite", "", "(Optional) Rewrite artifact path to specified string (experimental)") f.StringVar(&o.Rewrite, "rewrite", "", "(Optional) Rewrite artifact path to specified string (EXPERIMENTAL)")
cmd.MarkFlagsRequiredTogether("username", "password")
cmd.MarkFlagsRequiredTogether("cert-file", "key-file", "ca-file")
} }

View File

@@ -21,6 +21,8 @@ func (o *CopyOpts) AddFlags(cmd *cobra.Command) {
f.BoolVar(&o.PlainHTTP, "plain-http", false, "(Optional) Allow plain HTTP connections") f.BoolVar(&o.PlainHTTP, "plain-http", false, "(Optional) Allow plain HTTP connections")
f.StringVarP(&o.Only, "only", "o", "", "(Optional) Custom string array to only copy specific 'image' items") f.StringVarP(&o.Only, "only", "o", "", "(Optional) Custom string array to only copy specific 'image' items")
cmd.MarkFlagsRequiredTogether("username", "password")
if err := f.MarkDeprecated("username", "please use 'hauler login'"); err != nil { if err := f.MarkDeprecated("username", "please use 'hauler login'"); err != nil {
panic(err) panic(err)
} }

View File

@@ -1,11 +0,0 @@
package flags
import "github.com/spf13/cobra"
type DeleteArtifactOpts struct {
Force bool // skip delete confirmation
}
func (o *DeleteArtifactOpts) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVarP(&o.Force, "force", "f", false, "(Optional) Delete artifacts without confirmation")
}

11
internal/flags/remove.go Normal file
View File

@@ -0,0 +1,11 @@
package flags
import "github.com/spf13/cobra"
type RemoveOpts struct {
Force bool // skip remove confirmation
}
func (o *RemoveOpts) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVarP(&o.Force, "force", "f", false, "(Optional) Remove artifact(s) without confirmation")
}

View File

@@ -262,8 +262,8 @@ func (l *Layout) writeLayer(layer v1.Layer) error {
return err return err
} }
// Delete artifact reference from the store // Remove artifact reference from the store
func (l *Layout) DeleteArtifact(ctx context.Context, reference string, desc ocispec.Descriptor) error { func (l *Layout) RemoveArtifact(ctx context.Context, reference string, desc ocispec.Descriptor) error {
if err := l.OCI.LoadIndex(); err != nil { if err := l.OCI.LoadIndex(); err != nil {
return err return err
} }