github.com/DaAlbrecht/cf-cli@v0.0.0-20231128151943-1fe19bb400b9/integration/helpers/app.go (about) 1 package helpers 2 3 import ( 4 "archive/zip" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "math/rand" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "github.com/onsi/gomega/gbytes" 14 15 . "github.com/onsi/gomega" 16 . "github.com/onsi/gomega/gexec" 17 "gopkg.in/yaml.v2" 18 ) 19 20 // CreateApp creates an empty app in CloudController with no package or droplet 21 func CreateApp(app string) { 22 Eventually(CF("create-app", app)).Should(Exit(0)) 23 } 24 25 // QuickDeleteApp deletes the app with the given name, if provided, using 26 // 'cf curl /v3/app... -X DELETE'. 27 func QuickDeleteApp(appName string) { 28 guid := AppGUID(appName) 29 url := fmt.Sprintf("/v3/apps/%s", guid) 30 session := CF("curl", "-X", "DELETE", url) 31 Eventually(session).Should(Exit(0)) 32 } 33 34 // WithHelloWorldApp creates a simple application to use with your CLI command 35 // (typically CF Push). When pushing, be aware of specifying '-b 36 // staticfile_buildpack" so that your app will correctly start up with the 37 // proper buildpack. 38 func WithHelloWorldApp(f func(dir string)) { 39 dir := TempDirAbsolutePath("", "simple-app") 40 defer os.RemoveAll(dir) 41 42 tempfile := filepath.Join(dir, "index.html") 43 err := ioutil.WriteFile(tempfile, []byte(fmt.Sprintf("hello world %d", rand.Int())), 0666) 44 Expect(err).ToNot(HaveOccurred()) 45 46 err = ioutil.WriteFile(filepath.Join(dir, "Staticfile"), nil, 0666) 47 Expect(err).ToNot(HaveOccurred()) 48 49 f(dir) 50 } 51 52 // WithMultiEndpointApp creates a simple application to use with your CLI command 53 // (typically CF Push). It has multiple endpoints which are helpful when testing 54 // http healthchecks. 55 func WithMultiEndpointApp(f func(dir string)) { 56 dir := TempDirAbsolutePath("", "simple-app") 57 defer os.RemoveAll(dir) 58 59 tempfile := filepath.Join(dir, "index.html") 60 err := ioutil.WriteFile(tempfile, []byte(fmt.Sprintf("hello world %d", rand.Int())), 0666) 61 Expect(err).ToNot(HaveOccurred()) 62 63 tempfile = filepath.Join(dir, "other_endpoint.html") 64 err = ioutil.WriteFile(tempfile, []byte("other endpoint"), 0666) 65 Expect(err).ToNot(HaveOccurred()) 66 67 tempfile = filepath.Join(dir, "third_endpoint.html") 68 err = ioutil.WriteFile(tempfile, []byte("third endpoint"), 0666) 69 Expect(err).ToNot(HaveOccurred()) 70 71 err = ioutil.WriteFile(filepath.Join(dir, "Staticfile"), nil, 0666) 72 Expect(err).ToNot(HaveOccurred()) 73 74 f(dir) 75 } 76 77 func WithSidecarApp(f func(dir string), appName string) { 78 withSidecarManifest := func(dir string) { 79 err := ioutil.WriteFile(filepath.Join(dir, "manifest.yml"), []byte(fmt.Sprintf(`--- 80 applications: 81 - name: %s 82 sidecars: 83 - name: sidecar_name 84 process_types: [web] 85 command: sleep infinity`, 86 appName)), 0666) 87 Expect(err).ToNot(HaveOccurred()) 88 89 f(dir) 90 } 91 92 WithHelloWorldApp(withSidecarManifest) 93 } 94 95 func WithTaskApp(f func(dir string), appName string) { 96 withTaskManifest := func(dir string) { 97 err := ioutil.WriteFile(filepath.Join(dir, "manifest.yml"), []byte(fmt.Sprintf(`--- 98 applications: 99 - name: %s 100 processes: 101 - type: task 102 command: echo hi`, 103 appName)), 0666) 104 Expect(err).ToNot(HaveOccurred()) 105 106 f(dir) 107 } 108 109 WithHelloWorldApp(withTaskManifest) 110 } 111 112 // WithNoResourceMatchedApp creates a simple application to use with your CLI 113 // command (typically CF Push). When pushing, be aware of specifying '-b 114 // staticfile_buildpack" so that your app will correctly start up with the 115 // proper buildpack. 116 func WithNoResourceMatchedApp(f func(dir string)) { 117 dir := TempDirAbsolutePath("", "simple-app") 118 defer os.RemoveAll(dir) 119 120 tempfile := filepath.Join(dir, "index.html") 121 122 err := ioutil.WriteFile(tempfile, []byte(fmt.Sprintf("hello world %s", strings.Repeat("a", 65*1024))), 0666) 123 Expect(err).ToNot(HaveOccurred()) 124 125 f(dir) 126 } 127 128 // WithMultiBuildpackApp creates a multi-buildpack application to use with the CF push command. 129 func WithMultiBuildpackApp(f func(dir string)) { 130 f("../../assets/go_calls_ruby") 131 } 132 133 // WithProcfileApp creates an application to use with your CLI command 134 // that contains Procfile defining web and worker processes. 135 func WithProcfileApp(f func(dir string)) { 136 dir := TempDirAbsolutePath("", "simple-ruby-app") 137 defer os.RemoveAll(dir) 138 139 err := ioutil.WriteFile(filepath.Join(dir, "Procfile"), []byte(`--- 140 web: ruby -run -e httpd . -p $PORT 141 console: bundle exec irb`, 142 ), 0666) 143 Expect(err).ToNot(HaveOccurred()) 144 145 //TODO: Remove the ruby version once bundler issue(https://github.com/rubygems/rubygems/issues/6280) 146 // is solved 147 err = ioutil.WriteFile(filepath.Join(dir, "Gemfile"), []byte(`source 'http://rubygems.org' 148 ruby '~> 3.0' 149 gem 'irb' 150 gem 'webrick'`, 151 ), 0666) 152 Expect(err).ToNot(HaveOccurred()) 153 154 err = ioutil.WriteFile(filepath.Join(dir, "Gemfile.lock"), []byte(` 155 GEM 156 remote: http://rubygems.org/ 157 specs: 158 io-console (0.6.0) 159 irb (1.6.4) 160 reline (>= 0.3.0) 161 reline (0.3.3) 162 io-console (~> 0.5) 163 webrick (1.8.1) 164 165 PLATFORMS 166 ruby 167 168 DEPENDENCIES 169 irb 170 webrick 171 `, 172 ), 0666) 173 Expect(err).ToNot(HaveOccurred()) 174 175 f(dir) 176 } 177 178 // WithCrashingApp creates an application to use with your CLI command 179 // that will not successfully start its `web` process 180 func WithCrashingApp(f func(dir string)) { 181 dir := TempDirAbsolutePath("", "crashing-ruby-app") 182 defer os.RemoveAll(dir) 183 184 err := ioutil.WriteFile(filepath.Join(dir, "Procfile"), []byte(`--- 185 web: bogus bogus`, 186 ), 0666) 187 Expect(err).ToNot(HaveOccurred()) 188 189 err = ioutil.WriteFile(filepath.Join(dir, "Gemfile"), nil, 0666) 190 Expect(err).ToNot(HaveOccurred()) 191 192 err = ioutil.WriteFile(filepath.Join(dir, "Gemfile.lock"), []byte(` 193 GEM 194 specs: 195 196 PLATFORMS 197 ruby 198 199 DEPENDENCIES 200 201 BUNDLED WITH 202 2.1.4 203 `), 0666) 204 Expect(err).ToNot(HaveOccurred()) 205 206 f(dir) 207 } 208 209 // WithBananaPantsApp creates a simple application to use with your CLI command 210 // (typically CF Push). When pushing, be aware of specifying '-b 211 // staticfile_buildpack" so that your app will correctly start up with the 212 // proper buildpack. 213 func WithBananaPantsApp(f func(dir string)) { 214 dir := TempDirAbsolutePath("", "simple-app") 215 defer os.RemoveAll(dir) 216 217 tempfile := filepath.Join(dir, "index.html") 218 err := ioutil.WriteFile(tempfile, []byte("Banana Pants"), 0666) 219 Expect(err).ToNot(HaveOccurred()) 220 221 err = ioutil.WriteFile(filepath.Join(dir, "Staticfile"), nil, 0666) 222 Expect(err).ToNot(HaveOccurred()) 223 224 f(dir) 225 } 226 227 func WithEmptyFilesApp(f func(dir string)) { 228 dir := TempDirAbsolutePath("", "simple-app") 229 defer os.RemoveAll(dir) 230 231 tempfile := filepath.Join(dir, "index.html") 232 err := ioutil.WriteFile(tempfile, nil, 0666) 233 Expect(err).ToNot(HaveOccurred()) 234 235 err = ioutil.WriteFile(filepath.Join(dir, "Staticfile"), nil, 0666) 236 Expect(err).ToNot(HaveOccurred()) 237 238 f(dir) 239 } 240 241 // AppGUID returns the GUID for an app in the currently targeted space. 242 func AppGUID(appName string) string { 243 session := CF("app", appName, "--guid") 244 Eventually(session).Should(Exit(0)) 245 return strings.TrimSpace(string(session.Out.Contents())) 246 } 247 248 // AppJSON returns the JSON representation of an app by name. 249 func AppJSON(appName string) string { 250 appGUID := AppGUID(appName) 251 session := CF("curl", fmt.Sprintf("/v3/apps/%s", appGUID)) 252 Eventually(session).Should(Exit(0)) 253 return strings.TrimSpace(string(session.Out.Contents())) 254 } 255 256 // WriteManifest will write out a YAML manifest file at the specified path. 257 func WriteManifest(path string, manifest map[string]interface{}) { 258 body, err := yaml.Marshal(manifest) 259 Expect(err).ToNot(HaveOccurred()) 260 err = ioutil.WriteFile(path, body, 0666) 261 Expect(err).ToNot(HaveOccurred()) 262 } 263 264 func ReadManifest(path string) map[string]interface{} { 265 manifestBytes, err := ioutil.ReadFile(path) 266 Expect(err).ToNot(HaveOccurred()) 267 var manifest map[string]interface{} 268 err = yaml.Unmarshal(manifestBytes, &manifest) 269 Expect(err).ToNot(HaveOccurred()) 270 return manifest 271 } 272 273 // Zipit zips the source into a .zip file in the target dir. 274 func Zipit(source, target, prefix string) error { 275 // Thanks to Svett Ralchev 276 // http://blog.ralch.com/tutorial/golang-working-with-zip/ 277 278 zipfile, err := os.Create(target) 279 if err != nil { 280 return err 281 } 282 defer zipfile.Close() 283 284 if prefix != "" { 285 _, err = io.WriteString(zipfile, prefix) 286 if err != nil { 287 return err 288 } 289 } 290 291 archive := zip.NewWriter(zipfile) 292 defer archive.Close() 293 294 err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 295 if err != nil { 296 return err 297 } 298 299 if path == source { 300 return nil 301 } 302 303 header, err := zip.FileInfoHeader(info) 304 if err != nil { 305 return err 306 } 307 header.Name, err = filepath.Rel(source, path) 308 if err != nil { 309 return err 310 } 311 312 header.Name = filepath.ToSlash(header.Name) 313 314 if info.IsDir() { 315 header.Name += "/" 316 header.SetMode(0755) 317 } else { 318 header.Method = zip.Deflate 319 header.SetMode(0744) 320 } 321 322 writer, err := archive.CreateHeader(header) 323 if err != nil { 324 return err 325 } 326 327 if info.IsDir() { 328 return nil 329 } 330 331 file, err := os.Open(path) 332 if err != nil { 333 return err 334 } 335 defer file.Close() 336 337 _, err = io.Copy(writer, file) 338 return err 339 }) 340 341 return err 342 } 343 344 // ConfirmStagingLogs checks session for log output 345 // indicating that staging is working. 346 func ConfirmStagingLogs(session *Session) { 347 Eventually(session).Should(gbytes.Say(`(?i)Creating container|Successfully created container|Staging\.\.\.|Staging process started \.\.\.|Staging Complete|Exit status 0|Uploading droplet\.\.\.|Uploading complete`)) 348 } 349 350 func WaitForAppMemoryToTakeEffect(appName string, processIndex int, instanceIndex int, shouldRestartFirst bool, expectedMemory string) { 351 if shouldRestartFirst { 352 session := CF("restart", appName) 353 Eventually(session).Should(Exit(0)) 354 } 355 356 Eventually(func() string { 357 session := CF("app", appName) 358 Eventually(session).Should(Exit(0)) 359 appTable := ParseV3AppProcessTable(session.Out.Contents()) 360 return appTable.Processes[processIndex].Instances[instanceIndex].Memory 361 }).Should(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]? of %s`, expectedMemory))) 362 } 363 364 func WaitForAppDiskToTakeEffect(appName string, processIndex int, instanceIndex int, shouldRestartFirst bool, expectedDisk string) { 365 if shouldRestartFirst { 366 session := CF("restart", appName) 367 Eventually(session).Should(Exit(0)) 368 } 369 370 Eventually(func() string { 371 session := CF("app", appName) 372 Eventually(session).Should(Exit(0)) 373 appTable := ParseV3AppProcessTable(session.Out.Contents()) 374 return appTable.Processes[processIndex].Instances[instanceIndex].Disk 375 }).Should(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]? of %s`, expectedDisk))) 376 } 377 378 func WaitForLogRateLimitToTakeEffect(appName string, processIndex int, instanceIndex int, shouldRestartFirst bool, expectedLogRateLimit string) { 379 if shouldRestartFirst { 380 session := CF("restart", appName) 381 Eventually(session).Should(Exit(0)) 382 } 383 384 Eventually(func() string { 385 session := CF("app", appName) 386 Eventually(session).Should(Exit(0)) 387 appTable := ParseV3AppProcessTable(session.Out.Contents()) 388 return appTable.Processes[processIndex].Instances[instanceIndex].LogRate 389 }).Should(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]?/s of %s/s`, expectedLogRateLimit))) 390 }