Skip to content

Commit

Permalink
Merge pull request #4 from hickeyma/fix/upd-nomenclature
Browse files Browse the repository at this point in the history
Update text to include removed also
  • Loading branch information
hickeyma authored Apr 17, 2020
2 parents e9d0d7d + 3535bfc commit 8f4dcf1
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 69 deletions.
80 changes: 43 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
[![CircleCI](https://circleci.com/gh/hickeyma/helm-mapkubeapis/tree/master.svg?style=svg)](https://circleci.com/gh/hickeyma/helm-mapkubeapis/tree/master)
[![Release](https://img.shields.io/github/release/hickeyma/helm-mapkubeapis.svg?style=flat-square)](https://github.com/hickeyma/helm-mapkubeapis/releases/latest)

mapkubeapis is a simple Helm plugin which is designed to update Helm release metadata that contains deprecated Kubernetes APIs to a new instance with supported Kubernetes APIs. Jump to [background to the issue](#background-to-the-issue) for more details on the problem space that the plugin solves.

> Note: It currently supports Helm v3 only.
mapkubeapis is a simple Helm plugin which is designed to update Helm release metadata that contains deprecated or removed Kubernetes APIs to a new instance with supported Kubernetes APIs. Jump to [background to the issue](#background-to-the-issue) for more details on the problem space that the plugin solves.

## Prerequisite

- Helm v3 client with `mapkubeapis` plugin installed on the same system
- Access to the cluster(s) that Helm v3 manages. This access is similar to `kubectl` access using [kubeconfig files](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/).
- Helm client with `mapkubeapis` plugin installed on the same system
- Access to the cluster(s) that Helm manages. This access is similar to `kubectl` access using [kubeconfig files](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/).
The `--kubeconfig`, `--kube-context` and `--namespace` flags can be used to set the kubeconfig path, kube context and namespace context to override the environment configuration.

## Install
Expand All @@ -37,67 +35,75 @@ $ ./linux-amd64/helm plugin install https://github.com/hickeyma/helm-mapkubeapis

## Usage

### Map Helm deprecated Kubernetes APIs
### Map Helm deprecated or removed Kubernetes APIs

Map release deprecated Kubernetes APIs in-place:
Map release deprecated or removed Kubernetes APIs in-place:

```console
$ helm mapkubeapis [flags] RELEASE

Flags:
--dry-run simulate a command
-h, --help help for v3map
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to the kubeconfig file
--namespace string namespace scope of the release
--dry-run simulate a command
-h, --help help for mapkubeapis
--kube-context string name of the kubeconfig context to use
--kubeconfig string path to the kubeconfig file
--namespace string namespace scope of the release. For Helm v2, this is the Tiller namespace (e.g. kube-system)
-s, --release-storage string for Helm v2 only - release storage type/object. It can be 'secrets' or 'configmaps'. This is only used with the 'tiller-out-cluster' flag (default "secrets")
--tiller-out-cluster for Helm v2 only - when Tiller is not running in the cluster e.g. Tillerless
--v2 run for Helm v2 release (default is Helm v3)
```

Example output:

```console
$ helm mapkubeapis oldapi-chrt
2020/04/08 16:15:30 Release 'oldapi-chrt' will be checked for deprecated Kubernetes APIs and will be updated if necessary to supported API versions.
2020/04/08 16:15:30 Get release 'oldapi-chrt' latest version.
2020/04/08 16:15:30 Check release 'oldapi-chrt' for deprecated APIs...
2020/04/08 16:15:30 Found deprecated Kubernetes API:
"apiVersion: extensions/v1beta1
kind: Ingress"
Supported API equivalent:
"apiVersion: networking.k8s.io/v1beta1
kind: Ingress"
2020/04/08 16:15:30 Found deprecated Kubernetes API:
$ helm mapkubeapis oldapi-chrt --namespace test
2020/04/17 13:05:45 Release 'v2-oldapi' will be checked for deprecated or removed Kubernetes APIs and will be updated if necessary to supported API versions.
2020/04/17 13:05:45 Get release 'v2-oldapi' latest version.
2020/04/17 13:05:45 Check release 'v2-oldapi' for deprecated or removed APIs...
2020/04/17 13:05:45 Found deprecated or removed Kubernetes API:
"apiVersion: apps/v1beta1
kind: Deployment"
Supported API equivalent:
"apiVersion: apps/v1
kind: Deployment"
2020/04/08 16:15:30 Finished checking release 'oldapi-chrt' for deprecated APIs.
2020/04/08 16:15:30 Deprecated APIs exist, updating release: oldapi-chrt.
2020/04/08 16:15:30 Set status of release version 'oldapi-chrt.v1' to 'superseded'.
2020/04/08 16:15:30 Release version 'oldapi-chrt.v1' updated successfully.
2020/04/08 16:15:30 Add release version 'oldapi-chrt.v2' with updated supported APIs.
2020/04/08 16:15:30 Release version 'oldapi-chrt.v2' added successfully.
2020/04/08 16:15:30 Release 'oldapi-chrt' with deprecated APIs updated successfully to new version.
2020/04/08 16:15:30 Map of release 'oldapi-chrt' deprecated APIs to supported APIs, completed successfully.
2020/04/17 13:05:45 Found deprecated or removed Kubernetes API:
"apiVersion: extensions/v1beta1
kind: Ingress"
Supported API equivalent:
"apiVersion: networking.k8s.io/v1beta1
kind: Ingress"
2020/04/17 13:05:45 Finished checking release 'v2-oldapi' for deprecated or removed APIs.
2020/04/17 13:05:45 Deprecated or removed APIs exist, updating release: v2-oldapi.
2020/04/17 13:05:45 Set status of release version 'v2-oldapi.v1' to 'superseded'.
2020/04/17 13:05:45 Release version 'v2-oldapi.v1' updated successfully.
2020/04/17 13:05:45 Add release version 'v2-oldapi.v2' with updated supported APIs.
2020/04/17 13:05:45 Release version 'v2-oldapi.v2' added successfully.
2020/04/17 13:05:45 Release 'v2-oldapi' with deprecated or removed APIs updated successfully to new version.
2020/04/17 13:05:45 Map of release 'v2-oldapi' deprecated or removed APIs to supported versions, completed successfully.
```

## Background to the issue

Helm chart templates uses `API version` and `Kind` properties when defining Kuberentes resources, similar to manifest files. Kubernetes can deprecate `API versions` between minor releases. An example of such [deprecation is in Kubernetes 1.16](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/).
Kubernetes is an API-driven system and the API evolves over time to reflect the evolving understanding of the problem space. This is common practice across systems and their APIs. An important part of evolving APIs is a good deprecation policy and process to inform users of how changes to APIs are implemented. In other words, consumers of your API need to know in advance in what release an API will be removed or changed. This removes the element of surprise and unexpected breaking changes to the consumers.

The [Kubernetes deprecation policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) documents how Kubernetes handles the changes to its API versions. The policy for deprecation states the timeframe that API versions will be supported following a deprecation announcement. It is therefore important to be aware of deprecation announcements to mimimize the effect to you once an API version goes out of support.
This is an example of [the removal of deprecated API versions in Kubernetes 1.16](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/).

Helm chart templates uses Kubernetes `API version` and `Kind` properties when defining Kuberentes resources, similar to manifest files. This therefore means that Helm users and chart maintainers need to be aware when Kubvernetes API versions have been deprecated and in what Kubernetes version they will removed.

There is a Kubernetes API deprecation policy and all chart maintainers needs to be cognisant of this and update chart Kubernetes APIs appropriately. This is not such an issue when installing a chart as it will just fail if the chart API versions are not fully compliant. You then need to get the latest chart version or update the chart yourself.
This is not such a big issue when installing a chart as it will just fail if the chart API versions are no longer supported. In this situation, you then need to get the latest chart version or update the chart yourself.

This is however become a problem for Helm releases that are already deployed with APIs that are within the deprecation time period. If the Kubernetes cluster (containing such releases) is updated to a later version where the APIs become deprecated, then Helm becomes unable to manage such releases anymore. It does not matter is the chart being passed in the upgrade contains the supported API.
This does however become a problem for Helm releases that are already deployed with APIs that are no longer supported. If the Kubernetes cluster (containing such releases) is updated to a version where the APIs are removed, then Helm becomes unable to manage such releases anymore. It does not matter if the chart being passed in the upgrade contains the supported API versions.

An example of this is the `helm upgrade` command. It fails with an error similar to the following:
It fails with an error similar to the following:

```
Error: UPGRADE FAILED: unable to build kubernetes objects from current release manifest: unable to recognize "": no matches for kind "Deployment" in version "apps/v1beta1"
```

Helm fails because it attempts to create a diff patch between the current deployed release which contains the Kubernetes APIs that are deprecated against the chart you are passing with the updated/supported API versions. The underlying reason for failure is due to when Kubernetes removes an API version, the Go libraries can no longer parse the deprecated objects and Helm therefore fails calling the libraries.
Helm fails because it attempts to create a diff patch between the current deployed release which contains the Kubernetes APIs that are removed against the chart you are passing with the updated/supported API versions. The underlying reason for failure is due because when Kubernetes removes an API version, its Go libraries can no longer parse the removed objects and Helm therefore fails calling the libraries.

The `mapkubeapis` plugin fixes the issue by mapping releases which contain deprecated Kubernetes APIs to supported APIs. This is performed inline in the release metadata where the existing release is `superseded` and a new release (metadata only) is added. The deployed Kubernetes resources are updated automatically by Kubernetes during upgrade of its version. Once this operation is completed, you can then upgrade using the chart with supported APIs.
The `mapkubeapis` plugin fixes the issue by mapping releases which contain deprecated or removed Kubernetes APIs to supported APIs. This is performed inline in the release metadata where the existing release is `superseded` and a new release (metadata only) is added. The deployed Kubernetes resources are updated automatically by Kubernetes during upgrade of its version. Once this operation is completed, you can then upgrade using the chart with supported APIs.

## Developer (From Source) Install

Expand Down
20 changes: 10 additions & 10 deletions cmd/mapkubeapis/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ var (
func newMapCmd(out io.Writer, args []string) *cobra.Command {
cmd := &cobra.Command{
Use: "mapkubeapis [flags] RELEASE",
Short: "Map release deprecated Kubernetes APIs in-place",
Long: "Map release deprecated Kubernetes APIs in-place",
Short: "Map release deprecated or removed Kubernetes APIs in-place",
Long: "Map release deprecated or removed Kubernetes APIs in-place",
SilenceUsage: true,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("name of release to be mapped need to be passed")
return errors.New("name of release to be mapped needs to be passed")
}
return nil
},
Expand Down Expand Up @@ -98,17 +98,17 @@ func runMap(cmd *cobra.Command, args []string) error {
return Map(mapOptions, kubeConfig)
}

// Map checks for Kubernetes deprectaed APIs in the manifest of the last deployed release version
// and maps those deprecated APIs to the supported versions. It then adds a new release version with
// the updated APIs and supersedes the version with the deprecated APIs.
// Map checks for Kubernetes deprecated or removed APIs in the manifest of the last deployed release version
// and maps those API versions to supported versions. It then adds a new release version with
// the updated APIs and supersedes the version with the unsupported APIs.
func Map(mapOptions MapOptions, kubeConfig common.KubeConfig) error {
if mapOptions.DryRun {
log.Println("NOTE: This is in dry-run mode, the following actions will not be executed.")
log.Println("Run without --dry-run to take the actions described below:")
log.Println()
}

log.Printf("Release '%s' will be checked for deprecated Kubernetes APIs and will be updated if necessary to supported API versions.\n", mapOptions.ReleaseName)
log.Printf("Release '%s' will be checked for deprecated or removed Kubernetes APIs and will be updated if necessary to supported API versions.\n", mapOptions.ReleaseName)

options := common.MapOptions{
DryRun: mapOptions.DryRun,
Expand All @@ -124,16 +124,16 @@ func Map(mapOptions MapOptions, kubeConfig common.KubeConfig) error {
if options.ReleaseNamespace == "" {
options.ReleaseNamespace = "kube-system"
}
if err := v2.MapReleaseWithDeprecatedAPIs(options); err != nil {
if err := v2.MapReleaseWithUnSupportedAPIs(options); err != nil {
return err
}
} else {
if err := v3.MapReleaseWithDeprecatedAPIs(options); err != nil {
if err := v3.MapReleaseWithUnSupportedAPIs(options); err != nil {
return err
}
}

log.Printf("Map of release '%s' deprecated APIs to supported APIs, completed successfully.\n", mapOptions.ReleaseName)
log.Printf("Map of release '%s' deprecated or removed APIs to supported versions, completed successfully.\n", mapOptions.ReleaseName)

return nil
}
10 changes: 5 additions & 5 deletions pkg/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,24 @@ var mappedAPIs = map[string]string{
"apiVersion: apps/v1beta12\nkind: ReplicaSet": "apiVersion: apps/v1\nkind: ReplicaSet",
"apiVersion: extensions/v1beta1\nkind: Ingress": "apiVersion: networking.k8s.io/v1beta1\nkind: Ingress"}

// ReplaceManifestDeprecatedAPIs returns a release manifest with deprecated or removed
// ReplaceManifestUnSupportedAPIs returns a release manifest with deprecated or removed
// Kubernetes APIs updated to supported APIs
func ReplaceManifestDeprecatedAPIs(origManifest string) string {
func ReplaceManifestUnSupportedAPIs(origManifest string) string {
var modifiedManifest string

// Check for deprecated APIs and map accordingly to supported versions
// Check for deprecated or removed APIs and map accordingly to supported versions
for deprecatedAPI, supportedAPI := range mappedAPIs {
var modManifestForAPI string
if len(modifiedManifest) <= 0 {
modManifestForAPI = strings.ReplaceAll(origManifest, deprecatedAPI, supportedAPI)
if modManifestForAPI != origManifest {
log.Printf("Found deprecated Kubernetes API:\n\"%s\"\nSupported API equivalent:\n\"%s\"\n", deprecatedAPI, supportedAPI)
log.Printf("Found deprecated or removed Kubernetes API:\n\"%s\"\nSupported API equivalent:\n\"%s\"\n", deprecatedAPI, supportedAPI)
}

} else {
modManifestForAPI = strings.ReplaceAll(modifiedManifest, deprecatedAPI, supportedAPI)
if modManifestForAPI != modifiedManifest {
log.Printf("Found deprecated Kubernetes API:\n\"%s\"\nSupported API equivalent:\n\"%s\"\n", deprecatedAPI, supportedAPI)
log.Printf("Found deprecated or removed Kubernetes API:\n\"%s\"\nSupported API equivalent:\n\"%s\"\n", deprecatedAPI, supportedAPI)
}
}
modifiedManifest = modManifestForAPI
Expand Down
16 changes: 8 additions & 8 deletions pkg/v2/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import (
common "github.com/hickeyma/helm-mapkubeapis/pkg/common"
)

// MapReleaseWithDeprecatedAPIs checks the latest release version for any deprecated APIs in its metadata
// MapReleaseWithUnSupportedAPIs checks the latest release version for any deprecated or removed APIs in its metadata
// If it finds any, it will create a new release version with the APIs mapped to the supported versions
func MapReleaseWithDeprecatedAPIs(mapOptions common.MapOptions) error {
func MapReleaseWithUnSupportedAPIs(mapOptions common.MapOptions) error {
var releaseName = mapOptions.ReleaseName
log.Printf("Get release '%s' latest version.\n", releaseName)
storageDriver := GetStorageDriver(mapOptions)
Expand All @@ -39,21 +39,21 @@ func MapReleaseWithDeprecatedAPIs(mapOptions common.MapOptions) error {
return fmt.Errorf("Failed to get release '%s' latest version due to the following error: %s", mapOptions.ReleaseName, err)
}

log.Printf("Check release '%s' for deprecated APIs...\n", releaseName)
log.Printf("Check release '%s' for deprecated or removed APIs...\n", releaseName)
var origManifest = releaseToMap.Manifest
modifiedManifest := common.ReplaceManifestDeprecatedAPIs(origManifest)
log.Printf("Finished checking release '%s' for deprecated APIs.\n", releaseName)
modifiedManifest := common.ReplaceManifestUnSupportedAPIs(origManifest)
log.Printf("Finished checking release '%s' for deprecated or removed APIs.\n", releaseName)
if modifiedManifest == origManifest {
log.Printf("Release '%s' has no deprecated APIs.\n", releaseName)
log.Printf("Release '%s' has no deprecated or removed APIs.\n", releaseName)
return nil
}

log.Printf("Deprecated APIs exist, updating release: %s.\n", releaseName)
log.Printf("Deprecated or removed APIs exist, updating release: %s.\n", releaseName)
if !mapOptions.DryRun {
if err := updateRelease(releaseToMap, modifiedManifest, storageDriver); err != nil {
return fmt.Errorf("Failed to update release '%s' due to the following error: %s", releaseName, err)
}
log.Printf("Release '%s' with deprecated APIs updated successfully to new version.\n", releaseName)
log.Printf("Release '%s' with deprecated or removed APIs updated successfully to new version.\n", releaseName)
}

return nil
Expand Down
Loading

0 comments on commit 8f4dcf1

Please sign in to comment.