1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 21:39:40 +02:00
portainer/api/kubernetes/yaml.go
Dmitry Salakhov 2ecc8ab5c9
feat(k8s): support git automated sync for k8s applications [EE-577] (#5548)
* feat(stack): backport changes to CE EE-1189

* feat(stack): front end backport changes to CE EE-1199 (#5455)

* feat(stack): front end backport changes to CE EE-1199

* fix k8s deploy logic

* fixed web editor confirmation message typo. EE-1501

* fix(stack): fixed issue auth detail not remembered EE-1502 (#5459)

* show status in buttons

* removed onChangeRef function.

* moved buttons in git form to its own component

* removed unused variable.

Co-authored-by: ArrisLee <arris_li@hotmail.com>

* moved formvalue to kube app component

* fix(stack): failed to pull and redeploy compose format k8s stack

* fixed form value

* fix(k8s): file content overridden when deployment failed with compose format EE-1548

* updated API response to get IsComposeFormat and show appropriate text.

* feat(k8s): front end backport to CE

* feat(kube): kube app auto update backend (#5547)

* error message updates for different file type

* not display creation source for external application

* added confirmation modal to advanced app created by web editor

* stop showing confirmation modal when updating application

* disable rollback button when application type is not applicatiom form

* only update file after deployment succeded

* Revert "only update file after deployment succeded"

This reverts commit b94bd2e96f.

* fix(k8s): file content overridden when deployment failed with compose format EE-1556

* added analytics-on directive to pull and redeploy button

* fix(kube): don't valide resource control access for kube (#5568)

* added missing question mark to k8s confirmation modal

* fixed webhook format issue

* added question marks to k8s app confirmation modal

* added space in additional file list.

* ignoring error on deletion

* fix(k8s): Git authentication info not persisted

* added RepositoryMechanismTypes constant

* updated analytics functions

* covert RepositoryMechanism to constant

* fixed typo

* removed unused function.

* post tech review updates

* fixed save settings n redeploy button

* refact kub deploy logic

* Revert "refact kub deploy logic"

This reverts commit cbfdd58ece.

* feat(k8s): utilize user token for k8s auto update EE-1594

* feat(k8s): persist kub stack name EE-1630

* feat(k8s): support delete kub stack

* fix(app): updated logic to delete stack for different kind apps. (#5648)

* fix(app): updated logic to delete stack for different kind apps.

* renamed variable

* fix import

* added StackName field.

* fixed stack id not found issue.

* fix(k8s): fixed qusetion mark alignment issue in PAT field. (#5611)

* fix(k8s): fixed qusetion mark alignment issue in PAT field.

* moved inline css to file.

* fix(git-form: made auth input text full width

* add ignore deleted arg

* tech review updates

* typo fix

* fix(k8s): added console error when deleting k8s service.

* fix(console): added no-console config

* fix(deploy): added missing service.

* fix: use stack editor as an owner when exists (#5678)

* fix: tempalte/content based stacks edit/delete

* fix(stack): remove stack when no app. (#5769)

* fix(stack): remove stack when no app.

* support compose format in delete

Co-authored-by: ArrisLee <arris_li@hotmail.com>

Co-authored-by: Hui <arris_li@hotmail.com>
Co-authored-by: fhanportainer <79428273+fhanportainer@users.noreply.github.com>
Co-authored-by: Felix Han <felix.han@portainer.io>
2021-09-30 12:58:10 +13:00

174 lines
4.6 KiB
Go

package kubernetes
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)
const (
labelPortainerAppStack = "io.portainer.kubernetes.application.stack"
labelPortainerAppStackID = "io.portainer.kubernetes.application.stackid"
labelPortainerAppName = "io.portainer.kubernetes.application.name"
labelPortainerAppOwner = "io.portainer.kubernetes.application.owner"
labelPortainerAppKind = "io.portainer.kubernetes.application.kind"
)
// KubeAppLabels are labels applied to all resources deployed in a kubernetes stack
type KubeAppLabels struct {
StackID int
StackName string
Owner string
Kind string
}
// ToMap converts KubeAppLabels to a map[string]string
func (kal *KubeAppLabels) ToMap() map[string]string {
return map[string]string{
labelPortainerAppStackID: strconv.Itoa(kal.StackID),
labelPortainerAppStack: kal.StackName,
labelPortainerAppName: kal.StackName,
labelPortainerAppOwner: kal.Owner,
labelPortainerAppKind: kal.Kind,
}
}
// GetHelmAppLabels returns the labels to be applied to portainer deployed helm applications
func GetHelmAppLabels(name, owner string) map[string]string {
return map[string]string{
labelPortainerAppName: name,
labelPortainerAppOwner: owner,
}
}
// AddAppLabels adds required labels to "Resource"->metadata->labels.
// It'll add those labels to all Resource (nodes with a kind property exluding a list) it can find in provided yaml.
// Items in the yaml file could either be organised as a list or broken into multi documents.
func AddAppLabels(manifestYaml []byte, appLabels map[string]string) ([]byte, error) {
if bytes.Equal(manifestYaml, []byte("")) {
return manifestYaml, nil
}
postProcessYaml := func(yamlDoc interface{}) error {
addResourceLabels(yamlDoc, appLabels)
return nil
}
docs, err := ExtractDocuments(manifestYaml, postProcessYaml)
if err != nil {
return nil, err
}
return bytes.Join(docs, []byte("---\n")), nil
}
// ExtractDocuments extracts all the documents from a yaml file
// Optionally post-process each document with a function, which can modify the document in place.
// Pass in nil for postProcessYaml to skip post-processing.
func ExtractDocuments(manifestYaml []byte, postProcessYaml func(interface{}) error) ([][]byte, error) {
docs := make([][]byte, 0)
yamlDecoder := yaml.NewDecoder(bytes.NewReader(manifestYaml))
for {
m := make(map[string]interface{})
err := yamlDecoder.Decode(&m)
// if decoded document is empty
if m == nil {
continue
}
// if there are no more documents in the file
if errors.Is(err, io.EOF) {
break
}
// optionally post-process yaml
if postProcessYaml != nil {
if err := postProcessYaml(m); err != nil {
return nil, errors.Wrap(err, "failed to post process yaml document")
}
}
var out bytes.Buffer
yamlEncoder := yaml.NewEncoder(&out)
yamlEncoder.SetIndent(2)
if err := yamlEncoder.Encode(m); err != nil {
return nil, errors.Wrap(err, "failed to marshal yaml manifest")
}
docs = append(docs, out.Bytes())
}
return docs, nil
}
// GetNamespace returns the namespace of a kubernetes resource from its metadata
// It returns an empty string if namespace is not found in the resource
func GetNamespace(manifestYaml []byte) (string, error) {
yamlDecoder := yaml.NewDecoder(bytes.NewReader(manifestYaml))
m := make(map[string]interface{})
err := yamlDecoder.Decode(&m)
if err != nil {
return "", errors.Wrap(err, "failed to unmarshal yaml manifest when obtaining namespace")
}
if _, ok := m["metadata"]; ok {
if namespace, ok := m["metadata"].(map[string]interface{})["namespace"]; ok {
return namespace.(string), nil
}
}
return "", nil
}
func addResourceLabels(yamlDoc interface{}, appLabels map[string]string) {
m, ok := yamlDoc.(map[string]interface{})
if !ok {
return
}
kind, ok := m["kind"]
if ok && !strings.EqualFold(kind.(string), "list") {
addLabels(m, appLabels)
return
}
for _, v := range m {
switch v.(type) {
case map[string]interface{}:
addResourceLabels(v, appLabels)
case []interface{}:
for _, item := range v.([]interface{}) {
addResourceLabels(item, appLabels)
}
}
}
}
func addLabels(obj map[string]interface{}, appLabels map[string]string) {
metadata := make(map[string]interface{})
if m, ok := obj["metadata"]; ok {
metadata = m.(map[string]interface{})
}
labels := make(map[string]string)
if l, ok := metadata["labels"]; ok {
for k, v := range l.(map[string]interface{}) {
labels[k] = fmt.Sprintf("%v", v)
}
}
// merge app labels with existing labels
for k, v := range appLabels {
labels[k] = v
}
metadata["labels"] = labels
obj["metadata"] = metadata
}