Compare commits

..

10 Commits

Author SHA1 Message Date
02b4035409 fix(deploy): Uses actual chaos version 2025-03-03 17:54:01 +01:00
213d4a9214 fix(overview): Uses correct env version in deploy / upgrade overview 2025-03-03 17:37:37 +01:00
ba0c0c793d fix(app): Do not write recipe version on undeploy 2025-03-03 16:54:21 +01:00
d09a19a385 fix: Adds chaos flag to restart command
All checks were successful
continuous-integration/drone/push Build is passing
2025-02-11 10:01:44 +00:00
cee808ff06 fix: Changes how the deploy version is detected in app deploy command 2025-02-11 10:01:44 +00:00
4326d1d259 fix: Sorts git tags with tagcmp 2025-02-11 10:01:44 +00:00
b976872f77 fix(overview): Adds linebreak after compose file in deploy overview
All checks were successful
continuous-integration/drone/push Build is passing
2025-02-11 09:57:09 +00:00
7b6ea76437 fix(secret): Checks for enough arguments
All checks were successful
continuous-integration/drone/push Build is passing
2025-02-11 09:55:03 +00:00
9069758969 fix(cmd): Uses uppercase t for tty shorthand flag
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-02-10 15:13:26 +01:00
15d6b1a2a5 fix: app new with chaos should just take the local repo as it is (#495)
All checks were successful
continuous-integration/drone/push Build is passing
Fixes #494

Reviewed-on: #495
Co-authored-by: p4u1 <p4u1_f4u1@riseup.net>
Co-committed-by: p4u1 <p4u1_f4u1@riseup.net>
2025-02-10 14:00:42 +00:00
15 changed files with 140 additions and 117 deletions

View File

@ -261,7 +261,7 @@ func init() {
AppCmdCommand.Flags().BoolVarP( AppCmdCommand.Flags().BoolVarP(
&requestTTY, &requestTTY,
"tty", "tty",
"t", "T",
false, false,
"request remote TTY", "request remote TTY",
) )

View File

@ -46,7 +46,8 @@ checkout as-is. Recipe commit hashes are also supported as values for
ValidArgsFunction: func( ValidArgsFunction: func(
cmd *cobra.Command, cmd *cobra.Command,
args []string, args []string,
toComplete string) ([]string, cobra.ShellCompDirective) { toComplete string,
) ([]string, cobra.ShellCompDirective) {
switch l := len(args); l { switch l := len(args); l {
case 0: case 0:
return autocomplete.AppNameComplete() 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) log.Fatalf("%s is already deployed", app.Name)
} }
if len(args) == 2 && args[1] != "" { toDeployVersion, toDeployChaosVersion, err = getDeployVersion(args, deployMeta, app)
toDeployVersion = args[1] if err != nil {
} log.Fatal(err)
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)
}
} }
if !internal.Chaos { if !internal.Chaos {
@ -271,32 +254,22 @@ func getChaosVersion(app app.App, toDeployVersion, toDeployChaosVersion *string)
return nil return nil
} }
func getLatestVersionOrCommit(app app.App, toDeployVersion *string) error { func getLatestVersionOrCommit(app app.App) (string, string, error) {
versions, err := app.Recipe.Tags() versions, err := app.Recipe.Tags()
if err != nil { if err != nil {
return err return "", "", err
} }
if len(versions) > 0 && !internal.Chaos { if len(versions) > 0 && !internal.Chaos {
*toDeployVersion = versions[len(versions)-1] return versions[len(versions)-1], "", nil
log.Debugf("choosing %s as version to deploy", *toDeployVersion)
if _, err := app.Recipe.EnsureVersion(*toDeployVersion); err != nil {
return err
}
return nil
} }
head, err := app.Recipe.Head() head, err := app.Recipe.Head()
if err != nil { if err != nil {
return err return "", "", err
} }
*toDeployVersion = formatter.SmallSHA(head.String()) return "", formatter.SmallSHA(head.String()), nil
return nil
} }
// validateArgsAndFlags ensures compatible args/flags. // validateArgsAndFlags ensures compatible args/flags.
@ -323,6 +296,46 @@ func validateSecrets(cl *dockerClient.Client, app app.App) error {
return nil 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() { func init() {
AppDeployCommand.Flags().BoolVarP( AppDeployCommand.Flags().BoolVarP(
&internal.Chaos, &internal.Chaos,

View File

@ -81,35 +81,8 @@ var AppNewCommand = &cobra.Command{
log.Fatal(err) log.Fatal(err)
} }
// NOTE(d1): rely on tags as there is no recipe.EnvVersion yet because recipeVersion = chaosVersion
// the app has not been fully created. we rely on the local git state of } else {
// the repository
tags, err := recipe.Tags()
if err != nil {
log.Fatal(err)
}
internal.SortVersionsDesc(tags)
if len(tags) == 0 {
// NOTE(d1): this is a new recipe with no released versions
recipeVersion = config.UNKNOWN_DEFAULT
} else {
recipeVersion = tags[len(tags)-1]
}
if err := recipe.IsDirty(); err != nil {
log.Fatal(err)
}
if !internal.Offline && !recipe.Dirty {
if err := recipe.EnsureUpToDate(); err != nil {
log.Fatal(err)
}
}
}
if !internal.Chaos {
if err := recipe.EnsureIsClean(); err != nil { if err := recipe.EnsureIsClean(); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -123,6 +123,13 @@ Pass "--all-services/-a" to restart all services.`,
var allServices bool var allServices bool
func init() { func init() {
AppRestartCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
false,
"ignore uncommitted recipes changes",
)
AppRestartCommand.Flags().BoolVarP( AppRestartCommand.Flags().BoolVarP(
&allServices, &allServices,
"all-services", "all-services",

View File

@ -49,11 +49,11 @@ var AppSecretGenerateCommand = &cobra.Command{
log.Fatal(err) log.Fatal(err)
} }
if len(args) == 1 && !generateAllSecrets { if len(args) <= 2 && !generateAllSecrets {
log.Fatal("missing arguments [secret]/[version] or '--all'") 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") log.Fatal("cannot use '[secret] [version]' and '--all' together")
} }

View File

@ -59,16 +59,16 @@ Passing "--prune/-p" does not remove those volumes.`,
chaosVersion = deployMeta.ChaosVersion chaosVersion = deployMeta.ChaosVersion
} }
toWriteVersion := deployMeta.Version version := deployMeta.Version
if deployMeta.IsChaos { if deployMeta.IsChaos {
toWriteVersion = chaosVersion version = chaosVersion
} }
if err := internal.UndeployOverview( if err := internal.UndeployOverview(
app, app,
deployMeta.Version, deployMeta.Version,
chaosVersion, chaosVersion,
toWriteVersion, version,
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -86,10 +86,6 @@ Passing "--prune/-p" does not remove those volumes.`,
log.Fatal(err) log.Fatal(err)
} }
} }
if err := app.WriteRecipeVersion(toWriteVersion, false); err != nil {
log.Fatalf("writing recipe version failed: %s", err)
}
}, },
} }

View File

@ -49,7 +49,7 @@ func NewVersionOverview(
releaseNotes string) error { releaseNotes string) error {
deployConfig := "compose.yml" deployConfig := "compose.yml"
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
deployConfig = composeFiles deployConfig = formatComposeFiles(composeFiles)
} }
server := app.Server server := app.Server
@ -64,10 +64,7 @@ func NewVersionOverview(
upperKind := strings.ToUpper(kind) upperKind := strings.ToUpper(kind)
envVersion, err := recipe.GetEnvVersionRaw(app.Recipe.Name) envVersion := app.Recipe.EnvVersionRaw
if err != nil {
return err
}
if envVersion == "" { if envVersion == "" {
envVersion = config.NO_VERSION_DEFAULT envVersion = config.NO_VERSION_DEFAULT
@ -128,6 +125,10 @@ func NewVersionOverview(
return nil return nil
} }
func formatComposeFiles(composeFiles string) string {
return strings.ReplaceAll(composeFiles, ":", "\n")
}
// DeployOverview shows a deployment overview // DeployOverview shows a deployment overview
func DeployOverview( func DeployOverview(
app appPkg.App, app appPkg.App,
@ -140,7 +141,7 @@ func DeployOverview(
) error { ) error {
deployConfig := "compose.yml" deployConfig := "compose.yml"
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
deployConfig = composeFiles deployConfig = formatComposeFiles(composeFiles)
} }
server := app.Server server := app.Server
@ -225,7 +226,7 @@ func UndeployOverview(
) error { ) error {
deployConfig := "compose.yml" deployConfig := "compose.yml"
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
deployConfig = composeFiles deployConfig = formatComposeFiles(composeFiles)
} }
server := app.Server server := app.Server

View File

@ -4,11 +4,13 @@ import (
"fmt" "fmt"
"os" "os"
"slices" "slices"
"sort"
"strings" "strings"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
gitPkg "coopcloud.tech/abra/pkg/git" gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/tagcmp"
"github.com/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"
@ -345,6 +347,18 @@ func (r Recipe) Tags() ([]string, error) {
return tags, err 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) log.Debugf("detected %s as tags for recipe %s", strings.Join(tags, ", "), r.Name)
return tags, nil return tags, nil

View File

@ -135,6 +135,8 @@ func GetEnvVersionRaw(name string) (string, error) {
func Get(name string) Recipe { func Get(name string) Recipe {
version := "" version := ""
versionRaw := ""
dirty := false
if strings.Contains(name, ":") { if strings.Contains(name, ":") {
split := strings.Split(name, ":") split := strings.Split(name, ":")
if len(split) > 2 { if len(split) > 2 {
@ -143,7 +145,9 @@ func Get(name string) Recipe {
name = split[0] name = split[0]
version = split[1] version = split[1]
versionRaw = version
if strings.HasSuffix(version, config.DIRTY_DEFAULT) { if strings.HasSuffix(version, config.DIRTY_DEFAULT) {
dirty = true
version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1) version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1)
log.Debugf("removed dirty suffix from .env version: %s -> %s", split[1], version) 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)) dir := path.Join(config.RECIPES_DIR, escapeRecipeName(name))
r := Recipe{ r := Recipe{
Name: name, Name: name,
EnvVersion: version, EnvVersion: version,
Dir: dir, EnvVersionRaw: versionRaw,
GitURL: gitURL, Dirty: dirty,
SSHURL: sshURL, Dir: dir,
GitURL: gitURL,
SSHURL: sshURL,
ComposePath: path.Join(dir, "compose.yml"), ComposePath: path.Join(dir, "compose.yml"),
ReadmePath: path.Join(dir, "README.md"), ReadmePath: path.Join(dir, "README.md"),
@ -187,12 +193,14 @@ func Get(name string) Recipe {
} }
type Recipe struct { type Recipe struct {
Name string Name string
EnvVersion string // EnvVersion without +U
Dirty bool // NOTE(d1): git terminology for unstaged changes EnvVersion string
Dir string EnvVersionRaw string
GitURL string Dirty bool // NOTE(d1): git terminology for unstaged changes
SSHURL string Dir string
GitURL string
SSHURL string
ComposePath string ComposePath string
ReadmePath string ReadmePath string

View File

@ -54,13 +54,21 @@ teardown(){
} }
# bats test_tags=slow # 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 run $ABRA app deploy "$TEST_APP_DOMAIN" "1e83340e" --no-input --no-converge-checks
assert_success assert_success
run grep -q "TYPE=$TEST_RECIPE:1e83340e" \ run grep -q "TYPE=$TEST_RECIPE:1e83340e" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success 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 # bats test_tags=slow
@ -98,12 +106,15 @@ teardown(){
} }
# bats test_tags=slow # bats test_tags=slow
@test "deploy overwrites chaos deploy" { @test "takes deployed version when no .env version is present " {
run $ABRA app deploy "$TEST_APP_DOMAIN" "1e83340e" \ run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks --ignore-env-version
--no-input --no-converge-checks
assert_success 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" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success assert_success
@ -111,7 +122,7 @@ teardown(){
--force --no-input --no-converge-checks --force --no-input --no-converge-checks
assert_success 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" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_failure assert_success
} }

View File

@ -56,7 +56,7 @@ teardown(){
assert_success assert_success
} }
@test "create new app with chaos commit" { @test "create new app with version commit" {
tagHash=$(_get_tag_hash "0.3.0+1.21.0") tagHash=$(_get_tag_hash "0.3.0+1.21.0")
run $ABRA app new "$TEST_RECIPE" "$tagHash" \ run $ABRA app new "$TEST_RECIPE" "$tagHash" \
@ -129,6 +129,10 @@ teardown(){
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_equal "$(_git_status)" "?? foo" assert_equal "$(_git_status)" "?? foo"
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
assert_success
assert_output --partial 'foo'
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
} }
@ -210,7 +214,7 @@ teardown(){
--chaos --chaos
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"
assert_output --partial "version: $latestRelease" assert_output --partial "version: ${currentHash:0:8}"
assert_output --partial "chaos: ${currentHash:0:8}" assert_output --partial "chaos: ${currentHash:0:8}"
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
@ -238,7 +242,7 @@ teardown(){
--chaos --chaos
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"
assert_output --partial "version: unknown" assert_output --partial "version: ${currentHash:0:8}"
assert_output --partial "chaos: ${currentHash:0:8}" assert_output --partial "chaos: ${currentHash:0:8}"
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"

View File

@ -165,14 +165,13 @@ teardown(){
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
assert_success assert_success
assert_output --partial "0.1.1+1.20.2" 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 run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
assert_success assert_success
assert_output --partial "0.1.1+1.20.2"
assert_output --partial "0.1.0+1.20.0" 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" assert_output --partial "false"
} }

View File

@ -41,6 +41,11 @@ teardown(){
run $ABRA app secret generate "$TEST_APP_DOMAIN" run $ABRA app secret generate "$TEST_APP_DOMAIN"
assert_failure 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 run $ABRA app secret generate "$TEST_APP_DOMAIN" testSecret testVersion --all
assert_failure assert_failure

View File

@ -64,10 +64,6 @@ teardown(){
# env version # env version
assert_output --regexp 'CURRENT VERSION.*' + "${latestRelease}" assert_output --regexp 'CURRENT VERSION.*' + "${latestRelease}"
assert_output --regexp 'NEW VERSION.*' + "${headHash:0:8}" 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" { @test "chaos deploy with unstaged commits and undeploy" {
@ -93,10 +89,6 @@ teardown(){
assert_output --regexp 'CURRENT VERSION.*' + "${latestRelease}" assert_output --regexp 'CURRENT VERSION.*' + "${latestRelease}"
assert_output --regexp 'NEW VERSION.*' + "${headHash:0:8}+U" 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" run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
} }

View File

@ -217,7 +217,7 @@ teardown(){
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
assert_success assert_success
assert_output --partial "0.1.1+1.20.2" 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 run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input --no-converge-checks
assert_success assert_success