forked from toolshed/abra
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			overview-c
			...
			fix-integr
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 02b4035409 | |||
| 213d4a9214 | |||
| ba0c0c793d | |||
| d09a19a385 | |||
| cee808ff06 | |||
| 4326d1d259 | |||
| b976872f77 | |||
| 7b6ea76437 | |||
| 9069758969 | 
@ -261,7 +261,7 @@ func init() {
 | 
			
		||||
	AppCmdCommand.Flags().BoolVarP(
 | 
			
		||||
		&requestTTY,
 | 
			
		||||
		"tty",
 | 
			
		||||
		"t",
 | 
			
		||||
		"T",
 | 
			
		||||
		false,
 | 
			
		||||
		"request remote TTY",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,8 @@ checkout as-is. Recipe commit hashes are also supported as values for
 | 
			
		||||
	ValidArgsFunction: func(
 | 
			
		||||
		cmd *cobra.Command,
 | 
			
		||||
		args []string,
 | 
			
		||||
		toComplete string) ([]string, cobra.ShellCompDirective) {
 | 
			
		||||
		toComplete string,
 | 
			
		||||
	) ([]string, cobra.ShellCompDirective) {
 | 
			
		||||
		switch l := len(args); l {
 | 
			
		||||
		case 0:
 | 
			
		||||
			return autocomplete.AppNameComplete()
 | 
			
		||||
@ -99,27 +100,9 @@ checkout as-is. Recipe commit hashes are also supported as values for
 | 
			
		||||
			log.Fatalf("%s is already deployed", app.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(args) == 2 && args[1] != "" {
 | 
			
		||||
			toDeployVersion = args[1]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !deployMeta.IsDeployed &&
 | 
			
		||||
			toDeployVersion == "" &&
 | 
			
		||||
			app.Recipe.EnvVersion != "" && !internal.IgnoreEnvVersion {
 | 
			
		||||
			log.Debugf("new deployment, choosing .env version: %s", app.Recipe.EnvVersion)
 | 
			
		||||
			toDeployVersion = app.Recipe.EnvVersion
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !internal.Chaos && toDeployVersion == "" {
 | 
			
		||||
			if err := getLatestVersionOrCommit(app, &toDeployVersion); err != nil {
 | 
			
		||||
				log.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if internal.Chaos {
 | 
			
		||||
			if err := getChaosVersion(app, &toDeployVersion, &toDeployChaosVersion); err != nil {
 | 
			
		||||
				log.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		toDeployVersion, toDeployChaosVersion, err = getDeployVersion(args, deployMeta, app)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !internal.Chaos {
 | 
			
		||||
@ -271,32 +254,22 @@ func getChaosVersion(app app.App, toDeployVersion, toDeployChaosVersion *string)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLatestVersionOrCommit(app app.App, toDeployVersion *string) error {
 | 
			
		||||
func getLatestVersionOrCommit(app app.App) (string, string, error) {
 | 
			
		||||
	versions, err := app.Recipe.Tags()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return "", "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(versions) > 0 && !internal.Chaos {
 | 
			
		||||
		*toDeployVersion = versions[len(versions)-1]
 | 
			
		||||
 | 
			
		||||
		log.Debugf("choosing %s as version to deploy", *toDeployVersion)
 | 
			
		||||
 | 
			
		||||
		if _, err := app.Recipe.EnsureVersion(*toDeployVersion); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
		return versions[len(versions)-1], "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	head, err := app.Recipe.Head()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return "", "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*toDeployVersion = formatter.SmallSHA(head.String())
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	return "", formatter.SmallSHA(head.String()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateArgsAndFlags ensures compatible args/flags.
 | 
			
		||||
@ -323,6 +296,46 @@ func validateSecrets(cl *dockerClient.Client, app app.App) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app app.App) (string, string, error) {
 | 
			
		||||
	// Chaos mode overrides everything
 | 
			
		||||
	if internal.Chaos {
 | 
			
		||||
		v, err := app.Recipe.ChaosVersion()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", "", err
 | 
			
		||||
		}
 | 
			
		||||
		log.Debugf("version: taking chaos version: %s", v)
 | 
			
		||||
		return v, v, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if the deploy version is set with a cli argument
 | 
			
		||||
	if len(cliArgs) == 2 && cliArgs[1] != "" {
 | 
			
		||||
		log.Debugf("version: taking version from cli arg: %s", cliArgs[1])
 | 
			
		||||
		return cliArgs[1], "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if the recipe has a version in the .env file
 | 
			
		||||
	if app.Recipe.EnvVersion != "" && !internal.IgnoreEnvVersion {
 | 
			
		||||
		log.Debugf("version: taking version from .env file: %s", app.Recipe.EnvVersion)
 | 
			
		||||
		return app.Recipe.EnvVersion, "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Take deployed version
 | 
			
		||||
	if deployMeta.IsDeployed {
 | 
			
		||||
		log.Debugf("version: taking deployed version: %s", deployMeta.Version)
 | 
			
		||||
		return deployMeta.Version, "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, vc, err := getLatestVersionOrCommit(app)
 | 
			
		||||
	log.Debugf("version: taking new recipe versio: %s, %s", v, vc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if v == "" {
 | 
			
		||||
		return vc, vc, nil
 | 
			
		||||
	}
 | 
			
		||||
	return v, vc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	AppDeployCommand.Flags().BoolVarP(
 | 
			
		||||
		&internal.Chaos,
 | 
			
		||||
 | 
			
		||||
@ -123,6 +123,13 @@ Pass "--all-services/-a" to restart all services.`,
 | 
			
		||||
var allServices bool
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	AppRestartCommand.Flags().BoolVarP(
 | 
			
		||||
		&internal.Chaos,
 | 
			
		||||
		"chaos",
 | 
			
		||||
		"C",
 | 
			
		||||
		false,
 | 
			
		||||
		"ignore uncommitted recipes changes",
 | 
			
		||||
	)
 | 
			
		||||
	AppRestartCommand.Flags().BoolVarP(
 | 
			
		||||
		&allServices,
 | 
			
		||||
		"all-services",
 | 
			
		||||
 | 
			
		||||
@ -49,11 +49,11 @@ var AppSecretGenerateCommand = &cobra.Command{
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(args) == 1 && !generateAllSecrets {
 | 
			
		||||
		if len(args) <= 2 && !generateAllSecrets {
 | 
			
		||||
			log.Fatal("missing arguments [secret]/[version] or '--all'")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(args) > 1 && generateAllSecrets {
 | 
			
		||||
		if len(args) > 2 && generateAllSecrets {
 | 
			
		||||
			log.Fatal("cannot use '[secret] [version]' and '--all' together")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -59,16 +59,16 @@ Passing "--prune/-p" does not remove those volumes.`,
 | 
			
		||||
			chaosVersion = deployMeta.ChaosVersion
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		toWriteVersion := deployMeta.Version
 | 
			
		||||
		version := deployMeta.Version
 | 
			
		||||
		if deployMeta.IsChaos {
 | 
			
		||||
			toWriteVersion = chaosVersion
 | 
			
		||||
			version = chaosVersion
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := internal.UndeployOverview(
 | 
			
		||||
			app,
 | 
			
		||||
			deployMeta.Version,
 | 
			
		||||
			chaosVersion,
 | 
			
		||||
			toWriteVersion,
 | 
			
		||||
			version,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
@ -86,10 +86,6 @@ Passing "--prune/-p" does not remove those volumes.`,
 | 
			
		||||
				log.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := app.WriteRecipeVersion(toWriteVersion, false); err != nil {
 | 
			
		||||
			log.Fatalf("writing recipe version failed: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -64,10 +64,7 @@ func NewVersionOverview(
 | 
			
		||||
 | 
			
		||||
	upperKind := strings.ToUpper(kind)
 | 
			
		||||
 | 
			
		||||
	envVersion, err := recipe.GetEnvVersionRaw(app.Recipe.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	envVersion := app.Recipe.EnvVersionRaw
 | 
			
		||||
 | 
			
		||||
	if envVersion == "" {
 | 
			
		||||
		envVersion = config.NO_VERSION_DEFAULT
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,13 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	gitPkg "coopcloud.tech/abra/pkg/git"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/log"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
	"github.com/distribution/reference"
 | 
			
		||||
	"github.com/go-git/go-git/v5"
 | 
			
		||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
			
		||||
@ -345,6 +347,18 @@ func (r Recipe) Tags() ([]string, error) {
 | 
			
		||||
		return tags, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Slice(tags, func(i, j int) bool {
 | 
			
		||||
		version1, err := tagcmp.Parse(tags[i])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		version2, err := tagcmp.Parse(tags[j])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		return version1.IsLessThan(version2)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	log.Debugf("detected %s as tags for recipe %s", strings.Join(tags, ", "), r.Name)
 | 
			
		||||
 | 
			
		||||
	return tags, nil
 | 
			
		||||
 | 
			
		||||
@ -135,6 +135,8 @@ func GetEnvVersionRaw(name string) (string, error) {
 | 
			
		||||
 | 
			
		||||
func Get(name string) Recipe {
 | 
			
		||||
	version := ""
 | 
			
		||||
	versionRaw := ""
 | 
			
		||||
	dirty := false
 | 
			
		||||
	if strings.Contains(name, ":") {
 | 
			
		||||
		split := strings.Split(name, ":")
 | 
			
		||||
		if len(split) > 2 {
 | 
			
		||||
@ -143,7 +145,9 @@ func Get(name string) Recipe {
 | 
			
		||||
		name = split[0]
 | 
			
		||||
 | 
			
		||||
		version = split[1]
 | 
			
		||||
		versionRaw = version
 | 
			
		||||
		if strings.HasSuffix(version, config.DIRTY_DEFAULT) {
 | 
			
		||||
			dirty = true
 | 
			
		||||
			version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1)
 | 
			
		||||
			log.Debugf("removed dirty suffix from .env version: %s -> %s", split[1], version)
 | 
			
		||||
		}
 | 
			
		||||
@ -167,11 +171,13 @@ func Get(name string) Recipe {
 | 
			
		||||
	dir := path.Join(config.RECIPES_DIR, escapeRecipeName(name))
 | 
			
		||||
 | 
			
		||||
	r := Recipe{
 | 
			
		||||
		Name:       name,
 | 
			
		||||
		EnvVersion: version,
 | 
			
		||||
		Dir:        dir,
 | 
			
		||||
		GitURL:     gitURL,
 | 
			
		||||
		SSHURL:     sshURL,
 | 
			
		||||
		Name:          name,
 | 
			
		||||
		EnvVersion:    version,
 | 
			
		||||
		EnvVersionRaw: versionRaw,
 | 
			
		||||
		Dirty:         dirty,
 | 
			
		||||
		Dir:           dir,
 | 
			
		||||
		GitURL:        gitURL,
 | 
			
		||||
		SSHURL:        sshURL,
 | 
			
		||||
 | 
			
		||||
		ComposePath:   path.Join(dir, "compose.yml"),
 | 
			
		||||
		ReadmePath:    path.Join(dir, "README.md"),
 | 
			
		||||
@ -187,12 +193,14 @@ func Get(name string) Recipe {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Recipe struct {
 | 
			
		||||
	Name       string
 | 
			
		||||
	EnvVersion string
 | 
			
		||||
	Dirty      bool // NOTE(d1): git terminology for unstaged changes
 | 
			
		||||
	Dir        string
 | 
			
		||||
	GitURL     string
 | 
			
		||||
	SSHURL     string
 | 
			
		||||
	Name string
 | 
			
		||||
	// EnvVersion without +U
 | 
			
		||||
	EnvVersion    string
 | 
			
		||||
	EnvVersionRaw string
 | 
			
		||||
	Dirty         bool // NOTE(d1): git terminology for unstaged changes
 | 
			
		||||
	Dir           string
 | 
			
		||||
	GitURL        string
 | 
			
		||||
	SSHURL        string
 | 
			
		||||
 | 
			
		||||
	ComposePath   string
 | 
			
		||||
	ReadmePath    string
 | 
			
		||||
 | 
			
		||||
@ -54,13 +54,21 @@ teardown(){
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# bats test_tags=slow
 | 
			
		||||
@test "chaos commit written to env" {
 | 
			
		||||
@test "deploy commit written to env and redeploy keeps that version" {
 | 
			
		||||
  run $ABRA app deploy "$TEST_APP_DOMAIN" "1e83340e" --no-input --no-converge-checks
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
  run grep -q "TYPE=$TEST_RECIPE:1e83340e" \
 | 
			
		||||
    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
  run $ABRA app deploy "$TEST_APP_DOMAIN" \
 | 
			
		||||
    --force --no-input --no-converge-checks
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
  run grep -q "TYPE=$TEST_RECIPE:1e83340e" \
 | 
			
		||||
    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
			
		||||
  assert_success
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# bats test_tags=slow
 | 
			
		||||
@ -98,12 +106,15 @@ teardown(){
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# bats test_tags=slow
 | 
			
		||||
@test "deploy overwrites chaos deploy" {
 | 
			
		||||
  run $ABRA app deploy "$TEST_APP_DOMAIN" "1e83340e" \
 | 
			
		||||
    --no-input --no-converge-checks
 | 
			
		||||
@test "takes deployed version when no .env version is present " {
 | 
			
		||||
  run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks --ignore-env-version
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
  run grep -q "TYPE=$TEST_RECIPE:1e83340e" \
 | 
			
		||||
  run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
 | 
			
		||||
    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
  run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=abra-test-recipe/g' \
 | 
			
		||||
    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
@ -111,7 +122,7 @@ teardown(){
 | 
			
		||||
    --force --no-input --no-converge-checks
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
  run grep -q "TYPE=$TEST_RECIPE:1e83340e" \
 | 
			
		||||
  run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
 | 
			
		||||
    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
			
		||||
  assert_failure
 | 
			
		||||
  assert_success
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -165,14 +165,13 @@ teardown(){
 | 
			
		||||
  run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
 | 
			
		||||
  assert_success
 | 
			
		||||
  assert_output --partial "0.1.1+1.20.2"
 | 
			
		||||
  assert_output --partial "${tagHash:0:8}"
 | 
			
		||||
  assert_output --partial "0.2.0+1.21.0"
 | 
			
		||||
  refute_output --partial "false"
 | 
			
		||||
 | 
			
		||||
  run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
 | 
			
		||||
  assert_success
 | 
			
		||||
  assert_output --partial "0.1.1+1.20.2"
 | 
			
		||||
  assert_output --partial "0.1.0+1.20.0"
 | 
			
		||||
 | 
			
		||||
  tagHash=$(_get_tag_hash "0.1.1+1.20.2")
 | 
			
		||||
  refute_output --partial "${tagHash:0:8}"
 | 
			
		||||
  assert_output --partial "false"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,11 @@ teardown(){
 | 
			
		||||
 | 
			
		||||
  run $ABRA app secret generate "$TEST_APP_DOMAIN"
 | 
			
		||||
  assert_failure
 | 
			
		||||
  assert_output --partial 'missing arguments'
 | 
			
		||||
 | 
			
		||||
  run $ABRA app secret generate "$TEST_APP_DOMAIN" test_pass_one
 | 
			
		||||
  assert_failure
 | 
			
		||||
  assert_output --partial 'missing arguments'
 | 
			
		||||
 | 
			
		||||
  run $ABRA app secret generate "$TEST_APP_DOMAIN" testSecret testVersion --all
 | 
			
		||||
  assert_failure
 | 
			
		||||
 | 
			
		||||
@ -64,10 +64,6 @@ teardown(){
 | 
			
		||||
  # env version
 | 
			
		||||
  assert_output --regexp 'CURRENT VERSION.*' + "${latestRelease}"
 | 
			
		||||
  assert_output --regexp 'NEW VERSION.*' + "${headHash:0:8}"
 | 
			
		||||
 | 
			
		||||
  run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \
 | 
			
		||||
    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
			
		||||
  assert_success
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@test "chaos deploy with unstaged commits and undeploy" {
 | 
			
		||||
@ -93,10 +89,6 @@ teardown(){
 | 
			
		||||
  assert_output --regexp 'CURRENT VERSION.*' + "${latestRelease}"
 | 
			
		||||
  assert_output --regexp 'NEW VERSION.*' + "${headHash:0:8}+U"
 | 
			
		||||
 | 
			
		||||
  run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \
 | 
			
		||||
    "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
  run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
 | 
			
		||||
  assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -217,7 +217,7 @@ teardown(){
 | 
			
		||||
  run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
 | 
			
		||||
  assert_success
 | 
			
		||||
  assert_output --partial "0.1.1+1.20.2"
 | 
			
		||||
  assert_output --partial "${tagHash:0:8}"
 | 
			
		||||
  assert_output --partial "0.1.0+1.20.0"
 | 
			
		||||
 | 
			
		||||
  run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input --no-converge-checks
 | 
			
		||||
  assert_success
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user