Skip to content

Commit

Permalink
fix: autodeploy flag on okteto up (okteto#4671)
Browse files Browse the repository at this point in the history
* fix: autodeploy flag on okteto up

Signed-off-by: Javier Lopez <[email protected]>

* refactor: address  feedback

Signed-off-by: Javier Lopez <[email protected]>

* fix: golangci

Signed-off-by: Javier Lopez <[email protected]>

* refactor: remove unused parameter from NewDevEnvDeployerManager

Signed-off-by: Javier Lopez <[email protected]>

* fix: e2e test

Signed-off-by: Javier Lopez <[email protected]>

* test: uncomment and enable test cases for okteto context and k8s client error

Signed-off-by: Javier Lopez <[email protected]>

---------

Signed-off-by: Javier Lopez <[email protected]>
  • Loading branch information
jLopezbarb authored Feb 24, 2025
1 parent 21efc74 commit 9f5a1b2
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 75 deletions.
140 changes: 140 additions & 0 deletions cmd/up/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2025 The Okteto Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package up

import (
"context"
"errors"
"fmt"
"time"

"github.com/okteto/okteto/cmd/deploy"
pipelineCMD "github.com/okteto/okteto/cmd/pipeline"
"github.com/okteto/okteto/pkg/analytics"
"github.com/okteto/okteto/pkg/cmd/pipeline"
"github.com/okteto/okteto/pkg/env"
oktetoErrors "github.com/okteto/okteto/pkg/errors"
oktetoLog "github.com/okteto/okteto/pkg/log"
"github.com/okteto/okteto/pkg/log/io"
"github.com/okteto/okteto/pkg/model"
"github.com/okteto/okteto/pkg/okteto"
"github.com/spf13/afero"
"k8s.io/client-go/kubernetes"
)

const (
// oktetoAutoDeployEnvVar if set the application will be deployed while running okteto up
oktetoAutoDeployEnvVar = "OKTETO_AUTODEPLOY"
)

// devEnvDeployerManager deploys the dev environment
type devEnvDeployerManager struct {
isDevEnvDeployed func(ctx context.Context, name, namespace string, c kubernetes.Interface) bool

k8sClientProvider okteto.K8sClientProvider
ioCtrl *io.Controller
getDeployer func(deployParams) (deployer, error)
}

type deployer interface {
Run(ctx context.Context, opts *deploy.Options) error
TrackDeploy(manifest *model.Manifest, runInRemoteFlag bool, startTime time.Time, err error)
}

type deployParams struct {
deployFlag bool
okCtx *okteto.Context
devenvName, ns string
manifestPathFlag, manifestPath string
manifest *model.Manifest
}

// NewDevEnvDeployerManager creates a new DevEnvDeployer
func NewDevEnvDeployerManager(up *upContext, ioCtrl *io.Controller, k8sLogger *io.K8sLogger) *devEnvDeployerManager {
return &devEnvDeployerManager{
ioCtrl: ioCtrl,
k8sClientProvider: up.K8sClientProvider,
isDevEnvDeployed: pipeline.IsDeployed,
getDeployer: func(params deployParams) (deployer, error) {
k8sProvider := okteto.NewK8sClientProviderWithLogger(k8sLogger)
pc, err := pipelineCMD.NewCommand()
if err != nil {
return nil, err
}
c := &deploy.Command{
// reuse the manifest we already have in the upContext so we don't open a file again
GetManifest: func(string, afero.Fs) (*model.Manifest, error) {
return params.manifest, nil
},
GetDeployer: deploy.GetDeployer,
K8sClientProvider: k8sProvider,
Builder: up.builder,
Fs: up.Fs,
CfgMapHandler: deploy.NewConfigmapHandler(k8sProvider, k8sLogger),
PipelineCMD: pc,
DeployWaiter: deploy.NewDeployWaiter(k8sProvider, k8sLogger),
EndpointGetter: deploy.NewEndpointGetter,
AnalyticsTracker: up.analyticsTracker,
IoCtrl: ioCtrl,
}
return c, nil
},
}
}

// DeployIfNeeded deploys the app if it's not already deployed or if the user has set the auto deploy env var or the --deploy flag
func (dd *devEnvDeployerManager) DeployIfNeeded(ctx context.Context, params deployParams, analyticsMeta *analytics.UpMetricsMetadata) error {
if !params.okCtx.IsOkteto {
dd.ioCtrl.Logger().Infof("Deploy is skipped because is not okteto context")
return nil
}
k8sClient, _, err := dd.k8sClientProvider.Provide(params.okCtx.Cfg)
if err != nil {
return err
}

mustDeploy := params.deployFlag
if env.LoadBoolean(oktetoAutoDeployEnvVar) {
mustDeploy = true
}

if mustDeploy || !dd.isDevEnvDeployed(ctx, params.devenvName, params.ns, k8sClient) {
deployer, err := dd.getDeployer(params)
if err != nil {
dd.ioCtrl.Logger().Infof("failed to create deployer: %s", err)
return fmt.Errorf("failed to create deployer: %w", err)
}

deployOpts := &deploy.Options{
Name: params.devenvName,
Namespace: params.ns,
ManifestPathFlag: params.manifestPathFlag,
ManifestPath: params.manifestPath,
Timeout: 5 * time.Minute,
NoBuild: false,
}
startTime := time.Now()
err = deployer.Run(ctx, deployOpts)
go analyticsMeta.HasRunDeploy()
deployer.TrackDeploy(params.manifest, deploy.ShouldRunInRemote(deployOpts), startTime, err)
// only allow error.ErrManifestFoundButNoDeployAndDependenciesCommands to go forward - autocreate property will deploy the app
if err != nil && !errors.Is(err, oktetoErrors.ErrManifestFoundButNoDeployAndDependenciesCommands) {
return err
}

} else {
oktetoLog.Information("'%s' was already deployed. To redeploy run 'okteto deploy' or 'okteto up --deploy'", params.devenvName)
}
return nil
}
132 changes: 132 additions & 0 deletions cmd/up/deploy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2025 The Okteto Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package up

import (
"context"
"testing"
"time"

"github.com/okteto/okteto/cmd/deploy"
"github.com/okteto/okteto/internal/test"
"github.com/okteto/okteto/pkg/analytics"
oktetoErrors "github.com/okteto/okteto/pkg/errors"
"github.com/okteto/okteto/pkg/log/io"
"github.com/okteto/okteto/pkg/model"
"github.com/okteto/okteto/pkg/okteto"
"github.com/stretchr/testify/assert"
"k8s.io/client-go/kubernetes"
)

type fakeDeployer struct {
deployed bool
err error
tracked bool
}

// Deploy implements the devEnvEnvDeployStrategy interface
func (f *fakeDeployer) Run(context.Context, *deploy.Options) error {
f.deployed = true
return f.err
}

func (f *fakeDeployer) TrackDeploy(*model.Manifest, bool, time.Time, error) {
f.tracked = true
}

func TestDeployIfNeeded(t *testing.T) {
type input struct {
isOkteto bool
mustDeploy bool
isDeployedApp bool
k8sClientProviderErr error
deployErr error
}
type expected struct {
isDeploymentExpected bool
expectedErr error
}
tests := []struct {
name string
input input
expected expected
}{
{
name: "not okteto context",
input: input{isOkteto: false},
expected: expected{isDeploymentExpected: false, expectedErr: nil},
},
{
name: "error creating k8s client",
input: input{isOkteto: true, k8sClientProviderErr: assert.AnError},
expected: expected{isDeploymentExpected: false, expectedErr: assert.AnError},
},
{
name: "must deploy: true // isDeployedApp: false",
input: input{isOkteto: true, k8sClientProviderErr: nil, mustDeploy: true},
expected: expected{isDeploymentExpected: true, expectedErr: nil},
},
{
name: "must deploy: true // isDeployedApp: true",
input: input{isOkteto: true, k8sClientProviderErr: nil, mustDeploy: true, isDeployedApp: true},
expected: expected{isDeploymentExpected: true, expectedErr: nil},
},
{
name: "must deploy: false // isDeployedApp: true",
input: input{isOkteto: true, k8sClientProviderErr: nil, mustDeploy: false, isDeployedApp: true},
expected: expected{isDeploymentExpected: false, expectedErr: nil},
},
{
name: "must deploy: false // isDeployedApp: false",
input: input{isOkteto: true, k8sClientProviderErr: nil, mustDeploy: false},
expected: expected{isDeploymentExpected: true, expectedErr: nil},
},
{
name: "deploy but error deploying",
input: input{isOkteto: true, mustDeploy: false, deployErr: assert.AnError},
expected: expected{isDeploymentExpected: true, expectedErr: assert.AnError},
},
{
name: "must deploy: false // isDeployedApp: false",
input: input{isOkteto: true, mustDeploy: true, deployErr: oktetoErrors.ErrManifestFoundButNoDeployAndDependenciesCommands},
expected: expected{isDeploymentExpected: true, expectedErr: nil},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeDeployer := &fakeDeployer{err: tt.input.deployErr}
deployer := &devEnvDeployerManager{
ioCtrl: io.NewIOController(),
k8sClientProvider: &test.FakeK8sProvider{
ErrProvide: tt.input.k8sClientProviderErr,
},
isDevEnvDeployed: func(ctx context.Context, name, namespace string, c kubernetes.Interface) bool {
return tt.input.isDeployedApp
},
getDeployer: func(params deployParams) (deployer, error) {
return fakeDeployer, nil
},
}
deployParams := deployParams{
deployFlag: tt.input.mustDeploy,
okCtx: &okteto.Context{IsOkteto: tt.input.isOkteto},
}
err := deployer.DeployIfNeeded(context.Background(), deployParams, &analytics.UpMetricsMetadata{})
assert.ErrorIs(t, tt.expected.expectedErr, err)
assert.Equal(t, tt.expected.isDeploymentExpected, fakeDeployer.deployed)
assert.Equal(t, tt.expected.isDeploymentExpected, fakeDeployer.tracked)
})
}

}
84 changes: 12 additions & 72 deletions cmd/up/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,9 @@ import (
oargs "github.com/okteto/okteto/cmd/args"
buildv2 "github.com/okteto/okteto/cmd/build/v2"
contextCMD "github.com/okteto/okteto/cmd/context"
"github.com/okteto/okteto/cmd/deploy"
"github.com/okteto/okteto/cmd/namespace"
pipelineCMD "github.com/okteto/okteto/cmd/pipeline"
"github.com/okteto/okteto/cmd/utils"
"github.com/okteto/okteto/pkg/analytics"
"github.com/okteto/okteto/pkg/cmd/pipeline"
"github.com/okteto/okteto/pkg/config"
"github.com/okteto/okteto/pkg/constants"
"github.com/okteto/okteto/pkg/devenvironment"
Expand Down Expand Up @@ -256,15 +253,18 @@ okteto up api -- echo this is a test
return fmt.Errorf("failed to load k8s client: %w", err)
}

if okteto.GetContext().IsOkteto && upOptions.Deploy || !pipeline.IsDeployed(ctx, up.Manifest.Name, up.Namespace, k8sClient) {
err := up.deployApp(ctx, ioCtrl, k8sLogger)

// only allow error.ErrManifestFoundButNoDeployAndDependenciesCommands to go forward - autocreate property will deploy the app
if err != nil && !errors.Is(err, oktetoErrors.ErrManifestFoundButNoDeployAndDependenciesCommands) {
return err
}
} else if okteto.GetContext().IsOkteto && !upOptions.Deploy && pipeline.IsDeployed(ctx, up.Manifest.Name, okteto.GetContext().Namespace, k8sClient) {
oktetoLog.Information("'%s' was already deployed. To redeploy run 'okteto deploy' or 'okteto up --deploy'", up.Manifest.Name)
devEnvDeployer := NewDevEnvDeployerManager(up, ioCtrl, k8sLogger)
deployParams := deployParams{
deployFlag: upOptions.Deploy,
okCtx: okteto.GetContext(),
devenvName: up.Manifest.Name,
ns: okteto.GetContext().Namespace,
manifestPathFlag: upOptions.ManifestPathFlag,
manifestPath: upOptions.ManifestPath,
manifest: oktetoManifest,
}
if err := devEnvDeployer.DeployIfNeeded(ctx, deployParams, up.analyticsMeta); err != nil {
return err
}

devCommandParser := oargs.NewDevCommandArgParser(oargs.NewManifestDevLister(), ioCtrl, false)
Expand Down Expand Up @@ -348,10 +348,6 @@ okteto up api -- echo this is a test
return err
}

if _, ok := os.LookupEnv(model.OktetoAutoDeployEnvVar); ok {
upOptions.Deploy = true
}

if err = up.start(); err != nil {
switch err.(type) {
default:
Expand Down Expand Up @@ -467,62 +463,6 @@ func getOverridedEnvVarsFromCmd(manifestEnvVars env.Environment, commandEnvVaria
return &overridedEnvVars, nil
}

func (up *upContext) deployApp(ctx context.Context, ioCtrl *io.Controller, k8slogger *io.K8sLogger) error {
k8sProvider := okteto.NewK8sClientProviderWithLogger(k8slogger)
pc, err := pipelineCMD.NewCommand()
if err != nil {
return err
}
c := &deploy.Command{
GetManifest: model.GetManifestV2,
GetDeployer: deploy.GetDeployer,
K8sClientProvider: k8sProvider,
Builder: up.builder,
Fs: up.Fs,
CfgMapHandler: deploy.NewConfigmapHandler(k8sProvider, k8slogger),
PipelineCMD: pc,
DeployWaiter: deploy.NewDeployWaiter(k8sProvider, k8slogger),
EndpointGetter: deploy.NewEndpointGetter,
AnalyticsTracker: up.analyticsTracker,
IoCtrl: ioCtrl,
}

startTime := time.Now()
err = c.Run(ctx, &deploy.Options{
Name: up.Manifest.Name,
Namespace: up.Namespace,
ManifestPathFlag: up.Options.ManifestPathFlag,
ManifestPath: up.Options.ManifestPath,
Timeout: 5 * time.Minute,
NoBuild: false,
})
up.analyticsMeta.HasRunDeploy()

isRemote := false
if up.Manifest.Deploy != nil {
isRemote = up.Manifest.Deploy.Image != ""
}

// We keep DeprecatedOktetoCurrentDeployBelongsToPreviewEnvVar for backward compatibility in case an old version of the backend
// is being used
isPreview := os.Getenv(model.DeprecatedOktetoCurrentDeployBelongsToPreviewEnvVar) == "true" ||
os.Getenv(constants.OktetoIsPreviewEnvVar) == "true"
// tracking deploy either its been successful or not
c.AnalyticsTracker.TrackDeploy(analytics.DeployMetadata{
Success: err == nil,
IsOktetoRepo: utils.IsOktetoRepo(),
Duration: time.Since(startTime),
PipelineType: up.Manifest.Type,
DeployType: "automatic",
IsPreview: isPreview,
HasDependenciesSection: up.Manifest.HasDependenciesSection(),
HasBuildSection: up.Manifest.HasBuildSection(),
Err: err,
IsRemote: isRemote,
})
return err
}

func (up *upContext) start() error {
up.pidController = newPIDController(up.Namespace, up.Dev.Name)

Expand Down
3 changes: 0 additions & 3 deletions pkg/model/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,6 @@ const (
// OktetoSkipContextTestEnvVar if set skips the context test
OktetoSkipContextTestEnvVar = "OKTETO_SKIP_CONTEXT_TEST"

// OktetoAutoDeployEnvVar if set the application will be deployed while running okteto up
OktetoAutoDeployEnvVar = "OKTETO_AUTODEPLOY"

// OktetoAppsSubdomainEnvVar defines which is the subdomain for urls
OktetoAppsSubdomainEnvVar = "OKTETO_APPS_SUBDOMAIN"

Expand Down

0 comments on commit 9f5a1b2

Please sign in to comment.