Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion cli/cmd/release_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ func (r *runners) InitReleaseCreate(parent *cobra.Command) error {
cmd.Flags().BoolVar(&r.args.createReleaseAutoDefaults, "auto", false, "generate default values for use in CI")
cmd.Flags().BoolVarP(&r.args.createReleaseAutoDefaultsAccept, "confirm-auto", "y", false, "auto-accept the configuration generated by the --auto flag")

// output format
cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table")

// not supported for KOTS
cmd.Flags().MarkHidden("required")
cmd.Flags().MarkHidden("yaml-file")
Expand Down Expand Up @@ -160,6 +163,10 @@ func (r *runners) releaseCreate(cmd *cobra.Command, args []string) (err error) {
}()

log := logger.NewLogger(r.w)
if r.outputFormat == "json" {
// suppress log lines for machine-readable output
log.Silence()
}

if !r.hasApp() {
return errors.New("no app specified")
Expand Down Expand Up @@ -283,7 +290,21 @@ Prepared to create release with defaults:
}
log.FinishSpinner()

log.ChildActionWithoutSpinner("SEQUENCE: %d", release.Sequence)
if r.outputFormat == "json" {
type createReleaseOutput struct {
Sequence int64 `json:"sequence"`
AppID string `json:"appId,omitempty"`
Charts []types.Chart `json:"charts,omitempty"`
}
out := createReleaseOutput{Sequence: release.Sequence, AppID: release.AppID, Charts: release.Charts}
enc := json.NewEncoder(r.w)
enc.SetIndent("", " ")
if err := enc.Encode(out); err != nil {
return errors.Wrap(err, "encode json output")
}
} else {
log.ChildActionWithoutSpinner("SEQUENCE: %d", release.Sequence)
}

if promoteChanID != "" {
log.ActionWithSpinner("Promoting")
Expand Down
2 changes: 2 additions & 0 deletions pkg/integration/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ func TestAPI(t *testing.T) {
out, err := cmd.CombinedOutput()

if tt.wantError != "" {
assert.Regexp(t, `^Error:`, string(out))
assert.Contains(t, string(out), tt.wantError)
return
} else {
assert.NoError(t, err)
}
Expand Down
116 changes: 116 additions & 0 deletions pkg/integration/release_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package integration

import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"

"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestReleaseCreate(t *testing.T) {
tests := []struct {
name string
cliArgs []string
wantFormat format
wantLines int
wantOutput string
setup func(t *testing.T) *httptest.Server
}{
{
name: "release create table",
cliArgs: []string{"release", "create"},
wantFormat: FormatTable,
wantLines: 2, // Creating Release ✓ + SEQUENCE line
setup: func(t *testing.T) *httptest.Server {
r := mux.NewRouter()

// List apps used by GetAppType("test-app") path; our CLI resolves app via API
r.Methods(http.MethodGet).Path("/v1/apps").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`[{"app":{"id":"app-123","name":"test-app","slug":"test-app","scheduler":"native"}}]`))
})

// Create release
r.Methods(http.MethodPost).Path("/v1/app/app-123/release").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
w.Write([]byte(`{"appId":"app-123","sequence":42}`))
})

// Update release YAML after creation
r.Methods(http.MethodPut).Path("/v1/app/app-123/42/raw").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
assert.NoError(t, err)
assert.Contains(t, string(body), "apiVersion:")
w.WriteHeader(http.StatusOK)
})

return httptest.NewServer(r)
},
},
{
name: "release create json",
cliArgs: []string{"release", "create", "--output", "json"},
wantFormat: FormatJSON,
wantLines: 0,
wantOutput: `{
"sequence": 43,
"appId": "app-123"
}
`,
setup: func(t *testing.T) *httptest.Server {
r := mux.NewRouter()

r.Methods(http.MethodGet).Path("/v1/apps").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`[{"app":{"id":"app-123","name":"test-app","slug":"test-app","scheduler":"native"}}]`))
})

r.Methods(http.MethodPost).Path("/v1/app/app-123/release").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
w.Write([]byte(`{"appId":"app-123","sequence":43}`))
})

r.Methods(http.MethodPut).Path("/v1/app/app-123/43/raw").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})

return httptest.NewServer(r)
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := tt.setup(t)
defer server.Close()

// Create a temporary yaml-dir with a simple manifest and pass it
tempDir := t.TempDir()
manifest := "apiVersion: kots.io/v1beta1\nkind: Application\nmetadata:\n name: test-app\nspec:\n title: Test App\n"
require.NoError(t, os.WriteFile(filepath.Join(tempDir, "app.yaml"), []byte(manifest), 0644))
// Append flag at the end to avoid changing per-test cliArgs above
tt.cliArgs = append(tt.cliArgs, "--yaml-file", filepath.Join(tempDir, "app.yaml"))

// Set REPLICATED_APP to allow resolving the app
cmd := getCommand(tt.cliArgs, server)
cmd.Env = append(cmd.Env, "REPLICATED_APP=test-app")
cmd.Env = append(cmd.Env, "HOME="+tempDir)

out, err := cmd.CombinedOutput()
assert.NoError(t, err)

if tt.wantOutput != "" {
require.Equal(t, tt.wantOutput, string(out))
return
}

AssertCLIOutput(t, string(out), tt.wantFormat, tt.wantLines)
})
}
}
5 changes: 5 additions & 0 deletions pkg/integration/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"os/exec"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func getCommand(cliArgs []string, server *httptest.Server) *exec.Cmd {
Expand Down Expand Up @@ -46,6 +48,9 @@ func AssertCLIOutput(t *testing.T, got string, wantFormat format, wantLines int)
t.Errorf("got %d lines, want %d:\n%s", len(gotLines), wantLines, got)
}
}

// require that the output does not start with "Error:"
assert.NotRegexp(t, `^Error:`, got)
}

func AssertAPIRequests(t *testing.T, wantAPIRequests []string, apiCallLogFilename string) {
Expand Down