github.com/jlmeeker/kismatic@v1.10.1-0.20180612190640-57f9005a1f1a/pkg/cli/seed.go (about) 1 package cli 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strings" 12 13 yaml "gopkg.in/yaml.v2" 14 15 "github.com/apprenda/kismatic/pkg/install" 16 "github.com/apprenda/kismatic/pkg/util" 17 "github.com/spf13/cobra" 18 ) 19 20 const seedRegistryLong = ` 21 Seed a registry with the container images required by KET during the installation 22 or upgrade of your Kubernetes cluster. 23 24 The docker command line client must be installed on this machine to be able 25 to push the images to the registry. 26 27 The location of the registry is obtained from the plan file by default. If you 28 don't have a plan file, you can pass the location of the registry using the 29 --server flag. The server specified through the flag takes precedence over the 30 one defined in the plan file. 31 32 If you want to further control how your registry is seeded, or if you are only 33 interested in the list of all images that can be used in a KET installation, you 34 may use the --list-only flag. 35 ` 36 37 const imagesManifestFile = "./ansible/playbooks/group_vars/container_images.yaml" 38 39 type seedRegistryOptions struct { 40 listOnly bool 41 verbose bool 42 planFile string 43 imagesManifestsFile string 44 registryServer string 45 } 46 47 type imageManifest struct { 48 OfficialImages map[string]image `yaml:"official_images"` 49 } 50 51 type image struct { 52 Name string `yaml:"name"` 53 Version string `yaml:"version"` 54 } 55 56 func (i image) String() string { 57 return fmt.Sprintf("%s:%s", i.Name, i.Version) 58 } 59 60 // NewCmdSeedRegistry returns the command for seeding a container image registry 61 // with the images required by KET 62 func NewCmdSeedRegistry(stdout, stderr io.Writer) *cobra.Command { 63 var options seedRegistryOptions 64 cmd := &cobra.Command{ 65 Use: "seed-registry", 66 Short: "seed a registry with the container images required by KET", 67 Long: seedRegistryLong, 68 RunE: func(cmd *cobra.Command, args []string) error { 69 if len(args) != 0 { 70 return cmd.Usage() 71 } 72 if options.listOnly { 73 return doListImages(stdout, options) 74 } 75 return doSeedRegistry(stdout, stderr, options) 76 }, 77 } 78 cmd.Flags().BoolVar(&options.listOnly, "list-only", false, "when true, the images will only be listed but not pushed to the registry") 79 cmd.Flags().BoolVar(&options.verbose, "verbose", false, "enable verbose logging") 80 cmd.Flags().StringVar(&options.registryServer, "server", "", "set to the location of the registry server, without the protocol (e.g. localhost:5000)") 81 cmd.Flags().StringVar(&options.imagesManifestsFile, "images-manifest-file", "", "path to the container images manifest file") 82 addPlanFileFlag(cmd.Flags(), &options.planFile) 83 return cmd 84 } 85 86 func doListImages(stdout io.Writer, options seedRegistryOptions) error { 87 // try to get path relative to executable 88 manifest := manifestPath(options.imagesManifestsFile) 89 // set default image versions 90 versions := install.VersionOverrides() 91 92 // try to read the plan file to get component versions 93 planner := install.FilePlanner{File: options.planFile} 94 if planner.PlanExists() { 95 plan, err := planner.Read() 96 if err != nil { 97 util.PrettyPrintErr(stdout, "Reading installation plan file %q", options.planFile) 98 return fmt.Errorf("error reading plan file: %v", err) 99 } 100 // set versions from the plan file 101 versions = plan.Versions() 102 } 103 104 im, err := readImageManifest(manifest, versions) 105 if err != nil { 106 return err 107 } 108 for _, img := range im.OfficialImages { 109 fmt.Fprintf(stdout, "%s\n", img) 110 } 111 return nil 112 } 113 114 func doSeedRegistry(stdout, stderr io.Writer, options seedRegistryOptions) error { 115 util.PrintHeader(stdout, "Seed Container Image Registry", '=') 116 117 // Validate that docker is available 118 if _, err := exec.LookPath("docker"); err != nil { 119 return errors.New("Did not find docker installed on this node. The docker CLI must be available for seeding the registry.") 120 } 121 122 // try to get path relative to executable 123 manifest := manifestPath(options.imagesManifestsFile) 124 // set default image versions 125 versions := install.VersionOverrides() 126 127 // Figure out the registry we are to seed 128 // The registry specified through the command-line flag takes precedence 129 // over the one defined in the plan file. 130 server := options.registryServer 131 if server == "" { 132 // we need to get the server from the plan file 133 planner := install.FilePlanner{File: options.planFile} 134 if !planner.PlanExists() { 135 util.PrettyPrintErr(stdout, "Reading installation plan file %q", options.planFile) 136 fmt.Fprintln(stdout, `Run "kismatic install plan" to generate it or use the "--server" option`) 137 return fmt.Errorf("plan does not exist") 138 } 139 plan, err := planner.Read() 140 if err != nil { 141 util.PrettyPrintErr(stdout, "Reading installation plan file %q", options.planFile) 142 return fmt.Errorf("error reading plan file: %v", err) 143 } 144 util.PrettyPrintOk(stdout, "Reading installation plan file %q", options.planFile) 145 // Validate the registry info in the plan file 146 errs := []error{} 147 if plan.DockerRegistry.Server == "" { 148 errs = append(errs, errors.New("The private registry's address must be set in the plan file.")) 149 } 150 if len(errs) > 0 { 151 util.PrettyPrintErr(stdout, "Validating registry configured in plan file") 152 util.PrintValidationErrors(stdout, errs) 153 return errors.New("Invalid registry configuration found in plan file") 154 } 155 server = plan.DockerRegistry.Server 156 // set versions from the plan file 157 versions = plan.Versions() 158 } 159 160 im, err := readImageManifest(manifest, versions) 161 if err != nil { 162 return err 163 } 164 165 // Seed the registry with the images 166 n := len(im.OfficialImages) 167 i := 1 168 for _, img := range im.OfficialImages { 169 l := fmt.Sprintf("(%d/%d) Seeding %s ", i, n, img) 170 pad := 80 - len(l) 171 if pad < 0 { 172 pad = 0 173 } 174 fmt.Fprintf(stdout, l+strings.Repeat(" ", pad)) 175 if err := seedImage(stdout, stderr, img, server, options.verbose); err != nil { 176 return fmt.Errorf("Error seeding image %q: %v", img, err) 177 } 178 util.PrintOkln(stdout) 179 i++ 180 } 181 182 util.PrintColor(stdout, util.Green, "\nThe registry %q was seeded successfully.\n", server) 183 fmt.Fprintln(stdout) 184 return nil 185 } 186 187 func seedImage(stdout, stderr io.Writer, img image, registry string, verbose bool) error { 188 runDockerCmd := func(args ...string) error { 189 command := exec.Command("docker", args...) 190 command.Stderr = stderr 191 if verbose { 192 command.Stdout = stdout 193 } 194 return command.Run() 195 } 196 197 // pull 198 if err := runDockerCmd("pull", img.String()); err != nil { 199 return err 200 } 201 // tag 202 privateImgTag := fmt.Sprintf("%s/%s", registry, img) 203 if err := runDockerCmd("tag", img.String(), privateImgTag); err != nil { 204 return err 205 } 206 // push 207 if err := runDockerCmd("push", privateImgTag); err != nil { 208 return err 209 } 210 return nil 211 } 212 213 func readImageManifest(file string, versions map[string]string) (imageManifest, error) { 214 im := imageManifest{} 215 imBytes, err := ioutil.ReadFile(file) 216 if err != nil { 217 return im, fmt.Errorf("Error reading the list of images: %v", err) 218 } 219 if err := yaml.Unmarshal(imBytes, &im); err != nil { 220 return im, fmt.Errorf("Error unmarshalling the list of images: %v", err) 221 } 222 // subsitute versions 223 for k, img := range im.OfficialImages { 224 if val, ok := versions[k]; ok { 225 img.Version = val 226 im.OfficialImages[k] = img 227 } 228 } 229 return im, nil 230 } 231 232 func manifestPath(customManifestPath string) string { 233 if customManifestPath != "" { 234 return customManifestPath 235 } 236 manifest := imagesManifestFile 237 // to support running the command from not the current path 238 // try to get the path of the executable 239 ex, err := os.Executable() 240 if err == nil { 241 exPath := filepath.Dir(ex) 242 manifest = filepath.Join(exPath, imagesManifestFile) 243 } 244 245 return manifest 246 }