cniconf.go (2879B)
1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path" 9 "text/template" 10 11 _ "embed" 12 ) 13 14 const ( 15 cniPluginDir = "/opt/cni/bin" 16 ) 17 18 //go:embed cni.conflist 19 var cniConfListData []byte 20 21 var cniConfListTemplate = template.Must( 22 template.New("cni.conflist"). 23 Parse(string(cniConfListData)), 24 ) 25 26 type cniTemplateData struct { 27 Name string 28 BridgeName string 29 Subnet string 30 } 31 32 func generateCNIConfList(data *cniTemplateData) ([]byte, error) { 33 buf := bytes.NewBuffer(nil) 34 35 err := cniConfListTemplate.Execute(buf, data) 36 if err != nil { 37 return nil, fmt.Errorf("failed to generate fake CNI config: %w", err) 38 } 39 40 return buf.Bytes(), nil 41 } 42 43 type cniConfList struct { 44 Version string `json:"cniVersion"` 45 Name string `json:"name"` 46 Plugins []cniPluginConf `json:"plugins"` 47 } 48 49 func (c *cniConfList) RequiredPlugins() []string { 50 var plugins []string 51 52 for _, plugin := range c.Plugins { 53 plugins = append(plugins, plugin.RequiredPlugins()...) 54 } 55 56 return plugins 57 } 58 59 type cniPluginConf struct { 60 Type string `json:"type"` 61 IPAM *cniIPAMConf `json:"ipam,omitempty"` 62 } 63 64 func (c *cniPluginConf) RequiredPlugins() []string { 65 plugins := []string{c.Type} 66 67 if c.IPAM != nil { 68 plugins = append(plugins, c.IPAM.RequiredPlugins()...) 69 } 70 71 return plugins 72 } 73 74 type cniIPAMConf struct { 75 Type string `json:"type"` 76 } 77 78 func (c *cniIPAMConf) RequiredPlugins() []string { 79 return []string{c.Type} 80 } 81 82 // checkCNIPlugin checks that a CNI plugin is installed 83 func checkCNIPlugin(plugin string) (bool, error) { 84 stat, err := os.Stat(path.Join(cniPluginDir, plugin)) 85 if err != nil { 86 if os.IsNotExist(err) { 87 return false, nil 88 } 89 90 return false, fmt.Errorf("failed to stat CNI plugin: %w", err) 91 } 92 93 // Check that the plugin is a file 94 if !stat.Mode().IsRegular() { 95 return false, nil 96 } 97 98 // Check that the plugin is executable 99 if stat.Mode()&0111 != 0111 { 100 return false, nil 101 } 102 103 return true, nil 104 } 105 106 func checkCNIPlugins(conf *cniConfList) (bool, error) { 107 plugins := conf.RequiredPlugins() 108 for _, plugin := range plugins { 109 if ok, err := checkCNIPlugin(plugin); !ok { 110 return false, err 111 } 112 } 113 114 return true, nil 115 } 116 117 func checkNetwork() error { 118 confListBytes, err := generateCNIConfList(&cniTemplateData{ 119 Name: "fakenet0", 120 BridgeName: "fnbr0", 121 Subnet: "1.2.3.0/24", 122 }) 123 if err != nil { 124 return fmt.Errorf("failed to generate fake CNI config: %w", err) 125 } 126 127 var fakeConf cniConfList 128 err = json.Unmarshal(confListBytes, &fakeConf) 129 if err != nil { 130 return fmt.Errorf("invalid CNI configuration template: %w", err) 131 } 132 133 // Check that the CNI plugins are installed 134 if ok, err := checkCNIPlugins(&fakeConf); err != nil { 135 return fmt.Errorf("failed to check CNI plugins: %w", err) 136 } else if !ok { 137 return fmt.Errorf( 138 "CNI plugins are not installed. required: %v", 139 fakeConf.RequiredPlugins(), 140 ) 141 } 142 143 return nil 144 }