diff --git a/cmd/amp/registry.go b/cmd/amp/registry.go index c0c614d06..7c8f31b98 100644 --- a/cmd/amp/registry.go +++ b/cmd/amp/registry.go @@ -1,16 +1,20 @@ package main import ( + "encoding/base64" + "encoding/json" "errors" "fmt" - "io/ioutil" - "net/http" - "os" - "os/exec" - "strings" - "github.com/appcelerator/amp/api/client" + distreference "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/reference" + docker "github.com/docker/docker/client" "github.com/spf13/cobra" + "golang.org/x/net/context" + "io/ioutil" + "net/http" + "regexp" ) // RegCmd is the main command for attaching registry subcommands. @@ -21,8 +25,10 @@ var RegCmd = &cobra.Command{ } var ( - domain = "local.appcelerator.io" - pushCmd = &cobra.Command{ + endpoint string + domain string + insecure bool + pushCmd = &cobra.Command{ Use: "push [image]", Short: "Push an image to the amp registry", Long: `Push an image to the amp registry`, @@ -42,53 +48,85 @@ var ( func init() { RootCmd.AddCommand(RegCmd) - pushCmd.Flags().StringVar(&domain, "domain", domain, "The amp domain") + RegCmd.PersistentFlags().BoolVarP(&insecure, "insecure", "i", true, "Insecure registry") + RegCmd.PersistentFlags().StringVarP(&domain, "domain", "d", "local.appcelerator.io", "The amp registry domain (hostname or IP)") + RegCmd.PersistentFlags().StringVarP(&endpoint, "endpoint", "e", "", "The amp registry endpoint (hostname or IP), overrides the domain option") RegCmd.AddCommand(pushCmd) RegCmd.AddCommand(reglsCmd) } +// registryEndpoint returns the registry endpoint +func registryEndpoint() (ep string) { + if endpoint != "" { + ep = endpoint + return + } + ep = "registry." + domain + return +} + // RegistryPush displays resource usage statistics func RegistryPush(amp *client.AMP, cmd *cobra.Command, args []string) error { - _, err := amp.GetAuthorizedContext() + defaultHeaders := map[string]string{"User-Agent": "amp-cli"} + dclient, err := docker.NewClient(DockerURL, DockerVersion, nil, defaultHeaders) + if err != nil { + return err + } + ctx := context.Background() + _, err = amp.GetAuthorizedContext() if err != nil { return err } + // @todo: read the .dockercfg file for authentication, or use credentials from amp.yaml + ac := types.AuthConfig{Username: "none"} + jsonString, err := json.Marshal(ac) + if err != nil { + return errors.New("Failed to marshal authconfig") + } + dst := make([]byte, base64.URLEncoding.EncodedLen(len(jsonString))) + base64.URLEncoding.Encode(dst, jsonString) + authConfig := string(dst) + imagePushOptions := types.ImagePushOptions{RegistryAuth: authConfig} image := args[0] + distributionRef, err := distreference.ParseNamed(image) + if err != nil { + return fmt.Errorf("Error parsing reference: %q is not a valid repository/tag", image) + } + if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { + return errors.New("refusing to create a tag with a digest reference") + } + tag := reference.GetTagFromNamedRef(distributionRef) + hostname, name := distreference.SplitHostname(distributionRef) if amp.Verbose() { - fmt.Println("Execute registry push command with:") - fmt.Printf("image: %s\n", image) + fmt.Println("Registry push request with:") + fmt.Printf(" image: %s\n", image) } - if err = validateRegistryImage(image); err != nil { - return err - } taggedImage := image - if !strings.HasPrefix(image, "registry."+domain) { - nn := strings.Index(image, "/") - if nn < 0 { - return fmt.Errorf("Invalid image name %s", image) - } - taggedImage = "registry." + domain + "/" + image[nn+1:] + if hostname != registryEndpoint() { + taggedImage = registryEndpoint() + "/" + name + ":" + tag fmt.Printf("Tag image from %s to %s\n", image, taggedImage) - cmdexe := exec.Command("docker", "tag", image, taggedImage) - cmdexe.Stdout = os.Stdout - cmdexe.Stderr = os.Stderr - err = cmdexe.Run() - if err != nil { + if err := dclient.ImageTag(ctx, image, taggedImage); err != nil { return err } } fmt.Printf("push image %s\n", taggedImage) - cmdexe := exec.Command("docker", "push", taggedImage) - cmdexe.Stdout = os.Stdout - cmdexe.Stderr = os.Stderr - err = cmdexe.Run() + resp, err := dclient.ImagePush(ctx, taggedImage, imagePushOptions) if err != nil { return err } - return err + body, err := ioutil.ReadAll(resp) + if err != nil { + return err + } + re := regexp.MustCompile(`: digest: sha256:`) + if !re.Match(body) { + fmt.Print(string(body)) + return errors.New("Push failed") + } + return nil } // RegistryLs lists images @@ -97,7 +135,13 @@ func RegistryLs(amp *client.AMP, cmd *cobra.Command, args []string) error { if err != nil { return err } - resp, err := http.Get("http://registry." + domain + "/v2/_catalog") + var protocol string + if insecure { + protocol = "http" + } else { + protocol = "https" + } + resp, err := http.Get(protocol + "://" + registryEndpoint() + "/v2/_catalog") if err != nil { return err } @@ -106,10 +150,3 @@ func RegistryLs(amp *client.AMP, cmd *cobra.Command, args []string) error { fmt.Println(string(body)) return err } - -func validateRegistryImage(image string) error { - if image == "" { - return errors.New("Need a valid image name") - } - return nil -} diff --git a/docs/README.md b/docs/README.md index 79cbd5895..ff98305f5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -AMP: A... Microservices Platform ![WIP](./static_files/amp--docs-WIP-yellow.svg) +AMP: A... Microservices Platform ![WIP](static_files/amp--docs-WIP-yellow.svg) ============================ AMP is currently under development and this section is here to help you get started based on latest stable tagged version of the project. If you're here, then you **ARE** pioneering with us and we encourage you to [get in touch](./contributing.md) ! @@ -207,3 +207,10 @@ A few useful examples: ``` $ amp stats --task --cpu --mem ``` + +### Managing Docker images + + amp registry ls + amp registry push + +More details in the [userguide](userguide/registry.md) diff --git a/docs/userguide/registry.md b/docs/userguide/registry.md new file mode 100644 index 000000000..c5a1135ce --- /dev/null +++ b/docs/userguide/registry.md @@ -0,0 +1,56 @@ +AMP registry +============ + +# Usage + +AMP comes with an internal registry available through the AMP cli and providing images to the Swarm cluster. It is meant to host the Docker images used by the application services (defined in a stack). + +## CLI + +### localhost (development) + +#### Push an image + +```amp registry push appcelerator/pinger:latest``` + +### remote cluster + +the cluster should have a FQDN with sub level aliases. Let's say the domain is amp.example.com, the registry is available on registry.amp.example.com. + +If there's no legit certificate for this registry with this name (default use case), this url should be declared as insecure registry in your Docker configuration. + +#### Configuration on Linux + +```systemctl edit docker.service``` + +add the block (or adapt the existing file if you already have a customization) + +``` +[Service] +Environment="INSECURE_REGISTRY=registry.amp.example.com" +ExecStart=- +ExecStart=/usr/bin/dockerd $OPTIONS \ + $INSECURE_REGISTRY +``` + +#### Configuration on Mac OS + +Go in Preferences, advanced tab, add an insecure registry. + +#### Push an image + +```amp registry push --domain amp.example.com appcelerator/pinger:latest``` + +#### Check the registry catalog + +```amp registry ls --domain amp.example.com``` + +## Stack definition + +The internal registry images are available with the local alias local.appcelerator.io. +In the stack definition, use this alias in the hostname part of the image: + +``` +myservice1: + image: local.appcelerator.io/pinger:latest +```