forked from toolshed/abra
		
	Compare commits
	
		
			37 Commits
		
	
	
		
			recipe-ini
			...
			refactor-r
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 89e1046882 | |||
| cbab9b5907 | |||
| b688ddc4b1 | |||
| 6cd331ebd6 | |||
| 
						
						
							
						
						40517171f7
	
				 | 
					
					
						|||
| b2485cc122 | |||
| 9ec99c7712 | |||
| 
						
						
							
						
						aa3910f8df
	
				 | 
					
					
						|||
| 
						
						
							
						
						43990b6fae
	
				 | 
					
					
						|||
| 
						
						
							
						
						91ea2c01a5
	
				 | 
					
					
						|||
| 
						
						
							
						
						316fdd3643
	
				 | 
					
					
						|||
| 
						
						
							
						
						e07ae8cccd
	
				 | 
					
					
						|||
| 
						
						
							
						
						300a4ead01
	
				 | 
					
					
						|||
| 
						
						
							
						
						f209b6f564
	
				 | 
					
					
						|||
| 
						
						
							
						
						791183adfe
	
				 | 
					
					
						|||
| e6b35e8524 | |||
| 8a0274cac0 | |||
| e609924af0 | |||
| 70e2943301 | |||
| 0590c1824d | |||
| 459abecfa5 | |||
| 183ad8f576 | |||
| 
						
						
							
						
						03f94da2d8
	
				 | 
					
					
						|||
| 766f69b0fd | |||
| 
						
						
							
						
						004cd70aed
	
				 | 
					
					
						|||
| 
						
						
							
						
						a4de446f58
	
				 | 
					
					
						|||
| d21c35965d | |||
| 63ea58ffaa | |||
| 
						
						
							
						
						2ecace3e90
	
				 | 
					
					
						|||
| d5ac3958a4 | |||
| 72c20e0039 | |||
| 
						
						
							
						
						575f9905f1
	
				 | 
					
					
						|||
| 
						
						
							
						
						e3a0af5840
	
				 | 
					
					
						|||
| 
						
						
							
						
						9a3a39a185
	
				 | 
					
					
						|||
| 
						
						
							
						
						cea56dddde
	
				 | 
					
					
						|||
| 
						
						
							
						
						2c515ce70a
	
				 | 
					
					
						|||
| 40c0fb4bac | 
@ -29,7 +29,7 @@ steps:
 | 
				
			|||||||
      event: tag
 | 
					      event: tag
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - name: release
 | 
					  - name: release
 | 
				
			||||||
    image: goreleaser/goreleaser:v1.18.2
 | 
					    image: goreleaser/goreleaser:v1.24.0
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      GITEA_TOKEN:
 | 
					      GITEA_TOKEN:
 | 
				
			||||||
        from_secret: goreleaser_gitea_token
 | 
					        from_secret: goreleaser_gitea_token
 | 
				
			||||||
 | 
				
			|||||||
@ -29,6 +29,8 @@ builds:
 | 
				
			|||||||
    ldflags:
 | 
					    ldflags:
 | 
				
			||||||
      - "-X 'main.Commit={{ .Commit }}'"
 | 
					      - "-X 'main.Commit={{ .Commit }}'"
 | 
				
			||||||
      - "-X 'main.Version={{ .Version }}'"
 | 
					      - "-X 'main.Version={{ .Version }}'"
 | 
				
			||||||
 | 
					      - "-s"
 | 
				
			||||||
 | 
					      - "-w"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - id: kadabra
 | 
					  - id: kadabra
 | 
				
			||||||
    binary: kadabra
 | 
					    binary: kadabra
 | 
				
			||||||
@ -50,12 +52,8 @@ builds:
 | 
				
			|||||||
    ldflags:
 | 
					    ldflags:
 | 
				
			||||||
      - "-X 'main.Commit={{ .Commit }}'"
 | 
					      - "-X 'main.Commit={{ .Commit }}'"
 | 
				
			||||||
      - "-X 'main.Version={{ .Version }}'"
 | 
					      - "-X 'main.Version={{ .Version }}'"
 | 
				
			||||||
 | 
					      - "-s"
 | 
				
			||||||
archives:
 | 
					      - "-w"
 | 
				
			||||||
  - replacements:
 | 
					 | 
				
			||||||
      386: i386
 | 
					 | 
				
			||||||
      amd64: x86_64
 | 
					 | 
				
			||||||
    format: binary
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
checksum:
 | 
					checksum:
 | 
				
			||||||
  name_template: "checksums.txt"
 | 
					  name_template: "checksums.txt"
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@
 | 
				
			|||||||
- cassowary
 | 
					- cassowary
 | 
				
			||||||
- codegod100
 | 
					- codegod100
 | 
				
			||||||
- decentral1se
 | 
					- decentral1se
 | 
				
			||||||
 | 
					- fauno
 | 
				
			||||||
- frando
 | 
					- frando
 | 
				
			||||||
- kawaiipunk
 | 
					- kawaiipunk
 | 
				
			||||||
- knoflook
 | 
					- knoflook
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								Dockerfile
									
									
									
									
									
								
							@ -1,23 +1,29 @@
 | 
				
			|||||||
 | 
					# Build image
 | 
				
			||||||
FROM golang:1.21-alpine AS build
 | 
					FROM golang:1.21-alpine AS build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV GOPRIVATE coopcloud.tech
 | 
					ENV GOPRIVATE coopcloud.tech
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apk add --no-cache \
 | 
					RUN apk add --no-cache \
 | 
				
			||||||
  ca-certificates \
 | 
					 | 
				
			||||||
  gcc \
 | 
					  gcc \
 | 
				
			||||||
  git \
 | 
					  git \
 | 
				
			||||||
  make \
 | 
					  make \
 | 
				
			||||||
  musl-dev
 | 
					  musl-dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN update-ca-certificates
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY . /app
 | 
					COPY . /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /app
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN CGO_ENABLED=0 make build
 | 
					RUN CGO_ENABLED=0 make build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM scratch
 | 
					# Release image ("slim")
 | 
				
			||||||
 | 
					FROM alpine:3.19.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apk add --no-cache \
 | 
				
			||||||
 | 
					  ca-certificates \
 | 
				
			||||||
 | 
					  git \
 | 
				
			||||||
 | 
					  openssh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN update-ca-certificates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=build /app/abra /abra
 | 
					COPY --from=build /app/abra /abra
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
									
									
									
									
								
							@ -53,3 +53,6 @@ test:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
loc:
 | 
					loc:
 | 
				
			||||||
	@find . -name "*.go" | xargs wc -l
 | 
						@find . -name "*.go" | xargs wc -l
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					deps:
 | 
				
			||||||
 | 
						@go get -t -u ./...
 | 
				
			||||||
 | 
				
			|||||||
@ -47,22 +47,26 @@ var appBackupListCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -110,22 +114,26 @@ var appBackupDownloadCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -180,22 +188,27 @@ var appBackupCreateCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -238,22 +251,27 @@ var appBackupSnapshotsCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,6 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/config"
 | 
						"coopcloud.tech/abra/pkg/config"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
						"coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
					 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -38,22 +37,26 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipePkg.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipePkg.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/client"
 | 
						"coopcloud.tech/abra/pkg/client"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/config"
 | 
						"coopcloud.tech/abra/pkg/config"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
						"coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
					 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -60,22 +59,27 @@ Example:
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipePkg.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipePkg.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -228,22 +232,27 @@ var appCmdListCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipePkg.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipePkg.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -56,28 +56,32 @@ recipes.
 | 
				
			|||||||
			logrus.Fatal("cannot use <version> and --chaos together")
 | 
								logrus.Fatal("cannot use <version> and --chaos together")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		r, err := recipe.Get(app.Recipe, internal.Offline)
 | 
							if err := r.LoadConfig(); err != nil {
 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -97,6 +101,19 @@ recipes.
 | 
				
			|||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// NOTE(d1): check out specific version before dealing with secrets. This
 | 
				
			||||||
 | 
							// is because we need to deal with GetComposeFiles under the hood and these
 | 
				
			||||||
 | 
							// files change from version to version which therefore affects which
 | 
				
			||||||
 | 
							// secrets might be generated
 | 
				
			||||||
 | 
							version := deployedVersion
 | 
				
			||||||
 | 
							if specificVersion != "" {
 | 
				
			||||||
 | 
								version = specificVersion
 | 
				
			||||||
 | 
								logrus.Debugf("choosing %s as version to deploy", version)
 | 
				
			||||||
 | 
								if err := r.EnsureVersion(version); err != nil {
 | 
				
			||||||
 | 
									logrus.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		secStats, err := secret.PollSecretsStatus(cl, app)
 | 
							secStats, err := secret.PollSecretsStatus(cl, app)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
@ -116,15 +133,6 @@ recipes.
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		version := deployedVersion
 | 
					 | 
				
			||||||
		if specificVersion != "" {
 | 
					 | 
				
			||||||
			version = specificVersion
 | 
					 | 
				
			||||||
			logrus.Debugf("choosing %s as version to deploy", version)
 | 
					 | 
				
			||||||
			if err := recipe.EnsureVersion(app.Recipe, version); err != nil {
 | 
					 | 
				
			||||||
				logrus.Fatal(err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if !internal.Chaos && specificVersion == "" {
 | 
							if !internal.Chaos && specificVersion == "" {
 | 
				
			||||||
			catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
 | 
								catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
@ -137,7 +145,7 @@ recipes.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			if len(versions) == 0 && !internal.Chaos {
 | 
								if len(versions) == 0 && !internal.Chaos {
 | 
				
			||||||
				logrus.Warn("no published versions in catalogue, trying local recipe repository")
 | 
									logrus.Warn("no published versions in catalogue, trying local recipe repository")
 | 
				
			||||||
				recipeVersions, err := recipe.GetRecipeVersions(app.Recipe, internal.Offline)
 | 
									recipeVersions, err := r.GetVersions(internal.Offline)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					logrus.Warn(err)
 | 
										logrus.Warn(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@ -151,11 +159,11 @@ recipes.
 | 
				
			|||||||
			if len(versions) > 0 && !internal.Chaos {
 | 
								if len(versions) > 0 && !internal.Chaos {
 | 
				
			||||||
				version = versions[len(versions)-1]
 | 
									version = versions[len(versions)-1]
 | 
				
			||||||
				logrus.Debugf("choosing %s as version to deploy", version)
 | 
									logrus.Debugf("choosing %s as version to deploy", version)
 | 
				
			||||||
				if err := recipe.EnsureVersion(app.Recipe, version); err != nil {
 | 
									if err := r.EnsureVersion(version); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				head, err := git.GetRecipeHead(app.Recipe)
 | 
									head, err := git.GetHead(app.Recipe)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@ -167,7 +175,7 @@ recipes.
 | 
				
			|||||||
		if internal.Chaos {
 | 
							if internal.Chaos {
 | 
				
			||||||
			logrus.Warnf("chaos mode engaged")
 | 
								logrus.Warnf("chaos mode engaged")
 | 
				
			||||||
			var err error
 | 
								var err error
 | 
				
			||||||
			version, err = recipe.ChaosVersion(app.Recipe)
 | 
								version, err = r.ChaosVersion()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/config"
 | 
						"coopcloud.tech/abra/pkg/config"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
						"coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
						stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						containerTypes "github.com/docker/docker/api/types/container"
 | 
				
			||||||
	"github.com/docker/docker/api/types/filters"
 | 
						"github.com/docker/docker/api/types/filters"
 | 
				
			||||||
	dockerClient "github.com/docker/docker/client"
 | 
						dockerClient "github.com/docker/docker/client"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
@ -88,16 +88,19 @@ the logs.
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error {
 | 
					func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error {
 | 
				
			||||||
	recipe, err := recipe.Get(app.Recipe, internal.Offline)
 | 
						r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logrus.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := r.LoadConfig(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, service := range recipe.Config.Services {
 | 
						for _, service := range r.Config.Services {
 | 
				
			||||||
		filters := filters.NewArgs()
 | 
							filters := filters.NewArgs()
 | 
				
			||||||
		filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))
 | 
							filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
 | 
							containers, err := cl.ContainerList(context.Background(), containerTypes.ListOptions{Filters: filters})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
						"coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/upstream/stack"
 | 
						"coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						"github.com/docker/docker/api/types"
 | 
				
			||||||
 | 
						containerTypes "github.com/docker/docker/api/types/container"
 | 
				
			||||||
	"github.com/docker/docker/api/types/filters"
 | 
						"github.com/docker/docker/api/types/filters"
 | 
				
			||||||
	"github.com/docker/docker/api/types/swarm"
 | 
						"github.com/docker/docker/api/types/swarm"
 | 
				
			||||||
	dockerClient "github.com/docker/docker/client"
 | 
						dockerClient "github.com/docker/docker/client"
 | 
				
			||||||
@ -38,7 +39,12 @@ var appLogsCommand = cli.Command{
 | 
				
			|||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
		stackName := app.StackName()
 | 
							stackName := app.StackName()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -110,7 +116,7 @@ func tailLogs(cl *dockerClient.Client, app config.App, serviceNames []string) er
 | 
				
			|||||||
		// collected in parallel.
 | 
							// collected in parallel.
 | 
				
			||||||
		wg.Add(1)
 | 
							wg.Add(1)
 | 
				
			||||||
		go func(serviceID string) {
 | 
							go func(serviceID string) {
 | 
				
			||||||
			logs, err := cl.ServiceLogs(context.Background(), serviceID, types.ContainerLogsOptions{
 | 
								logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{
 | 
				
			||||||
				ShowStderr: true,
 | 
									ShowStderr: true,
 | 
				
			||||||
				ShowStdout: !internal.StdErrOnly,
 | 
									ShowStdout: !internal.StdErrOnly,
 | 
				
			||||||
				Since:      internal.SinceLogs,
 | 
									Since:      internal.SinceLogs,
 | 
				
			||||||
 | 
				
			|||||||
@ -10,8 +10,6 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/config"
 | 
						"coopcloud.tech/abra/pkg/config"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/jsontable"
 | 
						"coopcloud.tech/abra/pkg/jsontable"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
					 | 
				
			||||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
					 | 
				
			||||||
	"coopcloud.tech/abra/pkg/secret"
 | 
						"coopcloud.tech/abra/pkg/secret"
 | 
				
			||||||
	"github.com/AlecAivazis/survey/v2"
 | 
						"github.com/AlecAivazis/survey/v2"
 | 
				
			||||||
	dockerClient "github.com/docker/docker/client"
 | 
						dockerClient "github.com/docker/docker/client"
 | 
				
			||||||
@ -66,23 +64,43 @@ var appNewCommand = cli.Command{
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		recipe := internal.ValidateRecipe(c)
 | 
							r := internal.ValidateRecipe(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipePkg.EnsureIsClean(recipe.Name); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if c.Args().Get(1) == "" {
 | 
								if c.Args().Get(1) == "" {
 | 
				
			||||||
				if err := recipePkg.EnsureLatest(recipe.Name); err != nil {
 | 
									var version string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									recipeVersions, err := r.GetVersions(internal.Offline)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										logrus.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// NOTE(d1): determine whether recipe versions exist or not and check
 | 
				
			||||||
 | 
									// out the latest version or current HEAD
 | 
				
			||||||
 | 
									if len(recipeVersions) > 0 {
 | 
				
			||||||
 | 
										latest := recipeVersions[len(recipeVersions)-1]
 | 
				
			||||||
 | 
										for tag := range latest {
 | 
				
			||||||
 | 
											version = tag
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if err := r.EnsureVersion(version); err != nil {
 | 
				
			||||||
						logrus.Fatal(err)
 | 
											logrus.Fatal(err)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
				if err := recipePkg.EnsureVersion(recipe.Name, c.Args().Get(1)); err != nil {
 | 
										if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
 | 
											logrus.Fatal(err)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									if err := r.EnsureVersion(c.Args().Get(1)); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -92,7 +110,7 @@ var appNewCommand = cli.Command{
 | 
				
			|||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := ensureDomainFlag(recipe, internal.NewAppServer); err != nil {
 | 
							if err := ensureDomainFlag(r.Name, internal.NewAppServer); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -100,7 +118,7 @@ var appNewCommand = cli.Command{
 | 
				
			|||||||
		logrus.Debugf("%s sanitised as %s for new app", internal.Domain, sanitisedAppName)
 | 
							logrus.Debugf("%s sanitised as %s for new app", internal.Domain, sanitisedAppName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := config.TemplateAppEnvSample(
 | 
							if err := config.TemplateAppEnvSample(
 | 
				
			||||||
			recipe.Name,
 | 
								r.Name,
 | 
				
			||||||
			internal.Domain,
 | 
								internal.Domain,
 | 
				
			||||||
			internal.NewAppServer,
 | 
								internal.NewAppServer,
 | 
				
			||||||
			internal.Domain,
 | 
								internal.Domain,
 | 
				
			||||||
@ -111,23 +129,23 @@ var appNewCommand = cli.Command{
 | 
				
			|||||||
		var secrets AppSecrets
 | 
							var secrets AppSecrets
 | 
				
			||||||
		var secretTable *jsontable.JSONTable
 | 
							var secretTable *jsontable.JSONTable
 | 
				
			||||||
		if internal.Secrets {
 | 
							if internal.Secrets {
 | 
				
			||||||
			sampleEnv, err := recipe.SampleEnv()
 | 
								sampleEnv, err := r.SampleEnv()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			composeFiles, err := config.GetComposeFiles(recipe.Name, sampleEnv)
 | 
								composeFiles, err := config.GetComposeFiles(r.NameEscaped, sampleEnv)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
 | 
								envSamplePath := path.Join(r.Dir, ".env.sample")
 | 
				
			||||||
			secretsConfig, err := secret.ReadSecretsConfig(envSamplePath, composeFiles, config.StackName(internal.Domain))
 | 
								secretsConfig, err := secret.ReadSecretsConfig(envSamplePath, composeFiles, config.StackName(internal.Domain))
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return err
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := promptForSecrets(recipe.Name, secretsConfig); err != nil {
 | 
								if err := promptForSecrets(r.Name, secretsConfig); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -154,9 +172,9 @@ var appNewCommand = cli.Command{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		tableCol := []string{"server", "recipe", "domain"}
 | 
							tableCol := []string{"server", "recipe", "domain"}
 | 
				
			||||||
		table := formatter.CreateTable(tableCol)
 | 
							table := formatter.CreateTable(tableCol)
 | 
				
			||||||
		table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain})
 | 
							table.Append([]string{internal.NewAppServer, r.Name, internal.Domain})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name))
 | 
							fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", r.Name))
 | 
				
			||||||
		fmt.Println("")
 | 
							fmt.Println("")
 | 
				
			||||||
		table.Render()
 | 
							table.Render()
 | 
				
			||||||
		fmt.Println("")
 | 
							fmt.Println("")
 | 
				
			||||||
@ -183,6 +201,12 @@ type AppSecrets map[string]string
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// createSecrets creates all secrets for a new app.
 | 
					// createSecrets creates all secrets for a new app.
 | 
				
			||||||
func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secret, sanitisedAppName string) (AppSecrets, error) {
 | 
					func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secret, sanitisedAppName string) (AppSecrets, error) {
 | 
				
			||||||
 | 
						// NOTE(d1): trim to match app.StackName() implementation
 | 
				
			||||||
 | 
						if len(sanitisedAppName) > config.MAX_SANITISED_APP_NAME_LENGTH {
 | 
				
			||||||
 | 
							logrus.Debugf("trimming %s to %s to avoid runtime limits", sanitisedAppName, sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH])
 | 
				
			||||||
 | 
							sanitisedAppName = sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	secrets, err := secret.GenerateSecrets(cl, secretsConfig, internal.NewAppServer)
 | 
						secrets, err := secret.GenerateSecrets(cl, secretsConfig, internal.NewAppServer)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@ -206,11 +230,11 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
 | 
					// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
 | 
				
			||||||
func ensureDomainFlag(recipe recipe.Recipe, server string) error {
 | 
					func ensureDomainFlag(recipeName string, server string) error {
 | 
				
			||||||
	if internal.Domain == "" && !internal.NoInput {
 | 
						if internal.Domain == "" && !internal.NoInput {
 | 
				
			||||||
		prompt := &survey.Input{
 | 
							prompt := &survey.Input{
 | 
				
			||||||
			Message: "Specify app domain",
 | 
								Message: "Specify app domain",
 | 
				
			||||||
			Default: fmt.Sprintf("%s.%s", recipe.Name, server),
 | 
								Default: fmt.Sprintf("%s.%s", recipeName, server),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if err := survey.AskOne(prompt, &internal.Domain); err != nil {
 | 
							if err := survey.AskOne(prompt, &internal.Domain); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										107
									
								
								cli/app/ps.go
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								cli/app/ps.go
									
									
									
									
									
								
							@ -2,7 +2,8 @@ package app
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"strings"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"coopcloud.tech/abra/cli/internal"
 | 
						"coopcloud.tech/abra/cli/internal"
 | 
				
			||||||
@ -10,11 +11,13 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/client"
 | 
						"coopcloud.tech/abra/pkg/client"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/config"
 | 
						"coopcloud.tech/abra/pkg/config"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/service"
 | 
						"coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
 | 
						abraService "coopcloud.tech/abra/pkg/service"
 | 
				
			||||||
	stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
						stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
	"github.com/buger/goterm"
 | 
						"github.com/buger/goterm"
 | 
				
			||||||
	dockerFormatter "github.com/docker/cli/cli/command/formatter"
 | 
						dockerFormatter "github.com/docker/cli/cli/command/formatter"
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						containerTypes "github.com/docker/docker/api/types/container"
 | 
				
			||||||
 | 
						"github.com/docker/docker/api/types/filters"
 | 
				
			||||||
	dockerClient "github.com/docker/docker/client"
 | 
						dockerClient "github.com/docker/docker/client"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
@ -27,6 +30,7 @@ var appPsCommand = cli.Command{
 | 
				
			|||||||
	ArgsUsage:   "<domain>",
 | 
						ArgsUsage:   "<domain>",
 | 
				
			||||||
	Description: "Show a more detailed status output of a specific deployed app",
 | 
						Description: "Show a more detailed status output of a specific deployed app",
 | 
				
			||||||
	Flags: []cli.Flag{
 | 
						Flags: []cli.Flag{
 | 
				
			||||||
 | 
							internal.MachineReadableFlag,
 | 
				
			||||||
		internal.WatchFlag,
 | 
							internal.WatchFlag,
 | 
				
			||||||
		internal.DebugFlag,
 | 
							internal.DebugFlag,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
@ -35,12 +39,17 @@ var appPsCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		cl, err := client.New(app.Server)
 | 
							cl, err := client.New(app.Server)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName())
 | 
							isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, app.StackName())
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -49,6 +58,15 @@ var appPsCommand = cli.Command{
 | 
				
			|||||||
			logrus.Fatalf("%s is not deployed?", app.Name)
 | 
								logrus.Fatalf("%s is not deployed?", app.Name)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							statuses, err := config.GetAppStatuses([]config.App{app}, true)
 | 
				
			||||||
 | 
							if statusMeta, ok := statuses[app.StackName()]; ok {
 | 
				
			||||||
 | 
								if _, exists := statusMeta["chaos"]; !exists {
 | 
				
			||||||
 | 
									if err := r.EnsureVersion(deployedVersion); err != nil {
 | 
				
			||||||
 | 
										logrus.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Watch {
 | 
							if !internal.Watch {
 | 
				
			||||||
			showPSOutput(c, app, cl)
 | 
								showPSOutput(c, app, cl)
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
@ -66,36 +84,77 @@ var appPsCommand = cli.Command{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// showPSOutput renders ps output.
 | 
					// showPSOutput renders ps output.
 | 
				
			||||||
func showPSOutput(c *cli.Context, app config.App, cl *dockerClient.Client) {
 | 
					func showPSOutput(c *cli.Context, app config.App, cl *dockerClient.Client) {
 | 
				
			||||||
	filters, err := app.Filters(true, true)
 | 
						composeFiles, err := config.GetComposeFiles(app.Recipe, app.Env)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		logrus.Fatal(err)
 | 
							logrus.Fatal(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
 | 
						deployOpts := stack.Deploy{
 | 
				
			||||||
 | 
							Composefiles: composeFiles,
 | 
				
			||||||
 | 
							Namespace:    app.StackName(),
 | 
				
			||||||
 | 
							Prune:        false,
 | 
				
			||||||
 | 
							ResolveImage: stack.ResolveImageAlways,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		logrus.Fatal(err)
 | 
							logrus.Fatal(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var tablerows [][]string
 | 
				
			||||||
 | 
						allContainerStats := make(map[string]map[string]string)
 | 
				
			||||||
 | 
						for _, service := range compose.Services {
 | 
				
			||||||
 | 
							filters := filters.NewArgs()
 | 
				
			||||||
 | 
							filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							containers, err := cl.ContainerList(context.Background(), containerTypes.ListOptions{Filters: filters})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var containerStats map[string]string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(containers) == 0 {
 | 
				
			||||||
 | 
								containerStats = map[string]string{
 | 
				
			||||||
 | 
									"service name": service.Name,
 | 
				
			||||||
 | 
									"image":        "unknown",
 | 
				
			||||||
 | 
									"created":      "unknown",
 | 
				
			||||||
 | 
									"status":       "unknown",
 | 
				
			||||||
 | 
									"state":        "unknown",
 | 
				
			||||||
 | 
									"ports":        "unknown",
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								container := containers[0]
 | 
				
			||||||
 | 
								containerStats = map[string]string{
 | 
				
			||||||
 | 
									"service name": abraService.ContainerToServiceName(container.Names, app.StackName()),
 | 
				
			||||||
 | 
									"image":        formatter.RemoveSha(container.Image),
 | 
				
			||||||
 | 
									"created":      formatter.HumanDuration(container.Created),
 | 
				
			||||||
 | 
									"status":       container.Status,
 | 
				
			||||||
 | 
									"state":        container.State,
 | 
				
			||||||
 | 
									"ports":        dockerFormatter.DisplayablePorts(container.Ports),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							allContainerStats[containerStats["service name"]] = containerStats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var tablerow []string = []string{containerStats["service name"], containerStats["image"], containerStats["created"], containerStats["status"], containerStats["state"], containerStats["ports"]}
 | 
				
			||||||
 | 
							tablerows = append(tablerows, tablerow)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if internal.MachineReadable {
 | 
				
			||||||
 | 
							jsonstring, err := json.Marshal(allContainerStats)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								fmt.Println(string(jsonstring))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
		tableCol := []string{"service name", "image", "created", "status", "state", "ports"}
 | 
							tableCol := []string{"service name", "image", "created", "status", "state", "ports"}
 | 
				
			||||||
		table := formatter.CreateTable(tableCol)
 | 
							table := formatter.CreateTable(tableCol)
 | 
				
			||||||
 | 
							for _, row := range tablerows {
 | 
				
			||||||
	for _, container := range containers {
 | 
								table.Append(row)
 | 
				
			||||||
		var containerNames []string
 | 
					 | 
				
			||||||
		for _, containerName := range container.Names {
 | 
					 | 
				
			||||||
			trimmed := strings.TrimPrefix(containerName, "/")
 | 
					 | 
				
			||||||
			containerNames = append(containerNames, trimmed)
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		tableRow := []string{
 | 
					 | 
				
			||||||
			service.ContainerToServiceName(container.Names, app.StackName()),
 | 
					 | 
				
			||||||
			formatter.RemoveSha(container.Image),
 | 
					 | 
				
			||||||
			formatter.HumanDuration(container.Created),
 | 
					 | 
				
			||||||
			container.Status,
 | 
					 | 
				
			||||||
			container.State,
 | 
					 | 
				
			||||||
			dockerFormatter.DisplayablePorts(container.Ports),
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		table.Append(tableRow)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		table.Render()
 | 
							table.Render()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,6 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"coopcloud.tech/abra/cli/internal"
 | 
						"coopcloud.tech/abra/cli/internal"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
						"coopcloud.tech/abra/pkg/autocomplete"
 | 
				
			||||||
@ -13,7 +12,6 @@ import (
 | 
				
			|||||||
	stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
						stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
	"github.com/AlecAivazis/survey/v2"
 | 
						"github.com/AlecAivazis/survey/v2"
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						"github.com/docker/docker/api/types"
 | 
				
			||||||
	"github.com/docker/docker/api/types/volume"
 | 
					 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -112,28 +110,19 @@ flag.
 | 
				
			|||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		volumeListOptions := volume.ListOptions{fs}
 | 
							volumeList, err := client.GetVolumes(cl, context.Background(), app.Server, fs)
 | 
				
			||||||
		volumeListOKBody, err := cl.VolumeList(context.Background(), volumeListOptions)
 | 
					 | 
				
			||||||
		volumeList := volumeListOKBody.Volumes
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							volumeNames := client.GetVolumeNames(volumeList)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var vols []string
 | 
							if len(volumeNames) > 0 {
 | 
				
			||||||
		for _, vol := range volumeList {
 | 
								err := client.RemoveVolumes(cl, context.Background(), volumeNames, internal.Force, 5)
 | 
				
			||||||
			vols = append(vols, vol.Name)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if len(vols) > 0 {
 | 
					 | 
				
			||||||
			for _, vol := range vols {
 | 
					 | 
				
			||||||
				err = retryFunc(5, func() error {
 | 
					 | 
				
			||||||
					return cl.VolumeRemove(context.Background(), vol, internal.Force) // last argument is for force removing
 | 
					 | 
				
			||||||
				})
 | 
					 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Fatalf("removing volumes failed: %s", err)
 | 
									log.Fatalf("removing volumes failed: %s", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
				logrus.Info(fmt.Sprintf("volume %s removed", vol))
 | 
					
 | 
				
			||||||
			}
 | 
								logrus.Infof("%d volumes removed successfully", len(volumeNames))
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			logrus.Info("no volumes to remove")
 | 
								logrus.Info("no volumes to remove")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -147,21 +136,3 @@ flag.
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// retryFunc retries the given function for the given retries. After the nth
 | 
					 | 
				
			||||||
// retry it waits (n + 1)^2 seconds before the next retry (starting with n=0).
 | 
					 | 
				
			||||||
// It returns an error if the function still failed after the last retry.
 | 
					 | 
				
			||||||
func retryFunc(retries int, fn func() error) error {
 | 
					 | 
				
			||||||
	for i := 0; i < retries; i++ {
 | 
					 | 
				
			||||||
		err := fn()
 | 
					 | 
				
			||||||
		if err == nil {
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if i+1 < retries {
 | 
					 | 
				
			||||||
			sleep := time.Duration(i+1) * time.Duration(i+1)
 | 
					 | 
				
			||||||
			logrus.Infof("%s: waiting %d seconds before next retry", err, sleep)
 | 
					 | 
				
			||||||
			time.Sleep(sleep * time.Second)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return fmt.Errorf("%d retries failed", retries)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -33,22 +33,27 @@ var appRestoreCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -51,33 +51,37 @@ recipes.
 | 
				
			|||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
		stackName := app.StackName()
 | 
							stackName := app.StackName()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		specificVersion := c.Args().Get(1)
 | 
							specificVersion := c.Args().Get(1)
 | 
				
			||||||
		if specificVersion != "" && internal.Chaos {
 | 
							if specificVersion != "" && internal.Chaos {
 | 
				
			||||||
			logrus.Fatal("cannot use <version> and --chaos together")
 | 
								logrus.Fatal("cannot use <version> and --chaos together")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		r, err := recipe.Get(app.Recipe, internal.Offline)
 | 
							if err := r.LoadConfig(); err != nil {
 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -113,7 +117,7 @@ recipes.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		if len(versions) == 0 && !internal.Chaos {
 | 
							if len(versions) == 0 && !internal.Chaos {
 | 
				
			||||||
			logrus.Warn("no published versions in catalogue, trying local recipe repository")
 | 
								logrus.Warn("no published versions in catalogue, trying local recipe repository")
 | 
				
			||||||
			recipeVersions, err := recipe.GetRecipeVersions(app.Recipe, internal.Offline)
 | 
								recipeVersions, err := r.GetVersions(internal.Offline)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Warn(err)
 | 
									logrus.Warn(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -183,7 +187,7 @@ recipes.
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureVersion(app.Recipe, chosenDowngrade); err != nil {
 | 
								if err := r.EnsureVersion(chosenDowngrade); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -191,13 +195,13 @@ recipes.
 | 
				
			|||||||
		if internal.Chaos {
 | 
							if internal.Chaos {
 | 
				
			||||||
			logrus.Warn("chaos mode engaged")
 | 
								logrus.Warn("chaos mode engaged")
 | 
				
			||||||
			var err error
 | 
								var err error
 | 
				
			||||||
			chosenDowngrade, err = recipe.ChaosVersion(app.Recipe)
 | 
								chosenDowngrade, err = r.ChaosVersion()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "abra.sh")
 | 
							abraShPath := fmt.Sprintf("%s/%s/%s", r.Dir, "abra.sh")
 | 
				
			||||||
		abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
 | 
							abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"coopcloud.tech/abra/cli/internal"
 | 
						"coopcloud.tech/abra/cli/internal"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
						"coopcloud.tech/abra/pkg/autocomplete"
 | 
				
			||||||
@ -56,22 +57,27 @@ var appSecretGenerateCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -156,6 +162,8 @@ var appSecretInsertCommand = cli.Command{
 | 
				
			|||||||
	Flags: []cli.Flag{
 | 
						Flags: []cli.Flag{
 | 
				
			||||||
		internal.DebugFlag,
 | 
							internal.DebugFlag,
 | 
				
			||||||
		internal.PassFlag,
 | 
							internal.PassFlag,
 | 
				
			||||||
 | 
							internal.FileFlag,
 | 
				
			||||||
 | 
							internal.TrimFlag,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	Before:       internal.SubCommandBefore,
 | 
						Before:       internal.SubCommandBefore,
 | 
				
			||||||
	ArgsUsage:    "<domain> <secret-name> <version> <data>",
 | 
						ArgsUsage:    "<domain> <secret-name> <version> <data>",
 | 
				
			||||||
@ -188,6 +196,18 @@ Example:
 | 
				
			|||||||
		version := c.Args().Get(2)
 | 
							version := c.Args().Get(2)
 | 
				
			||||||
		data := c.Args().Get(3)
 | 
							data := c.Args().Get(3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if internal.File {
 | 
				
			||||||
 | 
								raw, err := os.ReadFile(data)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									logrus.Fatalf("reading secret from file: %s", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								data = string(raw)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if internal.Trim {
 | 
				
			||||||
 | 
								data = strings.TrimSpace(data)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		secretName := fmt.Sprintf("%s_%s_%s", app.StackName(), name, version)
 | 
							secretName := fmt.Sprintf("%s_%s_%s", app.StackName(), name, version)
 | 
				
			||||||
		if err := client.StoreSecret(cl, secretName, data, app.Server); err != nil {
 | 
							if err := client.StoreSecret(cl, secretName, data, app.Server); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
@ -249,22 +269,27 @@ Example:
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -357,22 +382,27 @@ var appSecretLsCommand = cli.Command{
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/service"
 | 
						"coopcloud.tech/abra/pkg/service"
 | 
				
			||||||
	stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
						stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						containerTypes "github.com/docker/docker/api/types/container"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -48,7 +48,7 @@ var appServicesCommand = cli.Command{
 | 
				
			|||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
 | 
							containers, err := cl.ContainerList(context.Background(), containerTypes.ListOptions{Filters: filters})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -31,6 +31,7 @@ var appUpgradeCommand = cli.Command{
 | 
				
			|||||||
		internal.NoDomainChecksFlag,
 | 
							internal.NoDomainChecksFlag,
 | 
				
			||||||
		internal.DontWaitConvergeFlag,
 | 
							internal.DontWaitConvergeFlag,
 | 
				
			||||||
		internal.OfflineFlag,
 | 
							internal.OfflineFlag,
 | 
				
			||||||
 | 
							internal.ReleaseNotesFlag,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	Before: internal.SubCommandBefore,
 | 
						Before: internal.SubCommandBefore,
 | 
				
			||||||
	Description: `
 | 
						Description: `
 | 
				
			||||||
@ -61,28 +62,32 @@ recipes.
 | 
				
			|||||||
			logrus.Fatal("cannot use <version> and --chaos together")
 | 
								logrus.Fatal("cannot use <version> and --chaos together")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
			if err := recipe.EnsureIsClean(app.Recipe); err != nil {
 | 
					 | 
				
			||||||
				logrus.Fatal(err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if !internal.Offline {
 | 
					 | 
				
			||||||
				if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
					 | 
				
			||||||
					logrus.Fatal(err)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
					 | 
				
			||||||
				logrus.Fatal(err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		recipe, err := recipePkg.Get(app.Recipe, internal.Offline)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := lint.LintForErrors(recipe); err != nil {
 | 
							if !internal.Chaos {
 | 
				
			||||||
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
 | 
									logrus.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !internal.Offline {
 | 
				
			||||||
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
 | 
										logrus.Fatal(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
 | 
									logrus.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.LoadConfig(); err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := lint.LintForErrors(r); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -114,7 +119,7 @@ recipes.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		if len(versions) == 0 && !internal.Chaos {
 | 
							if len(versions) == 0 && !internal.Chaos {
 | 
				
			||||||
			logrus.Warn("no published versions in catalogue, trying local recipe repository")
 | 
								logrus.Warn("no published versions in catalogue, trying local recipe repository")
 | 
				
			||||||
			recipeVersions, err := recipePkg.GetRecipeVersions(app.Recipe, internal.Offline)
 | 
								recipeVersions, err := r.GetVersions(internal.Offline)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Warn(err)
 | 
									logrus.Warn(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -193,17 +198,17 @@ recipes.
 | 
				
			|||||||
		// check out the tag and then they'll appear to be missing. this covers
 | 
							// check out the tag and then they'll appear to be missing. this covers
 | 
				
			||||||
		// when we obviously will forget to write release notes before publishing
 | 
							// when we obviously will forget to write release notes before publishing
 | 
				
			||||||
		var releaseNotes string
 | 
							var releaseNotes string
 | 
				
			||||||
 | 
							if chosenUpgrade != "" {
 | 
				
			||||||
 | 
								parsedChosenUpgrade, err := tagcmp.Parse(chosenUpgrade)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									logrus.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			for _, version := range versions {
 | 
								for _, version := range versions {
 | 
				
			||||||
				parsedVersion, err := tagcmp.Parse(version)
 | 
									parsedVersion, err := tagcmp.Parse(version)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			parsedChosenUpgrade, err := tagcmp.Parse(chosenUpgrade)
 | 
									if parsedVersion.IsGreaterThan(parsedDeployedVersion) && parsedVersion.IsLessThan(parsedChosenUpgrade) {
 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				logrus.Fatal(err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if !(parsedVersion.Equals(parsedDeployedVersion)) && parsedVersion.IsLessThan(parsedChosenUpgrade) {
 | 
					 | 
				
			||||||
					note, err := internal.GetReleaseNotes(app.Recipe, version)
 | 
										note, err := internal.GetReleaseNotes(app.Recipe, version)
 | 
				
			||||||
					if err != nil {
 | 
										if err != nil {
 | 
				
			||||||
						return err
 | 
											return err
 | 
				
			||||||
@ -213,9 +218,10 @@ recipes.
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipePkg.EnsureVersion(app.Recipe, chosenUpgrade); err != nil {
 | 
								if err := r.EnsureVersion(chosenUpgrade); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -223,13 +229,13 @@ recipes.
 | 
				
			|||||||
		if internal.Chaos {
 | 
							if internal.Chaos {
 | 
				
			||||||
			logrus.Warn("chaos mode engaged")
 | 
								logrus.Warn("chaos mode engaged")
 | 
				
			||||||
			var err error
 | 
								var err error
 | 
				
			||||||
			chosenUpgrade, err = recipePkg.ChaosVersion(app.Recipe)
 | 
								chosenUpgrade, err = r.ChaosVersion()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "abra.sh")
 | 
							abraShPath := fmt.Sprintf("%s/%s/%s", r.Dir, "abra.sh")
 | 
				
			||||||
		abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
 | 
							abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
@ -269,6 +275,12 @@ recipes.
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if internal.ReleaseNotes {
 | 
				
			||||||
 | 
								fmt.Println()
 | 
				
			||||||
 | 
								fmt.Print(releaseNotes)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil {
 | 
							if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
						"coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/upstream/stack"
 | 
						"coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
	"github.com/docker/distribution/reference"
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
	"github.com/olekukonko/tablewriter"
 | 
						"github.com/olekukonko/tablewriter"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
@ -58,6 +58,11 @@ var appVersionCommand = cli.Command{
 | 
				
			|||||||
		app := internal.ValidateApp(c)
 | 
							app := internal.ValidateApp(c)
 | 
				
			||||||
		stackName := app.StackName()
 | 
							stackName := app.StackName()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							r, err := recipe.Get(app.Recipe)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		cl, err := client.New(app.Server)
 | 
							cl, err := client.New(app.Server)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
@ -78,7 +83,7 @@ var appVersionCommand = cli.Command{
 | 
				
			|||||||
			logrus.Fatalf("failed to determine version of deployed %s", app.Name)
 | 
								logrus.Fatalf("failed to determine version of deployed %s", app.Name)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		recipeMeta, err := recipe.GetRecipeMeta(app.Recipe, internal.Offline)
 | 
							recipeMeta, err := r.GetRecipeMeta(internal.Offline)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ package app
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"coopcloud.tech/abra/cli/internal"
 | 
						"coopcloud.tech/abra/cli/internal"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
						"coopcloud.tech/abra/pkg/autocomplete"
 | 
				
			||||||
@ -131,12 +132,12 @@ Passing "--force/-f" will select all volumes for removal. Be careful.
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if len(volumesToRemove) > 0 {
 | 
							if len(volumesToRemove) > 0 {
 | 
				
			||||||
			err = client.RemoveVolumes(cl, context.Background(), app.Server, volumesToRemove, internal.Force)
 | 
								err := client.RemoveVolumes(cl, context.Background(), volumesToRemove, internal.Force, 5)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									log.Fatalf("removing volumes failed: %s", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			logrus.Info("volumes removed successfully")
 | 
								logrus.Infof("%d volumes removed successfully", len(volumesToRemove))
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			logrus.Info("no volumes removed")
 | 
								logrus.Info("no volumes removed")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import (
 | 
				
			|||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"coopcloud.tech/abra/cli/internal"
 | 
						"coopcloud.tech/abra/cli/internal"
 | 
				
			||||||
@ -57,9 +58,10 @@ keys configured on your account.
 | 
				
			|||||||
	BashComplete: autocomplete.RecipeNameComplete,
 | 
						BashComplete: autocomplete.RecipeNameComplete,
 | 
				
			||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		recipeName := c.Args().First()
 | 
							recipeName := c.Args().First()
 | 
				
			||||||
 | 
							r := recipe.Recipe{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if recipeName != "" {
 | 
							if recipeName != "" {
 | 
				
			||||||
			internal.ValidateRecipe(c)
 | 
								r = internal.ValidateRecipe(c)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
@ -98,12 +100,12 @@ keys configured on your account.
 | 
				
			|||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			versions, err := recipe.GetRecipeVersions(recipeMeta.Name, internal.Offline)
 | 
								versions, err := r.GetVersions(internal.Offline)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Warn(err)
 | 
									logrus.Warn(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			features, category, err := recipe.GetRecipeFeaturesAndCategory(recipeMeta.Name)
 | 
								features, category, err := r.GetRecipeFeaturesAndCategory()
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				logrus.Warn(err)
 | 
									logrus.Warn(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -130,7 +132,7 @@ keys configured on your account.
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if recipeName == "" {
 | 
							if recipeName == "" {
 | 
				
			||||||
			if err := ioutil.WriteFile(config.RECIPES_JSON, recipesJSON, 0764); err != nil {
 | 
								if err := os.WriteFile(config.RECIPES_JSON, recipesJSON, 0764); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,20 @@ var PassRemoveFlag = &cli.BoolFlag{
 | 
				
			|||||||
	Destination: &PassRemove,
 | 
						Destination: &PassRemove,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var File bool
 | 
				
			||||||
 | 
					var FileFlag = &cli.BoolFlag{
 | 
				
			||||||
 | 
						Name:        "file, f",
 | 
				
			||||||
 | 
						Usage:       "Treat input as a file",
 | 
				
			||||||
 | 
						Destination: &File,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Trim bool
 | 
				
			||||||
 | 
					var TrimFlag = &cli.BoolFlag{
 | 
				
			||||||
 | 
						Name:        "trim, t",
 | 
				
			||||||
 | 
						Usage:       "Trim input",
 | 
				
			||||||
 | 
						Destination: &Trim,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Force force functionality without asking.
 | 
					// Force force functionality without asking.
 | 
				
			||||||
var Force bool
 | 
					var Force bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -95,6 +109,16 @@ var OfflineFlag = &cli.BoolFlag{
 | 
				
			|||||||
	Usage:       "Prefer offline & filesystem access when possible",
 | 
						Usage:       "Prefer offline & filesystem access when possible",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReleaseNotes stores the variable from ReleaseNotesFlag.
 | 
				
			||||||
 | 
					var ReleaseNotes bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReleaseNotesFlag turns on/off printing only release notes when upgrading.
 | 
				
			||||||
 | 
					var ReleaseNotesFlag = &cli.BoolFlag{
 | 
				
			||||||
 | 
						Name:        "releasenotes, r",
 | 
				
			||||||
 | 
						Destination: &ReleaseNotes,
 | 
				
			||||||
 | 
						Usage:       "Only show release notes",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MachineReadable stores the variable from MachineReadableFlag
 | 
					// MachineReadable stores the variable from MachineReadableFlag
 | 
				
			||||||
var MachineReadable bool
 | 
					var MachineReadable bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
						"coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	"github.com/AlecAivazis/survey/v2"
 | 
						"github.com/AlecAivazis/survey/v2"
 | 
				
			||||||
	"github.com/docker/distribution/reference"
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -57,7 +57,10 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
 | 
				
			|||||||
		ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
 | 
							ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	chosenRecipe, err := recipe.Get(recipeName, Offline)
 | 
						r, err := recipe.Get(recipeName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logrus.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if c.Command.Name == "generate" {
 | 
							if c.Command.Name == "generate" {
 | 
				
			||||||
			if strings.Contains(err.Error(), "missing a compose") {
 | 
								if strings.Contains(err.Error(), "missing a compose") {
 | 
				
			||||||
@ -74,7 +77,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("validated %s as recipe argument", recipeName)
 | 
						logrus.Debugf("validated %s as recipe argument", recipeName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return chosenRecipe
 | 
						return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateApp ensures the app name arg is valid.
 | 
					// ValidateApp ensures the app name arg is valid.
 | 
				
			||||||
 | 
				
			|||||||
@ -24,9 +24,14 @@ var recipeFetchCommand = cli.Command{
 | 
				
			|||||||
	BashComplete: autocomplete.RecipeNameComplete,
 | 
						BashComplete: autocomplete.RecipeNameComplete,
 | 
				
			||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		recipeName := c.Args().First()
 | 
							recipeName := c.Args().First()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if recipeName != "" {
 | 
							if recipeName != "" {
 | 
				
			||||||
			internal.ValidateRecipe(c)
 | 
								internal.ValidateRecipe(c)
 | 
				
			||||||
			if err := recipe.Ensure(recipeName); err != nil {
 | 
								r, err := recipe.Get(recipeName)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									logrus.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := r.Ensure(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
@ -39,7 +44,11 @@ var recipeFetchCommand = cli.Command{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		catlBar := formatter.CreateProgressbar(len(catalogue), "fetching latest recipes...")
 | 
							catlBar := formatter.CreateProgressbar(len(catalogue), "fetching latest recipes...")
 | 
				
			||||||
		for recipeName := range catalogue {
 | 
							for recipeName := range catalogue {
 | 
				
			||||||
			if err := recipe.Ensure(recipeName); err != nil {
 | 
								r, err := recipe.Get(recipeName)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									logrus.Fatal(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := r.Ensure(); err != nil {
 | 
				
			||||||
				logrus.Error(err)
 | 
									logrus.Error(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			catlBar.Add(1)
 | 
								catlBar.Add(1)
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,6 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
						"coopcloud.tech/abra/pkg/autocomplete"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/lint"
 | 
						"coopcloud.tech/abra/pkg/lint"
 | 
				
			||||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
					 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -27,24 +26,27 @@ var recipeLintCommand = cli.Command{
 | 
				
			|||||||
	Before:       internal.SubCommandBefore,
 | 
						Before:       internal.SubCommandBefore,
 | 
				
			||||||
	BashComplete: autocomplete.RecipeNameComplete,
 | 
						BashComplete: autocomplete.RecipeNameComplete,
 | 
				
			||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		recipe := internal.ValidateRecipe(c)
 | 
							r := internal.ValidateRecipe(c)
 | 
				
			||||||
 | 
							if err := r.LoadConfig(); err != nil {
 | 
				
			||||||
 | 
								logrus.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipePkg.EnsureExists(recipe.Name); err != nil {
 | 
							if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !internal.Chaos {
 | 
							if !internal.Chaos {
 | 
				
			||||||
			if err := recipePkg.EnsureIsClean(recipe.Name); err != nil {
 | 
								if err := r.EnsureIsClean(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !internal.Offline {
 | 
								if !internal.Offline {
 | 
				
			||||||
				if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
 | 
									if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
					logrus.Fatal(err)
 | 
										logrus.Fatal(err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err := recipePkg.EnsureLatest(recipe.Name); err != nil {
 | 
								if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -62,7 +64,7 @@ var recipeLintCommand = cli.Command{
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				skipped := false
 | 
									skipped := false
 | 
				
			||||||
				if rule.Skip(recipe) {
 | 
									if rule.Skip(r) {
 | 
				
			||||||
					skipped = true
 | 
										skipped = true
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -73,7 +75,7 @@ var recipeLintCommand = cli.Command{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				satisfied := false
 | 
									satisfied := false
 | 
				
			||||||
				if !skipped {
 | 
									if !skipped {
 | 
				
			||||||
					ok, err := rule.Function(recipe)
 | 
										ok, err := rule.Function(r)
 | 
				
			||||||
					if err != nil {
 | 
										if err != nil {
 | 
				
			||||||
						logrus.Warn(err)
 | 
											logrus.Warn(err)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import (
 | 
				
			|||||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
						recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	"coopcloud.tech/tagcmp"
 | 
						"coopcloud.tech/tagcmp"
 | 
				
			||||||
	"github.com/AlecAivazis/survey/v2"
 | 
						"github.com/AlecAivazis/survey/v2"
 | 
				
			||||||
	"github.com/docker/distribution/reference"
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
	"github.com/go-git/go-git/v5"
 | 
						"github.com/go-git/go-git/v5"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
@ -108,14 +108,14 @@ your SSH keys configured on your account.
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		isClean, err := gitPkg.IsClean(recipe.Dir())
 | 
							isClean, err := gitPkg.IsClean(recipe.Dir)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !isClean {
 | 
							if !isClean {
 | 
				
			||||||
			logrus.Infof("%s currently has these unstaged changes 👇", recipe.Name)
 | 
								logrus.Infof("%s currently has these unstaged changes 👇", recipe.Name)
 | 
				
			||||||
			if err := gitPkg.DiffUnstaged(recipe.Dir()); err != nil {
 | 
								if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -184,7 +184,7 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
 | 
				
			|||||||
func createReleaseFromTag(recipe recipe.Recipe, tagString, mainAppVersion string) error {
 | 
					func createReleaseFromTag(recipe recipe.Recipe, tagString, mainAppVersion string) error {
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	directory := path.Join(config.RECIPES_DIR, recipe.Name)
 | 
						directory := path.Join(recipe.Dir)
 | 
				
			||||||
	repo, err := git.PlainOpen(directory)
 | 
						repo, err := git.PlainOpen(directory)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@ -246,8 +246,7 @@ func getTagCreateOptions(tag string) (git.CreateTagOptions, error) {
 | 
				
			|||||||
// addReleaseNotes checks if the release/next release note exists and moves the
 | 
					// addReleaseNotes checks if the release/next release note exists and moves the
 | 
				
			||||||
// file to release/<tag>.
 | 
					// file to release/<tag>.
 | 
				
			||||||
func addReleaseNotes(recipe recipe.Recipe, tag string) error {
 | 
					func addReleaseNotes(recipe recipe.Recipe, tag string) error {
 | 
				
			||||||
	repoPath := path.Join(config.RECIPES_DIR, recipe.Name)
 | 
						tagReleaseNotePath := path.Join(recipe.Dir, "release", tag)
 | 
				
			||||||
	tagReleaseNotePath := path.Join(repoPath, "release", tag)
 | 
					 | 
				
			||||||
	if _, err := os.Stat(tagReleaseNotePath); err == nil {
 | 
						if _, err := os.Stat(tagReleaseNotePath); err == nil {
 | 
				
			||||||
		// Release note for current tag already exist exists.
 | 
							// Release note for current tag already exist exists.
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
@ -255,7 +254,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	nextReleaseNotePath := path.Join(repoPath, "release", "next")
 | 
						nextReleaseNotePath := path.Join(recipe.Dir, "release", "next")
 | 
				
			||||||
	if _, err := os.Stat(nextReleaseNotePath); err == nil {
 | 
						if _, err := os.Stat(nextReleaseNotePath); err == nil {
 | 
				
			||||||
		// release/next note exists. Move it to release/<tag>
 | 
							// release/next note exists. Move it to release/<tag>
 | 
				
			||||||
		if internal.Dry {
 | 
							if internal.Dry {
 | 
				
			||||||
@ -278,11 +277,11 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
 | 
				
			|||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		err = gitPkg.Add(repoPath, path.Join("release", "next"), internal.Dry)
 | 
							err = gitPkg.Add(recipe.Dir, path.Join("release", "next"), internal.Dry)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		err = gitPkg.Add(repoPath, path.Join("release", tag), internal.Dry)
 | 
							err = gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -311,7 +310,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = gitPkg.Add(repoPath, path.Join("release", tag), internal.Dry)
 | 
						err = gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -325,14 +324,14 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	isClean, err := gitPkg.IsClean(recipe.Dir())
 | 
						isClean, err := gitPkg.IsClean(recipe.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if isClean {
 | 
						if isClean {
 | 
				
			||||||
		if !internal.Dry {
 | 
							if !internal.Dry {
 | 
				
			||||||
			return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir())
 | 
								return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -402,8 +401,7 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recipe.Recipe, tags []string) error {
 | 
					func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recipe.Recipe, tags []string) error {
 | 
				
			||||||
	directory := path.Join(config.RECIPES_DIR, recipe.Name)
 | 
						repo, err := git.PlainOpen(recipe.Dir)
 | 
				
			||||||
	repo, err := git.PlainOpen(directory)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -199,13 +199,13 @@ likely to change.
 | 
				
			|||||||
			logrus.Infof("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name)
 | 
								logrus.Infof("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		isClean, err := gitPkg.IsClean(recipe.Dir())
 | 
							isClean, err := gitPkg.IsClean(recipe.Dir)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if !isClean {
 | 
							if !isClean {
 | 
				
			||||||
			logrus.Infof("%s currently has these unstaged changes 👇", recipe.Name)
 | 
								logrus.Infof("%s currently has these unstaged changes 👇", recipe.Name)
 | 
				
			||||||
			if err := gitPkg.DiffUnstaged(recipe.Dir()); err != nil {
 | 
								if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
 | 
				
			||||||
				logrus.Fatal(err)
 | 
									logrus.Fatal(err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@ import (
 | 
				
			|||||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
						recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	"coopcloud.tech/tagcmp"
 | 
						"coopcloud.tech/tagcmp"
 | 
				
			||||||
	"github.com/AlecAivazis/survey/v2"
 | 
						"github.com/AlecAivazis/survey/v2"
 | 
				
			||||||
	"github.com/docker/distribution/reference"
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	"github.com/urfave/cli"
 | 
						"github.com/urfave/cli"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -73,19 +73,19 @@ You may invoke this command in "wizard" mode and be prompted for input:
 | 
				
			|||||||
	Action: func(c *cli.Context) error {
 | 
						Action: func(c *cli.Context) error {
 | 
				
			||||||
		recipe := internal.ValidateRecipe(c)
 | 
							recipe := internal.ValidateRecipe(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipePkg.EnsureIsClean(recipe.Name); err != nil {
 | 
							if err := recipe.EnsureIsClean(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipePkg.EnsureExists(recipe.Name); err != nil {
 | 
							if err := recipe.EnsureExists(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
 | 
							if err := recipe.EnsureUpToDate(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := recipePkg.EnsureLatest(recipe.Name); err != nil {
 | 
							if err := recipe.EnsureLatest(); err != nil {
 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -54,8 +54,9 @@ var recipeVersionCommand = cli.Command{
 | 
				
			|||||||
			logrus.Fatalf("%s has no catalogue published versions?", recipe.Name)
 | 
								logrus.Fatalf("%s has no catalogue published versions?", recipe.Name)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
 | 
					 | 
				
			||||||
		tableCols := []string{"version", "service", "image", "tag"}
 | 
							tableCols := []string{"version", "service", "image", "tag"}
 | 
				
			||||||
 | 
							aggregated_table := formatter.CreateTable(tableCols)
 | 
				
			||||||
 | 
							for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
 | 
				
			||||||
			table := formatter.CreateTable(tableCols)
 | 
								table := formatter.CreateTable(tableCols)
 | 
				
			||||||
			for version, meta := range recipeMeta.Versions[i] {
 | 
								for version, meta := range recipeMeta.Versions[i] {
 | 
				
			||||||
				var versions [][]string
 | 
									var versions [][]string
 | 
				
			||||||
@ -67,11 +68,10 @@ var recipeVersionCommand = cli.Command{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				for _, version := range versions {
 | 
									for _, version := range versions {
 | 
				
			||||||
					table.Append(version)
 | 
										table.Append(version)
 | 
				
			||||||
 | 
										aggregated_table.Append(version)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if internal.MachineReadable {
 | 
									if !internal.MachineReadable {
 | 
				
			||||||
					table.JSONRender()
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					table.SetAutoMergeCellsByColumnIndex([]int{0})
 | 
										table.SetAutoMergeCellsByColumnIndex([]int{0})
 | 
				
			||||||
					table.SetAlignment(tablewriter.ALIGN_LEFT)
 | 
										table.SetAlignment(tablewriter.ALIGN_LEFT)
 | 
				
			||||||
					table.Render()
 | 
										table.Render()
 | 
				
			||||||
@ -79,6 +79,9 @@ var recipeVersionCommand = cli.Command{
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if internal.MachineReadable {
 | 
				
			||||||
 | 
								aggregated_table.JSONRender()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,7 @@ func cleanUp(domainName string) {
 | 
				
			|||||||
// Docker manages SSH connection details. These are stored to disk in
 | 
					// Docker manages SSH connection details. These are stored to disk in
 | 
				
			||||||
// ~/.docker. Abra can manage this completely for the user, so it's an
 | 
					// ~/.docker. Abra can manage this completely for the user, so it's an
 | 
				
			||||||
// implementation detail.
 | 
					// implementation detail.
 | 
				
			||||||
func newContext(c *cli.Context, domainName, username, port string) error {
 | 
					func newContext(c *cli.Context, domainName string) error {
 | 
				
			||||||
	store := contextPkg.NewDefaultDockerContextStore()
 | 
						store := contextPkg.NewDefaultDockerContextStore()
 | 
				
			||||||
	contexts, err := store.Store.List()
 | 
						contexts, err := store.Store.List()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@ -67,9 +67,9 @@ func newContext(c *cli.Context, domainName, username, port string) error {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("creating context with domain %s, username %s and port %s", domainName, username, port)
 | 
						logrus.Debugf("creating context with domain %s", domainName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := client.CreateContext(domainName, username, port); err != nil {
 | 
						if err := client.CreateContext(domainName); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -158,12 +158,7 @@ developer machine.
 | 
				
			|||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		hostConfig, err := sshPkg.GetHostConfig(domainName)
 | 
							if err := newContext(c, domainName); err != nil {
 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			logrus.Fatal(err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if err := newContext(c, domainName, hostConfig.User, hostConfig.Port); err != nil {
 | 
					 | 
				
			||||||
			logrus.Fatal(err)
 | 
								logrus.Fatal(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -316,22 +316,23 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// processRecipeRepoVersion clones, pulls, checks out the version and lints the
 | 
					// processRecipeRepoVersion clones, pulls, checks out the version and lints the
 | 
				
			||||||
// recipe repository.
 | 
					// recipe repository.
 | 
				
			||||||
func processRecipeRepoVersion(recipeName, version string) error {
 | 
					func processRecipeRepoVersion(recipe recipe.Recipe, version string) error {
 | 
				
			||||||
	if err := recipe.EnsureExists(recipeName); err != nil {
 | 
						if err := recipe.EnsureExists(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := recipe.EnsureUpToDate(recipeName); err != nil {
 | 
						if err := recipe.EnsureUpToDate(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := recipe.EnsureVersion(recipeName, version); err != nil {
 | 
						if err := recipe.EnsureVersion(version); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if r, err := recipe.Get(recipeName, internal.Offline); err != nil {
 | 
						if err := recipe.LoadConfig(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	} else if err := lint.LintForErrors(r); err != nil {
 | 
						}
 | 
				
			||||||
 | 
						if err := lint.LintForErrors(recipe); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -442,7 +443,12 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName,
 | 
				
			|||||||
		Env:    env,
 | 
							Env:    env,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = processRecipeRepoVersion(recipeName, upgradeVersion); err != nil {
 | 
						recipe, err := recipe.Get(recipeName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = processRecipeRepoVersion(recipe, upgradeVersion); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										133
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								go.mod
									
									
									
									
									
								
							@ -3,118 +3,131 @@ module coopcloud.tech/abra
 | 
				
			|||||||
go 1.21
 | 
					go 1.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52
 | 
						coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
 | 
				
			||||||
	git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
 | 
						git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
 | 
				
			||||||
	github.com/AlecAivazis/survey/v2 v2.3.7
 | 
						github.com/AlecAivazis/survey/v2 v2.3.7
 | 
				
			||||||
	github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
 | 
						github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
 | 
				
			||||||
	github.com/docker/cli v24.0.7+incompatible
 | 
						github.com/distribution/distribution v2.8.3+incompatible
 | 
				
			||||||
	github.com/docker/distribution v2.8.3+incompatible
 | 
						github.com/docker/cli v26.1.4+incompatible
 | 
				
			||||||
	github.com/docker/docker v24.0.7+incompatible
 | 
						github.com/docker/docker v26.1.4+incompatible
 | 
				
			||||||
	github.com/docker/go-units v0.5.0
 | 
						github.com/docker/go-units v0.5.0
 | 
				
			||||||
	github.com/go-git/go-git/v5 v5.10.0
 | 
						github.com/go-git/go-git/v5 v5.12.0
 | 
				
			||||||
	github.com/google/go-cmp v0.5.9
 | 
						github.com/google/go-cmp v0.6.0
 | 
				
			||||||
	github.com/moby/sys/signal v0.7.0
 | 
						github.com/moby/sys/signal v0.7.0
 | 
				
			||||||
	github.com/moby/term v0.5.0
 | 
						github.com/moby/term v0.5.0
 | 
				
			||||||
	github.com/olekukonko/tablewriter v0.0.5
 | 
						github.com/olekukonko/tablewriter v0.0.5
 | 
				
			||||||
	github.com/pkg/errors v0.9.1
 | 
						github.com/pkg/errors v0.9.1
 | 
				
			||||||
	github.com/schollz/progressbar/v3 v3.14.1
 | 
						github.com/schollz/progressbar/v3 v3.14.4
 | 
				
			||||||
	github.com/sirupsen/logrus v1.9.3
 | 
						github.com/sirupsen/logrus v1.9.3
 | 
				
			||||||
	gotest.tools/v3 v3.5.1
 | 
						gotest.tools/v3 v3.5.1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	dario.cat/mergo v1.0.0 // indirect
 | 
						dario.cat/mergo v1.0.0 // indirect
 | 
				
			||||||
	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 | 
						github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
 | 
				
			||||||
	github.com/BurntSushi/toml v1.0.0 // indirect
 | 
						github.com/BurntSushi/toml v1.4.0 // indirect
 | 
				
			||||||
	github.com/Microsoft/go-winio v0.6.1 // indirect
 | 
						github.com/Microsoft/go-winio v0.6.2 // indirect
 | 
				
			||||||
	github.com/Microsoft/hcsshim v0.9.2 // indirect
 | 
						github.com/ProtonMail/go-crypto v1.0.0 // indirect
 | 
				
			||||||
	github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
 | 
					 | 
				
			||||||
	github.com/acomagu/bufpipe v1.0.4 // indirect
 | 
					 | 
				
			||||||
	github.com/beorn7/perks v1.0.1 // indirect
 | 
						github.com/beorn7/perks v1.0.1 // indirect
 | 
				
			||||||
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 | 
						github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 | 
				
			||||||
	github.com/cloudflare/circl v1.3.3 // indirect
 | 
						github.com/cespare/xxhash/v2 v2.3.0 // indirect
 | 
				
			||||||
	github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
 | 
						github.com/cloudflare/circl v1.3.9 // indirect
 | 
				
			||||||
	github.com/cyphar/filepath-securejoin v0.2.4 // indirect
 | 
						github.com/containerd/log v0.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
 | 
				
			||||||
 | 
						github.com/cyphar/filepath-securejoin v0.2.5 // indirect
 | 
				
			||||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
	github.com/distribution/reference v0.5.0 // indirect
 | 
						github.com/distribution/reference v0.6.0 // indirect
 | 
				
			||||||
 | 
						github.com/docker/distribution v2.7.1+incompatible // indirect
 | 
				
			||||||
	github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
 | 
						github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
 | 
				
			||||||
	github.com/docker/go-connections v0.4.0 // indirect
 | 
						github.com/docker/go-connections v0.5.0 // indirect
 | 
				
			||||||
	github.com/docker/go-metrics v0.0.1 // indirect
 | 
						github.com/docker/go-metrics v0.0.1 // indirect
 | 
				
			||||||
	github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
 | 
						github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
 | 
				
			||||||
	github.com/emirpasic/gods v1.18.1 // indirect
 | 
						github.com/emirpasic/gods v1.18.1 // indirect
 | 
				
			||||||
 | 
						github.com/felixge/httpsnoop v1.0.4 // indirect
 | 
				
			||||||
	github.com/ghodss/yaml v1.0.0 // indirect
 | 
						github.com/ghodss/yaml v1.0.0 // indirect
 | 
				
			||||||
	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 | 
						github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 | 
				
			||||||
	github.com/go-git/go-billy/v5 v5.5.0 // indirect
 | 
						github.com/go-git/go-billy/v5 v5.5.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-logr/logr v1.4.2 // indirect
 | 
				
			||||||
 | 
						github.com/go-logr/stdr v1.2.2 // indirect
 | 
				
			||||||
	github.com/gogo/protobuf v1.3.2 // indirect
 | 
						github.com/gogo/protobuf v1.3.2 // indirect
 | 
				
			||||||
	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 | 
						github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 | 
				
			||||||
	github.com/golang/protobuf v1.5.3 // indirect
 | 
						github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
 | 
				
			||||||
	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 | 
						github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 | 
				
			||||||
	github.com/imdario/mergo v0.3.12 // indirect
 | 
						github.com/inconshreveable/mousetrap v1.1.0 // indirect
 | 
				
			||||||
	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 | 
					 | 
				
			||||||
	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 | 
						github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 | 
				
			||||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 | 
						github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 | 
				
			||||||
	github.com/kevinburke/ssh_config v1.2.0 // indirect
 | 
						github.com/kevinburke/ssh_config v1.2.0 // indirect
 | 
				
			||||||
	github.com/klauspost/compress v1.14.2 // indirect
 | 
						github.com/klauspost/compress v1.17.9 // indirect
 | 
				
			||||||
	github.com/mattn/go-colorable v0.1.12 // indirect
 | 
						github.com/mattn/go-colorable v0.1.13 // indirect
 | 
				
			||||||
	github.com/mattn/go-isatty v0.0.20 // indirect
 | 
						github.com/mattn/go-isatty v0.0.20 // indirect
 | 
				
			||||||
	github.com/mattn/go-runewidth v0.0.14 // indirect
 | 
						github.com/mattn/go-runewidth v0.0.15 // indirect
 | 
				
			||||||
	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 | 
						github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
 | 
				
			||||||
	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
 | 
						github.com/miekg/pkcs11 v1.1.1 // indirect
 | 
				
			||||||
	github.com/miekg/pkcs11 v1.0.3 // indirect
 | 
					 | 
				
			||||||
	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
 | 
						github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
 | 
				
			||||||
	github.com/mitchellh/mapstructure v1.4.3 // indirect
 | 
						github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
				
			||||||
 | 
						github.com/moby/docker-image-spec v1.3.1 // indirect
 | 
				
			||||||
 | 
						github.com/moby/sys/user v0.1.0 // indirect
 | 
				
			||||||
	github.com/morikuni/aec v1.0.0 // indirect
 | 
						github.com/morikuni/aec v1.0.0 // indirect
 | 
				
			||||||
	github.com/opencontainers/go-digest v1.0.0 // indirect
 | 
						github.com/opencontainers/go-digest v1.0.0 // indirect
 | 
				
			||||||
	github.com/opencontainers/runc v1.1.0 // indirect
 | 
						github.com/opencontainers/runc v1.1.13 // indirect
 | 
				
			||||||
	github.com/pjbgf/sha1cd v0.3.0 // indirect
 | 
						github.com/pjbgf/sha1cd v0.3.0 // indirect
 | 
				
			||||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
	github.com/prometheus/client_model v0.3.0 // indirect
 | 
						github.com/prometheus/client_model v0.6.1 // indirect
 | 
				
			||||||
	github.com/prometheus/common v0.42.0 // indirect
 | 
						github.com/prometheus/common v0.54.0 // indirect
 | 
				
			||||||
	github.com/prometheus/procfs v0.10.1 // indirect
 | 
						github.com/prometheus/procfs v0.15.1 // indirect
 | 
				
			||||||
	github.com/rivo/uniseg v0.4.4 // indirect
 | 
						github.com/rivo/uniseg v0.4.7 // indirect
 | 
				
			||||||
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
						github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
				
			||||||
	github.com/skeema/knownhosts v1.2.0 // indirect
 | 
						github.com/skeema/knownhosts v1.2.2 // indirect
 | 
				
			||||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
						github.com/spf13/pflag v1.0.5 // indirect
 | 
				
			||||||
	github.com/xanzy/ssh-agent v0.3.3 // indirect
 | 
						github.com/xanzy/ssh-agent v0.3.3 // indirect
 | 
				
			||||||
	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 | 
						github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 | 
				
			||||||
	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 | 
						github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.14.0 // indirect
 | 
						go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
 | 
				
			||||||
	golang.org/x/mod v0.12.0 // indirect
 | 
						go.opentelemetry.io/otel v1.27.0 // indirect
 | 
				
			||||||
	golang.org/x/net v0.17.0 // indirect
 | 
						go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 // indirect
 | 
				
			||||||
	golang.org/x/sync v0.3.0 // indirect
 | 
						go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect
 | 
				
			||||||
	golang.org/x/term v0.14.0 // indirect
 | 
						go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect
 | 
				
			||||||
	golang.org/x/text v0.13.0 // indirect
 | 
						go.opentelemetry.io/otel/metric v1.27.0 // indirect
 | 
				
			||||||
	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
 | 
						go.opentelemetry.io/otel/sdk v1.27.0 // indirect
 | 
				
			||||||
	golang.org/x/tools v0.13.0 // indirect
 | 
						go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect
 | 
				
			||||||
	google.golang.org/protobuf v1.30.0 // indirect
 | 
						go.opentelemetry.io/otel/trace v1.27.0 // indirect
 | 
				
			||||||
 | 
						go.opentelemetry.io/proto/otlp v1.3.1 // indirect
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.24.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/net v0.26.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sync v0.7.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/term v0.21.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/text v0.16.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/time v0.5.0 // indirect
 | 
				
			||||||
 | 
						google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
 | 
				
			||||||
 | 
						google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
 | 
				
			||||||
 | 
						google.golang.org/grpc v1.64.0 // indirect
 | 
				
			||||||
 | 
						google.golang.org/protobuf v1.34.2 // indirect
 | 
				
			||||||
	gopkg.in/warnings.v0 v0.1.2 // indirect
 | 
						gopkg.in/warnings.v0 v0.1.2 // indirect
 | 
				
			||||||
	gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
						gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
				
			||||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
 | 
					 | 
				
			||||||
	github.com/buger/goterm v1.0.4
 | 
						github.com/buger/goterm v1.0.4
 | 
				
			||||||
	github.com/containerd/containerd v1.5.9 // indirect
 | 
						github.com/containerd/containerd v1.7.18 // indirect
 | 
				
			||||||
	github.com/containers/image v3.0.2+incompatible
 | 
						github.com/containers/image v3.0.2+incompatible
 | 
				
			||||||
	github.com/containers/storage v1.38.2 // indirect
 | 
						github.com/containers/storage v1.38.2 // indirect
 | 
				
			||||||
	github.com/decentral1se/passgen v1.0.1
 | 
						github.com/decentral1se/passgen v1.0.1
 | 
				
			||||||
	github.com/docker/docker-credential-helpers v0.6.4 // indirect
 | 
						github.com/docker/docker-credential-helpers v0.8.2 // indirect
 | 
				
			||||||
	github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
 | 
						github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
 | 
				
			||||||
	github.com/fvbommel/sortorder v1.0.2 // indirect
 | 
						github.com/fvbommel/sortorder v1.1.0 // indirect
 | 
				
			||||||
	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 | 
						github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 | 
				
			||||||
	github.com/gorilla/mux v1.8.0 // indirect
 | 
						github.com/gorilla/mux v1.8.1 // indirect
 | 
				
			||||||
	github.com/hashicorp/go-retryablehttp v0.7.5
 | 
						github.com/hashicorp/go-retryablehttp v0.7.7
 | 
				
			||||||
	github.com/klauspost/pgzip v1.2.6
 | 
						github.com/moby/patternmatcher v0.6.0 // indirect
 | 
				
			||||||
	github.com/moby/patternmatcher v0.5.0 // indirect
 | 
					 | 
				
			||||||
	github.com/moby/sys/sequential v0.5.0 // indirect
 | 
						github.com/moby/sys/sequential v0.5.0 // indirect
 | 
				
			||||||
	github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84 // indirect
 | 
						github.com/opencontainers/image-spec v1.1.0 // indirect
 | 
				
			||||||
	github.com/prometheus/client_golang v1.16.0 // indirect
 | 
						github.com/prometheus/client_golang v1.19.1 // indirect
 | 
				
			||||||
	github.com/sergi/go-diff v1.2.0 // indirect
 | 
						github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
 | 
				
			||||||
	github.com/spf13/cobra v1.3.0 // indirect
 | 
						github.com/spf13/cobra v1.8.1 // indirect
 | 
				
			||||||
	github.com/stretchr/testify v1.8.4
 | 
						github.com/stretchr/testify v1.9.0
 | 
				
			||||||
	github.com/theupdateframework/notary v0.7.0 // indirect
 | 
						github.com/theupdateframework/notary v0.7.0 // indirect
 | 
				
			||||||
	github.com/urfave/cli v1.22.9
 | 
						github.com/urfave/cli v1.22.15
 | 
				
			||||||
	github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b // indirect
 | 
						github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 | 
				
			||||||
	golang.org/x/sys v0.14.0
 | 
						golang.org/x/sys v0.21.0
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -14,19 +14,16 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type Context = contextStore.Metadata
 | 
					type Context = contextStore.Metadata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CreateContext(contextName string, user string, port string) error {
 | 
					// CreateContext creates a new Docker context.
 | 
				
			||||||
	host := contextName
 | 
					func CreateContext(contextName string) error {
 | 
				
			||||||
	if user != "" {
 | 
						host := fmt.Sprintf("ssh://%s", contextName)
 | 
				
			||||||
		host = fmt.Sprintf("%s@%s", user, host)
 | 
					
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if port != "" {
 | 
					 | 
				
			||||||
		host = fmt.Sprintf("%s:%s", host, port)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	host = fmt.Sprintf("ssh://%s", host)
 | 
					 | 
				
			||||||
	if err := createContext(contextName, host); err != nil {
 | 
						if err := createContext(contextName, host); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("created the %s context", contextName)
 | 
						logrus.Debugf("created the %s context", contextName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/containers/image/docker"
 | 
						"github.com/containers/image/docker"
 | 
				
			||||||
	"github.com/containers/image/types"
 | 
						"github.com/containers/image/types"
 | 
				
			||||||
	"github.com/docker/distribution/reference"
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRegistryTags retrieves all tags of an image from a container registry.
 | 
					// GetRegistryTags retrieves all tags of an image from a container registry.
 | 
				
			||||||
 | 
				
			|||||||
@ -2,15 +2,17 @@ package client
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/docker/docker/api/types/filters"
 | 
						"github.com/docker/docker/api/types/filters"
 | 
				
			||||||
	"github.com/docker/docker/api/types/volume"
 | 
						"github.com/docker/docker/api/types/volume"
 | 
				
			||||||
	"github.com/docker/docker/client"
 | 
						"github.com/docker/docker/client"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetVolumes(cl *client.Client, ctx context.Context, server string, fs filters.Args) ([]*volume.Volume, error) {
 | 
					func GetVolumes(cl *client.Client, ctx context.Context, server string, fs filters.Args) ([]*volume.Volume, error) {
 | 
				
			||||||
	volumeListOptions := volume.ListOptions{fs}
 | 
						volumeListOKBody, err := cl.VolumeList(ctx, volume.ListOptions{Filters: fs})
 | 
				
			||||||
	volumeListOKBody, err := cl.VolumeList(ctx, volumeListOptions)
 | 
					 | 
				
			||||||
	volumeList := volumeListOKBody.Volumes
 | 
						volumeList := volumeListOKBody.Volumes
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return volumeList, err
 | 
							return volumeList, err
 | 
				
			||||||
@ -29,13 +31,32 @@ func GetVolumeNames(volumes []*volume.Volume) []string {
 | 
				
			|||||||
	return volumeNames
 | 
						return volumeNames
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RemoveVolumes(cl *client.Client, ctx context.Context, server string, volumeNames []string, force bool) error {
 | 
					func RemoveVolumes(cl *client.Client, ctx context.Context, volumeNames []string, force bool, retries int) error {
 | 
				
			||||||
	for _, volName := range volumeNames {
 | 
						for _, volName := range volumeNames {
 | 
				
			||||||
		err := cl.VolumeRemove(ctx, volName, force)
 | 
							err := retryFunc(5, func() error {
 | 
				
			||||||
 | 
								return cl.VolumeRemove(context.Background(), volName, force)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return fmt.Errorf("volume %s: %s", volName, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// retryFunc retries the given function for the given retries. After the nth
 | 
				
			||||||
 | 
					// retry it waits (n + 1)^2 seconds before the next retry (starting with n=0).
 | 
				
			||||||
 | 
					// It returns an error if the function still failed after the last retry.
 | 
				
			||||||
 | 
					func retryFunc(retries int, fn func() error) error {
 | 
				
			||||||
 | 
						for i := 0; i < retries; i++ {
 | 
				
			||||||
 | 
							err := fn()
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if i+1 < retries {
 | 
				
			||||||
 | 
								sleep := time.Duration(i+1) * time.Duration(i+1)
 | 
				
			||||||
 | 
								logrus.Infof("%s: waiting %d seconds before next retry", err, sleep)
 | 
				
			||||||
 | 
								time.Sleep(sleep * time.Second)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fmt.Errorf("%d retries failed", retries)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package app
 | 
					package client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
@ -11,8 +11,8 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/upstream/stack"
 | 
						"coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
	loader "coopcloud.tech/abra/pkg/upstream/stack"
 | 
						loader "coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
	composetypes "github.com/docker/cli/cli/compose/types"
 | 
						composetypes "github.com/docker/cli/cli/compose/types"
 | 
				
			||||||
	"github.com/docker/distribution/reference"
 | 
					 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -69,9 +69,9 @@ func (a App) StackName() string {
 | 
				
			|||||||
func StackName(appName string) string {
 | 
					func StackName(appName string) string {
 | 
				
			||||||
	stackName := SanitiseAppName(appName)
 | 
						stackName := SanitiseAppName(appName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(stackName) > 45 {
 | 
						if len(stackName) > MAX_SANITISED_APP_NAME_LENGTH {
 | 
				
			||||||
		logrus.Debugf("trimming %s to %s to avoid runtime limits", stackName, stackName[:45])
 | 
							logrus.Debugf("trimming %s to %s to avoid runtime limits", stackName, stackName[:MAX_SANITISED_APP_NAME_LENGTH])
 | 
				
			||||||
		stackName = stackName[:45]
 | 
							stackName = stackName[:MAX_SANITISED_APP_NAME_LENGTH]
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return stackName
 | 
						return stackName
 | 
				
			||||||
 | 
				
			|||||||
@ -45,7 +45,7 @@ func TestGetApp(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestGetComposeFiles(t *testing.T) {
 | 
					func TestGetComposeFiles(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	r, err := recipe.Get("abra-test-recipe", offline)
 | 
						r, err := recipe.Get2("abra-test-recipe", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -92,7 +92,7 @@ func TestGetComposeFiles(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestGetComposeFilesError(t *testing.T) {
 | 
					func TestGetComposeFilesError(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	r, err := recipe.Get("abra-test-recipe", offline)
 | 
						r, err := recipe.Get2("abra-test-recipe", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,9 @@ var REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
 | 
				
			|||||||
var CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
 | 
					var CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
 | 
				
			||||||
var SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
 | 
					var SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MAX_SANITISED_APP_NAME_LENGTH = 45
 | 
				
			||||||
 | 
					const MAX_DOCKER_SECRET_LENGTH = 64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var BackupbotLabel = "coop-cloud.backupbot.enabled"
 | 
					var BackupbotLabel = "coop-cloud.backupbot.enabled"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// envVarModifiers is a list of env var modifier strings. These are added to
 | 
					// envVarModifiers is a list of env var modifier strings. These are added to
 | 
				
			||||||
 | 
				
			|||||||
@ -94,7 +94,7 @@ func TestReadEnv(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestReadAbraShEnvVars(t *testing.T) {
 | 
					func TestReadAbraShEnvVars(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	r, err := recipe.Get("abra-test-recipe", offline)
 | 
						r, err := recipe.Get2("abra-test-recipe", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -124,7 +124,7 @@ func TestReadAbraShEnvVars(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestReadAbraShCmdNames(t *testing.T) {
 | 
					func TestReadAbraShCmdNames(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	r, err := recipe.Get("abra-test-recipe", offline)
 | 
						r, err := recipe.Get2("abra-test-recipe", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -149,7 +149,7 @@ func TestReadAbraShCmdNames(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestCheckEnv(t *testing.T) {
 | 
					func TestCheckEnv(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	r, err := recipe.Get("abra-test-recipe", offline)
 | 
						r, err := recipe.Get2("abra-test-recipe", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -183,7 +183,7 @@ func TestCheckEnv(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestCheckEnvError(t *testing.T) {
 | 
					func TestCheckEnvError(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	r, err := recipe.Get("abra-test-recipe", offline)
 | 
						r, err := recipe.Get2("abra-test-recipe", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -219,7 +219,7 @@ func TestCheckEnvError(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestEnvVarCommentsRemoved(t *testing.T) {
 | 
					func TestEnvVarCommentsRemoved(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	r, err := recipe.Get("abra-test-recipe", offline)
 | 
						r, err := recipe.Get2("abra-test-recipe", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -251,7 +251,7 @@ func TestEnvVarCommentsRemoved(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestEnvVarModifiersIncluded(t *testing.T) {
 | 
					func TestEnvVarModifiersIncluded(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	r, err := recipe.Get("abra-test-recipe", offline)
 | 
						r, err := recipe.Get2("abra-test-recipe", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
						"coopcloud.tech/abra/pkg/formatter"
 | 
				
			||||||
	"github.com/AlecAivazis/survey/v2"
 | 
						"github.com/AlecAivazis/survey/v2"
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						"github.com/docker/docker/api/types"
 | 
				
			||||||
 | 
						containerTypes "github.com/docker/docker/api/types/container"
 | 
				
			||||||
	"github.com/docker/docker/api/types/filters"
 | 
						"github.com/docker/docker/api/types/filters"
 | 
				
			||||||
	"github.com/docker/docker/client"
 | 
						"github.com/docker/docker/client"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
@ -17,7 +18,7 @@ import (
 | 
				
			|||||||
// count of containers does not match 1, then a prompt is presented to let the
 | 
					// count of containers does not match 1, then a prompt is presented to let the
 | 
				
			||||||
// user choose. A count of 0 is handled gracefully.
 | 
					// user choose. A count of 0 is handled gracefully.
 | 
				
			||||||
func GetContainer(c context.Context, cl *client.Client, filters filters.Args, noInput bool) (types.Container, error) {
 | 
					func GetContainer(c context.Context, cl *client.Client, filters filters.Args, noInput bool) (types.Container, error) {
 | 
				
			||||||
	containerOpts := types.ContainerListOptions{Filters: filters}
 | 
						containerOpts := containerTypes.ListOptions{Filters: filters}
 | 
				
			||||||
	containers, err := cl.ContainerList(c, containerOpts)
 | 
						containers, err := cl.ContainerList(c, containerOpts)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return types.Container{}, err
 | 
							return types.Container{}, err
 | 
				
			||||||
 | 
				
			|||||||
@ -4,11 +4,9 @@ import (
 | 
				
			|||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"os/user"
 | 
						"os/user"
 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/config"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5"
 | 
						"github.com/go-git/go-git/v5"
 | 
				
			||||||
	gitConfigPkg "github.com/go-git/go-git/v5/config"
 | 
						gitConfigPkg "github.com/go-git/go-git/v5/config"
 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
@ -16,11 +14,9 @@ import (
 | 
				
			|||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRecipeHead retrieves latest HEAD metadata.
 | 
					// GetHead retrieves latest HEAD metadata.
 | 
				
			||||||
func GetRecipeHead(recipeName string) (*plumbing.Reference, error) {
 | 
					func GetHead(dir string) (*plumbing.Reference, error) {
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
						repo, err := git.PlainOpen(dir)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	repo, err := git.PlainOpen(recipeDir)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ import (
 | 
				
			|||||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
						"coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
						recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
				
			||||||
	"coopcloud.tech/tagcmp"
 | 
						"coopcloud.tech/tagcmp"
 | 
				
			||||||
	"github.com/docker/distribution/reference"
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
	"github.com/go-git/go-git/v5"
 | 
						"github.com/go-git/go-git/v5"
 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
@ -115,6 +115,13 @@ var LintRules = map[string][]LintRule{
 | 
				
			|||||||
			HowToResolve: "upload your recipe to git.coopcloud.tech/coop-cloud/...",
 | 
								HowToResolve: "upload your recipe to git.coopcloud.tech/coop-cloud/...",
 | 
				
			||||||
			Function:     LintHasRecipeRepo,
 | 
								Function:     LintHasRecipeRepo,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Ref:          "R015",
 | 
				
			||||||
 | 
								Level:        "warn",
 | 
				
			||||||
 | 
								Description:  "long secret names",
 | 
				
			||||||
 | 
								HowToResolve: "reduce length of secret names to 12 chars",
 | 
				
			||||||
 | 
								Function:     LintSecretLengths,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"error": {
 | 
						"error": {
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
@ -203,7 +210,7 @@ func LintComposeVersion(recipe recipe.Recipe) (bool, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func LintEnvConfigPresent(recipe recipe.Recipe) (bool, error) {
 | 
					func LintEnvConfigPresent(recipe recipe.Recipe) (bool, error) {
 | 
				
			||||||
	envSample := fmt.Sprintf("%s/%s/.env.sample", config.RECIPES_DIR, recipe.Name)
 | 
						envSample := fmt.Sprintf("%s/%s/.env.sample", recipe.Dir)
 | 
				
			||||||
	if _, err := os.Stat(envSample); !os.IsNotExist(err) {
 | 
						if _, err := os.Stat(envSample); !os.IsNotExist(err) {
 | 
				
			||||||
		return true, nil
 | 
							return true, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -226,7 +233,7 @@ func LintAppService(recipe recipe.Recipe) (bool, error) {
 | 
				
			|||||||
// the recipe. This typically means that no domain is required to deploy and
 | 
					// the recipe. This typically means that no domain is required to deploy and
 | 
				
			||||||
// therefore no matching traefik deploy label will be present.
 | 
					// therefore no matching traefik deploy label will be present.
 | 
				
			||||||
func LintTraefikEnabledSkipCondition(recipe recipe.Recipe) (bool, error) {
 | 
					func LintTraefikEnabledSkipCondition(recipe recipe.Recipe) (bool, error) {
 | 
				
			||||||
	envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
 | 
						envSamplePath := path.Join(recipe.Dir, ".env.sample")
 | 
				
			||||||
	sampleEnv, err := config.ReadEnv(envSamplePath)
 | 
						sampleEnv, err := config.ReadEnv(envSamplePath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return false, fmt.Errorf("Unable to discover .env.sample for %s", recipe.Name)
 | 
							return false, fmt.Errorf("Unable to discover .env.sample for %s", recipe.Name)
 | 
				
			||||||
@ -351,7 +358,7 @@ func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func LintMetadataFilledIn(r recipe.Recipe) (bool, error) {
 | 
					func LintMetadataFilledIn(r recipe.Recipe) (bool, error) {
 | 
				
			||||||
	features, category, err := recipe.GetRecipeFeaturesAndCategory(r.Name)
 | 
						features, category, err := r.GetRecipeFeaturesAndCategory()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return false, err
 | 
							return false, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -401,6 +408,16 @@ func LintHasRecipeRepo(recipe recipe.Recipe) (bool, error) {
 | 
				
			|||||||
	return true, nil
 | 
						return true, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func LintSecretLengths(recipe recipe.Recipe) (bool, error) {
 | 
				
			||||||
 | 
						for name := range recipe.Config.Secrets {
 | 
				
			||||||
 | 
							if len(name) > 12 {
 | 
				
			||||||
 | 
								return false, fmt.Errorf("secret %s is longer than 12 characters", name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func LintValidTags(recipe recipe.Recipe) (bool, error) {
 | 
					func LintValidTags(recipe recipe.Recipe) (bool, error) {
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipe.Name)
 | 
						recipeDir := path.Join(config.RECIPES_DIR, recipe.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ package recipe
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io/ioutil"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
@ -22,8 +22,8 @@ import (
 | 
				
			|||||||
	loader "coopcloud.tech/abra/pkg/upstream/stack"
 | 
						loader "coopcloud.tech/abra/pkg/upstream/stack"
 | 
				
			||||||
	"coopcloud.tech/abra/pkg/web"
 | 
						"coopcloud.tech/abra/pkg/web"
 | 
				
			||||||
	"coopcloud.tech/tagcmp"
 | 
						"coopcloud.tech/tagcmp"
 | 
				
			||||||
 | 
						"github.com/distribution/reference"
 | 
				
			||||||
	composetypes "github.com/docker/cli/cli/compose/types"
 | 
						composetypes "github.com/docker/cli/cli/compose/types"
 | 
				
			||||||
	"github.com/docker/distribution/reference"
 | 
					 | 
				
			||||||
	"github.com/go-git/go-git/v5"
 | 
						"github.com/go-git/go-git/v5"
 | 
				
			||||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
@ -130,17 +130,10 @@ type Features struct {
 | 
				
			|||||||
	SSO         string `json:"sso"`
 | 
						SSO         string `json:"sso"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Recipe represents a recipe.
 | 
					 | 
				
			||||||
type Recipe struct {
 | 
					 | 
				
			||||||
	Name   string
 | 
					 | 
				
			||||||
	Config *composetypes.Config
 | 
					 | 
				
			||||||
	Meta   RecipeMeta
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Push pushes the latest changes to a SSH URL remote. You need to have your
 | 
					// Push pushes the latest changes to a SSH URL remote. You need to have your
 | 
				
			||||||
// local SSH configuration for git.coopcloud.tech working for this to work
 | 
					// local SSH configuration for git.coopcloud.tech working for this to work
 | 
				
			||||||
func (r Recipe) Push(dryRun bool) error {
 | 
					func (r Recipe) Push(dryRun bool) error {
 | 
				
			||||||
	repo, err := git.PlainOpen(r.Dir())
 | 
						repo, err := git.PlainOpen(r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -149,21 +142,16 @@ func (r Recipe) Push(dryRun bool) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := gitPkg.Push(r.Dir(), "origin-ssh", true, dryRun); err != nil {
 | 
						if err := gitPkg.Push(r.Dir, "origin-ssh", true, dryRun); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Dir retrieves the recipe repository path
 | 
					 | 
				
			||||||
func (r Recipe) Dir() string {
 | 
					 | 
				
			||||||
	return path.Join(config.RECIPES_DIR, r.Name)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// UpdateLabel updates a recipe label
 | 
					// UpdateLabel updates a recipe label
 | 
				
			||||||
func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
 | 
					func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
 | 
				
			||||||
	fullPattern := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, r.Name, pattern)
 | 
						fullPattern := fmt.Sprintf("%s/%s", r.Dir, pattern)
 | 
				
			||||||
	if err := compose.UpdateLabel(fullPattern, serviceName, label, r.Name); err != nil {
 | 
						if err := compose.UpdateLabel(fullPattern, serviceName, label, r.Name); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -172,7 +160,7 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// UpdateTag updates a recipe tag
 | 
					// UpdateTag updates a recipe tag
 | 
				
			||||||
func (r Recipe) UpdateTag(image, tag string) (bool, error) {
 | 
					func (r Recipe) UpdateTag(image, tag string) (bool, error) {
 | 
				
			||||||
	pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, r.Name)
 | 
						pattern := fmt.Sprintf("%s/compose**yml", r.Dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	image = formatter.StripTagMeta(image)
 | 
						image = formatter.StripTagMeta(image)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -188,7 +176,7 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
 | 
				
			|||||||
func (r Recipe) Tags() ([]string, error) {
 | 
					func (r Recipe) Tags() ([]string, error) {
 | 
				
			||||||
	var tags []string
 | 
						var tags []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repo, err := git.PlainOpen(r.Dir())
 | 
						repo, err := git.PlainOpen(r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return tags, err
 | 
							return tags, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -210,49 +198,51 @@ func (r Recipe) Tags() ([]string, error) {
 | 
				
			|||||||
	return tags, nil
 | 
						return tags, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Get retrieves a recipe.
 | 
					// // Get2 retrieves a recipe.
 | 
				
			||||||
func Get(recipeName string, offline bool) (Recipe, error) {
 | 
					// func (r Recipe) Load(offline bool) (Recipe2, error) {
 | 
				
			||||||
	if err := EnsureExists(recipeName); err != nil {
 | 
					//
 | 
				
			||||||
		return Recipe{}, err
 | 
					// 	meta, err := r.GetRecipeMeta(offline)
 | 
				
			||||||
 | 
					// 	if err != nil {
 | 
				
			||||||
 | 
					// 		switch err.(type) {
 | 
				
			||||||
 | 
					// 		case RecipeMissingFromCatalogue:
 | 
				
			||||||
 | 
					// 			meta = RecipeMeta{}
 | 
				
			||||||
 | 
					// 		default:
 | 
				
			||||||
 | 
					// 			return Recipe2{}, err
 | 
				
			||||||
 | 
					// 		}
 | 
				
			||||||
 | 
					// 	}
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 	return Recipe2{
 | 
				
			||||||
 | 
					// 		Name:   r.Name,
 | 
				
			||||||
 | 
					// 		Config: config,
 | 
				
			||||||
 | 
					// 		Meta:   meta,
 | 
				
			||||||
 | 
					// 	}, nil
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r Recipe) LoadConfig() error {
 | 
				
			||||||
 | 
						if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, recipeName)
 | 
						pattern := fmt.Sprintf("%s/compose**yml", r.Dir)
 | 
				
			||||||
	composeFiles, err := filepath.Glob(pattern)
 | 
						composeFiles, err := filepath.Glob(pattern)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return Recipe{}, err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(composeFiles) == 0 {
 | 
						if len(composeFiles) == 0 {
 | 
				
			||||||
		return Recipe{}, fmt.Errorf("%s is missing a compose.yml or compose.*.yml file?", recipeName)
 | 
							return fmt.Errorf("%s is missing a compose.yml or compose.*.yml file?", r.Name)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
 | 
					 | 
				
			||||||
	sampleEnv, err := config.ReadEnv(envSamplePath)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return Recipe{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	opts := stack.Deploy{Composefiles: composeFiles}
 | 
						opts := stack.Deploy{Composefiles: composeFiles}
 | 
				
			||||||
 | 
						sampleEnv, err := r.SampleEnv()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	config, err := loader.LoadComposefile(opts, sampleEnv)
 | 
						config, err := loader.LoadComposefile(opts, sampleEnv)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return Recipe{}, err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						r.Config = config
 | 
				
			||||||
	meta, err := GetRecipeMeta(recipeName, offline)
 | 
						return nil
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		switch err.(type) {
 | 
					 | 
				
			||||||
		case RecipeMissingFromCatalogue:
 | 
					 | 
				
			||||||
			meta = RecipeMeta{}
 | 
					 | 
				
			||||||
		default:
 | 
					 | 
				
			||||||
			return Recipe{}, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return Recipe{
 | 
					 | 
				
			||||||
		Name:   recipeName,
 | 
					 | 
				
			||||||
		Config: config,
 | 
					 | 
				
			||||||
		Meta:   meta,
 | 
					 | 
				
			||||||
	}, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r Recipe) SampleEnv() (map[string]string, error) {
 | 
					func (r Recipe) SampleEnv() (map[string]string, error) {
 | 
				
			||||||
@ -264,33 +254,71 @@ func (r Recipe) SampleEnv() (map[string]string, error) {
 | 
				
			|||||||
	return sampleEnv, nil
 | 
						return sampleEnv, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Recipe struct {
 | 
				
			||||||
 | 
						Name        string
 | 
				
			||||||
 | 
						NameEscaped string
 | 
				
			||||||
 | 
						Dir         string
 | 
				
			||||||
 | 
						GitURL      string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Config *composetypes.Config
 | 
				
			||||||
 | 
						Meta   RecipeMeta
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Get(recipeName string) (Recipe, error) {
 | 
				
			||||||
 | 
						if !strings.Contains(recipeName, "/") {
 | 
				
			||||||
 | 
							return Recipe{
 | 
				
			||||||
 | 
								Name:   recipeName,
 | 
				
			||||||
 | 
								GitURL: fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName),
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						u, err := url.Parse(recipeName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return Recipe{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						u.Scheme = "https"
 | 
				
			||||||
 | 
						u.RawPath, err = url.JoinPath(u.RawPath, ".git")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return Recipe{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return Recipe{
 | 
				
			||||||
 | 
							Name:        recipeName,
 | 
				
			||||||
 | 
							NameEscaped: escapeRecipeName(recipeName),
 | 
				
			||||||
 | 
							Dir:         path.Join(config.RECIPES_DIR, escapeRecipeName(recipeName)),
 | 
				
			||||||
 | 
							GitURL:      u.String(),
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func escapeRecipeName(recipeName string) string {
 | 
				
			||||||
 | 
						recipeName = strings.ReplaceAll(recipeName, "/", "_")
 | 
				
			||||||
 | 
						recipeName = strings.ReplaceAll(recipeName, ".", "_")
 | 
				
			||||||
 | 
						return recipeName
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Ensure makes sure the recipe exists, is up to date and has the latest version checked out.
 | 
					// Ensure makes sure the recipe exists, is up to date and has the latest version checked out.
 | 
				
			||||||
func Ensure(recipeName string) error {
 | 
					func (r Recipe) Ensure() error {
 | 
				
			||||||
	if err := EnsureExists(recipeName); err != nil {
 | 
						if err := r.EnsureExists(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := EnsureUpToDate(recipeName); err != nil {
 | 
						if err := r.EnsureUpToDate(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := EnsureLatest(recipeName); err != nil {
 | 
						if err := r.EnsureLatest(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EnsureExists ensures that a recipe is locally cloned
 | 
					// EnsureExists ensures that the recipe is locally cloned
 | 
				
			||||||
func EnsureExists(recipeName string) error {
 | 
					func (r Recipe) EnsureExists() error {
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
						if _, err := os.Stat(r.Dir); os.IsNotExist(err) {
 | 
				
			||||||
 | 
							logrus.Debugf("%s does not exist, attemmpting to clone", r.Dir)
 | 
				
			||||||
	if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
 | 
							if err := gitPkg.Clone(r.Dir, r.GitURL); err != nil {
 | 
				
			||||||
		logrus.Debugf("%s does not exist, attemmpting to clone", recipeDir)
 | 
					 | 
				
			||||||
		url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName)
 | 
					 | 
				
			||||||
		if err := gitPkg.Clone(recipeDir, url); err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
 | 
						if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -298,14 +326,12 @@ func EnsureExists(recipeName string) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EnsureVersion checks whether a specific version exists for a recipe.
 | 
					// EnsureVersion checks whether a specific version exists for a recipe.
 | 
				
			||||||
func EnsureVersion(recipeName, version string) error {
 | 
					func (r Recipe) EnsureVersion(version string) error {
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
						if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repo, err := git.PlainOpen(recipeDir)
 | 
						repo, err := git.PlainOpen(r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -329,11 +355,11 @@ func EnsureVersion(recipeName, version string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	joinedTags := strings.Join(parsedTags, ", ")
 | 
						joinedTags := strings.Join(parsedTags, ", ")
 | 
				
			||||||
	if joinedTags != "" {
 | 
						if joinedTags != "" {
 | 
				
			||||||
		logrus.Debugf("read %s as tags for recipe %s", joinedTags, recipeName)
 | 
							logrus.Debugf("read %s as tags for recipe %s", joinedTags, r.Name)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if tagRef.String() == "" {
 | 
						if tagRef.String() == "" {
 | 
				
			||||||
		return fmt.Errorf("the local copy of %s doesn't seem to have version %s available?", recipeName, version)
 | 
							return fmt.Errorf("the local copy of %s doesn't seem to have version %s available?", r.Name, version)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	worktree, err := repo.Worktree()
 | 
						worktree, err := repo.Worktree()
 | 
				
			||||||
@ -350,37 +376,33 @@ func EnsureVersion(recipeName, version string) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("successfully checked %s out to %s in %s", recipeName, tagRef.Short(), recipeDir)
 | 
						logrus.Debugf("successfully checked %s out to %s in %s", r.Name, tagRef.Short(), r.Dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EnsureIsClean makes sure that the recipe repository has no unstaged changes.
 | 
					// EnsureIsClean makes sure that the recipe repository has no unstaged changes.
 | 
				
			||||||
func EnsureIsClean(recipeName string) error {
 | 
					func (r Recipe) EnsureIsClean() error {
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
						isClean, err := gitPkg.IsClean(r.Dir)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	isClean, err := gitPkg.IsClean(recipeDir)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("unable to check git clean status in %s: %s", recipeDir, err)
 | 
							return fmt.Errorf("unable to check git clean status in %s: %s", r.Dir, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !isClean {
 | 
						if !isClean {
 | 
				
			||||||
		msg := "%s (%s) has locally unstaged changes? please commit/remove your changes before proceeding"
 | 
							msg := "%s (%s) has locally unstaged changes? please commit/remove your changes before proceeding"
 | 
				
			||||||
		return fmt.Errorf(msg, recipeName, recipeDir)
 | 
							return fmt.Errorf(msg, r.Name, r.Dir)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EnsureLatest makes sure the latest commit is checked out for a local recipe repository
 | 
					// EnsureLatest makes sure the latest commit is checked out for the local recipe repository
 | 
				
			||||||
func EnsureLatest(recipeName string) error {
 | 
					func (r Recipe) EnsureLatest() error {
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
						if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repo, err := git.PlainOpen(recipeDir)
 | 
						repo, err := git.PlainOpen(r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -390,7 +412,7 @@ func EnsureLatest(recipeName string) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branch, err := gitPkg.GetDefaultBranch(repo, recipeDir)
 | 
						branch, err := gitPkg.GetDefaultBranch(repo, r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -402,7 +424,7 @@ func EnsureLatest(recipeName string) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := worktree.Checkout(checkOutOpts); err != nil {
 | 
						if err := worktree.Checkout(checkOutOpts); err != nil {
 | 
				
			||||||
		logrus.Debugf("failed to check out %s in %s", branch, recipeDir)
 | 
							logrus.Debugf("failed to check out %s in %s", branch, r.Dir)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -410,18 +432,17 @@ func EnsureLatest(recipeName string) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ChaosVersion constructs a chaos mode recipe version.
 | 
					// ChaosVersion constructs a chaos mode recipe version.
 | 
				
			||||||
func ChaosVersion(recipeName string) (string, error) {
 | 
					func (r Recipe) ChaosVersion() (string, error) {
 | 
				
			||||||
	var version string
 | 
						var version string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	head, err := gitPkg.GetRecipeHead(recipeName)
 | 
						head, err := gitPkg.GetHead(r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return version, err
 | 
							return version, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	version = formatter.SmallSHA(head.String())
 | 
						version = formatter.SmallSHA(head.String())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
						isClean, err := gitPkg.IsClean(r.Dir)
 | 
				
			||||||
	isClean, err := gitPkg.IsClean(recipeDir)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return version, err
 | 
							return version, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -464,22 +485,22 @@ func GetVersionLabelLocal(recipe Recipe) (string, error) {
 | 
				
			|||||||
	return label, nil
 | 
						return label, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetRecipeFeaturesAndCategory(recipeName string) (Features, string, error) {
 | 
					func (r Recipe) GetRecipeFeaturesAndCategory() (Features, string, error) {
 | 
				
			||||||
	feat := Features{}
 | 
						feat := Features{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var category string
 | 
						var category string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	readmePath := path.Join(config.RECIPES_DIR, recipeName, "README.md")
 | 
						readmePath := path.Join(r.Dir, "README.md")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("attempting to open %s for recipe metadata parsing", readmePath)
 | 
						logrus.Debugf("attempting to open %s for recipe metadata parsing", readmePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	readmeFS, err := ioutil.ReadFile(readmePath)
 | 
						readmeFS, err := os.ReadFile(readmePath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return feat, category, err
 | 
							return feat, category, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	readmeMetadata, err := GetStringInBetween( // Find text between delimiters
 | 
						readmeMetadata, err := GetStringInBetween( // Find text between delimiters
 | 
				
			||||||
		recipeName,
 | 
							r.Name,
 | 
				
			||||||
		string(readmeFS),
 | 
							string(readmeFS),
 | 
				
			||||||
		"<!-- metadata -->", "<!-- endmetadata -->",
 | 
							"<!-- metadata -->", "<!-- endmetadata -->",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
@ -530,7 +551,7 @@ func GetRecipeFeaturesAndCategory(recipeName string) (Features, string, error) {
 | 
				
			|||||||
		if strings.Contains(val, "**Image**") {
 | 
							if strings.Contains(val, "**Image**") {
 | 
				
			||||||
			imageMetadata, err := GetImageMetadata(strings.TrimSpace(
 | 
								imageMetadata, err := GetImageMetadata(strings.TrimSpace(
 | 
				
			||||||
				strings.TrimPrefix(val, "* **Image**:"),
 | 
									strings.TrimPrefix(val, "* **Image**:"),
 | 
				
			||||||
			), recipeName)
 | 
								), r.Name)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -597,38 +618,36 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EnsureUpToDate ensures that the local repo is synced to the remote
 | 
					// EnsureUpToDate ensures that the local repo is synced to the remote
 | 
				
			||||||
func EnsureUpToDate(recipeName string) error {
 | 
					func (r Recipe) EnsureUpToDate() error {
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
						repo, err := git.PlainOpen(r.Dir)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	repo, err := git.PlainOpen(recipeDir)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("unable to open %s: %s", recipeDir, err)
 | 
							return fmt.Errorf("unable to open %s: %s", r.Dir, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	remotes, err := repo.Remotes()
 | 
						remotes, err := repo.Remotes()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("unable to read remotes in %s: %s", recipeDir, err)
 | 
							return fmt.Errorf("unable to read remotes in %s: %s", r.Dir, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(remotes) == 0 {
 | 
						if len(remotes) == 0 {
 | 
				
			||||||
		logrus.Debugf("cannot ensure %s is up-to-date, no git remotes configured", recipeName)
 | 
							logrus.Debugf("cannot ensure %s is up-to-date, no git remotes configured", r.Name)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	worktree, err := repo.Worktree()
 | 
						worktree, err := repo.Worktree()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("unable to open git work tree in %s: %s", recipeDir, err)
 | 
							return fmt.Errorf("unable to open git work tree in %s: %s", r.Dir, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branch, err := gitPkg.CheckoutDefaultBranch(repo, recipeDir)
 | 
						branch, err := gitPkg.CheckoutDefaultBranch(repo, r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("unable to check out default branch in %s: %s", recipeDir, err)
 | 
							return fmt.Errorf("unable to check out default branch in %s: %s", r.Dir, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fetchOpts := &git.FetchOptions{Tags: git.AllTags}
 | 
						fetchOpts := &git.FetchOptions{Tags: git.AllTags}
 | 
				
			||||||
	if err := repo.Fetch(fetchOpts); err != nil {
 | 
						if err := repo.Fetch(fetchOpts); err != nil {
 | 
				
			||||||
		if !strings.Contains(err.Error(), "already up-to-date") {
 | 
							if !strings.Contains(err.Error(), "already up-to-date") {
 | 
				
			||||||
			return fmt.Errorf("unable to fetch tags in %s: %s", recipeDir, err)
 | 
								return fmt.Errorf("unable to fetch tags in %s: %s", r.Dir, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -640,11 +659,11 @@ func EnsureUpToDate(recipeName string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if err := worktree.Pull(opts); err != nil {
 | 
						if err := worktree.Pull(opts); err != nil {
 | 
				
			||||||
		if !strings.Contains(err.Error(), "already up-to-date") {
 | 
							if !strings.Contains(err.Error(), "already up-to-date") {
 | 
				
			||||||
			return fmt.Errorf("unable to git pull in %s: %s", recipeDir, err)
 | 
								return fmt.Errorf("unable to git pull in %s: %s", r.Dir, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("fetched latest git changes for %s", recipeName)
 | 
						logrus.Debugf("fetched latest git changes for %s", r.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -672,7 +691,7 @@ func ReadRecipeCatalogue(offline bool) (RecipeCatalogue, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// readRecipeCatalogueFS reads the catalogue from the file system.
 | 
					// readRecipeCatalogueFS reads the catalogue from the file system.
 | 
				
			||||||
func readRecipeCatalogueFS(target interface{}) error {
 | 
					func readRecipeCatalogueFS(target interface{}) error {
 | 
				
			||||||
	recipesJSONFS, err := ioutil.ReadFile(config.RECIPES_JSON)
 | 
						recipesJSONFS, err := os.ReadFile(config.RECIPES_JSON)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -724,20 +743,20 @@ func (r RecipeMissingFromCatalogue) Error() string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRecipeMeta retrieves the recipe metadata from the recipe catalogue.
 | 
					// GetRecipeMeta retrieves the recipe metadata from the recipe catalogue.
 | 
				
			||||||
func GetRecipeMeta(recipeName string, offline bool) (RecipeMeta, error) {
 | 
					func (r Recipe) GetRecipeMeta(offline bool) (RecipeMeta, error) {
 | 
				
			||||||
	catl, err := ReadRecipeCatalogue(offline)
 | 
						catl, err := ReadRecipeCatalogue(offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return RecipeMeta{}, err
 | 
							return RecipeMeta{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	recipeMeta, ok := catl[recipeName]
 | 
						recipeMeta, ok := catl[r.Name]
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return RecipeMeta{}, RecipeMissingFromCatalogue{
 | 
							return RecipeMeta{}, RecipeMissingFromCatalogue{
 | 
				
			||||||
			err: fmt.Sprintf("recipe %s does not exist?", recipeName),
 | 
								err: fmt.Sprintf("recipe %s does not exist?", r.Name),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("recipe metadata retrieved for %s", recipeName)
 | 
						logrus.Debugf("recipe metadata retrieved for %s", r.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return recipeMeta, nil
 | 
						return recipeMeta, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -864,13 +883,12 @@ func ReadReposMetadata() (RepoCatalogue, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRecipeVersions retrieves all recipe versions.
 | 
					// GetRecipeVersions retrieves all recipe versions.
 | 
				
			||||||
func GetRecipeVersions(recipeName string, offline bool) (RecipeVersions, error) {
 | 
					func (r Recipe) GetVersions(offline bool) (RecipeVersions, error) {
 | 
				
			||||||
	versions := RecipeVersions{}
 | 
						versions := RecipeVersions{}
 | 
				
			||||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("attempting to open git repository in %s", recipeDir)
 | 
						logrus.Debugf("attempting to open git repository in %s", r.Dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	repo, err := git.PlainOpen(recipeDir)
 | 
						repo, err := git.PlainOpen(r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return versions, err
 | 
							return versions, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -888,7 +906,7 @@ func GetRecipeVersions(recipeName string, offline bool) (RecipeVersions, error)
 | 
				
			|||||||
	if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
 | 
						if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
 | 
				
			||||||
		tag := strings.TrimPrefix(string(ref.Name()), "refs/tags/")
 | 
							tag := strings.TrimPrefix(string(ref.Name()), "refs/tags/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		logrus.Debugf("processing %s for %s", tag, recipeName)
 | 
							logrus.Debugf("processing %s for %s", tag, r.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		checkOutOpts := &git.CheckoutOptions{
 | 
							checkOutOpts := &git.CheckoutOptions{
 | 
				
			||||||
			Create: false,
 | 
								Create: false,
 | 
				
			||||||
@ -896,19 +914,18 @@ func GetRecipeVersions(recipeName string, offline bool) (RecipeVersions, error)
 | 
				
			|||||||
			Branch: plumbing.ReferenceName(ref.Name()),
 | 
								Branch: plumbing.ReferenceName(ref.Name()),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if err := worktree.Checkout(checkOutOpts); err != nil {
 | 
							if err := worktree.Checkout(checkOutOpts); err != nil {
 | 
				
			||||||
			logrus.Debugf("failed to check out %s in %s", tag, recipeDir)
 | 
								logrus.Debugf("failed to check out %s in %s", tag, r.Dir)
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		logrus.Debugf("successfully checked out %s in %s", ref.Name(), recipeDir)
 | 
							logrus.Debugf("successfully checked out %s in %s", ref.Name(), r.Dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		recipe, err := Get(recipeName, offline)
 | 
							if err := r.LoadConfig(); err != nil {
 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		versionMeta := make(map[string]ServiceMeta)
 | 
							versionMeta := make(map[string]ServiceMeta)
 | 
				
			||||||
		for _, service := range recipe.Config.Services {
 | 
							for _, service := range r.Config.Services {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			img, err := reference.ParseNormalizedNamed(service.Image)
 | 
								img, err := reference.ParseNormalizedNamed(service.Image)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
@ -941,13 +958,14 @@ func GetRecipeVersions(recipeName string, offline bool) (RecipeVersions, error)
 | 
				
			|||||||
		return versions, err
 | 
							return versions, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = gitPkg.CheckoutDefaultBranch(repo, recipeDir)
 | 
						_, err = gitPkg.CheckoutDefaultBranch(repo, r.Dir)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return versions, err
 | 
							return versions, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sortRecipeVersions(versions)
 | 
						sortRecipeVersions(versions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	logrus.Debugf("collected %s for %s", versions, recipeName)
 | 
						logrus.Debugf("collected %s for %s", versions, r.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return versions, nil
 | 
						return versions, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestGetVersionLabelLocalDoesNotUseTimeoutLabel(t *testing.T) {
 | 
					func TestGetVersionLabelLocalDoesNotUseTimeoutLabel(t *testing.T) {
 | 
				
			||||||
	offline := true
 | 
						offline := true
 | 
				
			||||||
	recipe, err := Get("traefik", offline)
 | 
						recipe, err := Get2("traefik", offline)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -89,7 +89,7 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
 | 
				
			|||||||
	appEnv["STACK_NAME"] = stackName
 | 
						appEnv["STACK_NAME"] = stackName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	opts := stack.Deploy{Composefiles: composeFiles}
 | 
						opts := stack.Deploy{Composefiles: composeFiles}
 | 
				
			||||||
	config, err := loader.LoadComposefile(opts, appEnv)
 | 
						composeConfig, err := loader.LoadComposefile(opts, appEnv)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -100,7 +100,7 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var enabledSecrets []string
 | 
						var enabledSecrets []string
 | 
				
			||||||
	for _, service := range config.Services {
 | 
						for _, service := range composeConfig.Services {
 | 
				
			||||||
		for _, secret := range service.Secrets {
 | 
							for _, secret := range service.Secrets {
 | 
				
			||||||
			enabledSecrets = append(enabledSecrets, secret.Source)
 | 
								enabledSecrets = append(enabledSecrets, secret.Source)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -112,7 +112,7 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	secretValues := map[string]Secret{}
 | 
						secretValues := map[string]Secret{}
 | 
				
			||||||
	for secretId, secretConfig := range config.Secrets {
 | 
						for secretId, secretConfig := range composeConfig.Secrets {
 | 
				
			||||||
		if string(secretConfig.Name[len(secretConfig.Name)-1]) == "_" {
 | 
							if string(secretConfig.Name[len(secretConfig.Name)-1]) == "_" {
 | 
				
			||||||
			return nil, fmt.Errorf("missing version for secret? (%s)", secretId)
 | 
								return nil, fmt.Errorf("missing version for secret? (%s)", secretId)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -126,6 +126,10 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
 | 
				
			|||||||
		secretVersion := secretConfig.Name[lastIdx+1:]
 | 
							secretVersion := secretConfig.Name[lastIdx+1:]
 | 
				
			||||||
		value := Secret{Version: secretVersion, RemoteName: secretConfig.Name}
 | 
							value := Secret{Version: secretVersion, RemoteName: secretConfig.Name}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(value.RemoteName) > config.MAX_DOCKER_SECRET_LENGTH {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("secret %s is > %d chars when combined with %s", secretId, config.MAX_DOCKER_SECRET_LENGTH, stackName)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Check if the length modifier is set for this secret.
 | 
							// Check if the length modifier is set for this secret.
 | 
				
			||||||
		for envName, modifierValues := range appModifiers {
 | 
							for envName, modifierValues := range appModifiers {
 | 
				
			||||||
			// configWithoutEnv contains the raw name as defined in the compose.yaml
 | 
								// configWithoutEnv contains the raw name as defined in the compose.yaml
 | 
				
			||||||
 | 
				
			|||||||
@ -28,3 +28,12 @@ func TestReadSecretsConfig(t *testing.T) {
 | 
				
			|||||||
	assert.Equal(t, "v2", secretsFromConfig["test_pass_three"].Version)
 | 
						assert.Equal(t, "v2", secretsFromConfig["test_pass_three"].Version)
 | 
				
			||||||
	assert.Equal(t, 0, secretsFromConfig["test_pass_three"].Length)
 | 
						assert.Equal(t, 0, secretsFromConfig["test_pass_three"].Length)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestReadSecretsConfigWithLongDomain(t *testing.T) {
 | 
				
			||||||
 | 
						composeFiles := []string{"./testdir/compose.yaml"}
 | 
				
			||||||
 | 
						_, err := ReadSecretsConfig("./testdir/.env.sample", composeFiles, "should_break_on_forty_eight_char_stack_nameeeeee")
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							t.Fatal("expected failure, stack name is too long")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assert.Contains(t, err.Error(), "is > 64 chars")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,73 +2,14 @@ package ssh
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os/exec"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HostConfig is a SSH host config.
 | 
					 | 
				
			||||||
type HostConfig struct {
 | 
					 | 
				
			||||||
	Host         string
 | 
					 | 
				
			||||||
	IdentityFile string
 | 
					 | 
				
			||||||
	Port         string
 | 
					 | 
				
			||||||
	User         string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// String presents a human friendly output for the HostConfig.
 | 
					 | 
				
			||||||
func (h HostConfig) String() string {
 | 
					 | 
				
			||||||
	return fmt.Sprintf(
 | 
					 | 
				
			||||||
		"{host: %s, username: %s, port: %s, identityfile: %s}",
 | 
					 | 
				
			||||||
		h.Host,
 | 
					 | 
				
			||||||
		h.User,
 | 
					 | 
				
			||||||
		h.Port,
 | 
					 | 
				
			||||||
		h.IdentityFile,
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetHostConfig retrieves a ~/.ssh/config config for a host using /usr/bin/ssh
 | 
					 | 
				
			||||||
// directly. We therefore maintain consistent interop with this standard
 | 
					 | 
				
			||||||
// tooling. This is useful because SSH confuses a lot of people and having to
 | 
					 | 
				
			||||||
// learn how two tools (`ssh` and `abra`) handle SSH connection details instead
 | 
					 | 
				
			||||||
// of one (just `ssh`) is Not Cool. Here's to less bug reports on this topic!
 | 
					 | 
				
			||||||
func GetHostConfig(hostname string) (HostConfig, error) {
 | 
					 | 
				
			||||||
	var hostConfig HostConfig
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	out, err := exec.Command("ssh", "-G", hostname).Output()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return hostConfig, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, line := range strings.Split(string(out), "\n") {
 | 
					 | 
				
			||||||
		entries := strings.Split(line, " ")
 | 
					 | 
				
			||||||
		for idx, entry := range entries {
 | 
					 | 
				
			||||||
			if entry == "hostname" {
 | 
					 | 
				
			||||||
				hostConfig.Host = entries[idx+1]
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if entry == "user" {
 | 
					 | 
				
			||||||
				hostConfig.User = entries[idx+1]
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if entry == "port" {
 | 
					 | 
				
			||||||
				hostConfig.Port = entries[idx+1]
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if entry == "identityfile" {
 | 
					 | 
				
			||||||
				if hostConfig.IdentityFile == "" {
 | 
					 | 
				
			||||||
					hostConfig.IdentityFile = entries[idx+1]
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	logrus.Debugf("retrieved ssh config for %s: %s", hostname, hostConfig.String())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return hostConfig, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Fatal is a error output wrapper which aims to make SSH failures easier to
 | 
					// Fatal is a error output wrapper which aims to make SSH failures easier to
 | 
				
			||||||
// parse through re-wording.
 | 
					// parse through re-wording.
 | 
				
			||||||
func Fatal(hostname string, err error) error {
 | 
					func Fatal(hostname string, err error) error {
 | 
				
			||||||
	out := err.Error()
 | 
						out := err.Error()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if strings.Contains(out, "Host key verification failed.") {
 | 
						if strings.Contains(out, "Host key verification failed.") {
 | 
				
			||||||
		return fmt.Errorf("SSH host key verification failed for %s", hostname)
 | 
							return fmt.Errorf("SSH host key verification failed for %s", hostname)
 | 
				
			||||||
	} else if strings.Contains(out, "Could not resolve hostname") {
 | 
						} else if strings.Contains(out, "Could not resolve hostname") {
 | 
				
			||||||
@ -79,7 +20,7 @@ func Fatal(hostname string, err error) error {
 | 
				
			|||||||
		return fmt.Errorf("ssh auth: permission denied for %s", hostname)
 | 
							return fmt.Errorf("ssh auth: permission denied for %s", hostname)
 | 
				
			||||||
	} else if strings.Contains(out, "Network is unreachable") {
 | 
						} else if strings.Contains(out, "Network is unreachable") {
 | 
				
			||||||
		return fmt.Errorf("unable to connect to %s, network is unreachable?", hostname)
 | 
							return fmt.Errorf("unable to connect to %s, network is unreachable?", hostname)
 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,12 +16,12 @@ import (
 | 
				
			|||||||
// GetConnectionHelper returns Docker-specific connection helper for the given URL.
 | 
					// GetConnectionHelper returns Docker-specific connection helper for the given URL.
 | 
				
			||||||
// GetConnectionHelper returns nil without error when no helper is registered for the scheme.
 | 
					// GetConnectionHelper returns nil without error when no helper is registered for the scheme.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// ssh://<user>@<host> URL requires Docker 18.09 or later on the remote host.
 | 
					// ssh://<host> URL requires Docker 18.09 or later on the remote host.
 | 
				
			||||||
func GetConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
 | 
					func GetConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
 | 
				
			||||||
	return getConnectionHelper(daemonURL, []string{"-o ConnectTimeout=60"})
 | 
						return getConnectionHelper(daemonURL)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getConnectionHelper(daemonURL string, sshFlags []string) (*connhelper.ConnectionHelper, error) {
 | 
					func getConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
 | 
				
			||||||
	url, err := url.Parse(daemonURL)
 | 
						url, err := url.Parse(daemonURL)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@ -35,7 +35,7 @@ func getConnectionHelper(daemonURL string, sshFlags []string) (*connhelper.Conne
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		return &connhelper.ConnectionHelper{
 | 
							return &connhelper.ConnectionHelper{
 | 
				
			||||||
			Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
 | 
								Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
 | 
				
			||||||
				return New(ctx, "ssh", append(sshFlags, ctxConnDetails.Args("docker", "system", "dial-stdio")...)...)
 | 
									return New(ctx, "ssh", ctxConnDetails.Args("docker", "system", "dial-stdio")...)
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Host: "http://docker.example.com",
 | 
								Host: "http://docker.example.com",
 | 
				
			||||||
		}, nil
 | 
							}, nil
 | 
				
			||||||
@ -45,6 +45,7 @@ func getConnectionHelper(daemonURL string, sshFlags []string) (*connhelper.Conne
 | 
				
			|||||||
	return nil, err
 | 
						return nil, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewConnectionHelper creates new connection helper for a remote docker daemon.
 | 
				
			||||||
func NewConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
 | 
					func NewConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
 | 
				
			||||||
	helper, err := GetConnectionHelper(daemonURL)
 | 
						helper, err := GetConnectionHelper(daemonURL)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@ -73,6 +74,7 @@ func getDockerEndpoint(host string) (docker.Endpoint, error) {
 | 
				
			|||||||
	return ep, nil
 | 
						return ep, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetDockerEndpointMetadataAndTLS retrieves the docker endpoint and TLS info for a remote host.
 | 
				
			||||||
func GetDockerEndpointMetadataAndTLS(host string) (docker.EndpointMeta, *dCliContextStore.EndpointTLSData, error) {
 | 
					func GetDockerEndpointMetadataAndTLS(host string) (docker.EndpointMeta, *dCliContextStore.EndpointTLSData, error) {
 | 
				
			||||||
	ep, err := getDockerEndpoint(host)
 | 
						ep, err := getDockerEndpoint(host)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/docker/cli/cli/command"
 | 
						"github.com/docker/cli/cli/command"
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						"github.com/docker/docker/api/types/container"
 | 
				
			||||||
	"github.com/docker/docker/client"
 | 
						"github.com/docker/docker/client"
 | 
				
			||||||
	apiclient "github.com/docker/docker/client"
 | 
						apiclient "github.com/docker/docker/client"
 | 
				
			||||||
	"github.com/moby/sys/signal"
 | 
						"github.com/moby/sys/signal"
 | 
				
			||||||
@ -22,7 +22,7 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	options := types.ResizeOptions{
 | 
						options := container.ResizeOptions{
 | 
				
			||||||
		Height: height,
 | 
							Height: height,
 | 
				
			||||||
		Width:  width,
 | 
							Width:  width,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -233,7 +233,7 @@ func validateExternalNetworks(ctx context.Context, client dockerClient.NetworkAP
 | 
				
			|||||||
		network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
 | 
							network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
 | 
				
			||||||
		switch {
 | 
							switch {
 | 
				
			||||||
		case dockerClient.IsErrNotFound(err):
 | 
							case dockerClient.IsErrNotFound(err):
 | 
				
			||||||
			return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName)
 | 
								return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy", networkName)
 | 
				
			||||||
		case err != nil:
 | 
							case err != nil:
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		case network.Scope != "swarm":
 | 
							case network.Scope != "swarm":
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
#!/usr/bin/env bash
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ABRA_VERSION="0.8.1-beta"
 | 
					ABRA_VERSION="0.9.0-beta"
 | 
				
			||||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
 | 
					ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
 | 
				
			||||||
RC_VERSION="0.8.1-beta"
 | 
					RC_VERSION="0.8.0-rc1-beta"
 | 
				
			||||||
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
 | 
					RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
for arg in "$@"; do
 | 
					for arg in "$@"; do
 | 
				
			||||||
@ -45,7 +45,9 @@ function install_abra_release {
 | 
				
			|||||||
  fi
 | 
					  fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ARCH=$(uname -m)
 | 
					  ARCH=$(uname -m)
 | 
				
			||||||
  if [[ $ARCH =~ "aarch64" ]]; then
 | 
					  if [[ $ARCH =~ "x86_64" ]]; then
 | 
				
			||||||
 | 
					      ARCH="amd64"
 | 
				
			||||||
 | 
					  elif [[ $ARCH =~ "aarch64" ]]; then
 | 
				
			||||||
      ARCH="arm64"
 | 
					      ARCH="arm64"
 | 
				
			||||||
  elif [[ $ARCH =~ "armv5l" ]]; then
 | 
					  elif [[ $ARCH =~ "armv5l" ]]; then
 | 
				
			||||||
      ARCH="armv5"
 | 
					      ARCH="armv5"
 | 
				
			||||||
@ -55,7 +57,7 @@ function install_abra_release {
 | 
				
			|||||||
      ARCH="armv7"
 | 
					      ARCH="armv7"
 | 
				
			||||||
  fi
 | 
					  fi
 | 
				
			||||||
  PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]')_$ARCH
 | 
					  PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]')_$ARCH
 | 
				
			||||||
  FILENAME="abra_"$ABRA_VERSION"_"$PLATFORM""
 | 
					  FILENAME="abra_"$ABRA_VERSION"_"$PLATFORM".tar.gz"
 | 
				
			||||||
  sed_command_rel='s/.*"assets":\[\{[^]]*"name":"'$FILENAME'"[^}]*"browser_download_url":"([^"]*)".*\].*/\1/p'
 | 
					  sed_command_rel='s/.*"assets":\[\{[^]]*"name":"'$FILENAME'"[^}]*"browser_download_url":"([^"]*)".*\].*/\1/p'
 | 
				
			||||||
  sed_command_checksums='s/.*"assets":\[\{[^\]*"name":"checksums.txt"[^}]*"browser_download_url":"([^"]*)".*\].*/\1/p'
 | 
					  sed_command_checksums='s/.*"assets":\[\{[^\]*"name":"checksums.txt"[^}]*"browser_download_url":"([^"]*)".*\].*/\1/p'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,7 +67,7 @@ function install_abra_release {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  checksums=$(wget -q -O- $checksums_url)
 | 
					  checksums=$(wget -q -O- $checksums_url)
 | 
				
			||||||
  checksum=$(echo "$checksums" | grep "$FILENAME" - | sed -En 's/([0-9a-f]{64})\s+'"$FILENAME"'.*/\1/p')
 | 
					  checksum=$(echo "$checksums" | grep "$FILENAME" - | sed -En 's/([0-9a-f]{64})\s+'"$FILENAME"'.*/\1/p')
 | 
				
			||||||
  abra_download="/tmp/abra-download"
 | 
					  abra_download="/tmp/abra-download.tar.gz"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  echo "downloading $ABRA_VERSION $PLATFORM binary release for abra..."
 | 
					  echo "downloading $ABRA_VERSION $PLATFORM binary release for abra..."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,7 +79,10 @@ function install_abra_release {
 | 
				
			|||||||
      exit 1
 | 
					      exit 1
 | 
				
			||||||
  fi
 | 
					  fi
 | 
				
			||||||
  echo "$(tput setaf 2)check successful!$(tput sgr0)"
 | 
					  echo "$(tput setaf 2)check successful!$(tput sgr0)"
 | 
				
			||||||
  mv "$abra_download" "$HOME/.local/bin/abra"
 | 
					  cd /tmp/
 | 
				
			||||||
 | 
					  tar xf abra-download.tar.gz
 | 
				
			||||||
 | 
					  mv abra "$HOME/.local/bin/abra"
 | 
				
			||||||
 | 
					  tar tf abra-download.tar.gz | xargs rm -f
 | 
				
			||||||
  chmod +x "$HOME/.local/bin/abra"
 | 
					  chmod +x "$HOME/.local/bin/abra"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  x=$(echo $PATH | grep $HOME/.local/bin)
 | 
					  x=$(echo $PATH | grep $HOME/.local/bin)
 | 
				
			||||||
 | 
				
			|||||||
@ -70,13 +70,13 @@ setup(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app check "$TEST_APP_DOMAIN"
 | 
					  run $ABRA app check "$TEST_APP_DOMAIN"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  refute_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -86,7 +86,7 @@ setup(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 1'
 | 
					  assert_output --partial "Your branch is behind 'origin/main' by 1 commit"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # NOTE(d1): we can't quite tell if this will fail or not in the future, so,
 | 
					  # NOTE(d1): we can't quite tell if this will fail or not in the future, so,
 | 
				
			||||||
  # since it isn't an important part of what we're testing here, we don't check
 | 
					  # since it isn't an important part of what we're testing here, we don't check
 | 
				
			||||||
@ -94,7 +94,7 @@ setup(){
 | 
				
			|||||||
  run $ABRA app check "$TEST_APP_DOMAIN" --offline
 | 
					  run $ABRA app check "$TEST_APP_DOMAIN" --offline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 1'
 | 
					  assert_output --partial "Your branch is behind 'origin/main' by 1 commit"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -58,7 +58,7 @@ test_cmd_export"
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
 | 
					  assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local
 | 
					  run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'baz'
 | 
					  assert_output --partial 'baz'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -70,7 +70,7 @@ test_cmd_export"
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
 | 
					  assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local
 | 
					  run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
  assert_output --partial 'locally unstaged changes'
 | 
					  assert_output --partial 'locally unstaged changes'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -83,7 +83,7 @@ test_cmd_export"
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
 | 
					  assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local --chaos
 | 
					  run $ABRA app cmd --local --chaos "$TEST_APP_DOMAIN" test_cmd
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'baz'
 | 
					  assert_output --partial 'baz'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -96,14 +96,14 @@ test_cmd_export"
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local
 | 
					  run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'baz'
 | 
					  assert_output --partial 'baz'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  refute_output --partial 'behind 3'
 | 
					  assert_output --partial "up to date"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe "$TEST_RECIPE"
 | 
					  _reset_recipe "$TEST_RECIPE"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -113,14 +113,14 @@ test_cmd_export"
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local --offline
 | 
					  run $ABRA app cmd --local --offline "$TEST_APP_DOMAIN" test_cmd
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'baz'
 | 
					  assert_output --partial 'baz'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe "$TEST_RECIPE"
 | 
					  _reset_recipe "$TEST_RECIPE"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -132,13 +132,13 @@ test_cmd_export"
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "error if missing arguments when passing --local" {
 | 
					@test "error if missing arguments when passing --local" {
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" --local
 | 
					  run $ABRA app cmd --local "$TEST_APP_DOMAIN"
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
  assert_output --partial 'missing arguments'
 | 
					  assert_output --partial 'missing arguments'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "cannot use --local and --user at same time" {
 | 
					@test "cannot use --local and --user at same time" {
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local --user root
 | 
					  run $ABRA app cmd --local --user root "$TEST_APP_DOMAIN" test_cmd
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
  assert_output --partial 'cannot use --local & --user together'
 | 
					  assert_output --partial 'cannot use --local & --user together'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -147,7 +147,7 @@ test_cmd_export"
 | 
				
			|||||||
  run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/abra.sh"
 | 
					  run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/abra.sh"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local --chaos
 | 
					  run $ABRA app cmd --local --chaos "$TEST_APP_DOMAIN" test_cmd
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
  assert_output --partial "$ABRA_DIR/recipes/$TEST_RECIPE/abra.sh does not exist"
 | 
					  assert_output --partial "$ABRA_DIR/recipes/$TEST_RECIPE/abra.sh does not exist"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -155,25 +155,25 @@ test_cmd_export"
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "error if missing command" {
 | 
					@test "error if missing command" {
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" doesnt_exist --local
 | 
					  run $ABRA app cmd --local "$TEST_APP_DOMAIN" doesnt_exist
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
  assert_output --partial "doesn't have a doesnt_exist function"
 | 
					  assert_output --partial "doesn't have a doesnt_exist function"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "run --local command" {
 | 
					@test "run --local command" {
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local
 | 
					  run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'baz'
 | 
					  assert_output --partial 'baz'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "run command with single arg" {
 | 
					@test "run command with single arg" {
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd_arg --local -- bing
 | 
					  run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd_arg -- bing
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'bing'
 | 
					  assert_output --partial 'bing'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "run command with several args" {
 | 
					@test "run command with several args" {
 | 
				
			||||||
  run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd_args --local -- bong bang
 | 
					  run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd_args -- bong bang
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'bong bang'
 | 
					  assert_output --partial 'bong bang'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
#!/usr/bin/env bash
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
setup_file(){
 | 
					setup_file(){
 | 
				
			||||||
 | 
					  load "$PWD/tests/integration/helpers/git"
 | 
				
			||||||
  load "$PWD/tests/integration/helpers/common"
 | 
					  load "$PWD/tests/integration/helpers/common"
 | 
				
			||||||
  _common_setup
 | 
					  _common_setup
 | 
				
			||||||
  _add_server
 | 
					  _add_server
 | 
				
			||||||
@ -16,6 +17,7 @@ teardown_file(){
 | 
				
			|||||||
setup(){
 | 
					setup(){
 | 
				
			||||||
  load "$PWD/tests/integration/helpers/common"
 | 
					  load "$PWD/tests/integration/helpers/common"
 | 
				
			||||||
  _common_setup
 | 
					  _common_setup
 | 
				
			||||||
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
teardown(){
 | 
					teardown(){
 | 
				
			||||||
@ -82,13 +84,13 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
 | 
					  run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  refute_output --partial 'behind 3'
 | 
					  refute_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
  _undeploy_app
 | 
					  _undeploy_app
 | 
				
			||||||
@ -100,7 +102,7 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # NOTE(d1): need to use --chaos to force same commit
 | 
					  # NOTE(d1): need to use --chaos to force same commit
 | 
				
			||||||
  run $ABRA app deploy "$TEST_APP_DOMAIN" \
 | 
					  run $ABRA app deploy "$TEST_APP_DOMAIN" \
 | 
				
			||||||
@ -108,7 +110,7 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _undeploy_app
 | 
					  _undeploy_app
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
@ -116,6 +118,9 @@ teardown(){
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# bats test_tags=slow
 | 
					# bats test_tags=slow
 | 
				
			||||||
@test "deploy latest commit if no published versions and no --chaos" {
 | 
					@test "deploy latest commit if no published versions and no --chaos" {
 | 
				
			||||||
 | 
					  # TODO(d1): fix with a new test recipe which has no published versions?
 | 
				
			||||||
 | 
					  skip "known issue, abra-test-recipe has published versions now"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
 | 
					  latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _remove_tags
 | 
					  _remove_tags
 | 
				
			||||||
@ -140,7 +145,7 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  threeCommitsBack="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
 | 
					  threeCommitsBack="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -273,6 +278,10 @@ teardown(){
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "ensure domain is checked" {
 | 
					@test "ensure domain is checked" {
 | 
				
			||||||
 | 
					  if [[ "$TEST_SERVER" == "default" ]]; then
 | 
				
			||||||
 | 
					      skip "domain checks are disabled for local server"
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  appDomain="custom-html.DOESNTEXIST"
 | 
					  appDomain="custom-html.DOESNTEXIST"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app new custom-html \
 | 
					  run $ABRA app new custom-html \
 | 
				
			||||||
@ -354,6 +363,7 @@ teardown(){
 | 
				
			|||||||
  _reset_app
 | 
					  _reset_app
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# bats test_tags=slow
 | 
				
			||||||
@test "recipe config comments not present in values" {
 | 
					@test "recipe config comments not present in values" {
 | 
				
			||||||
  run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
 | 
					  run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
@ -362,3 +372,36 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  refute_output --partial 'should be removed'
 | 
					  refute_output --partial 'should be removed'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# bats test_tags=slow
 | 
				
			||||||
 | 
					@test "deploy specific version with incompatible HEAD" {
 | 
				
			||||||
 | 
					  run sed -i 's/COMPOSE_FILE="compose.yml"/COMPOSE_FILE="compose.yml:compose.extra_secret.yml"/g' \
 | 
				
			||||||
 | 
					    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run sed -i 's/#SECRET_EXTRA_PASS_VERSION=v1/SECRET_EXTRA_PASS_VERSION=v1/g' \
 | 
				
			||||||
 | 
					    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  assert_output --partial 'extra_pass'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/compose.extra_secret.yml"
 | 
				
			||||||
 | 
					  assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/compose.extra_secret.yml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _git_commit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # NOTE(d1): 0.1.1+1.20.2 is a previous version which includes compose.extra_secret.yml
 | 
				
			||||||
 | 
					  run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  refute_output --partial 'no such file or directory'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _undeploy_app
 | 
				
			||||||
 | 
					  _reset_app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _reset_recipe
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,7 @@ teardown_file(){
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
setup(){
 | 
					setup(){
 | 
				
			||||||
  load "$PWD/tests/integration/helpers/common"
 | 
					  load "$PWD/tests/integration/helpers/common"
 | 
				
			||||||
 | 
					  load "$PWD/tests/integration/helpers/git"
 | 
				
			||||||
  _common_setup
 | 
					  _common_setup
 | 
				
			||||||
  _fetch_recipe
 | 
					  _fetch_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -26,14 +27,6 @@ teardown(){
 | 
				
			|||||||
  run $ABRA app new --generate-bash-completion
 | 
					  run $ABRA app new --generate-bash-completion
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial "traefik"
 | 
					  assert_output --partial "traefik"
 | 
				
			||||||
  assert_output --partial "abra-test-recipe"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Note: this test needs to be updated when a new version of the test recipe is published.
 | 
					 | 
				
			||||||
  run $ABRA app new abra-test-recipe --generate-bash-completion
 | 
					 | 
				
			||||||
  assert_success
 | 
					 | 
				
			||||||
  assert_output "0.1.0+1.20.0
 | 
					 | 
				
			||||||
0.1.1+1.20.2
 | 
					 | 
				
			||||||
0.2.0+1.21.0"
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "create new app" {
 | 
					@test "create new app" {
 | 
				
			||||||
@ -44,8 +37,9 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
					  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  _get_head_hash
 | 
				
			||||||
  assert_output --partial "Your branch is up to date with 'origin/main'."
 | 
					  _get_current_hash
 | 
				
			||||||
 | 
					  assert_equal "$headHash" "$currentHash"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "create new app with version" {
 | 
					@test "create new app with version" {
 | 
				
			||||||
@ -56,8 +50,7 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
					  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" log -1
 | 
					  assert_equal $(_get_tag_hash 0.1.1+1.20.2) $(_get_current_hash)
 | 
				
			||||||
  assert_output --partial "453db7121c0a56a7a8f15378f18fe3bf21ccfdef"
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "does not overwrite existing env files" {
 | 
					@test "does not overwrite existing env files" {
 | 
				
			||||||
@ -117,11 +110,13 @@ teardown(){
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "ensure recipe up to date if no --offline" {
 | 
					@test "ensure recipe up to date if no --offline" {
 | 
				
			||||||
 | 
					  _reset_recipe
 | 
				
			||||||
 | 
					  wantHash=$(_get_n_hash 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  assert_equal $(_get_current_hash) "$wantHash"
 | 
				
			||||||
  assert_output --partial "Your branch is behind 'origin/main' by 3 commits, and can be fast-forwarded."
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app new "$TEST_RECIPE" \
 | 
					  run $ABRA app new "$TEST_RECIPE" \
 | 
				
			||||||
    --no-input \
 | 
					    --no-input \
 | 
				
			||||||
@ -130,18 +125,19 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
					  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  assert_equal $(_get_head_hash) $(_get_current_hash)
 | 
				
			||||||
  assert_output --partial "Your branch is up to date with 'origin/main'."
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "ensure recipe not up to date if --offline" {
 | 
					@test "ensure recipe not up to date if --offline" {
 | 
				
			||||||
 | 
					  _reset_recipe
 | 
				
			||||||
 | 
					  wantHash=$(_get_n_hash 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  assert_equal $(_get_current_hash) "$wantHash"
 | 
				
			||||||
  assert_output --partial "Your branch is behind 'origin/main' by 3 commits, and can be fast-forwarded."
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # NOTE(d1): need to use --chaos to force same commit
 | 
					  # NOTE(d1): need to use --chaos to force same commit
 | 
				
			||||||
  run $ABRA app new "$TEST_RECIPE" \
 | 
					  run $ABRA app new "$TEST_RECIPE" \
 | 
				
			||||||
@ -153,12 +149,12 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
					  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  assert_equal $(_get_current_hash) "$wantHash"
 | 
				
			||||||
  assert_output --partial "Your branch is behind 'origin/main' by 3 commits, and can be fast-forwarded."
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# bats test_tags=slow
 | 
				
			||||||
@test "generate secrets" {
 | 
					@test "generate secrets" {
 | 
				
			||||||
  run $ABRA app new "$TEST_RECIPE" \
 | 
					  run $ABRA app new "$TEST_RECIPE" \
 | 
				
			||||||
    --no-input \
 | 
					    --no-input \
 | 
				
			||||||
 | 
				
			|||||||
@ -104,7 +104,7 @@ teardown(){
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  _undeploy_app
 | 
					  _undeploy_app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app volume rm "$TEST_APP_DOMAIN"
 | 
					  run $ABRA app volume rm "$TEST_APP_DOMAIN" --no-input
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app volume ls "$TEST_APP_DOMAIN"
 | 
					  run $ABRA app volume ls "$TEST_APP_DOMAIN"
 | 
				
			||||||
 | 
				
			|||||||
@ -109,13 +109,13 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app restore "$TEST_APP_DOMAIN" app DOESNTEXIST
 | 
					  run $ABRA app restore "$TEST_APP_DOMAIN" app
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  refute_output --partial 'behind 3'
 | 
					  assert_output --partial "up to date"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "ensure recipe not up to date if --offline" {
 | 
					@test "ensure recipe not up to date if --offline" {
 | 
				
			||||||
@ -126,19 +126,19 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app restore "$TEST_APP_DOMAIN" app DOESNTEXIST --offline
 | 
					  run $ABRA app restore "$TEST_APP_DOMAIN" app --offline
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit"
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  refute_output --partial 'behind 3'
 | 
					  assert_output --partial "HEAD detached at $latestCommit"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "error if missing service" {
 | 
					@test "error if missing service" {
 | 
				
			||||||
 | 
				
			|||||||
@ -50,13 +50,13 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input --no-converge-checks
 | 
					  run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input --no-converge-checks
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  refute_output --partial 'behind 3'
 | 
					  assert_output --partial "up to date"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "ensure recipe not up to date if --offline" {
 | 
					@test "ensure recipe not up to date if --offline" {
 | 
				
			||||||
@ -67,14 +67,14 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app rollback "$TEST_APP_DOMAIN" \
 | 
					  run $ABRA app rollback "$TEST_APP_DOMAIN" \
 | 
				
			||||||
    --no-input --no-converge-checks --offline
 | 
					    --no-input --no-converge-checks --offline
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit"
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
@ -131,7 +131,7 @@ teardown(){
 | 
				
			|||||||
  latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
 | 
					  latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app deploy "$TEST_APP_DOMAIN" \
 | 
					  run $ABRA app deploy "$TEST_APP_DOMAIN" \
 | 
				
			||||||
    --no-input --no-converge-checks --chaos
 | 
					    --no-input --chaos
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial "$latestCommit"
 | 
					  assert_output --partial "$latestCommit"
 | 
				
			||||||
  assert_output --partial 'chaos'
 | 
					  assert_output --partial 'chaos'
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@ setup_file(){
 | 
				
			|||||||
  run $ABRA app new "$TEST_RECIPE" \
 | 
					  run $ABRA app new "$TEST_RECIPE" \
 | 
				
			||||||
    --no-input \
 | 
					    --no-input \
 | 
				
			||||||
    --server "$TEST_SERVER" \
 | 
					    --server "$TEST_SERVER" \
 | 
				
			||||||
    --domain "$TEST_APP_DOMAIN" \
 | 
					    --domain "$TEST_APP_DOMAIN"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
					  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -19,11 +19,11 @@ teardown_file(){
 | 
				
			|||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
teardown(){
 | 
					teardown() {
 | 
				
			||||||
  # https://github.com/bats-core/bats-core/issues/383#issuecomment-738628888
 | 
					  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
 | 
				
			||||||
  if [[ -z "${BATS_TEST_COMPLETED}" ]]; then
 | 
					  _reset_app
 | 
				
			||||||
    _undeploy_app
 | 
					  _reset_recipe
 | 
				
			||||||
  fi
 | 
					  _checkout_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
setup(){
 | 
					setup(){
 | 
				
			||||||
@ -84,9 +84,6 @@ setup(){
 | 
				
			|||||||
  assert_output --partial 'test_pass_one'
 | 
					  assert_output --partial 'test_pass_one'
 | 
				
			||||||
  assert_output --partial 'test_pass_two'
 | 
					  assert_output --partial 'test_pass_two'
 | 
				
			||||||
  refute_output --partial 'extra_pass'
 | 
					  refute_output --partial 'extra_pass'
 | 
				
			||||||
 | 
					 | 
				
			||||||
  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
 | 
					 | 
				
			||||||
  assert_success
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "generate: broken if missing version" {
 | 
					@test "generate: broken if missing version" {
 | 
				
			||||||
@ -98,7 +95,6 @@ setup(){
 | 
				
			|||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
  assert_output --partial 'missing version'
 | 
					  assert_output --partial 'missing version'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_app
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "generate: use version from app env" {
 | 
					@test "generate: use version from app env" {
 | 
				
			||||||
@ -115,11 +111,6 @@ setup(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'v2'
 | 
					  assert_output --partial 'v2'
 | 
				
			||||||
  refute_output --partial 'v1'
 | 
					  refute_output --partial 'v1'
 | 
				
			||||||
 | 
					 | 
				
			||||||
  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
 | 
					 | 
				
			||||||
  assert_success
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _reset_app
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "generate: generate extra secret based on COMPOSE_FILE" {
 | 
					@test "generate: generate extra secret based on COMPOSE_FILE" {
 | 
				
			||||||
@ -138,11 +129,6 @@ setup(){
 | 
				
			|||||||
  run docker -c "$TEST_SERVER" secret ls
 | 
					  run docker -c "$TEST_SERVER" secret ls
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial "$TEST_APP_DOMAIN_extra_pass_v1"
 | 
					  assert_output --partial "$TEST_APP_DOMAIN_extra_pass_v1"
 | 
				
			||||||
 | 
					 | 
				
			||||||
  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
 | 
					 | 
				
			||||||
  assert_success
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  _reset_app
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "generate: bail if unstaged changes and no --chaos" {
 | 
					@test "generate: bail if unstaged changes and no --chaos" {
 | 
				
			||||||
@ -169,8 +155,6 @@ setup(){
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all --chaos
 | 
					  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all --chaos
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					 | 
				
			||||||
  _checkout_recipe
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "generate: ensure secret name uses trimmed stack name" {
 | 
					@test "generate: ensure secret name uses trimmed stack name" {
 | 
				
			||||||
@ -235,9 +219,22 @@ setup(){
 | 
				
			|||||||
  run $ABRA app secret ls "$TEST_APP_DOMAIN"
 | 
					  run $ABRA app secret ls "$TEST_APP_DOMAIN"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'true'
 | 
					  assert_output --partial 'true'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app secret rm "$TEST_APP_DOMAIN" test_pass_one
 | 
					@test "insert: create secret from file" {
 | 
				
			||||||
 | 
					  run $ABRA app secret ls "$TEST_APP_DOMAIN"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					  assert_output --partial 'false'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run bash -c "echo bar >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run $ABRA app secret insert --file "$TEST_APP_DOMAIN" test_pass_one v1 "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  assert_output --partial 'successfully stored on server'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run $ABRA app secret ls "$TEST_APP_DOMAIN"
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  assert_output --partial 'true'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "rm: validate arguments" {
 | 
					@test "rm: validate arguments" {
 | 
				
			||||||
@ -321,9 +318,6 @@ setup(){
 | 
				
			|||||||
  run $ABRA app secret ls "$TEST_APP_DOMAIN"
 | 
					  run $ABRA app secret ls "$TEST_APP_DOMAIN"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'true'
 | 
					  assert_output --partial 'true'
 | 
				
			||||||
 | 
					 | 
				
			||||||
  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
 | 
					 | 
				
			||||||
  assert_success
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "ls: show secrets as machine readable" {
 | 
					@test "ls: show secrets as machine readable" {
 | 
				
			||||||
@ -337,9 +331,6 @@ setup(){
 | 
				
			|||||||
  run $ABRA app secret ls "$TEST_APP_DOMAIN" --machine
 | 
					  run $ABRA app secret ls "$TEST_APP_DOMAIN" --machine
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial '"created-on-server":"true"'
 | 
					  assert_output --partial '"created-on-server":"true"'
 | 
				
			||||||
 | 
					 | 
				
			||||||
  run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
 | 
					 | 
				
			||||||
  assert_success
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "ls: bail if unstaged changes and no --chaos" {
 | 
					@test "ls: bail if unstaged changes and no --chaos" {
 | 
				
			||||||
 | 
				
			|||||||
@ -59,6 +59,8 @@ teardown(){
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# bats test_tags=slow
 | 
					# bats test_tags=slow
 | 
				
			||||||
@test "error if not in catalogue" {
 | 
					@test "error if not in catalogue" {
 | 
				
			||||||
 | 
					  skip "known issue, see https://git.coopcloud.tech/coop-cloud/recipes-catalogue-json/issues/6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _deploy_app
 | 
					  _deploy_app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app version "$TEST_APP_DOMAIN"
 | 
					  run $ABRA app version "$TEST_APP_DOMAIN"
 | 
				
			||||||
@ -92,7 +94,7 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 # NOTE(d1): to let the stack come down before nuking volumes
 | 
					 # NOTE(d1): to let the stack come down before nuking volumes
 | 
				
			||||||
  sleep 3
 | 
					  sleep 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app volume remove "$appDomain" --no-input
 | 
					  run $ABRA app volume remove "$appDomain" --no-input
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
				
			|||||||
@ -78,9 +78,6 @@ teardown(){
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  _undeploy_app
 | 
					  _undeploy_app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # NOTE(d1): to let the stack come down before nuking volumes
 | 
					 | 
				
			||||||
  sleep 5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  run $ABRA app volume rm "$TEST_APP_DOMAIN" --force
 | 
					  run $ABRA app volume rm "$TEST_APP_DOMAIN" --force
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'volumes removed successfully'
 | 
					  assert_output --partial 'volumes removed successfully'
 | 
				
			||||||
@ -92,9 +89,6 @@ teardown(){
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  _undeploy_app
 | 
					  _undeploy_app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # NOTE(d1): to let the stack come down before nuking volumes
 | 
					 | 
				
			||||||
  sleep 5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  run $ABRA app volume rm "$TEST_APP_DOMAIN" --force
 | 
					  run $ABRA app volume rm "$TEST_APP_DOMAIN" --force
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'volumes removed successfully'
 | 
					  assert_output --partial 'volumes removed successfully'
 | 
				
			||||||
 | 
				
			|||||||
@ -49,7 +49,7 @@ _reset_app(){
 | 
				
			|||||||
  run $ABRA app new "$TEST_RECIPE" \
 | 
					  run $ABRA app new "$TEST_RECIPE" \
 | 
				
			||||||
    --no-input \
 | 
					    --no-input \
 | 
				
			||||||
    --server "$TEST_SERVER" \
 | 
					    --server "$TEST_SERVER" \
 | 
				
			||||||
    --domain "$TEST_APP_DOMAIN" \
 | 
					    --domain "$TEST_APP_DOMAIN"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
					  assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,39 @@ _reset_tags() {
 | 
				
			|||||||
_set_git_author() {
 | 
					_set_git_author() {
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" config --local user.email test@example.com
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" config --local user.email test@example.com
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" config --local user.name test
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" config --local user.name test
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_git_commit() {
 | 
				
			||||||
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" add .
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" commit -m "test: helpers/git.bash: _git_commit"
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_get_tag_hash() {
 | 
				
			||||||
 | 
					  tagHash=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-list -n 1 "$1")
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  echo "$tagHash"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_get_head_hash() {
 | 
				
			||||||
 | 
					  headHash=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show -s --format="%H" HEAD)
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  echo "$headHash"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_get_current_hash() {
 | 
				
			||||||
 | 
					  currentHash=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show -s --format="%H")
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  echo "$currentHash"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_get_n_hash() {
 | 
				
			||||||
 | 
					  nHash=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show -s --format="%H" "HEAD~$1")
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  echo "$nHash"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,11 @@ _add_server() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_rm_server() {
 | 
					_rm_server() {
 | 
				
			||||||
 | 
					  if [[ "$TEST_SERVER" == "default" ]]; then
 | 
				
			||||||
 | 
					      run rm -rf "$ABRA_DIR/servers/default"
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
      run $ABRA server remove --no-input "$TEST_SERVER"
 | 
					      run $ABRA server remove --no-input "$TEST_SERVER"
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER"
 | 
					  assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -66,13 +66,13 @@ setup() {
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA recipe lint "$TEST_RECIPE"
 | 
					  run $ABRA recipe lint "$TEST_RECIPE"
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  refute_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -82,13 +82,13 @@ setup() {
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA recipe lint "$TEST_RECIPE" --offline
 | 
					  run $ABRA recipe lint "$TEST_RECIPE" --offline
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -23,14 +23,14 @@ teardown(){
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "create new recipe" {
 | 
					@test "create new recipe" {
 | 
				
			||||||
  run $ABRA recipe new foobar --git-name foo --git-email foo@example.com
 | 
					  run $ABRA recipe new foobar
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'Your new foobar recipe has been created'
 | 
					  assert_output --partial 'Your new foobar recipe has been created'
 | 
				
			||||||
  assert_exists "$ABRA_DIR/recipes/foobar"
 | 
					  assert_exists "$ABRA_DIR/recipes/foobar"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "create new app from new recipe" {
 | 
					@test "create new app from new recipe" {
 | 
				
			||||||
  run $ABRA recipe new foobar --git-name foo --git-email foo@example.com
 | 
					  run $ABRA recipe new foobar
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA app new foobar \
 | 
					  run $ABRA app new foobar \
 | 
				
			||||||
@ -40,3 +40,16 @@ teardown(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'A new foobar app has been created!'
 | 
					  assert_output --partial 'A new foobar app has been created!'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@test "create new recipe with git credentials" {
 | 
				
			||||||
 | 
					  run $ABRA recipe new foobar --git-name fooUser --git-email foo@example.com
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  assert_output --partial 'Your new foobar recipe has been created'
 | 
				
			||||||
 | 
					  assert_exists "$ABRA_DIR/recipes/foobar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  run bash -c 'git -C "$ABRA_DIR/recipes/foobar" log -n 1'
 | 
				
			||||||
 | 
					  assert_success
 | 
				
			||||||
 | 
					  assert_output --partial 'fooUser'
 | 
				
			||||||
 | 
					  assert_output --partial 'foo@example.com'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -61,14 +61,14 @@ setup(){
 | 
				
			|||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  assert_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA recipe upgrade "$TEST_RECIPE" --no-input
 | 
					  run $ABRA recipe upgrade "$TEST_RECIPE" --no-input
 | 
				
			||||||
  assert_success
 | 
					  assert_success
 | 
				
			||||||
  assert_output --partial 'can upgrade service: app'
 | 
					  assert_output --partial 'can upgrade service: app'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
					  run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
 | 
				
			||||||
  refute_output --partial 'behind 3'
 | 
					  assert_output --regexp 'behind .* 3 commits'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _reset_recipe
 | 
					  _reset_recipe
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,8 @@ setup() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@test "error if not present in catalogue" {
 | 
					@test "error if not present in catalogue" {
 | 
				
			||||||
 | 
					  skip "known issue, see https://git.coopcloud.tech/coop-cloud/recipes-catalogue-json/issues/6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run $ABRA recipe versions "$TEST_RECIPE"
 | 
					  run $ABRA recipe versions "$TEST_RECIPE"
 | 
				
			||||||
  assert_failure
 | 
					  assert_failure
 | 
				
			||||||
  assert_output --partial "is not published on the catalogue"
 | 
					  assert_output --partial "is not published on the catalogue"
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user