github.com/jenkins-x/test-infra@v0.0.7/kubetest/eks/eks.go (about) 1 /* 2 Copyright 2018 The Kubernetes 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 eks implements 'kubetest' deployer interface. 18 // It uses 'aws-k8s-tester' and 'kubectl' binaries, rather than importing internal packages. 19 // All underlying implementation and external dependencies are compiled into one binary. 20 package eks 21 22 import ( 23 "fmt" 24 "io" 25 "io/ioutil" 26 "log" 27 "net/http" 28 "os" 29 osexec "os/exec" 30 "path/filepath" 31 "syscall" 32 "time" 33 34 "github.com/aws/aws-k8s-tester/eksconfig" 35 "github.com/aws/aws-k8s-tester/ekstester" 36 "k8s.io/test-infra/kubetest/process" 37 "k8s.io/test-infra/kubetest/util" 38 ) 39 40 // deployer implements EKS deployer interface using "aws-k8s-tester" binary. 41 // Satisfies "k8s.io/test-infra/kubetest/main.go" 'deployer' and 'publisher" interfaces. 42 // Reference https://github.com/kubernetes/test-infra/blob/master/kubetest/main.go. 43 type deployer struct { 44 stopc chan struct{} 45 cfg *eksconfig.Config 46 ctrl *process.Control 47 } 48 49 // NewDeployer creates a new EKS deployer. 50 func NewDeployer(timeout time.Duration, verbose bool) (ekstester.Deployer, error) { 51 cfg := eksconfig.NewDefault() 52 err := cfg.UpdateFromEnvs() 53 if err != nil { 54 return nil, err 55 } 56 var f *os.File 57 f, err = ioutil.TempFile(os.TempDir(), "aws-k8s-tester-config") 58 if err != nil { 59 return nil, err 60 } 61 cfg.ConfigPath = f.Name() 62 if err = f.Close(); err != nil { 63 return nil, fmt.Errorf("failed to close aws-k8s-tester-config file %v", err) 64 } 65 if err = cfg.Sync(); err != nil { 66 return nil, err 67 } 68 69 dp := &deployer{ 70 stopc: make(chan struct{}), 71 cfg: cfg, 72 ctrl: process.NewControl( 73 timeout, 74 time.NewTimer(timeout), 75 time.NewTimer(timeout), 76 verbose, 77 ), 78 } 79 80 if err = os.RemoveAll(cfg.AWSK8sTesterPath); err != nil { 81 return nil, err 82 } 83 if err = os.MkdirAll(filepath.Dir(cfg.AWSK8sTesterPath), 0700); err != nil { 84 return nil, err 85 } 86 f, err = os.Create(cfg.AWSK8sTesterPath) 87 if err != nil { 88 return nil, fmt.Errorf("failed to create %q (%v)", cfg.AWSK8sTesterPath, err) 89 } 90 cfg.AWSK8sTesterPath = f.Name() 91 if err = httpRead(cfg.AWSK8sTesterDownloadURL, f); err != nil { 92 return nil, err 93 } 94 if err = f.Close(); err != nil { 95 return nil, fmt.Errorf("failed to close aws-k8s-tester file %v", err) 96 } 97 if err = util.EnsureExecutable(cfg.AWSK8sTesterPath); err != nil { 98 return nil, err 99 } 100 return dp, nil 101 } 102 103 // Up creates a new EKS cluster. 104 func (dp *deployer) Up() (err error) { 105 // "create cluster" command outputs cluster information 106 // in the configuration file (e.g. VPC ID, ALB DNS names, etc.) 107 // this needs be reloaded for other deployer method calls 108 createCmd := osexec.Command( 109 dp.cfg.AWSK8sTesterPath, 110 "eks", 111 "--path="+dp.cfg.ConfigPath, 112 "create", 113 "cluster", 114 ) 115 errc := make(chan error) 116 go func() { 117 _, oerr := dp.ctrl.Output(createCmd) 118 errc <- oerr 119 }() 120 select { 121 case <-dp.stopc: 122 fmt.Fprintln(os.Stderr, "received stop signal, interrupting 'create cluster' command...") 123 ierr := createCmd.Process.Signal(syscall.SIGINT) 124 err = fmt.Errorf("'create cluster' command interrupted (interrupt error %v)", ierr) 125 case err = <-errc: 126 } 127 return err 128 } 129 130 // Down tears down the existing EKS cluster. 131 func (dp *deployer) Down() (err error) { 132 // reload configuration from disk to read the latest configuration 133 if _, err = dp.LoadConfig(); err != nil { 134 return err 135 } 136 _, err = dp.ctrl.Output(osexec.Command( 137 dp.cfg.AWSK8sTesterPath, 138 "eks", 139 "--path="+dp.cfg.ConfigPath, 140 "delete", 141 "cluster", 142 )) 143 return err 144 } 145 146 // IsUp returns an error if the cluster is not up and running. 147 func (dp *deployer) IsUp() (err error) { 148 // reload configuration from disk to read the latest configuration 149 if _, err = dp.LoadConfig(); err != nil { 150 return err 151 } 152 _, err = dp.ctrl.Output(osexec.Command( 153 dp.cfg.AWSK8sTesterPath, 154 "eks", 155 "--path="+dp.cfg.ConfigPath, 156 "check", 157 "cluster", 158 )) 159 if err != nil { 160 return err 161 } 162 if _, err = dp.LoadConfig(); err != nil { 163 return err 164 } 165 if dp.cfg.ClusterState.Status != "ACTIVE" { 166 return fmt.Errorf("cluster %q status is %q", 167 dp.cfg.ClusterName, 168 dp.cfg.ClusterState.Status, 169 ) 170 } 171 return nil 172 } 173 174 // TestSetup checks if EKS testing cluster has been set up or not. 175 func (dp *deployer) TestSetup() error { 176 return dp.IsUp() 177 } 178 179 // GetClusterCreated returns EKS cluster creation time and error (if any). 180 func (dp *deployer) GetClusterCreated(v string) (time.Time, error) { 181 err := dp.IsUp() 182 if err != nil { 183 return time.Time{}, err 184 } 185 return dp.cfg.ClusterState.Created, nil 186 } 187 188 func (dp *deployer) GetWorkerNodeLogs() (err error) { 189 // reload configuration from disk to read the latest configuration 190 if _, err = dp.LoadConfig(); err != nil { 191 return err 192 } 193 _, err = dp.ctrl.Output(osexec.Command( 194 dp.cfg.AWSK8sTesterPath, 195 "eks", 196 "--path="+dp.cfg.ConfigPath, 197 "test", "get-worker-node-logs", 198 )) 199 return err 200 } 201 202 // DumpClusterLogs dumps all logs to artifact directory. 203 // Let default kubetest log dumper handle all artifact uploads. 204 // See https://github.com/kubernetes/test-infra/pull/9811/files#r225776067. 205 func (dp *deployer) DumpClusterLogs(artifactDir, _ string) (err error) { 206 // reload configuration from disk to read the latest configuration 207 if _, err = dp.LoadConfig(); err != nil { 208 return err 209 } 210 _, err = dp.ctrl.Output(osexec.Command( 211 dp.cfg.AWSK8sTesterPath, 212 "eks", 213 "--path="+dp.cfg.ConfigPath, 214 "test", "get-worker-node-logs", 215 )) 216 if err != nil { 217 return err 218 } 219 _, err = dp.ctrl.Output(osexec.Command( 220 dp.cfg.AWSK8sTesterPath, 221 "eks", 222 "--path="+dp.cfg.ConfigPath, 223 "test", "dump-cluster-logs", 224 artifactDir, 225 )) 226 return err 227 } 228 229 // KubectlCommand returns "kubectl" command object for API reachability tests. 230 func (dp *deployer) KubectlCommand() (*osexec.Cmd, error) { 231 // reload configuration from disk to read the latest configuration 232 if _, err := dp.LoadConfig(); err != nil { 233 return nil, err 234 } 235 return osexec.Command(dp.cfg.KubectlPath, "--kubeconfig="+dp.cfg.KubeConfigPath), nil 236 } 237 238 // Stop stops ongoing operations. 239 // This is useful for local development. 240 // For example, one may run "Up" but have to cancel ongoing "Up" 241 // operation. Then, it can just send syscall.SIGINT to trigger "Stop". 242 func (dp *deployer) Stop() { 243 close(dp.stopc) 244 } 245 246 // LoadConfig reloads configuration from disk to read the latest 247 // cluster configuration and its states. 248 func (dp *deployer) LoadConfig() (eksconfig.Config, error) { 249 var err error 250 dp.cfg, err = eksconfig.Load(dp.cfg.ConfigPath) 251 return *dp.cfg, err 252 } 253 254 var httpTransport *http.Transport 255 256 func init() { 257 httpTransport = new(http.Transport) 258 httpTransport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/"))) 259 } 260 261 // curl -L [URL] | writer 262 func httpRead(u string, wr io.Writer) error { 263 log.Printf("curl %s", u) 264 cli := &http.Client{Transport: httpTransport} 265 r, err := cli.Get(u) 266 if err != nil { 267 return err 268 } 269 defer r.Body.Close() 270 if r.StatusCode >= 400 { 271 return fmt.Errorf("%v returned %d", u, r.StatusCode) 272 } 273 _, err = io.Copy(wr, r.Body) 274 return err 275 }