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
49 changes: 0 additions & 49 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,52 +302,3 @@ tasks:
desc: Clean all binaries
cmds:
- rm -rf ./build ./dist ./d8

release:tag:
desc: Tag current main branch state as new release. Provide version tag as argument (eg. task {{.TASK}} -- v1.2.3).
preconditions:
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
msg: This can only be done on main branch
- sh: '[[ "{{.CLI_ARGS}}" =~ ^v\d+.\d+.\d+$ ]] || exit 1'
msg: Provide valid semver prefixed with "v" (eg. task {{.TASK}} -- v1.2.3) to be used as version
prompt:
- "Tag current main branch state as new release? ({{.CLI_ARGS}})"
- "Have you created Github release draft for {{.CLI_ARGS}} tag?"
cmds:
- git tag -s {{.CLI_ARGS}} -m 'Signed {{.CLI_ARGS}} release'
- git push origin {{.CLI_ARGS}}
- echo "Release tag created, now publish your Github release so that CI can upload build artifacts to it."

release:sign:
desc: "Sign last version tag + origin/main and push signatures."
deps: [checkKubectl]
preconditions:
- sh: '[[ "{{.CLI_ARGS}}" =~ ^v\d+.\d+.\d+$ ]] || exit 1'
msg: Provide valid semver prefixed with "v" (eg. task {{.TASK}} -- v1.2.3) to be used as version
cmds:
- git fetch --tags -f
- git signatures pull
- |
for ref in {{.refs | default "$(git tag --sort=v:refname | tail -n1) origin/main"}}; do
echo "Signing $ref..."
git signatures add {{.CLI_ARGS}} $ref
git signatures show {{.CLI_ARGS}} $ref
done
- git signatures push

release:publish-trdl-channels:
desc: Publish release channels to TRDL
deps: [checkKubectl]
preconditions:
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
msg: This can only be done on main branch
prompt:
- "Have you updated trdl_channels.yaml with new versions?"
cmds:
- git add trdl_channels.yaml
- git commit -S -m 'Signed release channels'
- git push
- git fetch
- git signatures pull
- git signatures add origin/main
- git signatures push
6 changes: 6 additions & 0 deletions internal/mirror/cmd/push/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ func addFlags(flagSet *pflag.FlagSet) {
"/modules",
"Suffix to append to source repo path to locate modules.",
)
flagSet.StringArrayVar(
&Files,
"file",
nil,
"`Path` to a tar or chunked package to push. May be repeated to push multiple packages. Can be used instead of the bundle directory argument or combined with it.",
)
}

func ParseEnvironmentVariables() {
Expand Down
7 changes: 7 additions & 0 deletions internal/mirror/cmd/push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ var (
Insecure bool
TLSSkipVerify bool
ImagesBundlePath string
Files []string

// Packages holds the resolved list of tar/chunked package archive paths to push,
// assembled from the bundle directory argument and/or the --file flag.
Packages []string

MirrorTimeout time.Duration = -1
)
Expand Down Expand Up @@ -92,6 +97,7 @@ func NewCommand() *cobra.Command {
Use: "push <images-bundle-path> <registry>",
Short: "Copy Deckhouse Kubernetes Platform distribution to the third-party registry",
Long: pushLong,
Args: cobra.RangeArgs(1, 2),
ValidArgs: []string{"images-bundle-path", "registry"},
SilenceErrors: true,
SilenceUsage: true,
Expand Down Expand Up @@ -239,6 +245,7 @@ func (p *Pusher) executeNewPush() error {
client,
&mirror.PushServiceOptions{
BundleDir: p.pushParams.BundleDir,
Packages: Packages,
WorkingDir: p.pushParams.WorkingDir,
},
logger.Named("push"),
Expand Down
120 changes: 105 additions & 15 deletions internal/mirror/cmd/push/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,71 @@ import (
)

func parseAndValidateParameters(_ *cobra.Command, args []string) error {
if len(args) != 2 {
return errors.New("invalid number of arguments, expected 2")
// The registry is always the last argument. The bundle path is an optional
// first argument; when it is omitted, packages must be provided via --file.
var (
registryArg string
bundleArg []string
)

switch len(args) {
case 1:
registryArg = args[0]
case 2:
bundleArg = args[:1]
registryArg = args[1]
default:
return errors.New("invalid number of arguments, expected <registry> with an optional bundle path before it")
}

var err error
if err = parseAndValidateRegistryURLArg(args); err != nil {
if err = parseAndValidateRegistryURLArg(registryArg); err != nil {
return err
}

if err = validateRegistryCredentials(); err != nil {
return err
}

if err = validateImagesBundlePathArg(args); err != nil {
if err = resolvePackages(bundleArg); err != nil {
return err
}

return nil
}

func validateImagesBundlePathArg(args []string) error {
ImagesBundlePath = filepath.Clean(args[0])
// resolvePackages builds the list of package archives to push from the optional
// bundle path argument and the --file flag, then sets the default temp dir.
func resolvePackages(bundleArg []string) error {
Packages = nil

if len(bundleArg) == 1 {
if err := collectBundlePathPackages(bundleArg[0]); err != nil {
return err
}
}

if err := collectFilesPackages(); err != nil {
return err
}

if len(Packages) == 0 {
return errors.New("no packages to push: specify a bundle directory before registry URL, or use --file to specify tar/chunked package")
}

Packages = lo.Uniq(Packages)

if TempDir == "" {
TempDir = filepath.Join(filepath.Dir(Packages[0]), ".tmp", mirror.TmpMirrorFolderName)
}

return nil
}

// collectBundlePathPackages resolves the bundle path argument, which may be a
// directory of packages or a single tar/chunked package, into Packages.
func collectBundlePathPackages(arg string) error {
ImagesBundlePath = filepath.Clean(arg)

s, err := os.Stat(ImagesBundlePath)
if err != nil {
Expand All @@ -67,31 +110,78 @@ func validateImagesBundlePathArg(args []string) error {
}

dirEntries = lo.Filter(dirEntries, func(item os.DirEntry, _ int) bool {
ext := filepath.Ext(item.Name())
return ext == ".tar" || ext == ".chunk"
return isPackageFile(item.Name())
})
if len(dirEntries) == 0 {
return errors.New("no packages found in bundle directory")
}

for _, entry := range dirEntries {
// Chunk files (<name>.tar.NNNN.chunk) collapse to a single <name>.tar
// package; the pusher reassembles the chunks at push time.
Packages = append(Packages, canonicalPackagePath(filepath.Join(ImagesBundlePath, entry.Name())))
}

// Default temp dir lives inside the bundle directory, as before.
if TempDir == "" {
TempDir = filepath.Join(ImagesBundlePath, ".tmp", mirror.TmpMirrorFolderName)
}

return nil
}

if bundleExtension := filepath.Ext(ImagesBundlePath); bundleExtension == ".tar" || bundleExtension == ".chunk" {
if TempDir == "" {
TempDir = filepath.Join(filepath.Dir(ImagesBundlePath), ".tmp", mirror.TmpMirrorFolderName)
}

if isPackageFile(ImagesBundlePath) {
Packages = append(Packages, canonicalPackagePath(ImagesBundlePath))
return nil
}

return fmt.Errorf("invalid images bundle: must be a directory, tar or a chunked package")
}

// collectFilesPackages validates and appends the packages passed via --file.
func collectFilesPackages() error {
for _, f := range Files {
path := filepath.Clean(f)

s, err := os.Stat(path)
if err != nil {
return fmt.Errorf("could not read package %q: %w", path, err)
}

if s.IsDir() {
return fmt.Errorf("--file entry %q is a directory, expected a tar or chunked package", path)
}

if !isPackageFile(path) {
return fmt.Errorf("--file entry %q is not a tar or chunked package", path)
}

Packages = append(Packages, canonicalPackagePath(path))
}

return nil
}

func isPackageFile(name string) bool {
ext := filepath.Ext(name)
return ext == ".tar" || ext == ".chunk"
}

// canonicalPackagePath maps a chunk file (<name>.tar.NNNN.chunk) to its canonical
// <name>.tar path so the pusher reassembles all chunks instead of reading a single
// chunk as a whole archive. Plain .tar paths are returned unchanged.
func canonicalPackagePath(path string) string {
if idx := strings.Index(path, ".tar.chunk"); idx != -1 {
return path[:idx] + ".tar"
}

if idx := strings.Index(path, ".tar."); idx != -1 && filepath.Ext(path) == ".chunk" {
return path[:idx] + ".tar"
}

return path
}

func validateRegistryCredentials() error {
if RegistryPassword != "" && RegistryUsername == "" {
return errors.New("registry username not specified")
Expand All @@ -100,8 +190,8 @@ func validateRegistryCredentials() error {
return nil
}

func parseAndValidateRegistryURLArg(args []string) error {
registry := strings.NewReplacer("http://", "", "https://", "").Replace(args[1])
func parseAndValidateRegistryURLArg(registryArg string) error {
registry := strings.NewReplacer("http://", "", "https://", "").Replace(registryArg)
if registry == "" {
return errors.New("<registry> argument is empty")
}
Expand Down
Loading
Loading