github.com/mgoltzsche/ctnr@v0.7.1-alpha/bundle/builder/hook.go (about) 1 // Copyright © 2017 Max Goltzsche 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package builder 16 17 import ( 18 "encoding/json" 19 "os" 20 "path/filepath" 21 "strconv" 22 23 utils "github.com/mgoltzsche/ctnr/pkg/sliceutils" 24 specs "github.com/opencontainers/runtime-spec/specs-go" 25 "github.com/opencontainers/runtime-tools/generate" 26 "github.com/pkg/errors" 27 ) 28 29 const ANNOTATION_HOOK_ARGS = "com.github.mgoltzsche.ctnr.bundle.hook.args" 30 31 type NetConfig struct { 32 DnsNameserver []string `json:"dns,omitempty"` 33 DnsSearch []string `json:"dns_search,omitempty"` 34 DnsOptions []string `json:"dns_options,omitempty"` 35 Domainname string `json:"domainname,omitempty"` 36 Hosts map[string]string `json:"hosts,omitempty"` 37 Networks []string `json:"networks,omitempty"` 38 Ports []PortMapEntry `json:"ports,omitempty"` 39 IPAMDataDir string `json:"dataDir,omitempty"` 40 } 41 42 type PortMapEntry struct { 43 Target uint16 `json:"target"` 44 Published uint16 `json:"published,omitempty"` 45 Protocol string `json:"protocol,omitempty"` 46 IP string `json:"ip,omitempty"` 47 } 48 49 func (p PortMapEntry) String() string { 50 var s string 51 pub := p.Published 52 if pub == 0 { 53 pub = p.Target 54 } 55 if p.IP == "" { 56 s = strconv.Itoa(int(pub)) + ":" 57 } else { 58 s = p.IP + ":" + strconv.Itoa(int(pub)) + ":" 59 } 60 s += strconv.Itoa(int(p.Target)) 61 if p.Protocol != "" && p.Protocol != "tcp" { 62 s += "/" + p.Protocol 63 } 64 return s 65 } 66 67 type HookBuilder struct { 68 hook NetConfig 69 } 70 71 func NewHookBuilderFromSpec(spec *specs.Spec) (b HookBuilder, err error) { 72 if spec != nil && spec.Annotations != nil { 73 if hookArgs := spec.Annotations[ANNOTATION_HOOK_ARGS]; hookArgs != "" { 74 if err = json.Unmarshal([]byte(hookArgs), &b.hook); err != nil { 75 err = errors.Wrap(err, "hook builder from spec: read spec's hook args") 76 } 77 } 78 } 79 return 80 } 81 82 func (b *HookBuilder) SetIPAMDataDir(ipamDataDir string) { 83 b.hook.IPAMDataDir = ipamDataDir 84 } 85 86 func (b *HookBuilder) SetDomainname(domainname string) { 87 b.hook.Domainname = domainname 88 } 89 90 func (b *HookBuilder) AddDnsNameserver(nameserver string) { 91 utils.AddToSet(&b.hook.DnsNameserver, nameserver) 92 } 93 94 func (b *HookBuilder) AddDnsSearch(search string) { 95 utils.AddToSet(&b.hook.DnsSearch, search) 96 } 97 98 func (b *HookBuilder) AddDnsOption(opt string) { 99 utils.AddToSet(&b.hook.DnsOptions, opt) 100 } 101 102 func (b *HookBuilder) AddHost(host, ip string) { 103 if b.hook.Hosts == nil { 104 b.hook.Hosts = map[string]string{} 105 } 106 b.hook.Hosts[host] = ip 107 } 108 109 func (b *HookBuilder) AddNetwork(networkID string) { 110 utils.AddToSet(&b.hook.Networks, networkID) 111 } 112 113 func (b *HookBuilder) AddPortMapEntry(entry PortMapEntry) { 114 b.hook.Ports = append(b.hook.Ports, entry) 115 } 116 117 func (b *HookBuilder) Build(spec *generate.Generator) (err error) { 118 defer func() { 119 if err != nil { 120 err = errors.Wrap(err, "generate hook call") 121 } 122 }() 123 124 //hookBinary, err := exec.LookPath("ctnr-hooks") 125 executable, err := os.Executable() 126 if err != nil { 127 return errors.Wrap(err, "find network hook binary") 128 } 129 cniPluginPaths := os.Getenv("CNI_PATH") 130 if cniPluginPaths == "" { 131 cniPluginPaths = filepath.Join(filepath.Dir(executable), "..", "cni-plugins") 132 if s, err := os.Stat(cniPluginPaths); err != nil || !s.IsDir() { 133 return errors.New("CNI plugin directory cannot be derived from executable (../cni-plugins) and CNI_PATH env var is not specified. See https://github.com/containernetworking/cni/blob/master/SPEC.md") 134 } 135 } 136 cniEnv := []string{ 137 "PATH=" + os.Getenv("PATH"), 138 "CNI_PATH=" + cniPluginPaths, 139 } 140 if netConfPath := os.Getenv("NETCONFPATH"); netConfPath != "" { 141 cniEnv = append(cniEnv, "NETCONFPATH="+netConfPath) 142 } 143 ipamDataDir := b.hook.IPAMDataDir 144 if ipamDataDir == "" { 145 ipamDataDir = os.Getenv("IPAMDATADIR") 146 } 147 if ipamDataDir != "" { 148 cniEnv = append(cniEnv, "IPAMDATADIR="+ipamDataDir) 149 } 150 151 netInitHookArgs := make([]string, 0, 10) 152 netInitHookArgs = append(netInitHookArgs, "ctnr", "net", "init") 153 netRmHookArgs := make([]string, 0, 5) 154 netRmHookArgs = append(netRmHookArgs, "ctnr", "net", "rm") 155 if b.hook.Domainname != "" { 156 netInitHookArgs = append(netInitHookArgs, "--domainname="+b.hook.Domainname) 157 } 158 for _, nameserver := range b.hook.DnsNameserver { 159 netInitHookArgs = append(netInitHookArgs, "--dns="+nameserver) 160 } 161 for _, search := range b.hook.DnsSearch { 162 netInitHookArgs = append(netInitHookArgs, "--dns-search="+search) 163 } 164 for _, opt := range b.hook.DnsOptions { 165 netInitHookArgs = append(netInitHookArgs, "--dns-opts="+opt) 166 } 167 for name, ip := range b.hook.Hosts { 168 netInitHookArgs = append(netInitHookArgs, "--hosts-entry="+name+"="+ip) 169 } 170 for _, p := range b.hook.Ports { 171 pOpt := "--publish=" + p.String() 172 netInitHookArgs = append(netInitHookArgs, pOpt) 173 netRmHookArgs = append(netRmHookArgs, pOpt) 174 } 175 if len(b.hook.Networks) > 0 { 176 netInitHookArgs = append(netInitHookArgs, b.hook.Networks...) 177 netRmHookArgs = append(netRmHookArgs, b.hook.Networks...) 178 } 179 180 // Add hooks 181 spec.ClearPreStartHooks() 182 spec.ClearPostStopHooks() 183 spec.AddPreStartHook(executable, netInitHookArgs) 184 spec.AddPreStartHookEnv(executable, cniEnv) 185 186 if len(b.hook.Networks) > 0 { 187 spec.AddPostStopHook(executable, netRmHookArgs) 188 spec.AddPostStopHookEnv(executable, cniEnv) 189 } 190 191 // Add hook args metadata as annotation to parse it when it should be modified 192 j, err := json.Marshal(b.hook) 193 if err != nil { 194 return 195 } 196 spec.AddAnnotation(ANNOTATION_HOOK_ARGS, string(j)) 197 return 198 }