Skip to content

Commit

Permalink
Copy platform provider kubernetes under the plugin directory (#5250)
Browse files Browse the repository at this point in the history
* Copy kubernetes platformprovider under the plugin

Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev>

* Remove pipedv0 toolregistry dependency from pipedv1

Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev>

---------

Signed-off-by: Shinnosuke Sawada-Dazai <shin@warashi.dev>
  • Loading branch information
Warashi authored Oct 4, 2024
1 parent 2ef9ad3 commit c866fa3
Show file tree
Hide file tree
Showing 55 changed files with 6,257 additions and 0 deletions.
283 changes: 283 additions & 0 deletions pkg/app/pipedv1/plugin/kubernetes/provider/applier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
// 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"
"errors"
"fmt"
"sync"

"go.uber.org/zap"

"github.com/pipe-cd/pipecd/pkg/app/pipedv1/plugin/kubernetes/toolregistry"
"github.com/pipe-cd/pipecd/pkg/config"
)

type Applier interface {
// ApplyManifest does applying the given manifest.
ApplyManifest(ctx context.Context, manifest Manifest) error
// CreateManifest does creating resource from given manifest.
CreateManifest(ctx context.Context, manifest Manifest) error
// ReplaceManifest does replacing resource from given manifest.
ReplaceManifest(ctx context.Context, manifest Manifest) error
// ForceReplaceManifest does force replacing resource from given manifest.
ForceReplaceManifest(ctx context.Context, manifest Manifest) error
// Delete deletes the given resource from Kubernetes cluster.
Delete(ctx context.Context, key ResourceKey) error
}

type applier struct {
input config.KubernetesDeploymentInput
platformProvider config.PlatformProviderKubernetesConfig
logger *zap.Logger
toolregistry toolregistry.Registry

kubectl *Kubectl
initOnce sync.Once
initErr error
}

func NewApplier(input config.KubernetesDeploymentInput, cp config.PlatformProviderKubernetesConfig, logger *zap.Logger, toolregistry toolregistry.Registry) Applier {
return &applier{
input: input,
platformProvider: cp,
logger: logger.Named("kubernetes-applier"),
toolregistry: toolregistry,
}
}

// ApplyManifest does applying the given manifest.
func (a *applier) ApplyManifest(ctx context.Context, manifest Manifest) error {
a.initOnce.Do(func() {
a.kubectl, a.initErr = a.findKubectl(ctx, a.getToolVersionToRun())
})
if a.initErr != nil {
return a.initErr
}

if a.input.AutoCreateNamespace {
err := a.kubectl.CreateNamespace(
ctx,
a.platformProvider.KubeConfigPath,
a.getNamespaceToRun(manifest.Key),
)
if err != nil && !errors.Is(err, errResourceAlreadyExists) {
return err
}
}

return a.kubectl.Apply(
ctx,
a.platformProvider.KubeConfigPath,
a.getNamespaceToRun(manifest.Key),
manifest,
)
}

// CreateManifest uses kubectl to create the given manifests.
func (a *applier) CreateManifest(ctx context.Context, manifest Manifest) error {
a.initOnce.Do(func() {
a.kubectl, a.initErr = a.findKubectl(ctx, a.getToolVersionToRun())
})
if a.initErr != nil {
return a.initErr
}

if a.input.AutoCreateNamespace {
err := a.kubectl.CreateNamespace(
ctx,
a.platformProvider.KubeConfigPath,
a.getNamespaceToRun(manifest.Key),
)
if err != nil && !errors.Is(err, errResourceAlreadyExists) {
return err
}
}

return a.kubectl.Create(
ctx,
a.platformProvider.KubeConfigPath,
a.getNamespaceToRun(manifest.Key),
manifest,
)
}

// ReplaceManifest uses kubectl to replace the given manifests.
func (a *applier) ReplaceManifest(ctx context.Context, manifest Manifest) error {
a.initOnce.Do(func() {
a.kubectl, a.initErr = a.findKubectl(ctx, a.getToolVersionToRun())
})
if a.initErr != nil {
return a.initErr
}

err := a.kubectl.Replace(
ctx,
a.platformProvider.KubeConfigPath,
a.getNamespaceToRun(manifest.Key),
manifest,
)
if err == nil {
return nil
}

if errors.Is(err, errorReplaceNotFound) {
return ErrNotFound
}

return err
}

// ForceReplaceManifest uses kubectl to forcefully replace the given manifests.
func (a *applier) ForceReplaceManifest(ctx context.Context, manifest Manifest) error {
a.initOnce.Do(func() {
a.kubectl, a.initErr = a.findKubectl(ctx, a.getToolVersionToRun())
})
if a.initErr != nil {
return a.initErr
}

err := a.kubectl.ForceReplace(
ctx,
a.platformProvider.KubeConfigPath,
a.getNamespaceToRun(manifest.Key),
manifest,
)
if err == nil {
return nil
}

if errors.Is(err, errorReplaceNotFound) {
return ErrNotFound
}

return err
}

// Delete deletes the given resource from Kubernetes cluster.
// If the resource key is different, this returns ErrNotFound.
func (a *applier) Delete(ctx context.Context, k ResourceKey) (err error) {
a.initOnce.Do(func() {
a.kubectl, a.initErr = a.findKubectl(ctx, a.getToolVersionToRun())
})
if a.initErr != nil {
return a.initErr
}

m, err := a.kubectl.Get(
ctx,
a.platformProvider.KubeConfigPath,
a.getNamespaceToRun(k),
k,
)

if err != nil {
return err
}

if k.String() != m.GetAnnotations()[LabelResourceKey] {
return ErrNotFound
}

return a.kubectl.Delete(
ctx,
a.platformProvider.KubeConfigPath,
a.getNamespaceToRun(k),
k,
)
}

// getNamespaceToRun returns namespace used on kubectl apply/delete commands.
// priority: config.KubernetesDeploymentInput > kubernetes.ResourceKey
func (a *applier) getNamespaceToRun(k ResourceKey) string {
if a.input.Namespace != "" {
return a.input.Namespace
}
return k.Namespace
}

// getToolVersionToRun returns version of kubectl which should be used for commands.
// priority: applicationConfig.KubectlVersion > pipedConfig.KubectlVersion
func (a *applier) getToolVersionToRun() string {
if a.input.KubectlVersion != "" {
return a.input.KubectlVersion
}
return a.platformProvider.KubectlVersion
}

func (a *applier) findKubectl(ctx context.Context, version string) (*Kubectl, error) {
path, err := a.toolregistry.Kubectl(ctx, version)
if err != nil {
return nil, fmt.Errorf("no kubectl %s (%v)", version, err)
}
return NewKubectl(version, path), nil
}

type multiApplier struct {
appliers []Applier
}

// NewMultiApplier creates an applier that duplicates its operations to all the provided appliers.
func NewMultiApplier(appliers ...Applier) Applier {
return &multiApplier{
appliers: appliers,
}
}

func (a *multiApplier) ApplyManifest(ctx context.Context, manifest Manifest) error {
for _, a := range a.appliers {
if err := a.ApplyManifest(ctx, manifest); err != nil {
return err
}
}
return nil
}

func (a *multiApplier) CreateManifest(ctx context.Context, manifest Manifest) error {
for _, a := range a.appliers {
if err := a.CreateManifest(ctx, manifest); err != nil {
return err
}
}
return nil
}

func (a *multiApplier) ReplaceManifest(ctx context.Context, manifest Manifest) error {
for _, a := range a.appliers {
if err := a.ReplaceManifest(ctx, manifest); err != nil {
return err
}
}
return nil
}

func (a *multiApplier) ForceReplaceManifest(ctx context.Context, manifest Manifest) error {
for _, a := range a.appliers {
if err := a.ForceReplaceManifest(ctx, manifest); err != nil {
return err
}
}
return nil
}

func (a *multiApplier) Delete(ctx context.Context, key ResourceKey) error {
for _, a := range a.appliers {
if err := a.Delete(ctx, key); err != nil {
return err
}
}
return nil
}
68 changes: 68 additions & 0 deletions pkg/app/pipedv1/plugin/kubernetes/provider/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 (
"errors"
"fmt"

"go.uber.org/zap"

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

type AppManifestsCache struct {
AppID string
Cache cache.Cache
Logger *zap.Logger
}

func (c AppManifestsCache) Get(commit string) ([]Manifest, bool) {
key := appManifestsCacheKey(c.AppID, commit)
item, err := c.Cache.Get(key)
if err == nil {
return item.([]Manifest), true
}

if errors.Is(err, cache.ErrNotFound) {
c.Logger.Info("app manifests were not found in cache",
zap.String("app-id", c.AppID),
zap.String("commit-hash", commit),
)
return nil, false
}

c.Logger.Error("failed while retrieving app manifests from cache",
zap.String("app-id", c.AppID),
zap.String("commit-hash", commit),
zap.Error(err),
)
return nil, false
}

func (c AppManifestsCache) Put(commit string, manifests []Manifest) {
key := appManifestsCacheKey(c.AppID, commit)
if err := c.Cache.Put(key, manifests); err != nil {
c.Logger.Error("failed while putting app manifests into cache",
zap.String("app-id", c.AppID),
zap.String("commit-hash", commit),
zap.Error(err),
)
}
}

func appManifestsCacheKey(appID, commit string) string {
return fmt.Sprintf("%s/%s", appID, commit)
}
Loading

0 comments on commit c866fa3

Please sign in to comment.