Skip to content
1 change: 1 addition & 0 deletions pkg/app/piped/controller/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ func (p *planner) reportDeploymentPlanned(ctx context.Context, out pln.Output) e
RunningCommitHash: p.lastSuccessfulCommitHash,
RunningConfigFilename: p.lastSuccessfulConfigFilename,
Version: out.Version,
Versions: out.Versions,
Stages: out.Stages,
DeploymentChainId: p.deployment.DeploymentChainId,
DeploymentChainBlockIndex: p.deployment.DeploymentChainBlockIndex,
Expand Down
1 change: 1 addition & 0 deletions pkg/app/piped/controller/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ func (s *scheduler) reportMostRecentlySuccessfulDeployment(ctx context.Context)
Trigger: s.deployment.Trigger,
Summary: s.deployment.Summary,
Version: s.deployment.Version,
Versions: s.deployment.Versions,
ConfigFilename: s.deployment.GitPath.GetApplicationConfigFilename(),
StartedAt: s.deployment.CreatedAt,
CompletedAt: s.deployment.CompletedAt,
Expand Down
51 changes: 51 additions & 0 deletions pkg/app/piped/planner/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ func (p *Planner) Plan(ctx context.Context, in planner.Input) (out planner.Outpu
out.Version = version
}

if versions, e := determineVersions(newManifests); e != nil || len(versions) == 0 {
in.Logger.Error("unable to determine versions", zap.Error(e))
out.Versions = []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_UNKNOWN,
Version: versionUnknown,
},
}
} else {
out.Versions = versions
}

autoRollback := *cfg.Input.AutoRollback

// In case the strategy has been decided by trigger.
Expand Down Expand Up @@ -480,3 +492,42 @@ func determineVersion(manifests []provider.Manifest) (string, error) {

return b.String(), nil
}

// determineVersions decides artifact versions of an application.
// It finds all container images that are being specified in the workload manifests then returns their names, version numbers, and urls.
func determineVersions(manifests []provider.Manifest) ([]*model.ArtifactVersion, error) {
imageMap := map[string]struct{}{}
for _, m := range manifests {
// TODO: Determine container image version from other workload kinds such as StatefulSet, Pod, Daemon, CronJob...
if !m.Key.IsDeployment() {
continue
}
data, err := m.MarshalJSON()
if err != nil {
return nil, err
}
var d resource.Deployment
if err := json.Unmarshal(data, &d); err != nil {
return nil, err
}

containers := d.Spec.Template.Spec.Containers
// remove duplicate images on multiple manifests
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// remove duplicate images on multiple manifests
// Remove duplicate images on multiple manifests.

As our convention, comments start with a capital character and finish with a dot.

for _, c := range containers {
imageMap[c.Image] = struct{}{}
}
}

versions := make([]*model.ArtifactVersion, 0, len(imageMap))
for i := range imageMap {
image := parseContainerImage(i)
versions = append(versions, &model.ArtifactVersion{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: image.tag,
Name: image.name,
Url: i,
})
}

return versions, nil
}
87 changes: 87 additions & 0 deletions pkg/app/piped/planner/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

provider "github.com/pipe-cd/pipecd/pkg/app/piped/cloudprovider/kubernetes"
"github.com/pipe-cd/pipecd/pkg/config"
"github.com/pipe-cd/pipecd/pkg/model"
)

func TestDecideStrategy(t *testing.T) {
Expand Down Expand Up @@ -438,6 +439,92 @@ func TestDetermineVersion(t *testing.T) {
}
}

func TestDetermineVersions(t *testing.T) {
testcases := []struct {
name string
manifests string
expected []*model.ArtifactVersion
expectedError error
}{
{
name: "no workload",
manifests: "testdata/version_no_workload.yaml",
expected: []*model.ArtifactVersion{},
},
{
name: "single container",
manifests: "testdata/version_single_container.yaml",
expected: []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v1.0.0",
Name: "helloworld",
Url: "gcr.io/pipecd/helloworld:v1.0.0",
},
},
},
{
name: "multiple containers",
manifests: "testdata/version_multi_containers.yaml",
expected: []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v1.0.0",
Name: "helloworld",
Url: "gcr.io/pipecd/helloworld:v1.0.0",
},
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v0.6.0",
Name: "my-service",
Url: "gcr.io/pipecd/my-service:v0.6.0",
},
},
},
{
name: "multiple workloads",
manifests: "testdata/version_multi_workloads.yaml",
expected: []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v1.0.0",
Name: "helloworld",
Url: "gcr.io/pipecd/helloworld:v1.0.0",
},
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v0.5.0",
Name: "my-service",
Url: "gcr.io/pipecd/my-service:v0.5.0",
},
},
},
{
name: "multiple workloads using same container image",
manifests: "testdata/version_multi_workloads_same_image.yaml",
expected: []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "v1.0.0",
Name: "helloworld",
Url: "gcr.io/pipecd/helloworld:v1.0.0",
},
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
manifests, err := provider.LoadManifestsFromYAMLFile(tc.manifests)
require.NoError(t, err)

versions, err := determineVersions(manifests)
assert.Equal(t, tc.expected, versions)
assert.Equal(t, tc.expectedError, err)
})
}
}

func TestCheckImageChange(t *testing.T) {
testcases := []struct {
name string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: simple
labels:
app: simple
pipecd.dev/managed-by: piped
spec:
replicas: 2
selector:
matchLabels:
app: simple
template:
metadata:
labels:
app: simple
spec:
containers:
- name: helloworld
image: gcr.io/pipecd/helloworld:v1.0.0
args:
- hello
- hi
ports:
- containerPort: 9085
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
labels:
pipecd.dev/managed-by: piped
app: simple
spec:
replicas: 2
selector:
matchLabels:
app: simple
template:
metadata:
labels:
app: simple
spec:
containers:
- name: helloworld
image: gcr.io/pipecd/helloworld:v1.0.0
args:
- hi
- hello
ports:
- containerPort: 9085
1 change: 1 addition & 0 deletions pkg/app/piped/planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type Input struct {

type Output struct {
Version string
Versions []*model.ArtifactVersion
SyncStrategy model.SyncStrategy
Summary string
Stages []*model.PipelineStage
Expand Down
1 change: 1 addition & 0 deletions pkg/app/piped/trigger/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func reportMostRecentlyTriggeredDeployment(ctx context.Context, client apiClient
Trigger: d.Trigger,
Summary: d.Summary,
Version: d.Version,
Versions: d.Versions,
StartedAt: d.CreatedAt,
CompletedAt: d.CompletedAt,
},
Expand Down
3 changes: 2 additions & 1 deletion pkg/app/server/grpcapi/piped_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type pipedApiDeploymentStore interface {
Add(ctx context.Context, app *model.Deployment) error
Get(ctx context.Context, id string) (*model.Deployment, error)
List(ctx context.Context, opts datastore.ListOptions) ([]*model.Deployment, string, error)
UpdateToPlanned(ctx context.Context, id, summary, reason, runningCommitHash, runningConfigFilename, version string, stages []*model.PipelineStage) error
UpdateToPlanned(ctx context.Context, id, summary, reason, runningCommitHash, runningConfigFilename, version string, versions []*model.ArtifactVersion, stages []*model.PipelineStage) error
UpdateToCompleted(ctx context.Context, id string, status model.DeploymentStatus, stageStatuses map[string]model.StageStatus, reason string, completedAt int64) error
UpdateStatus(ctx context.Context, id string, status model.DeploymentStatus, reason string) error
UpdateStageStatus(ctx context.Context, id, stageID string, status model.StageStatus, reason string, requires []string, visible bool, retriedCount int32, completedAt int64) error
Expand Down Expand Up @@ -403,6 +403,7 @@ func (a *PipedAPI) ReportDeploymentPlanned(ctx context.Context, req *pipedservic
req.RunningCommitHash,
req.RunningConfigFilename,
req.Version,
req.Versions,
req.Stages,
); err != nil {
return nil, gRPCEntityOperationError(err, fmt.Sprintf("update deployment %s as planned", req.DeploymentId))
Expand Down
1 change: 1 addition & 0 deletions pkg/app/server/service/pipedservice/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ message ReportDeploymentPlannedRequest {
string running_config_filename = 9;
// The application version this deployment is trying to deploy.
string version = 5;
repeated model.ArtifactVersion versions = 10;
// The planned stages.
// Empty means nothing has changed compared to when the deployment was created.
repeated model.PipelineStage stages = 6;
Expand Down
1 change: 1 addition & 0 deletions pkg/app/web/src/__fixtures__/dummy-deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const dummyDeployment: Deployment.AsObject = {
statusReason: "good",
trigger: dummyTrigger,
version: "0.0.0",
versionsList: [],
cloudProvider: "kube-1",
labelsMap: [],
createdAt: createdAt.unix(),
Expand Down
9 changes: 5 additions & 4 deletions pkg/datastore/deploymentstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,15 @@ func (d *deploymentCollection) Encode(e interface{}) (map[Shard][]byte, error) {
}

var (
toPlannedUpdateFunc = func(summary, statusReason, runningCommitHash, runningConfigFilename, version string, stages []*model.PipelineStage) func(*model.Deployment) error {
toPlannedUpdateFunc = func(summary, statusReason, runningCommitHash, runningConfigFilename, version string, versions []*model.ArtifactVersion, stages []*model.PipelineStage) func(*model.Deployment) error {
return func(d *model.Deployment) error {
d.Status = model.DeploymentStatus_DEPLOYMENT_PLANNED
d.Summary = summary
d.StatusReason = statusReason
d.RunningCommitHash = runningCommitHash
d.RunningConfigFilename = runningConfigFilename
d.Version = version
d.Versions = versions
d.Stages = stages
return nil
}
Expand Down Expand Up @@ -134,7 +135,7 @@ type DeploymentStore interface {
Add(ctx context.Context, d *model.Deployment) error
Get(ctx context.Context, id string) (*model.Deployment, error)
List(ctx context.Context, opts ListOptions) ([]*model.Deployment, string, error)
UpdateToPlanned(ctx context.Context, id, summary, reason, runningCommitHash, runningConfigFilename, version string, stages []*model.PipelineStage) error
UpdateToPlanned(ctx context.Context, id, summary, reason, runningCommitHash, runningConfigFilename, version string, versions []*model.ArtifactVersion, stages []*model.PipelineStage) error
UpdateToCompleted(ctx context.Context, id string, status model.DeploymentStatus, stageStatuses map[string]model.StageStatus, reason string, completedAt int64) error
UpdateStatus(ctx context.Context, id string, status model.DeploymentStatus, reason string) error
UpdateStageStatus(ctx context.Context, id, stageID string, status model.StageStatus, reason string, requires []string, visible bool, retriedCount int32, completedAt int64) error
Expand Down Expand Up @@ -222,8 +223,8 @@ func (s *deploymentStore) update(ctx context.Context, id string, updater func(*m
})
}

func (s *deploymentStore) UpdateToPlanned(ctx context.Context, id, summary, reason, runningCommitHash, runningConfigFilename, version string, stages []*model.PipelineStage) error {
updater := toPlannedUpdateFunc(summary, reason, runningCommitHash, runningConfigFilename, version, stages)
func (s *deploymentStore) UpdateToPlanned(ctx context.Context, id, summary, reason, runningCommitHash, runningConfigFilename, version string, versions []*model.ArtifactVersion, stages []*model.PipelineStage) error {
updater := toPlannedUpdateFunc(summary, reason, runningCommitHash, runningConfigFilename, version, versions, stages)
return s.update(ctx, id, updater)
}

Expand Down
12 changes: 11 additions & 1 deletion pkg/datastore/deploymentstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@ func TestDeploymentToPlannedUpdater(t *testing.T) {
expectedRunningCommitHash = "update-running-commit-hash"
expectedRunningConfigFilename = "update-running-config-filename"
expectedVersion = "update-version"
expectedStages = []*model.PipelineStage{
expectedVersions = []*model.ArtifactVersion{
{
Kind: model.ArtifactVersion_CONTAINER_IMAGE,
Version: "update-version",
Name: "update-image-name",
Url: "dummy-registry/update-image-name:update-version",
},
}
expectedStages = []*model.PipelineStage{
{
Id: "stage-id1",
Name: "stage1",
Expand Down Expand Up @@ -63,6 +71,7 @@ func TestDeploymentToPlannedUpdater(t *testing.T) {
expectedRunningCommitHash,
expectedRunningConfigFilename,
expectedVersion,
expectedVersions,
expectedStages,
)
)
Expand All @@ -75,6 +84,7 @@ func TestDeploymentToPlannedUpdater(t *testing.T) {
assert.Equal(t, expectedRunningCommitHash, d.RunningCommitHash)
assert.Equal(t, expectedRunningConfigFilename, d.RunningConfigFilename)
assert.Equal(t, expectedVersion, d.Version)
assert.Equal(t, expectedVersions, d.Versions)
assert.Equal(t, expectedStages, d.Stages)
}

Expand Down
12 changes: 0 additions & 12 deletions pkg/model/application.proto
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,6 @@ message ApplicationSyncState {
int64 timestamp = 5 [(validate.rules).int64.gt = 0];
}

message ArtifactVersion {
enum Kind {
UNKNOWN = 0;
CONTAINER_IMAGE = 1;
}

Kind kind = 1 [(validate.rules).enum.defined_only = true];
string version = 2 [(validate.rules).string.min_len = 1];
string name = 3;
string url = 4;
}

message ApplicationDeploymentReference {
string deployment_id = 1 [(validate.rules).string.min_len = 1];
DeploymentTrigger trigger = 2 [(validate.rules).message.required = true];
Expand Down
12 changes: 12 additions & 0 deletions pkg/model/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,15 @@ message ApplicationInfo {
// This field will be no longer needed as labels can be an alternative.
string env_name = 14 [deprecated=true];
}

message ArtifactVersion {
enum Kind {
UNKNOWN = 0;
CONTAINER_IMAGE = 1;
}

Kind kind = 1 [(validate.rules).enum.defined_only = true];
string version = 2 [(validate.rules).string.min_len = 1];
string name = 3;
string url = 4;
}
Loading