Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[k8s plugin] Prepare for implementing livestate apis #5510

Merged
merged 6 commits into from
Jan 31, 2025
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
18 changes: 2 additions & 16 deletions pkg/app/pipedv1/plugin/kubernetes/deployment/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"cmp"
"context"
"errors"
"fmt"
"time"

kubeconfig "github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/kubernetes/config"
Expand Down Expand Up @@ -118,22 +117,9 @@

lp.Info("Start finding all running resources but no longer defined in Git")

namespacedLiveResources, err := kubectl.GetAll(ctx, deployTargetConfig.KubeConfigPath,
"",
fmt.Sprintf("%s=%s", provider.LabelManagedBy, provider.ManagedByPiped),
fmt.Sprintf("%s=%s", provider.LabelApplication, input.GetDeployment().GetApplicationId()),
)
if err != nil {
lp.Errorf("Failed while listing all resources (%v)", err)
return model.StageStatus_STAGE_FAILURE
}

clusterScopedLiveResources, err := kubectl.GetAllClusterScoped(ctx, deployTargetConfig.KubeConfigPath,
fmt.Sprintf("%s=%s", provider.LabelManagedBy, provider.ManagedByPiped),
fmt.Sprintf("%s=%s", provider.LabelApplication, input.GetDeployment().GetApplicationId()),
)
namespacedLiveResources, clusterScopedLiveResources, err := provider.GetLiveResources(ctx, kubectl, deployTargetConfig.KubeConfigPath, input.GetDeployment().GetApplicationId())
if err != nil {
lp.Errorf("Failed while listing all cluster-scoped resources (%v)", err)
lp.Errorf("Failed while getting live resources (%v)", err)

Check warning on line 122 in pkg/app/pipedv1/plugin/kubernetes/deployment/sync.go

View check run for this annotation

Codecov / codecov/patch

pkg/app/pipedv1/plugin/kubernetes/deployment/sync.go#L122

Added line #L122 was not covered by tests
return model.StageStatus_STAGE_FAILURE
}

Expand Down
91 changes: 91 additions & 0 deletions pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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 provider

import (
"context"
"fmt"
"time"

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

// GetLiveResources returns all live resources that belong to the given application.
func GetLiveResources(ctx context.Context, kubectl *Kubectl, kubeconfig string, appID string, selector ...string) (namespaceScoped []Manifest, clusterScoped []Manifest, _ error) {
namespacedLiveResources, err := kubectl.GetAll(ctx, kubeconfig,
"",
fmt.Sprintf("%s=%s", LabelManagedBy, ManagedByPiped),
fmt.Sprintf("%s=%s", LabelApplication, appID),
)
if err != nil {
return nil, nil, fmt.Errorf("failed while listing all namespace-scoped resources (%v)", err)
}

Check warning on line 34 in pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go

View check run for this annotation

Codecov / codecov/patch

pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go#L26-L34

Added lines #L26 - L34 were not covered by tests

clusterScopedLiveResources, err := kubectl.GetAllClusterScoped(ctx, kubeconfig,
fmt.Sprintf("%s=%s", LabelManagedBy, ManagedByPiped),
fmt.Sprintf("%s=%s", LabelApplication, appID),
)
if err != nil {
return nil, nil, fmt.Errorf("failed while listing all cluster-scoped resources (%v)", err)
}

Check warning on line 42 in pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go

View check run for this annotation

Codecov / codecov/patch

pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go#L36-L42

Added lines #L36 - L42 were not covered by tests

return namespacedLiveResources, clusterScopedLiveResources, nil

Check warning on line 44 in pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go

View check run for this annotation

Codecov / codecov/patch

pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go#L44

Added line #L44 was not covered by tests
}

// BuildApplicationLiveState builds the live state of the application from the given manifests.
func BuildApplicationLiveState(deploytarget string, manifests []Manifest, now time.Time) *model.ApplicationLiveState {
Comment on lines +47 to +48
Copy link
Member

Choose a reason for hiding this comment

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

memo: If possible, it would be nice to implement it under the live state package.
I got the thought to hide the Manifest.body from the other package.
Not consider for now.

if len(manifests) == 0 {
return &model.ApplicationLiveState{
HealthStatus: model.ApplicationLiveState_UNKNOWN,
}
}

Check warning on line 53 in pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go

View check run for this annotation

Codecov / codecov/patch

pkg/app/pipedv1/plugin/kubernetes/provider/liveresources.go#L50-L53

Added lines #L50 - L53 were not covered by tests

states := make([]*model.ResourceState, 0, len(manifests))
for _, m := range manifests {
states = append(states, buildResourceState(m, now))
}

return &model.ApplicationLiveState{
Resources: states,
HealthStatus: model.ApplicationLiveState_UNKNOWN, // TODO: Implement health status calculation
}
}

// buildResourceState builds the resource state from the given manifest.
func buildResourceState(m Manifest, now time.Time) *model.ResourceState {
var parents []string // default as nil
if len(m.body.GetOwnerReferences()) > 0 {
parents = make([]string, 0, len(m.body.GetOwnerReferences()))
for _, o := range m.body.GetOwnerReferences() {
parents = append(parents, string(o.UID))
}
}

return &model.ResourceState{
Id: string(m.body.GetUID()),
Name: m.body.GetName(),
ParentIds: parents,
HealthStatus: model.ResourceState_UNKNOWN, // TODO: Implement health status calculation
HealthDescription: "", // TODO: Implement health status calculation
ResourceType: m.body.GetKind(),
ResourceMetadata: map[string]string{
"Namespace": m.body.GetNamespace(),
"API Version": m.body.GetAPIVersion(),
"Kind": m.body.GetKind(),
},
CreatedAt: m.body.GetCreationTimestamp().Unix(),
UpdatedAt: now.Unix(),
}
}
195 changes: 195 additions & 0 deletions pkg/app/pipedv1/plugin/kubernetes/provider/liveresources_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// 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 provider

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

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

func TestBuildApplicationLiveState(t *testing.T) {
now := time.Now()

tests := []struct {
name string
manifests []Manifest
want *model.ApplicationLiveState
}{
{
name: "single pod",
manifests: []Manifest{
{
body: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Pod",
"metadata": map[string]interface{}{
"name": "test-pod",
"namespace": "default",
"uid": "test-uid",
"creationTimestamp": now.Format(time.RFC3339),
},
},
},
},
},
want: &model.ApplicationLiveState{
Resources: []*model.ResourceState{
{
Id: "test-uid",
Name: "test-pod",
ResourceType: "Pod",
ResourceMetadata: map[string]string{
"Namespace": "default",
"API Version": "v1",
"Kind": "Pod",
},
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
},
},
HealthStatus: model.ApplicationLiveState_UNKNOWN,
},
},
{
name: "single pod with owner references",
manifests: []Manifest{
{
body: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Pod",
"metadata": map[string]interface{}{
"name": "test-pod",
"namespace": "default",
"uid": "test-uid",
"creationTimestamp": now.Format(time.RFC3339),
"ownerReferences": []interface{}{
map[string]interface{}{
"uid": "owner-uid",
},
},
},
},
},
},
},
want: &model.ApplicationLiveState{
Resources: []*model.ResourceState{
{
Id: "test-uid",
Name: "test-pod",
ResourceType: "Pod",
ResourceMetadata: map[string]string{
"Namespace": "default",
"API Version": "v1",
"Kind": "Pod",
},
ParentIds: []string{"owner-uid"},
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
},
},
HealthStatus: model.ApplicationLiveState_UNKNOWN,
},
},
{
name: "multiple resources with owner references",
manifests: []Manifest{
{
body: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Pod",
"metadata": map[string]interface{}{
"name": "test-pod-1",
"namespace": "default",
"uid": "test-uid-1",
"creationTimestamp": now.Format(time.RFC3339),
"ownerReferences": []interface{}{
map[string]interface{}{
"uid": "owner-uid-1",
},
},
},
},
},
},
{
body: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Service",
"metadata": map[string]interface{}{
"name": "test-service",
"namespace": "default",
"uid": "test-uid-2",
"creationTimestamp": now.Format(time.RFC3339),
"ownerReferences": []interface{}{
map[string]interface{}{
"uid": "owner-uid-2",
},
},
},
},
},
},
},
want: &model.ApplicationLiveState{
Resources: []*model.ResourceState{
{
Id: "test-uid-1",
Name: "test-pod-1",
ResourceType: "Pod",
ResourceMetadata: map[string]string{
"Namespace": "default",
"API Version": "v1",
"Kind": "Pod",
},
ParentIds: []string{"owner-uid-1"},
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
},
{
Id: "test-uid-2",
Name: "test-service",
ResourceType: "Service",
ResourceMetadata: map[string]string{
"Namespace": "default",
"API Version": "v1",
"Kind": "Service",
},
ParentIds: []string{"owner-uid-2"},
CreatedAt: now.Unix(),
UpdatedAt: now.Unix(),
},
},
HealthStatus: model.ApplicationLiveState_UNKNOWN,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := BuildApplicationLiveState("test-deploytarget", tt.manifests, now)
assert.Equal(t, tt.want, got, "expected live state to be equal to the expected one")
})
}
}