Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ go 1.26.0

require (
buf.build/gen/go/namespace/cloud/connectrpc/go v1.20.0-20260610154328-540a8ce9853d.1
buf.build/gen/go/namespace/cloud/grpc/go v1.6.2-20260610154328-540a8ce9853d.1
buf.build/gen/go/namespace/cloud/protocolbuffers/go v1.36.11-20260623071207-0391158f5cec.1
buf.build/gen/go/namespace/cloud/grpc/go v1.6.2-20260624153431-8e77f0a824d9.1
buf.build/gen/go/namespace/cloud/protocolbuffers/go v1.36.11-20260624153431-8e77f0a824d9.1
cloud.google.com/go/artifactregistry v1.17.2
cloud.google.com/go/container v1.45.0
connectrpc.com/connect v1.20.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
buf.build/gen/go/namespace/cloud/connectrpc/go v1.20.0-20260610154328-540a8ce9853d.1 h1:BMQ7VNoTTpLstAIzGzYJFbNtOTEO7m3qcfnMr/k7x6k=
buf.build/gen/go/namespace/cloud/connectrpc/go v1.20.0-20260610154328-540a8ce9853d.1/go.mod h1:hvHz7jMtG0TUy5XmDQcxhXNhMElaj0SU5TOSpx5HFyQ=
buf.build/gen/go/namespace/cloud/grpc/go v1.6.2-20260610154328-540a8ce9853d.1 h1:Nxs0ekOZSf4+yzTT0+KO9dFAmmHiPvf8N/7TruOB+WI=
buf.build/gen/go/namespace/cloud/grpc/go v1.6.2-20260610154328-540a8ce9853d.1/go.mod h1:S5jbhx/UZDUUajqANfQAjXL/qeEYMIY8VJsubujburI=
buf.build/gen/go/namespace/cloud/protocolbuffers/go v1.36.11-20260623071207-0391158f5cec.1 h1:iVj1+NtQo1x66y4xbeqyVXWwSBZ1Rz19kK+wICD1+Ic=
buf.build/gen/go/namespace/cloud/protocolbuffers/go v1.36.11-20260623071207-0391158f5cec.1/go.mod h1:Il2wpJNQB40Yj3Rmuhg5xKJPSXaZVwij+Q30d1PNuNY=
buf.build/gen/go/namespace/cloud/grpc/go v1.6.2-20260624153431-8e77f0a824d9.1 h1:7YEPBp1+4VodLK7gvn67225DKDB1xBC4fKijiZeTPx0=
buf.build/gen/go/namespace/cloud/grpc/go v1.6.2-20260624153431-8e77f0a824d9.1/go.mod h1:7D6jFYSgFIvJzSSOMi1wX+blvL5dGuC1qeWacY7vOxI=
buf.build/gen/go/namespace/cloud/protocolbuffers/go v1.36.11-20260624153431-8e77f0a824d9.1 h1:CDPUKLfM677Z/trVVQ8E2eybJIsLxLOblEkuo7q0RAc=
buf.build/gen/go/namespace/cloud/protocolbuffers/go v1.36.11-20260624153431-8e77f0a824d9.1/go.mod h1:Il2wpJNQB40Yj3Rmuhg5xKJPSXaZVwij+Q30d1PNuNY=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
Expand Down
78 changes: 78 additions & 0 deletions internal/cli/cmd/cluster/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/spf13/pflag"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/durationpb"
"namespacelabs.dev/foundation/framework/io/downloader"
"namespacelabs.dev/foundation/internal/cli/fncobra"
"namespacelabs.dev/foundation/internal/console"
Expand All @@ -50,6 +51,7 @@ func NewArtifactCmd() *cobra.Command {
cmd.AddCommand(newArtifactDownloadCmd())
cmd.AddCommand(newArtifactCacheURLCmd())
cmd.AddCommand(newArtifactExpireCmd())
cmd.AddCommand(newArtifactExtendCmd())
cmd.AddCommand(newArtifactDescribeCmd())

return cmd
Expand Down Expand Up @@ -346,6 +348,82 @@ func newArtifactExpireCmd() *cobra.Command {
})
}

func newArtifactExtendCmd() *cobra.Command {
var namespace string
var extendBy, ensureMinimum time.Duration
var extendByFlag, ensureMinimumFlag *pflag.Flag

return fncobra.Cmd(&cobra.Command{
Use: "extend [path]",
Short: "Extend the expiration of an artifact.",
Long: `Extend the expiration of a finalized artifact.

This enables dynamic, access-based retention: create artifacts with a short
expiration and push it out whenever they are accessed.

At least one of --by or --ensure_minimum must be set. When both are set, --by is
applied first and --ensure_minimum then acts as a lower bound on the result.`,
Args: cobra.ExactArgs(1),
}).WithFlags(func(flags *pflag.FlagSet) {
flags.StringVar(&namespace, "namespace", mainArtifactNamespace, "Namespace of the artifact.")
fncobra.DurationVar(flags, &extendBy, "by", 0, "Extend the current expiration by this duration, relative to the artifact's current expiration.")
fncobra.DurationVar(flags, &ensureMinimum, "ensure_minimum", 0, "Ensure the artifact expires no sooner than this duration from now.")
extendByFlag = flags.Lookup("by")
ensureMinimumFlag = flags.Lookup("ensure_minimum")
}).DoWithArgs(func(ctx context.Context, args []string) error {
path := args[0]

if !extendByFlag.Changed && !ensureMinimumFlag.Changed {
return fnerrors.BadInputError("at least one of --by or --ensure_minimum must be set")
}

req := &storagev1beta.ExtendArtifactRequest{
Path: path,
Namespace: namespace,
}

if extendByFlag.Changed {
if extendBy <= 0 {
return fnerrors.BadInputError("--by must be positive")
}
req.ExtendBy = durationpb.New(extendBy)
}

if ensureMinimumFlag.Changed {
if ensureMinimum <= 0 {
return fnerrors.BadInputError("--ensure_minimum must be positive")
}
req.EnsureMinimum = durationpb.New(ensureMinimum)
}

token, err := auth.LoadDefaults()
if err != nil {
return err
}

cli, err := storage.NewClient(ctx, token)
if err != nil {
return err
}
defer cli.Close()

res, err := cli.Artifacts.ExtendArtifact(ctx, req)
if err != nil {
return err
}

if res.GetExpiresAt() != nil {
fmt.Fprintf(console.Stdout(ctx), "Extended %s (namespace %s); now expires %s.\n",
path, namespace, res.GetExpiresAt().AsTime().Format(time.RFC3339))
} else {
fmt.Fprintf(console.Stdout(ctx), "Extended %s (namespace %s); now never expires.\n",
path, namespace)
}

return nil
})
}

func newArtifactDownloadCmd() *cobra.Command {
var namespace string
var resume, unpack bool
Expand Down
Loading