diff --git a/Gopkg.lock b/Gopkg.lock index 47b37ec8..78e9ea07 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1196,7 +1196,6 @@ "github.com/docker/distribution/reference", "github.com/docker/docker/api/types", "github.com/docker/docker/api/types/container", - "github.com/docker/docker/api/types/mount", "github.com/docker/docker/api/types/strslice", "github.com/docker/docker/builder/dockerignore", "github.com/docker/docker/client", @@ -1212,6 +1211,7 @@ "github.com/gosuri/uitable", "github.com/oklog/ulid", "github.com/opencontainers/go-digest", + "github.com/pkg/errors", "github.com/renstrom/fuzzysearch/fuzzy", "github.com/sirupsen/logrus", "github.com/spf13/cobra", @@ -1226,6 +1226,7 @@ "golang.org/x/net/context", "gopkg.in/AlecAivazis/survey.v1", "gopkg.in/yaml.v2", + "k8s.io/apimachinery/pkg/util/validation", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index bba6298f..ecd78982 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -53,3 +53,6 @@ [[constraint]] name = "github.com/docker/go" version = "1.5.1-1" + +[[constraint]] + name = "k8s.io/apimachinery" diff --git a/cmd/duffle/relocate.go b/cmd/duffle/relocate.go new file mode 100644 index 00000000..5f95f589 --- /dev/null +++ b/cmd/duffle/relocate.go @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2019-Present Pivotal Software, Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + "io" + "strconv" + "strings" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/validation" + + "github.com/deislabs/duffle/pkg/loader" +) + +const relocateDesc = ` +Relocates the images referenced by a bundle and creates a new bundle with an updated image map. + +The --repository-prefix flag determines the repositories for the relocated images. +Each image is tagged with a name starting with the given prefix and pushed to the repository. + +For example, if the repository-prefix is example.com/user, the image istio/proxyv2 is relocated +to a name starting with example.com/user/ and pushed to a repository hosted by example.com. +` + +type relocateCmd struct { + bundle string + dest string + repoPrefix string + out io.Writer + //verbose bool + //insecure bool +} + +func newRelocateCmd(w io.Writer) *cobra.Command { + relocate := &relocateCmd{out: w} + + cmd := &cobra.Command{ + Use: "relocate [INPUT-BUNDLE] [OUTPUT-BUNDLE]", + Short: "relocate images in a CNAB bundle", + Long: relocateDesc, + Example: `duffle relocate helloworld hellorelocated --repository-prefix example.com/user`, + Args: cobra.ExactArgs(2), + PreRunE: func(cmd *cobra.Command, args []string) error { + // validate --repository-prefix if it is set, otherwise allow flag omission to be diagnosed as such + if cmd.Flags().Changed("repository-prefix") { + if err := flagValidRepository("repository-prefix")(cmd); err != nil { + return err + } + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + relocate.bundle = args[0] + relocate.dest = args[1] + + return relocate.run() + }, + } + + f := cmd.Flags() + f.StringVarP(&relocate.repoPrefix, "repository-prefix", "r", "", "a prefix for relocated image names") + cmd.MarkFlagRequired("repository-prefix") + //f.BoolVarP(&relocate.verbose, "verbose", "v", false, "Verbose output") + //f.BoolVarP(&relocate.insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") + + return cmd +} + +func (ex *relocateCmd) run() error { + bundlefile, l, err := ex.setup() + if err != nil { + return err + } + if err := ex.Relocate(bundlefile, l); err != nil { + return err + } + + return nil +} + +func (ex *relocateCmd) Relocate(bundlefile string, l loader.Loader) error { + return nil +} + +func (ex *relocateCmd) setup() (string, loader.Loader, error) { + //bundlefile, err := resolveBundleFilePath(ex.bundle, ex.home.String(), ex.bundleIsFile, ex.insecure) + //if err != nil { + // return "", nil, err + //} + // + //l, err := getLoader(ex.home.String(), ex.insecure) + //if err != nil { + // return "", nil, err + //} + + return "", nil, nil +} + +type flagsValidator func(cmd *cobra.Command) error + +func flagValidRepository(flagName string) flagsValidator { + return func(cmd *cobra.Command) error { + repositoryValue := cmd.Flag(flagName).Value.String() + + if strings.HasSuffix(repositoryValue, "/") || strings.Contains(repositoryValue, "//") { + return fmt.Errorf("invalid repository: %s", repositoryValue) + } + + for i, part := range strings.Split(repositoryValue, "/") { + if i != 0 { + if strings.ContainsAny(part, ":@\" ") { + return fmt.Errorf("invalid repository: %s", repositoryValue) + } + continue + } + + authorityParts := strings.Split(part, ":") + if len(authorityParts) > 2 { + return fmt.Errorf("invalid repository hostname: %s", part) + } + if errs := validation.IsDNS1123Subdomain(authorityParts[0]); len(errs) > 0 { + return fmt.Errorf("invalid repository hostname: %s", strings.Join(errs, "; ")) + } + if len(authorityParts) == 2 { + portNumber, err := strconv.Atoi(authorityParts[1]) + if err != nil { + return fmt.Errorf("invalid repository port number: %s", authorityParts[1]) + } + + if errs := validation.IsValidPortNum(portNumber); len(errs) > 0 { + return fmt.Errorf("invalid repository port number: %s", strings.Join(errs, "; ")) + } + } + } + + return nil + } +} diff --git a/cmd/duffle/root.go b/cmd/duffle/root.go index 993e29a7..24f094c2 100644 --- a/cmd/duffle/root.go +++ b/cmd/duffle/root.go @@ -48,6 +48,7 @@ func newRootCmd(outputRedirect io.Writer) *cobra.Command { cmd.AddCommand(newListCmd(outLog)) cmd.AddCommand(newPullCmd(outLog)) cmd.AddCommand(newPushCmd(outLog)) + cmd.AddCommand(newRelocateCmd(outLog)) cmd.AddCommand(newSearchCmd(outLog)) cmd.AddCommand(newVersionCmd(outLog)) cmd.AddCommand(newInstallCmd(outLog))