Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 3 additions & 3 deletions pkg/app/api/grpcapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,17 +466,17 @@ func (a *API) GetPlanPreviewResults(ctx context.Context, req *apiservice.GetPlan
}
}

results := make([]*model.ApplicationPlanPreviewResult, 0, len(req.Commands))
results := make([]*model.PlanPreviewCommandResult, 0, len(req.Commands))

// Fetch ouput data to build results.
for _, commandID := range req.Commands {
data, err := a.commandOutputGetter.Get(ctx, commandID)
if err != nil {
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to retrieve output data of command %s", commandID))
}
var result model.ApplicationPlanPreviewResult
var result model.PlanPreviewCommandResult
if err := json.Unmarshal(data, &result); err != nil {
a.logger.Error("failed to unmarshal applicaiton plan preview result",
a.logger.Error("failed to unmarshal planpreview command result",
zap.String("command", commandID),
zap.Error(err),
)
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/api/service/apiservice/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,5 @@ message GetPlanPreviewResultsRequest {
}

message GetPlanPreviewResultsResponse {
repeated pipe.model.ApplicationPlanPreviewResult results = 1;
repeated pipe.model.PlanPreviewCommandResult results = 1;
}
14 changes: 8 additions & 6 deletions pkg/app/pipectl/cmd/planpreview/planpreview.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package planpreview
import (
"context"
"fmt"
"io"
"time"

"github.com/spf13/cobra"
Expand All @@ -35,11 +34,11 @@ type command struct {
headBranch string
headCommit string
baseBranch string
out string
timeout time.Duration
checkInterval time.Duration

clientOptions *client.Options
stdout io.Writer
}

func NewCommand() *cobra.Command {
Expand All @@ -60,11 +59,13 @@ func NewCommand() *cobra.Command {
cmd.Flags().StringVar(&c.headBranch, "head-branch", c.headBranch, "The head branch of the change.")
cmd.Flags().StringVar(&c.headCommit, "head-commit", c.headCommit, "The SHA of the head commit.")
cmd.Flags().StringVar(&c.baseBranch, "base-branch", c.baseBranch, "The base branch of the change.")
cmd.Flags().StringVar(&c.out, "out", c.out, "Write planpreview result to the given path.")

cmd.MarkFlagRequired("repo-remote-url")
cmd.MarkFlagRequired("head-branch")
cmd.MarkFlagRequired("head-commit")
cmd.MarkFlagRequired("base-branch")
cmd.MarkFlagRequired("out")

return cmd
}
Expand All @@ -88,18 +89,18 @@ func (c *command) run(ctx context.Context, _ cli.Telemetry) error {

resp, err := cli.RequestPlanPreview(ctx, req)
if err != nil {
fmt.Fprintf(c.stdout, "Failed to request plan preview: %v", err)
fmt.Printf("Failed to request plan preview: %v\n", err)
return err
}

getResults := func(commands []string) ([]*model.ApplicationPlanPreviewResult, error) {
getResults := func(commands []string) ([]*model.PlanPreviewCommandResult, error) {
req := &apiservice.GetPlanPreviewResultsRequest{
Commands: commands,
}

resp, err := cli.GetPlanPreviewResults(ctx, req)
if err != nil {
fmt.Fprintf(c.stdout, "Failed to get plan preview results: %v", err)
fmt.Printf("Failed to get plan preview results: %v", err)
return nil, err
}

Expand Down Expand Up @@ -129,7 +130,8 @@ func (c *command) run(ctx context.Context, _ cli.Telemetry) error {
return nil
}

func (c *command) printResults(results []*model.ApplicationPlanPreviewResult) error {
func (c *command) printResults(results []*model.PlanPreviewCommandResult) error {
// TODO: Format preview results and support writing the result into file.
fmt.Println(results)
return nil
}
20 changes: 20 additions & 0 deletions pkg/app/piped/apistore/commandstore/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Lister interface {
ListApplicationCommands() []model.ReportableCommand
ListDeploymentCommands() []model.ReportableCommand
ListStageCommands(deploymentID, stageID string) []model.ReportableCommand
ListBuildPlanPreviewCommands() []model.ReportableCommand
}

type store struct {
Expand All @@ -52,6 +53,7 @@ type store struct {
applicationCommands []model.ReportableCommand
deploymentCommands []model.ReportableCommand
stageCommands []model.ReportableCommand
planPreviewCommands []model.ReportableCommand
handledCommands map[string]time.Time
mu sync.RWMutex
gracePeriod time.Duration
Expand Down Expand Up @@ -116,6 +118,7 @@ func (s *store) sync(ctx context.Context) error {
applicationCommands = make([]model.ReportableCommand, 0)
deploymentCommands = make([]model.ReportableCommand, 0)
stageCommands = make([]model.ReportableCommand, 0)
planPreviewCommands = make([]model.ReportableCommand, 0)
)
for _, cmd := range resp.Commands {
switch cmd.Type {
Expand All @@ -125,13 +128,16 @@ func (s *store) sync(ctx context.Context) error {
deploymentCommands = append(deploymentCommands, s.makeReportableCommand(cmd))
case model.Command_APPROVE_STAGE:
stageCommands = append(stageCommands, s.makeReportableCommand(cmd))
case model.Command_BUILD_PLAN_PREVIEW:
planPreviewCommands = append(planPreviewCommands, s.makeReportableCommand(cmd))
}
}

s.mu.Lock()
s.applicationCommands = applicationCommands
s.deploymentCommands = deploymentCommands
s.stageCommands = stageCommands
s.planPreviewCommands = planPreviewCommands
s.mu.Unlock()

return nil
Expand Down Expand Up @@ -199,6 +205,20 @@ func (s *store) ListStageCommands(deploymentID, stageID string) []model.Reportab
return commands
}

func (s *store) ListBuildPlanPreviewCommands() []model.ReportableCommand {
s.mu.RLock()
defer s.mu.RUnlock()

commands := make([]model.ReportableCommand, 0, len(s.planPreviewCommands))
for _, cmd := range s.planPreviewCommands {
if _, ok := s.handledCommands[cmd.Id]; ok {
continue
}
commands = append(commands, cmd)
}
return commands
}

func (s *store) makeReportableCommand(c *model.Command) model.ReportableCommand {
return model.ReportableCommand{
Command: c,
Expand Down
1 change: 1 addition & 0 deletions pkg/app/piped/cmd/piped/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ go_library(
"//pkg/app/piped/livestatestore:go_default_library",
"//pkg/app/piped/notifier:go_default_library",
"//pkg/app/piped/planner/registry:go_default_library",
"//pkg/app/piped/planpreview:go_default_library",
"//pkg/app/piped/statsreporter:go_default_library",
"//pkg/app/piped/toolregistry:go_default_library",
"//pkg/app/piped/trigger:go_default_library",
Expand Down
17 changes: 17 additions & 0 deletions pkg/app/piped/cmd/piped/piped.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"github.com/pipe-cd/pipe/pkg/app/piped/livestatereporter"
"github.com/pipe-cd/pipe/pkg/app/piped/livestatestore"
"github.com/pipe-cd/pipe/pkg/app/piped/notifier"
"github.com/pipe-cd/pipe/pkg/app/piped/planpreview"
"github.com/pipe-cd/pipe/pkg/app/piped/statsreporter"
"github.com/pipe-cd/pipe/pkg/app/piped/toolregistry"
"github.com/pipe-cd/pipe/pkg/app/piped/trigger"
Expand Down Expand Up @@ -75,6 +76,7 @@ type piped struct {
toolsDir string
enableDefaultKubernetesCloudProvider bool
useFakeAPIClient bool
enablePlanPreview bool
gracePeriod time.Duration
addLoginUserToPasswd bool
}
Expand Down Expand Up @@ -105,6 +107,7 @@ func NewCommand() *cobra.Command {
cmd.Flags().BoolVar(&p.useFakeAPIClient, "use-fake-api-client", p.useFakeAPIClient, "Whether the fake api client should be used instead of the real one or not.")
cmd.Flags().BoolVar(&p.enableDefaultKubernetesCloudProvider, "enable-default-kubernetes-cloud-provider", p.enableDefaultKubernetesCloudProvider, "Whether the default kubernetes provider is enabled or not.")
cmd.Flags().BoolVar(&p.addLoginUserToPasswd, "add-login-user-to-passwd", p.addLoginUserToPasswd, "Whether to add login user to $HOME/passwd. This is typically for applications running as a random user ID.")
cmd.Flags().BoolVar(&p.enablePlanPreview, "enable-plan-preview", p.enablePlanPreview, "A temporary flag to enable planpreview feature.")
cmd.Flags().DurationVar(&p.gracePeriod, "grace-period", p.gracePeriod, "How long to wait for graceful shutdown.")

cmd.MarkFlagRequired("config-file")
Expand Down Expand Up @@ -368,6 +371,20 @@ func (p *piped) run(ctx context.Context, t cli.Telemetry) (runErr error) {
})
}

if p.enablePlanPreview {
// Start running planpreview handler.
h := planpreview.NewHandler(
gitClient,
commandLister,
applicationLister,
cfg,
planpreview.WithLogger(t.Logger),
)
group.Go(func() error {
return h.Run(ctx)
})
}

// Wait until all piped components have finished.
// A terminating signal or a finish of any components
// could trigger the finish of piped.
Expand Down
28 changes: 25 additions & 3 deletions pkg/app/piped/planpreview/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
srcs = ["planpreview.go"],
srcs = [
"builder.go",
"handler.go",
],
importpath = "github.com/pipe-cd/pipe/pkg/app/piped/planpreview",
visibility = ["//visibility:public"],
deps = ["//pkg/model:go_default_library"],
deps = [
"//pkg/config:go_default_library",
"//pkg/git:go_default_library",
"//pkg/model:go_default_library",
"@org_uber_go_zap//:go_default_library",
],
)

go_test(
name = "go_default_test",
size = "small",
srcs = [
"builder_test.go",
"handler_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/model:go_default_library",
"@com_github_stretchr_testify//require:go_default_library",
],
)
97 changes: 97 additions & 0 deletions pkg/app/piped/planpreview/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2021 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 planpreview

import (
"context"
"fmt"

"go.uber.org/zap"

"github.com/pipe-cd/pipe/pkg/config"
"github.com/pipe-cd/pipe/pkg/model"
)

type Builder interface {
Build(ctx context.Context, id string, cmd model.Command_BuildPlanPreview) ([]*model.ApplicationPlanPreviewResult, error)
}

type builder struct {
gitClient gitClient
applicationLister applicationLister
config *config.PipedSpec
logger *zap.Logger
}

func newBuilder(gc gitClient, al applicationLister, cfg *config.PipedSpec, logger *zap.Logger) *builder {
return &builder{
gitClient: gc,
applicationLister: al,
config: cfg,
logger: logger.Named("planpreview-builder"),
}
}

func (b *builder) Build(ctx context.Context, id string, cmd model.Command_BuildPlanPreview) ([]*model.ApplicationPlanPreviewResult, error) {
Comment thread
nghialv marked this conversation as resolved.
repoCfg, ok := b.config.GetRepository(cmd.RepositoryId)
if !ok {
return nil, fmt.Errorf("repository %s was not found in Piped config", cmd.RepositoryId)
}
if repoCfg.Branch != cmd.BaseBranch {
return nil, fmt.Errorf("base branch repository %s was not correct, requested %s, expected %s", cmd.RepositoryId, cmd.BaseBranch, repoCfg.Branch)
}

apps := b.listApplications(repoCfg)
if len(apps) == 0 {
return nil, nil
}

repo, err := b.gitClient.Clone(ctx, repoCfg.RepoID, repoCfg.Remote, repoCfg.Branch, "")
if err != nil {
return nil, fmt.Errorf("failed to clone git repository %s", cmd.RepositoryId)
}
defer repo.Clean()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I didn't know there is such a helper.


// TODO: Implement planpreview builder.
// 1. Fetch the source code at the head commit.
// 2. Determine the list of applications that will be triggered.
// - Based on the changed files between 2 commits: head commit and mostRecentlyTriggeredCommit
// 3. For each application:
// 3.1. Start a builder to check what/why strategy will be used
// 3.2. Check what resources should be added, deleted and modified
// - Terraform app: used terraform plan command
// - Kubernetes app: calculate the diff of resources at head commit and mostRecentlySuccessfulCommit
return nil, fmt.Errorf("Not Implemented")
Comment thread
nghialv marked this conversation as resolved.
}

func (b *builder) listApplications(repo config.PipedRepository) []*model.Application {
apps := b.applicationLister.List()
out := make([]*model.Application, 0, len(apps))

for _, app := range apps {
if app.GitPath.Repo.Id != repo.RepoID {
continue
}
if app.GitPath.Repo.Remote != repo.Remote {
continue
}
if app.GitPath.Repo.Branch != repo.Branch {
continue
}
out = append(out, app)
}

return out
}
15 changes: 15 additions & 0 deletions pkg/app/piped/planpreview/builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2021 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 planpreview
Loading