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
10 changes: 8 additions & 2 deletions pkg/app/piped/deploysource/sourcecloner.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ func (d *localSourceCloner) RevisionName() string {
}

func (d *localSourceCloner) Clone(ctx context.Context, dest string) error {
_, err := d.repo.Copy(dest)
return err
repo, err := d.repo.Copy(dest)
if err != nil {
return err
}
if err := repo.Checkout(ctx, d.revision); err != nil {
return err
}
return nil
}
10 changes: 8 additions & 2 deletions pkg/app/pipedv1/deploysource/sourcecloner.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ func (d *localSourceCloner) RevisionName() string {
}

func (d *localSourceCloner) Clone(ctx context.Context, dest string) error {
_, err := d.repo.Copy(dest)
return err
repo, err := d.repo.Copy(dest)
if err != nil {
return err
}
if err := repo.Checkout(ctx, d.revision); err != nil {
return err
}
return nil
}
38 changes: 38 additions & 0 deletions pkg/app/pipedv1/plugin/inputs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2024 The PipeCD 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 plugin

import (
"os/exec"

"github.com/pipe-cd/pipecd/pkg/app/pipedv1/deploysource"
"github.com/pipe-cd/pipecd/pkg/git"
"github.com/pipe-cd/pipecd/pkg/plugin/api/v1alpha1/platform"
)

func GetPlanSourceCloner(input *platform.PlanPluginInput) (deploysource.SourceCloner, error) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 I wonder where we should place utilities like this function.
I place it here because all plugins may need this utility, but should I create another package under the plugin package?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got your point, let's find out later. In this PR, place it here is LGTM 👍

gitPath, err := exec.LookPath("git")
if err != nil {
return nil, err
}

cloner := deploysource.NewLocalSourceCloner(
git.NewRepo(input.GetSourceRemoteUrl(), gitPath, input.GetSourceRemoteUrl(), input.GetDeployment().GetGitPath().GetRepo().GetBranch(), nil),
"target",
input.GetDeployment().GetGitPath().GetRepo().GetBranch(),
)

return cloner, nil
}
50 changes: 48 additions & 2 deletions pkg/app/pipedv1/plugin/platform/kubernetes/planner/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,27 @@ package planner
import (
"context"
"fmt"
"io"
"os"
"time"

"github.com/pipe-cd/pipecd/pkg/app/pipedv1/deploysource"
"github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin"
"github.com/pipe-cd/pipecd/pkg/plugin/api/v1alpha1/platform"
"github.com/pipe-cd/pipecd/pkg/regexpool"

"go.uber.org/zap"
"google.golang.org/grpc"
)

type secretDecrypter interface {
Decrypt(string) (string, error)
}

type PlannerService struct {
platform.UnimplementedPlannerServiceServer

Decrypter secretDecrypter
RegexPool *regexpool.Pool
Logger *zap.Logger
}
Expand All @@ -38,8 +48,12 @@ func (a *PlannerService) Register(server *grpc.Server) {
}

// NewPlannerService creates a new planService.
func NewPlannerService(logger *zap.Logger) *PlannerService {
func NewPlannerService(
decrypter secretDecrypter,
logger *zap.Logger,
) *PlannerService {
return &PlannerService{
Decrypter: decrypter,
RegexPool: regexpool.DefaultPool(),
Logger: logger.Named("planner"),
}
Expand All @@ -63,7 +77,39 @@ func (ps *PlannerService) DetermineStrategy(ctx context.Context, in *platform.De
}

func (ps *PlannerService) QuickSyncPlan(ctx context.Context, in *platform.QuickSyncPlanRequest) (*platform.QuickSyncPlanResponse, error) {
return nil, fmt.Errorf("not implemented yet")
now := time.Now()

cloner, err := plugin.GetPlanSourceCloner(in.GetInput())
if err != nil {
return nil, err
}

d, err := os.MkdirTemp("", "") // TODO
if err != nil {
return nil, fmt.Errorf("failed to prepare temporary directory (%w)", err)
}
defer os.RemoveAll(d)
Comment on lines +87 to +91
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Warashi Question:
Should we add workingDir as PlanPluginInput? 🤔 Currently, the planner in the controller uses workingDir passed at the time controller.planner object is created, which mean we have 1:1 for relationship between workingDir & planner which is used to build plan for a deployment
ref:

// Ensure the existence of the working directory for the deployment.
workingDir, err := os.MkdirTemp(c.workspaceDir, d.Id+"-planner-*")
if err != nil {
logger.Error("failed to create working directory for planner", zap.Error(err))
return nil, err
}

message PlanPluginInput {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we make it that way, then the piped.planner object will create the workingDir then clone/pull the deploy source, then we pass the workingDir (which contains cloned source) to the planner plugin via PlanPluginInput.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds nice.
In this PR, we clone the deploy source manually.
However, it's needed for each plugin implementation, so it's simpler if we handle it at piped and only use the prepared source in the plugin.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let address it by another PR 👍


p := deploysource.NewProvider(
d,
cloner,
*in.GetInput().GetDeployment().GetGitPath(),
ps.Decrypter,
)

ds, err := p.GetReadOnly(ctx, io.Discard /* TODO */)
if err != nil {
return nil, err
}

Copy link
Member

@khanhtc1202 khanhtc1202 Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits

	cfg := ds.ApplicationConfig.KubernetesApplicationSpec
	if cfg == nil {
		return nil, fmt.Errorf("missing KubernetesApplicationSpec in application configuration")
	}

Should check spec available to avoid nil pointer 👀

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed it on this commit.
d845f7a

cfg := ds.ApplicationConfig.KubernetesApplicationSpec
if cfg == nil {
return nil, fmt.Errorf("missing KubernetesApplicationSpec in application configuration")
}

return &platform.QuickSyncPlanResponse{
Stages: buildQuickSyncPipeline(*cfg.Input.AutoRollback, now),
}, nil
}

func (ps *PlannerService) PipelineSyncPlan(ctx context.Context, in *platform.PipelineSyncPlanRequest) (*platform.PipelineSyncPlanResponse, error) {
Expand Down
7 changes: 5 additions & 2 deletions pkg/app/pipedv1/plugin/platform/kubernetes/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ func (s *server) run(ctx context.Context, input cli.Input) (runErr error) {
// Start a gRPC server for handling external API requests.
{
var (
service = planner.NewPlannerService(input.Logger)
opts = []rpc.Option{
service = planner.NewPlannerService(
nil, // TODO: Inject the real secret decrypter. It should be a instance of pipedv1/plugin/secrets.Decrypter.
input.Logger,
)
opts = []rpc.Option{
rpc.WithPort(s.apiPort),
rpc.WithGracePeriod(s.gracePeriod),
rpc.WithLogger(input.Logger),
Expand Down
34 changes: 34 additions & 0 deletions pkg/app/pipedv1/plugin/secrets/decrypter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 The PipeCD 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 secrets

import (
"context"
"fmt"

"github.com/pipe-cd/pipecd/pkg/app/pipedv1/cmd/piped/service"
)

type Decrypter struct {
client service.PluginServiceClient
}

func (d *Decrypter) Decrypt(src string) (string, error) {
r, err := d.client.DecryptSecret(context.TODO(), &service.DecryptSecretRequest{Secret: src})
if err != nil {
return "", fmt.Errorf("failed to decrypt secret: %w", err)
}
return r.GetDecryptedSecret(), nil
}