Skip to content

Commit

Permalink
Add migration.PlanExecutor
Browse files Browse the repository at this point in the history
Signed-off-by: Alper Rifat Ulucinar <[email protected]>
  • Loading branch information
ulucinar committed Jul 6, 2023
1 parent 4df4a03 commit 2c01279
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 24 deletions.
91 changes: 69 additions & 22 deletions pkg/migration/fork_executor.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
// Copyright 2023 Upbound Inc.
//
// 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 migration

import (
"fmt"
"os"

"github.com/pkg/errors"
Expand All @@ -10,59 +23,93 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/logging"
)

type ForkExecutor struct {
const (
errForkExecutorNotSupported = "step type should be Exec or step's manualExecution should be non-empty"
errStepFailedFmt = "failed to execute the step %q"
)

// forkExecutor executes Exec steps or steps with the `manualExecution` hints
// by forking processes.
type forkExecutor struct {
executor exec.Interface
logger logging.Logger
cwd string
}

// ForkExecutorOption allows you to configure ForkExecutor objects.
type ForkExecutorOption func(executor *ForkExecutor)
// ForkExecutorOption allows you to configure forkExecutor objects.
type ForkExecutorOption func(executor *forkExecutor)

// WithLogger sets the logger of ForkExecutor.
// WithLogger sets the logger of forkExecutor.
func WithLogger(l logging.Logger) ForkExecutorOption {
return func(e *ForkExecutor) {
return func(e *forkExecutor) {
e.logger = l
}
}

// WithExecutor sets the executor of ForkExecutor.
func WithExecutor(e exec.Interface) ForkExecutorOption {
return func(fe *ForkExecutor) {
return func(fe *forkExecutor) {
fe.executor = e
}
}

// NewForkExecutor returns a new ForkExecutor with executor
func NewForkExecutor(opts ...ForkExecutorOption) *ForkExecutor {
fe := &ForkExecutor{
logger: logging.NewNopLogger(),
// WithWorkingDir sets the current working directory for the executor.
func WithWorkingDir(dir string) ForkExecutorOption {
return func(e *forkExecutor) {
e.cwd = dir
}
}

// NewForkExecutor returns a new fork executor using a process forker.
func NewForkExecutor(opts ...ForkExecutorOption) Executor {
fe := &forkExecutor{
executor: exec.New(),
logger: logging.NewNopLogger(),
}
for _, f := range opts {
f(fe)
}
return fe
}

func (f ForkExecutor) Init(config any) error {
func (f forkExecutor) Init(_ any) error {
return nil
}

func (f ForkExecutor) Step(s Step, ctx any) (any, error) {
if s.Type != StepTypeExec {
return nil, errors.Wrap(NewUnsupportedStepTypeError(s), "expected step type is Exec")
func (f forkExecutor) Step(s Step, _ any) (any, error) {
var cmd exec.Cmd
switch {
case s.Type == StepTypeExec:
return nil, errors.Wrapf(f.exec(f.executor.Command(s.Exec.Command, s.Exec.Args...)), errStepFailedFmt, s.Name)
// TODO: we had better have separate executors to handle the other types of
// steps
case len(s.ManualExecution) != 0:
for _, c := range s.ManualExecution {
cmd = f.executor.Command("sh", "-c", c)
if err := f.exec(cmd); err != nil {
return nil, errors.Wrapf(f.exec(cmd), errStepFailedFmt, s.Name)
}
}
return nil, nil
default:
return nil, errors.Wrap(NewUnsupportedStepTypeError(s), errForkExecutorNotSupported)
}
}

cmd := f.executor.Command(s.Exec.Command, s.Exec.Args...)
func (f forkExecutor) exec(cmd exec.Cmd) error {
cmd.SetEnv(os.Environ())
out, err := cmd.CombinedOutput()
fmt.Println(string(out))
if f.cwd != "" {
cmd.SetDir(f.cwd)
}
buff, err := cmd.CombinedOutput()
logMsg := "Successfully executed command"
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("could not execute step %s", s.Name))
logMsg = "Command execution failed"
}

return nil, nil
f.logger.Info(logMsg, "output", string(buff))
return errors.Wrapf(err, "failed to execute command")
}

func (f ForkExecutor) Destroy() error {
func (f forkExecutor) Destroy() error {
return nil
}
18 changes: 16 additions & 2 deletions pkg/migration/fork_executor_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2023 Upbound Inc.
//
// 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 migration

import (
Expand Down Expand Up @@ -82,15 +96,15 @@ func TestForkExecutorStep(t *testing.T) {
fakeExec: newFakeExec(errorWrongCommand),
},
want: want{
errors.Wrap(errorWrongCommand, "could not execute step wrong-command"),
errors.Wrap(errorWrongCommand, `failed to execute the step "wrong-command": failed to execute command`),
},
},
"WrongStepType": {
args: args{
step: wrongStepType,
},
want: want{
errors.Wrap(NewUnsupportedStepTypeError(wrongStepType), "expected step type is Exec"),
errors.Wrap(NewUnsupportedStepTypeError(wrongStepType), `step type should be Exec or step's manualExecution should be non-empty`),
},
},
}
Expand Down
41 changes: 41 additions & 0 deletions pkg/migration/plan_executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2023 Upbound Inc.
//
// 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 migration

import "github.com/pkg/errors"

type PlanExecutor struct {
executors []Executor
plan Plan
}

// NewPlanExecutor returns a new plan executor for executing the steps
// of a migration plan.
func NewPlanExecutor(plan Plan, executors ...Executor) *PlanExecutor {
return &PlanExecutor{
executors: executors,
plan: plan,
}
}

func (pe *PlanExecutor) Execute() error {
for i, s := range pe.plan.Spec.Steps {
// TODO: support multiple executors when multiple of them are available
if _, err := pe.executors[0].Step(s, nil); err != nil {
return errors.Wrapf(err, "failed to execute step %q at index %d", s.Name, i)
}
}
return nil
}

0 comments on commit 2c01279

Please sign in to comment.