Skip to content
Draft
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
39 changes: 9 additions & 30 deletions cmd/kosli/createEnvironment.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package main

import (
"context"
"io"
"net/http"
"net/url"

"github.com/kosli-dev/cli/internal/requests"
"github.com/kosli-dev/cli/internal/kosli"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -50,21 +49,12 @@ kosli create environment yourLogicalEnvironmentName
`

type createEnvOptions struct {
payload CreateEnvironmentPayload
payload kosli.CreateOrUpdateEnvironmentRequest
excludeScaling bool
includeScaling bool
requireProvenance bool
}

type CreateEnvironmentPayload struct {
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
IncludeScaling *bool `json:"include_scaling,omitempty"`
RequireProvenance *bool `json:"require_provenance,omitempty"`
IncludedEnvironments []string `json:"included_environments,omitempty"`
}

func newCreateEnvironmentCmd(out io.Writer) *cobra.Command {
o := new(createEnvOptions)
cmd := &cobra.Command{
Expand All @@ -86,7 +76,7 @@ func newCreateEnvironmentCmd(out io.Writer) *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return o.run(args)
return o.run(cmd.Context(), args)
},
}

Expand Down Expand Up @@ -117,34 +107,23 @@ func newCreateEnvironmentCmd(out io.Writer) *cobra.Command {
return cmd
}

func (o *createEnvOptions) run(args []string) error {
func (o *createEnvOptions) run(ctx context.Context, args []string) error {
o.payload.Name = args[0]
url, err := url.JoinPath(global.Host, "api/v2/environments", global.Org)
if err != nil {
return err
}

if o.includeScaling {
var myTrue = true
o.payload.IncludeScaling = &myTrue
o.payload.IncludeScaling = &myTrue //nolint:staticcheck // set only by the deprecated scaling flags
}
if o.excludeScaling {
var myFalse = false
o.payload.IncludeScaling = &myFalse
o.payload.IncludeScaling = &myFalse //nolint:staticcheck // set only by the deprecated scaling flags
}
if o.requireProvenance {
var myTrue = true
o.payload.RequireProvenance = &myTrue
o.payload.RequireProvenance = &myTrue //nolint:staticcheck // set only by the deprecated --require-provenance flag
}

reqParams := &requests.RequestParams{
Method: http.MethodPut,
URL: url,
Payload: o.payload,
DryRun: global.DryRun,
Token: global.ApiToken,
}
_, err = kosliClient.Do(reqParams)
_, err := kosliAPI().CreateOrUpdateEnvironment(ctx, o.payload)
if err == nil && !global.DryRun {
logger.Info("environment %s was created", o.payload.Name)
}
Expand Down
22 changes: 5 additions & 17 deletions cmd/kosli/getEnvironment.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"

"github.com/kosli-dev/cli/internal/output"
"github.com/kosli-dev/cli/internal/requests"
"github.com/spf13/cobra"
)

Expand All @@ -34,7 +32,7 @@ func newGetEnvironmentCmd(out io.Writer) *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return o.run(out, args)
return o.run(cmd.Context(), out, args)
},
}

Expand All @@ -43,23 +41,13 @@ func newGetEnvironmentCmd(out io.Writer) *cobra.Command {
return cmd
}

func (o *getEnvironmentOptions) run(out io.Writer, args []string) error {
url, err := url.JoinPath(global.Host, "api/v2/environments", global.Org, args[0])
func (o *getEnvironmentOptions) run(ctx context.Context, out io.Writer, args []string) error {
environment, err := kosliAPI().GetEnvironment(ctx, args[0])
if err != nil {
return err
}

reqParams := &requests.RequestParams{
Method: http.MethodGet,
URL: url,
Token: global.ApiToken,
}
response, err := kosliClient.Do(reqParams)
if err != nil {
return err
}

return output.FormattedPrint(response.Body, o.output, out, 0,
return output.FormattedPrint(string(environment.Raw), o.output, out, 0,
map[string]output.FormatOutputFunc{
"table": printEnvironmentAsTable,
"json": output.PrintJson,
Expand Down
3 changes: 2 additions & 1 deletion cmd/kosli/openapiContract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"testing"

"github.com/kosli-dev/cli/internal/kosli"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
Expand Down Expand Up @@ -66,7 +67,7 @@ func (suite *OpenAPIContractTestSuite) SetupSuite() {
func (suite *OpenAPIContractTestSuite) TestPayloadsMatchSchema() {
registry := []driftCase{
{name: "create control", payload: ControlPayload{}, component: "ControlPostInput"},
{name: "create environment", payload: CreateEnvironmentPayload{}, component: "CreateEnvironmentPutInput"},
{name: "create environment", payload: kosli.CreateOrUpdateEnvironmentRequest{}, component: "CreateEnvironmentPutInput"},
}

for _, c := range registry {
Expand Down
58 changes: 16 additions & 42 deletions cmd/kosli/snapshotAutoEnvironment.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package main

import (
"encoding/json"
"errors"
"context"
"fmt"
"net/http"
"net/url"
"strings"

"github.com/kosli-dev/cli/internal/requests"
"github.com/kosli-dev/cli/internal/kosli"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
Expand Down Expand Up @@ -60,7 +57,7 @@ func addAutoEnvironmentFlags(cmd *cobra.Command) {
// reported, when --auto-environment is set. It is a no-op when the flag is not
// set or when the environment already exists with a matching type. envType is
// inferred from the snapshot subcommand (e.g. "docker", "K8S").
func ensureEnvironment(envName, envType string) error {
func ensureEnvironment(ctx context.Context, envName, envType string) error {
o := snapshotAutoEnv

// Enforce the scaling-flag mutual exclusion unconditionally, mirroring
Expand All @@ -84,7 +81,7 @@ func ensureEnvironment(envName, envType string) error {
return nil
}

exists, existingType, err := getEnvironmentTypeIfExists(envName)
exists, existingType, err := getEnvironmentTypeIfExists(ctx, envName)
if err != nil {
return err
}
Expand All @@ -101,82 +98,59 @@ func ensureEnvironment(envName, envType string) error {
return nil
}

return createEnvironmentForSnapshot(envName, envType)
return createEnvironmentForSnapshot(ctx, envName, envType)
}

// getEnvironmentTypeIfExists fetches the environment's metadata and returns
// whether it exists along with its type. Only a genuine HTTP 404 is treated as
// "does not exist" (exists=false, nil error); any other error (auth, network,
// 5xx after retries) is returned to the caller so it is never mistaken for a
// missing environment.
func getEnvironmentTypeIfExists(envName string) (bool, string, error) {
reqURL, err := url.JoinPath(global.Host, "api/v2/environments", global.Org, envName)
if err != nil {
return false, "", err
}

reqParams := &requests.RequestParams{
Method: http.MethodGet,
URL: reqURL,
Token: global.ApiToken,
}
response, err := kosliClient.Do(reqParams)
func getEnvironmentTypeIfExists(ctx context.Context, envName string) (bool, string, error) {
response, err := kosliAPI().GetEnvironment(ctx, envName)
if err != nil {
// Only a genuine 404 means the environment does not exist yet. Any other
// error (auth, network, 5xx after retries) is propagated so we never fall
// through to a create-or-update PUT that could overwrite an existing
// environment or bypass the type/logical safety checks.
var apiErr *requests.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
if kosli.IsNotFound(err) {
logger.Debug("environment %s does not exist yet", envName)
return false, "", nil
}
return false, "", err
}

var env map[string]any
if err := json.Unmarshal([]byte(response.Body), &env); err != nil {
environment, err := response.Value()
if err != nil {
return false, "", err
}
envType, _ := env["type"].(string)
return true, envType, nil
return true, environment.Type, nil
}

// createEnvironmentForSnapshot creates a physical environment of the inferred
// type, applying the optional --environment-description and scaling flags. It
// reuses the same payload and endpoint as `kosli create environment`.
func createEnvironmentForSnapshot(envName, envType string) error {
reqURL, err := url.JoinPath(global.Host, "api/v2/environments", global.Org)
if err != nil {
return err
}

func createEnvironmentForSnapshot(ctx context.Context, envName, envType string) error {
description := snapshotAutoEnv.description
if description == "" {
description = defaultAutoEnvDescription
}

payload := CreateEnvironmentPayload{
payload := kosli.CreateOrUpdateEnvironmentRequest{
Name: envName,
Type: envType,
Description: description,
}
if snapshotAutoEnv.includeScaling {
myTrue := true
payload.IncludeScaling = &myTrue
payload.IncludeScaling = &myTrue //nolint:staticcheck // set only by the deprecated scaling flags
}
if snapshotAutoEnv.excludeScaling {
myFalse := false
payload.IncludeScaling = &myFalse
payload.IncludeScaling = &myFalse //nolint:staticcheck // set only by the deprecated scaling flags
}

reqParams := &requests.RequestParams{
Method: http.MethodPut,
URL: reqURL,
Payload: payload,
Token: global.ApiToken,
}
if _, err := kosliClient.Do(reqParams); err != nil {
if _, err := kosliAPI().CreateOrUpdateEnvironment(ctx, payload); err != nil {
return fmt.Errorf("failed to auto-create environment %s: %v", envName, err)
}
logger.Info("environment %s was created", envName)
Expand Down
6 changes: 4 additions & 2 deletions cmd/kosli/snapshotAutoEnvironment_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package main

import (
"context"
"fmt"
"os"
"testing"

"github.com/kosli-dev/cli/internal/kosli"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
Expand Down Expand Up @@ -53,14 +55,14 @@ func (suite *SnapshotAutoEnvironmentTestSuite) SetupSuite() {
func (suite *SnapshotAutoEnvironmentTestSuite) createLogicalEnv(name string, included []string) {
suite.T().Helper()
o := &createEnvOptions{
payload: CreateEnvironmentPayload{
payload: kosli.CreateOrUpdateEnvironmentRequest{
Name: name,
Type: "logical",
Description: "test logical env",
IncludedEnvironments: included,
},
}
require.NoError(suite.T(), o.run([]string{name}), "logical env should be created without error")
require.NoError(suite.T(), o.run(context.Background(), []string{name}), "logical env should be created without error")
}

func (suite *SnapshotAutoEnvironmentTestSuite) TestSnapshotAutoEnvironment() {
Expand Down
7 changes: 4 additions & 3 deletions cmd/kosli/snapshotAzureApps.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"fmt"
"io"

Expand Down Expand Up @@ -99,7 +100,7 @@ func newSnapshotAzureAppsCmd(out io.Writer) *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return o.run(args)
return o.run(cmd.Context(), args)
},
}

Expand All @@ -123,9 +124,9 @@ func newSnapshotAzureAppsCmd(out io.Writer) *cobra.Command {
return cmd
}

func (o *snapshotAzureAppsOptions) run(args []string) error {
func (o *snapshotAzureAppsOptions) run(ctx context.Context, args []string) error {
envName := args[0]
return newSnapshotReporter().report(envName, "azure-apps", func() (any, string, error) {
return newSnapshotReporter().report(ctx, envName, "azure-apps", func() (any, string, error) {
webAppsData, err := o.azureStaticCredentials.GetAzureAppsData(logger)
if err != nil {
return nil, "", err
Expand Down
6 changes: 3 additions & 3 deletions cmd/kosli/snapshotCloudRun.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func newSnapshotCloudRunCmd(out io.Writer) *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return o.run(args)
return o.run(cmd.Context(), args)
},
}

Expand All @@ -144,9 +144,9 @@ func newSnapshotCloudRunCmd(out io.Writer) *cobra.Command {
return cmd
}

func (o *snapshotCloudRunOptions) run(args []string) error {
func (o *snapshotCloudRunOptions) run(ctx context.Context, args []string) error {
envName := args[0]
return newSnapshotReporter().report(envName, "cloud-run", func() (any, string, error) {
return newSnapshotReporter().report(ctx, envName, "cloud-run", func() (any, string, error) {
// compile the filter patterns once, instead of once per service and job
compiledFilter := o.resourceFilter.Compile()

Expand Down
6 changes: 3 additions & 3 deletions cmd/kosli/snapshotDocker.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ func newSnapshotDockerCmd(out io.Writer) *cobra.Command {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return o.run(args)
return o.run(cmd.Context(), args)
},
}
addDryRunFlag(cmd)
return cmd
}

func (o *snapshotDockerOptions) run(args []string) error {
func (o *snapshotDockerOptions) run(ctx context.Context, args []string) error {
envName := args[0]
return newSnapshotReporter().report(envName, "docker", func() (any, string, error) {
return newSnapshotReporter().report(ctx, envName, "docker", func() (any, string, error) {
artifacts, err := CreateDockerArtifactsData()
if err != nil {
return nil, "", err
Expand Down
Loading
Loading