github.com/0xPolygon/supernets2-node@v0.0.0-20230711153321-2fe574524eaa/test/scripts/cmd/dependencies/images.go (about) 1 package dependencies 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "strings" 11 12 "github.com/0xPolygon/supernets2-node/log" 13 "github.com/spf13/afero" 14 "gopkg.in/yaml.v3" 15 ) 16 17 // ImagesConfig is the configuration for the images updater. 18 type ImagesConfig struct { 19 Names []string 20 TargetFilePath string 21 } 22 23 type imageUpdater struct { 24 fs afero.Fs 25 26 targetFilePath string 27 28 dockerUsername string 29 dockerPassword string 30 31 images []string 32 } 33 34 func newImageUpdater(images []string, targetFilePath string) *imageUpdater { 35 return &imageUpdater{ 36 fs: afero.NewOsFs(), 37 38 targetFilePath: targetFilePath, 39 40 dockerUsername: os.Getenv("DOCKERHUB_USERNAME"), 41 dockerPassword: os.Getenv("DOCKERHUB_PASSWORD"), 42 43 images: images, 44 } 45 } 46 47 func (iu *imageUpdater) update() error { 48 for _, image := range iu.images { 49 if err := iu.updateImage(image); err != nil { 50 return err 51 } 52 } 53 return nil 54 } 55 56 func (iu *imageUpdater) updateImage(imageName string) error { 57 currentDigest, err := iu.readCurrentDigest(imageName) 58 if err != nil { 59 return err 60 } 61 62 remoteDigest, err := iu.readRemoteDigest(imageName) 63 if err != nil { 64 return err 65 } 66 67 if currentDigest != remoteDigest { 68 if err := iu.updateDigest(imageName, currentDigest, remoteDigest); err != nil { 69 return err 70 } 71 } 72 return nil 73 } 74 75 func (iu *imageUpdater) readCurrentDigest(imageName string) (string, error) { 76 data, err := afero.ReadFile(iu.fs, getTargetPath(iu.targetFilePath)) 77 if err != nil { 78 return "", err 79 } 80 81 content := struct { 82 Version string 83 Services map[string]struct { 84 Image string 85 } 86 }{} 87 err = yaml.Unmarshal(data, &content) 88 if err != nil { 89 return "", err 90 } 91 for _, c := range content.Services { 92 if c.Image == "" { 93 continue 94 } 95 items := strings.Split(c.Image, "@") 96 const requiredItems = 2 97 if len(items) < requiredItems { 98 continue 99 } 100 if strings.HasPrefix(items[0], imageName) { 101 log.Infof("Current digest of %q is %q", imageName, items[1]) 102 return items[1], nil 103 } 104 } 105 return "", fmt.Errorf("image %q not found in %q", imageName, iu.targetFilePath) 106 } 107 108 func (iu *imageUpdater) readRemoteDigest(imageName string) (string, error) { 109 err := iu.dockerLogin() 110 if err != nil { 111 return "", err 112 } 113 return iu.readLatestTag(imageName) 114 } 115 116 func (iu *imageUpdater) updateDigest(imageName, currentDigest, remoteDigest string) error { 117 targetFilePath := getTargetPath(iu.targetFilePath) 118 log.Infof("Updating %q...", targetFilePath) 119 oldContent, err := afero.ReadFile(iu.fs, targetFilePath) 120 if err != nil { 121 return err 122 } 123 oldImageField := fmt.Sprintf("%s@%s", imageName, currentDigest) 124 newImageField := fmt.Sprintf("%s@%s", imageName, remoteDigest) 125 126 newContent := strings.ReplaceAll(string(oldContent), oldImageField, newImageField) 127 128 return afero.WriteFile(iu.fs, targetFilePath, []byte(newContent), 0664) //nolint:gomnd 129 } 130 131 func (iu *imageUpdater) dockerLogin() error { 132 c := exec.Command("docker", "login", "--username", iu.dockerUsername, "--password-stdin") // #nosec G204 133 stdin, err := c.StdinPipe() 134 if err != nil { 135 return err 136 } 137 passReader := strings.NewReader(iu.dockerPassword) 138 _, err = io.Copy(stdin, passReader) 139 if err != nil { 140 return err 141 } 142 err = stdin.Close() 143 if err != nil { 144 return err 145 } 146 147 return c.Run() 148 } 149 150 type result struct { 151 RepoDigests []string 152 } 153 154 func (iu *imageUpdater) readLatestTag(imageName string) (string, error) { 155 c := exec.Command("docker", "pull", imageName) // #nosec G204 156 err := c.Run() 157 if err != nil { 158 return "", err 159 } 160 161 c = exec.Command("docker", "inspect", imageName) // #nosec G204 162 output, err := c.CombinedOutput() 163 if err != nil { 164 return "", err 165 } 166 167 reader := bytes.NewReader(output) 168 decoder := json.NewDecoder(reader) 169 170 r := struct { 171 Results []result 172 }{} 173 err = decoder.Decode(&r.Results) 174 if err != nil { 175 return "", err 176 } 177 items := strings.Split(r.Results[0].RepoDigests[0], "@") 178 const requiredItems = 2 179 if len(items) < requiredItems { 180 return "", fmt.Errorf("Returned image does not include digest %q", r.Results[0].RepoDigests[0]) 181 } 182 log.Infof("Remote digest of %q is %q", imageName, items[1]) 183 return items[1], nil 184 }