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  }