Skip to content

Commit

Permalink
feat: Get deployment information for an app for a commit (#1894)
Browse files Browse the repository at this point in the history
* Added release number to `new-release` commit event.
* Added a new grpc endpoint in the cd-service that accepts a commit hash
and an application name. The output of the grpc message is a the
deployment information for the commit and the app on all environments.

Example output:
```
map[development:DEPLOYED development2:DEPLOYED fakeprod-ca:DEPLOYED fakeprod-de:UNKNOWN staging:UNKNOWN]
```  

Ref: SRX-514GKC
  • Loading branch information
ahmed-nour-fdc authored Aug 20, 2024
1 parent 40bc8da commit 91a826e
Show file tree
Hide file tree
Showing 12 changed files with 481 additions and 34 deletions.
25 changes: 22 additions & 3 deletions pkg/api/v1/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package api.v1;

service GitService {
rpc GetGitTags (GetGitTagsRequest) returns (GetGitTagsResponse) {}
// By "Product" we mean the entire collection of apps
// By "Product" we mean the entire collection of apps
rpc GetProductSummary(GetProductSummaryRequest) returns(GetProductSummaryResponse) {}
rpc GetCommitInfo(GetCommitInfoRequest) returns(GetCommitInfoResponse) {}
}
Expand All @@ -23,7 +23,7 @@ message GetGitTagsResponse {

message GetProductSummaryRequest {
string commit_hash = 1;
optional string environment = 2;
optional string environment = 2;
optional string environment_group = 3;
}

Expand Down Expand Up @@ -399,7 +399,7 @@ message EnvironmentGroup {
Environment priority is calculated based on the location of an environment in a chain. Environment group priority is calculated based on the distance to upstream **and** the maximum global distance to upstream.
This field therefore characterizes the "layer" of an environment group. The reason it is reusing the name and type of Environment.priority is to keep the calculation of environment colors in the frontend untouched.
Note: proper calculation of this field assumes there is not more than one environment group hierarchy; that is, there is only one group with distance_to_upstream = 0.
*/
Priority priority = 4;
Expand Down Expand Up @@ -707,3 +707,22 @@ message EslItem {
message GetFailedEslsResponse {
repeated EslItem failed_esls = 1;
}

enum CommitDeploymentStatus {
UNKNOWN = 0;
PENDING = 1;
DEPLOYED = 2;
}

service CommitDeploymentService {
rpc GetCommitDeploymentInfo (GetCommitDeploymentInfoRequest) returns (GetCommitDeploymentInfoResponse) {}
}

message GetCommitDeploymentInfoRequest {
string commit_id = 1;
string application = 2;
}

message GetCommitDeploymentInfoResponse {
map<string, CommitDeploymentStatus> deployment_status = 1;
}
23 changes: 13 additions & 10 deletions pkg/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -920,14 +920,14 @@ func (h *DBHandler) WriteEvent(ctx context.Context, transaction *sql.Tx, transfo
return nil
}

func (h *DBHandler) DBWriteNewReleaseEvent(ctx context.Context, transaction *sql.Tx, transformerID TransformerID, uuid, sourceCommitHash string, newReleaseEvent *event.NewRelease) error {
//func (h *DBHandler) DBWriteDeploymentEvent(ctx context.Context, transaction *sql.Tx, uuid, sourceCommitHash, email string, deployment *event.Deployment) error {
func (h *DBHandler) DBWriteNewReleaseEvent(ctx context.Context, transaction *sql.Tx, transformerID TransformerID, releaseVersion uint64, uuid, sourceCommitHash string, newReleaseEvent *event.NewRelease) error {
span, ctx := tracer.StartSpanFromContext(ctx, "DBWriteDeploymentEvent")
defer span.Finish()

metadata := event.Metadata{
Uuid: uuid,
EventType: string(event.EventTypeNewRelease),
Uuid: uuid,
EventType: string(event.EventTypeNewRelease),
ReleaseVersion: releaseVersion,
}
jsonToInsert, err := json.Marshal(event.DBEventGo{
EventData: newReleaseEvent,
Expand All @@ -942,8 +942,9 @@ func (h *DBHandler) DBWriteNewReleaseEvent(ctx context.Context, transaction *sql

func (h *DBHandler) DBWriteLockPreventedDeploymentEvent(ctx context.Context, transaction *sql.Tx, transformerID TransformerID, uuid, sourceCommitHash string, lockPreventedDeploymentEvent *event.LockPreventedDeployment) error {
metadata := event.Metadata{
Uuid: uuid,
EventType: string(event.EventTypeLockPreventedDeployment),
Uuid: uuid,
EventType: string(event.EventTypeLockPreventedDeployment),
ReleaseVersion: 0, // don't care about release version for this event
}
jsonToInsert, err := json.Marshal(event.DBEventGo{
EventData: lockPreventedDeploymentEvent,
Expand All @@ -958,8 +959,9 @@ func (h *DBHandler) DBWriteLockPreventedDeploymentEvent(ctx context.Context, tra

func (h *DBHandler) DBWriteReplacedByEvent(ctx context.Context, transaction *sql.Tx, transformerID TransformerID, uuid, sourceCommitHash string, replacedBy *event.ReplacedBy) error {
metadata := event.Metadata{
Uuid: uuid,
EventType: string(event.EventTypeReplaceBy),
Uuid: uuid,
EventType: string(event.EventTypeReplaceBy),
ReleaseVersion: 0, // don't care about release version for this event
}
jsonToInsert, err := json.Marshal(event.DBEventGo{
EventData: replacedBy,
Expand All @@ -974,8 +976,9 @@ func (h *DBHandler) DBWriteReplacedByEvent(ctx context.Context, transaction *sql

func (h *DBHandler) DBWriteDeploymentEvent(ctx context.Context, transaction *sql.Tx, transformerID TransformerID, uuid, sourceCommitHash string, deployment *event.Deployment) error {
metadata := event.Metadata{
Uuid: uuid,
EventType: string(event.EventTypeDeployment),
Uuid: uuid,
EventType: string(event.EventTypeDeployment),
ReleaseVersion: 0, // don't care about release version for this event
}
jsonToInsert, err := json.Marshal(event.DBEventGo{
EventData: deployment,
Expand Down
21 changes: 18 additions & 3 deletions pkg/db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,21 @@ func TestCommitEvents(t *testing.T) {
},
},
},
{
Name: "New release event",
commitHash: "abcdefabcdef",
email: "[email protected]",
event: event.DBEventGo{
EventData: &event.NewRelease{
Environments: map[string]struct{}{"dev": {}},
},
EventMetadata: event.Metadata{
Uuid: "00000000-0000-0000-0000-000000000001",
EventType: "new-release",
ReleaseVersion: 1,
},
},
},
}
for _, tc := range tcs {
tc := tc
Expand Down Expand Up @@ -592,7 +607,7 @@ func TestReadLockPreventedEvents(t *testing.T) {
Uuid: "00000000-0000-0000-0000-000000000000",
CommitHash: "test",
EventType: "lock-prevented-deployment",
EventJson: `{"EventData":{"Application":"app","Environment":"env","LockMessage":"test lock message","LockType":"Application"},"EventMetadata":{"Uuid":"00000000-0000-0000-0000-000000000000","EventType":"lock-prevented-deployment"}}`,
EventJson: `{"EventData":{"Application":"app","Environment":"env","LockMessage":"test lock message","LockType":"Application"},"EventMetadata":{"Uuid":"00000000-0000-0000-0000-000000000000","EventType":"lock-prevented-deployment","ReleaseVersion":0}}`,
},
},
},
Expand All @@ -618,13 +633,13 @@ func TestReadLockPreventedEvents(t *testing.T) {
Uuid: "00000000-0000-0000-0000-000000000000",
CommitHash: "test",
EventType: "lock-prevented-deployment",
EventJson: `{"EventData":{"Application":"app","Environment":"env","LockMessage":"test lock message","LockType":"Application"},"EventMetadata":{"Uuid":"00000000-0000-0000-0000-000000000000","EventType":"lock-prevented-deployment"}}`,
EventJson: `{"EventData":{"Application":"app","Environment":"env","LockMessage":"test lock message","LockType":"Application"},"EventMetadata":{"Uuid":"00000000-0000-0000-0000-000000000000","EventType":"lock-prevented-deployment","ReleaseVersion":0}}`,
},
{
Uuid: "00000000-0000-0000-0000-000000000001",
CommitHash: "test",
EventType: "lock-prevented-deployment",
EventJson: `{"EventData":{"Application":"app2","Environment":"env2","LockMessage":"message2","LockType":"Environment"},"EventMetadata":{"Uuid":"00000000-0000-0000-0000-000000000001","EventType":"lock-prevented-deployment"}}`,
EventJson: `{"EventData":{"Application":"app2","Environment":"env2","LockMessage":"message2","LockType":"Environment"},"EventMetadata":{"Uuid":"00000000-0000-0000-0000-000000000001","EventType":"lock-prevented-deployment","ReleaseVersion":0}}`,
},
},
},
Expand Down
5 changes: 3 additions & 2 deletions pkg/event/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,9 @@ func ToProto(eventID timeuuid.UUID, ev Event) *api.Event {
}

type Metadata struct {
Uuid string
EventType string
Uuid string
EventType string
ReleaseVersion uint64
}

type DBEventGo struct {
Expand Down
4 changes: 3 additions & 1 deletion services/cd-service/pkg/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,9 @@ func RunServer() {
api.RegisterEslServiceServer(srv, &service.EslServiceServer{Repository: repo})
reflection.Register(srv)
reposerver.Register(srv, repo, cfg)

if dbHandler != nil {
api.RegisterCommitDeploymentServiceServer(srv, &service.CommitDeploymentServer{DBHandler: dbHandler})
}
},
},
Background: []setup.BackgroundTaskConfig{
Expand Down
5 changes: 3 additions & 2 deletions services/cd-service/pkg/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -2279,8 +2279,9 @@ func (s *State) WriteAllCommitEvents(ctx context.Context, transaction *sql.Tx, d
currentEvent := event.DBEventGo{
EventData: fsEvent,
EventMetadata: event.Metadata{
Uuid: fileName,
EventType: string(eType),
Uuid: fileName,
EventType: string(eType),
ReleaseVersion: 0, // don't care about release version for this event
},
}
eventJson, err := json.Marshal(currentEvent)
Expand Down
6 changes: 3 additions & 3 deletions services/cd-service/pkg/repository/transformer.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ func (c *CreateApplicationVersion) Transform(
gen := getGenerator(ctx)
eventUuid := gen.Generate()
if c.WriteCommitData {
err = writeCommitData(ctx, state.DBHandler, transaction, c.TransformerEslVersion, c.SourceCommitId, c.SourceMessage, c.Application, eventUuid, allEnvsOfThisApp, c.PreviousCommit, state)
err = writeCommitData(ctx, state.DBHandler, transaction, version, c.TransformerEslVersion, c.SourceCommitId, c.SourceMessage, c.Application, eventUuid, allEnvsOfThisApp, c.PreviousCommit, state)
if err != nil {
return "", GetCreateReleaseGeneralFailure(err)
}
Expand Down Expand Up @@ -807,7 +807,7 @@ func AddGeneratorToContext(ctx context.Context, gen uuid.GenerateUUIDs) context.
return context.WithValue(ctx, ctxMarkerGenerateUuidKey, gen)
}

func writeCommitData(ctx context.Context, h *db.DBHandler, transaction *sql.Tx, transformerEslVersion db.TransformerID, sourceCommitId string, sourceMessage string, app string, eventId string, environments []string, previousCommitId string, state *State) error {
func writeCommitData(ctx context.Context, h *db.DBHandler, transaction *sql.Tx, releaseVersion uint64, transformerEslVersion db.TransformerID, sourceCommitId string, sourceMessage string, app string, eventId string, environments []string, previousCommitId string, state *State) error {
fs := state.Filesystem
if !valid.SHA1CommitID(sourceCommitId) {
return nil
Expand Down Expand Up @@ -852,7 +852,7 @@ func writeCommitData(ctx context.Context, h *db.DBHandler, transaction *sql.Tx,
if h.ShouldUseEslTable() {
gen := getGenerator(ctx)
eventUuid := gen.Generate()
writeError = state.DBHandler.DBWriteNewReleaseEvent(ctx, transaction, transformerEslVersion, eventUuid, sourceCommitId, ev)
writeError = state.DBHandler.DBWriteNewReleaseEvent(ctx, transaction, transformerEslVersion, releaseVersion, eventUuid, sourceCommitId, ev)
} else {
writeError = writeEvent(ctx, eventId, sourceCommitId, fs, ev)
}
Expand Down
168 changes: 168 additions & 0 deletions services/cd-service/pkg/service/commit_deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*This file is part of kuberpult.
Kuberpult is free software: you can redistribute it and/or modify
it under the terms of the Expat(MIT) License as published by
the Free Software Foundation.
Kuberpult is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MIT License for more details.
You should have received a copy of the MIT License
along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.
Copyright freiheit.com*/

package service

import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"

api "github.com/freiheit-com/kuberpult/pkg/api/v1"
"github.com/freiheit-com/kuberpult/pkg/db"
"github.com/freiheit-com/kuberpult/pkg/event"
)

type CommitStatus map[string]api.CommitDeploymentStatus

type CommitDeploymentServer struct {
DBHandler *db.DBHandler
}

func (s *CommitDeploymentServer) GetCommitDeploymentInfo(ctx context.Context, in *api.GetCommitDeploymentInfoRequest) (*api.GetCommitDeploymentInfoResponse, error) {
status, err := getCommitDeploymentInfoForApp(ctx, s.DBHandler, in.CommitId, in.Application)
if err != nil {
return nil, fmt.Errorf("Could not get commit deployment info: %v", err)
}
return &api.GetCommitDeploymentInfoResponse{
DeploymentStatus: status,
}, nil
}

func getCommitDeploymentInfoForApp(ctx context.Context, h *db.DBHandler, commit, app string) (CommitStatus, error) {
var jsonCommitEventsMetadata []byte
var jsonAllDeploymentsMetadata []byte
var jsonAllEnvironmentsMetadata []byte

err := h.WithTransaction(ctx, true, func(ctx context.Context, transaction *sql.Tx) error {
// Get the latest new-release event for the commit
query := h.AdaptQuery("SELECT json FROM commit_events WHERE commithash = ? AND eventtype = ? ORDER BY timestamp DESC LIMIT 1;")
row := transaction.QueryRow(query, commit, "new-release")
err := row.Scan(&jsonCommitEventsMetadata)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("commit \"%s\" could not be found", commit)
}
return err
}

// Get all deployments for the commit
query = h.AdaptQuery("SELECT json FROM all_deployments WHERE appname = ? ORDER BY eslversion DESC LIMIT 1;")
row = transaction.QueryRow(query, app)
err = row.Scan(&jsonAllDeploymentsMetadata)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("application \"%s\" does not exist or has no deployments yet.", app)
}
return err
}

// Get all environments
query = h.AdaptQuery("SELECT json FROM all_environments ORDER BY version DESC LIMIT 1;")
row = transaction.QueryRow(query)
err = row.Scan(&jsonAllEnvironmentsMetadata)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("no environments exist")
}
return err
}
return nil
})
if err != nil {
return nil, err
}
releaseNumber, err := getCommitReleaseNumber(jsonCommitEventsMetadata)
if err != nil {
return nil, fmt.Errorf("Could not get commit release number from commit_events metadata: %v", err)
}
allEnvironments, err := getAllEnvironments(jsonAllEnvironmentsMetadata)
if err != nil {
return nil, fmt.Errorf("Could not get all environments from all_environments metadata: %v", err)
}
environmentReleases, err := getEnvironmentReleases(jsonAllDeploymentsMetadata)
if err != nil {
return nil, fmt.Errorf("Could not get environment releases from all_deployments metadata: %v", err)
}

commitStatus := getCommitStatus(releaseNumber, environmentReleases, allEnvironments)
return commitStatus, nil
}

func getCommitStatus(commitReleaseNumber uint64, environmentReleases map[string]uint64, allEnvironments []string) CommitStatus {
commitStatus := make(CommitStatus)
if commitReleaseNumber == 0 {
// Since 0 is the default value for uint64, it might mean that the release version was not known when the commit was created.
// In this case, the commit status is unkown for all environments.
for _, env := range allEnvironments {
commitStatus[env] = api.CommitDeploymentStatus_UNKNOWN
}
return commitStatus
}

for _, env := range allEnvironments {
// by defauly, a commit is pending in all environments
commitStatus[env] = api.CommitDeploymentStatus_PENDING
}

for env, environmentReleaseVersion := range environmentReleases {
if environmentReleaseVersion >= commitReleaseNumber {
commitStatus[env] = api.CommitDeploymentStatus_DEPLOYED
} else {
commitStatus[env] = api.CommitDeploymentStatus_PENDING
}
}
return commitStatus
}

func getCommitReleaseNumber(jsonInput []byte) (uint64, error) {
releaseEvent := event.DBEventGo{
EventData: &event.NewRelease{
Environments: map[string]struct{}{},
},
EventMetadata: event.Metadata{
Uuid: "",
EventType: "",
ReleaseVersion: 0,
},
}

err := json.Unmarshal(jsonInput, &releaseEvent)
if err != nil {
return 0, err
}
return releaseEvent.EventMetadata.ReleaseVersion, nil
}

func getAllEnvironments(jsonInput []byte) ([]string, error) {
environments := []string{}
err := json.Unmarshal(jsonInput, &environments)
if err != nil {
return nil, err
}
return environments, nil
}

func getEnvironmentReleases(jsonInput []byte) (map[string]uint64, error) {
releases := map[string]uint64{}
err := json.Unmarshal(jsonInput, &releases)
if err != nil {
return nil, err
}
return releases, nil
}
Loading

0 comments on commit 91a826e

Please sign in to comment.