github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/dnsutil/hostsstore/updater.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package hostsstore 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "github.com/containerd/containerd/errdefs" 28 "github.com/containerd/log" 29 "github.com/containerd/nerdctl/v2/pkg/netutil" 30 ) 31 32 // newUpdater creates an updater for hostsD (/var/lib/nerdctl/<ADDRHASH>/etchosts) 33 func newUpdater(id, hostsD string) *updater { 34 u := &updater{ 35 id: id, 36 hostsD: hostsD, 37 metaByIPStr: make(map[string]*Meta), 38 nwNameByIPStr: make(map[string]string), 39 metaByDir: make(map[string]*Meta), 40 } 41 return u 42 } 43 44 // updater is the struct for updater.update() 45 type updater struct { 46 id string 47 hostsD string // "/var/lib/nerdctl/<ADDRHASH>/etchosts" 48 metaByIPStr map[string]*Meta // key: IP string 49 nwNameByIPStr map[string]string // key: IP string, value: key of Meta.Networks 50 metaByDir map[string]*Meta // key: "/var/lib/nerdctl/<ADDRHASH>/etchosts/<NS>/<ID>" 51 } 52 53 // update updates the hostsD tree. 54 // Must be called with a locker for the hostsD directory. 55 func (u *updater) update() error { 56 // phase1: read meta.json 57 if err := u.phase1(); err != nil { 58 return err 59 } 60 // phase2: write hosts 61 return u.phase2() 62 } 63 64 // phase1: read meta.json 65 func (u *updater) phase1() error { 66 readMetaWF := func(path string, _ os.FileInfo, walkErr error) error { 67 if walkErr != nil { 68 return walkErr 69 } 70 if filepath.Base(path) != metaJSON { 71 return nil 72 } 73 metaB, err := os.ReadFile(path) 74 if err != nil { 75 return err 76 } 77 var meta Meta 78 if err := json.Unmarshal(metaB, &meta); err != nil { 79 return err 80 } 81 u.metaByDir[filepath.Dir(path)] = &meta 82 for nwName, cniRes := range meta.Networks { 83 for _, ipCfg := range cniRes.IPs { 84 if ip := ipCfg.Address.IP; ip != nil { 85 if ip.IsLoopback() || ip.IsUnspecified() { 86 continue 87 } 88 ipStr := ip.String() 89 u.metaByIPStr[ipStr] = &meta 90 u.nwNameByIPStr[ipStr] = nwName 91 } 92 } 93 } 94 return nil 95 } 96 return filepath.Walk(u.hostsD, readMetaWF) 97 } 98 99 // phase2: write hosts 100 func (u *updater) phase2() error { 101 writeHostsWF := func(path string, _ os.FileInfo, walkErr error) error { 102 if walkErr != nil { 103 return walkErr 104 } 105 if filepath.Base(path) != "hosts" { 106 return nil 107 } 108 dir := filepath.Dir(path) 109 myMeta, ok := u.metaByDir[dir] 110 if !ok { 111 log.L.WithError(errdefs.ErrNotFound).Debugf("hostsstore metadata %q not found in %q?", metaJSON, dir) 112 return nil 113 } 114 myNetworks := make(map[string]struct{}) 115 for nwName := range myMeta.Networks { 116 myNetworks[nwName] = struct{}{} 117 } 118 119 // parse the hosts file, keep the original host record 120 // retain custom /etc/hosts entries outside <nerdctl> </nerdctl> region 121 r, err := os.Open(path) 122 if err != nil { 123 return err 124 } 125 var buf bytes.Buffer 126 if r != nil { 127 if err := parseHostsButSkipMarkedRegion(&buf, r); err != nil { 128 log.L.WithError(err).Warn("failed to read hosts file") 129 } 130 } 131 132 buf.WriteString(fmt.Sprintf("# %s\n", MarkerBegin)) 133 buf.WriteString("127.0.0.1 localhost localhost.localdomain\n") 134 buf.WriteString("::1 localhost localhost.localdomain\n") 135 136 // keep extra hosts first 137 for host, ip := range myMeta.ExtraHosts { 138 buf.WriteString(fmt.Sprintf("%-15s %s\n", ip, host)) 139 } 140 141 for ip, nwName := range u.nwNameByIPStr { 142 meta := u.metaByIPStr[ip] 143 if line := createLine(nwName, meta, myNetworks); len(line) != 0 { 144 buf.WriteString(fmt.Sprintf("%-15s %s\n", ip, strings.Join(line, " "))) 145 } 146 } 147 148 buf.WriteString(fmt.Sprintf("# %s\n", MarkerEnd)) 149 err = os.WriteFile(path, buf.Bytes(), 0644) 150 if err != nil { 151 return err 152 } 153 return nil 154 } 155 return filepath.Walk(u.hostsD, writeHostsWF) 156 } 157 158 // createLine returns a line string slice. 159 // line is like "foo foo.nw0 bar bar.nw0\n" 160 // for `nerdctl --name=foo --hostname=bar --network=n0`. 161 // 162 // May return an empty string slice 163 func createLine(thatNetwork string, meta *Meta, myNetworks map[string]struct{}) []string { 164 line := []string{} 165 if _, ok := myNetworks[thatNetwork]; !ok { 166 // Do not add lines for other networks 167 return line 168 } 169 baseHostnames := []string{meta.Hostname} 170 if meta.Name != "" { 171 baseHostnames = append(baseHostnames, meta.Name) 172 } 173 174 for _, baseHostname := range baseHostnames { 175 line = append(line, baseHostname) 176 if thatNetwork != netutil.DefaultNetworkName { 177 // Do not add a entry like "foo.bridge" 178 line = append(line, baseHostname+"."+thatNetwork) 179 } 180 } 181 return line 182 }