Add graceful shutdown when interrupted
If implemented this permit restoring a clean state in case of signal interruption. Signed-off-by: Soule BA <soule@weave.works>
This commit is contained in:
@@ -19,10 +19,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/kustomization"
|
"github.com/fluxcd/flux2/internal/build"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ var buildKsCmd = &cobra.Command{
|
|||||||
Long: `The build command queries the Kubernetes API and fetches the specified Flux Kustomization.
|
Long: `The build command queries the Kubernetes API and fetches the specified Flux Kustomization.
|
||||||
It then uses the fetched in cluster flux kustomization to perform needed transformation on the local kustomization.yaml
|
It then uses the fetched in cluster flux kustomization to perform needed transformation on the local kustomization.yaml
|
||||||
pointed at by --path. The local kustomization.yaml is generated if it does not exist. Finally it builds the overlays using the local kustomization.yaml, and write the resulting multi-doc YAML to stdout.`,
|
pointed at by --path. The local kustomization.yaml is generated if it does not exist. Finally it builds the overlays using the local kustomization.yaml, and write the resulting multi-doc YAML to stdout.`,
|
||||||
Example: `# Create a new overlay.
|
Example: `# Build the local manifests as they were built on the cluster
|
||||||
flux build kustomization my-app --path ./path/to/local/manifests`,
|
flux build kustomization my-app --path ./path/to/local/manifests`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
RunE: buildKsCmdRun,
|
RunE: buildKsCmdRun,
|
||||||
@@ -64,17 +65,35 @@ func buildKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
|
return fmt.Errorf("invalid resource path %q", buildKsArgs.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder, err := kustomization.NewBuilder(kubeconfigArgs, name, buildKsArgs.path, kustomization.WithTimeout(rootArgs.timeout))
|
builder, err := build.NewBuilder(kubeconfigArgs, name, buildKsArgs.path, build.WithTimeout(rootArgs.timeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifests, err := builder.Build()
|
// create a signal channel
|
||||||
if err != nil {
|
sigc := make(chan os.Signal, 1)
|
||||||
return err
|
signal.Notify(sigc, os.Interrupt)
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Print(string(manifests))
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
manifests, err := builder.Build()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Print(string(manifests))
|
||||||
|
errChan <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sigc:
|
||||||
|
fmt.Println("Build cancelled... exiting.")
|
||||||
|
return builder.Cancel()
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/kustomization"
|
"github.com/fluxcd/flux2/internal/build"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ var diffKsCmd = &cobra.Command{
|
|||||||
Aliases: []string{"ks"},
|
Aliases: []string{"ks"},
|
||||||
Short: "Diff Kustomization",
|
Short: "Diff Kustomization",
|
||||||
Long: `The diff command does a build, then it performs a server-side dry-run and output the diff.`,
|
Long: `The diff command does a build, then it performs a server-side dry-run and output the diff.`,
|
||||||
Example: `# Create a new overlay.
|
Example: `# Preview changes local changes as they were applied on the cluster
|
||||||
flux diff kustomization my-app --path ./path/to/local/manifests`,
|
flux diff kustomization my-app --path ./path/to/local/manifests`,
|
||||||
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
|
||||||
RunE: diffKsCmdRun,
|
RunE: diffKsCmdRun,
|
||||||
@@ -44,7 +45,7 @@ type diffKsFlags struct {
|
|||||||
var diffKsArgs diffKsFlags
|
var diffKsArgs diffKsFlags
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Name of a file containing a file to add to the kustomization file.)")
|
diffKsCmd.Flags().StringVar(&diffKsArgs.path, "path", "", "Path to a local directory that matches the specified Kustomization.spec.path.)")
|
||||||
diffCmd.AddCommand(diffKsCmd)
|
diffCmd.AddCommand(diffKsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,17 +63,35 @@ func diffKsCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("invalid resource path %q", diffKsArgs.path)
|
return fmt.Errorf("invalid resource path %q", diffKsArgs.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder, err := kustomization.NewBuilder(kubeconfigArgs, name, diffKsArgs.path, kustomization.WithTimeout(rootArgs.timeout))
|
builder, err := build.NewBuilder(kubeconfigArgs, name, diffKsArgs.path, build.WithTimeout(rootArgs.timeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := builder.Diff()
|
// create a signal channel
|
||||||
if err != nil {
|
sigc := make(chan os.Signal, 1)
|
||||||
return err
|
signal.Notify(sigc, os.Interrupt)
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Print(output)
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
output, err := builder.Diff()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Print(output)
|
||||||
|
errChan <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sigc:
|
||||||
|
fmt.Println("Build cancelled... exiting.")
|
||||||
|
return builder.Cancel()
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/kustomization"
|
"github.com/fluxcd/flux2/internal/build"
|
||||||
"github.com/fluxcd/pkg/ssa"
|
"github.com/fluxcd/pkg/ssa"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
@@ -85,7 +85,7 @@ func TestDiffKustomization(t *testing.T) {
|
|||||||
"fluxns": allocateNamespace("flux-system"),
|
"fluxns": allocateNamespace("flux-system"),
|
||||||
}
|
}
|
||||||
|
|
||||||
b, _ := kustomization.NewBuilder(kubeconfigArgs, "podinfo", "")
|
b, _ := build.NewBuilder(kubeconfigArgs, "podinfo", "")
|
||||||
|
|
||||||
resourceManager, err := b.Manager()
|
resourceManager, err := b.Manager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
9
go.mod
9
go.mod
@@ -12,10 +12,10 @@ require (
|
|||||||
github.com/fluxcd/image-reflector-controller/api v0.15.0
|
github.com/fluxcd/image-reflector-controller/api v0.15.0
|
||||||
github.com/fluxcd/kustomize-controller/api v0.19.1
|
github.com/fluxcd/kustomize-controller/api v0.19.1
|
||||||
github.com/fluxcd/notification-controller/api v0.20.1
|
github.com/fluxcd/notification-controller/api v0.20.1
|
||||||
github.com/fluxcd/pkg/apis/kustomize v0.3.1
|
github.com/fluxcd/pkg/apis/kustomize v0.3.1 // indirect
|
||||||
github.com/fluxcd/pkg/apis/meta v0.10.2
|
github.com/fluxcd/pkg/apis/meta v0.10.2
|
||||||
github.com/fluxcd/pkg/runtime v0.12.3
|
github.com/fluxcd/pkg/runtime v0.12.3
|
||||||
github.com/fluxcd/pkg/ssa v0.10.0
|
github.com/fluxcd/pkg/ssa v0.11.0
|
||||||
github.com/fluxcd/pkg/ssh v0.3.1
|
github.com/fluxcd/pkg/ssh v0.3.1
|
||||||
github.com/fluxcd/pkg/untar v0.0.5
|
github.com/fluxcd/pkg/untar v0.0.5
|
||||||
github.com/fluxcd/pkg/version v0.0.1
|
github.com/fluxcd/pkg/version v0.0.1
|
||||||
@@ -25,7 +25,7 @@ require (
|
|||||||
github.com/gonvenience/ytbx v1.4.2
|
github.com/gonvenience/ytbx v1.4.2
|
||||||
github.com/google/go-cmp v0.5.6
|
github.com/google/go-cmp v0.5.6
|
||||||
github.com/google/go-containerregistry v0.2.0
|
github.com/google/go-containerregistry v0.2.0
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||||
github.com/homeport/dyff v1.4.6
|
github.com/homeport/dyff v1.4.6
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||||
@@ -49,7 +49,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/drone/envsubst v1.0.3
|
github.com/fluxcd/pkg/kustomize v0.0.2
|
||||||
sigs.k8s.io/kustomize/kyaml v0.13.0
|
sigs.k8s.io/kustomize/kyaml v0.13.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ require (
|
|||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 // indirect
|
||||||
github.com/emirpasic/gods v1.12.0 // indirect
|
github.com/emirpasic/gods v1.12.0 // indirect
|
||||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -194,8 +194,8 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
|
|||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
|
github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46 h1:7QPwrLT79GlD5sizHf27aoY2RTvw62mO6x7mxkScNk0=
|
||||||
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
|
github.com/drone/envsubst/v2 v2.0.0-20210730161058-179042472c46/go.mod h1:esf2rsHFNlZlxsqsZDojNBcnNs5REqIvRrWRHqX0vEU=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
|
||||||
@@ -240,10 +240,12 @@ github.com/fluxcd/pkg/apis/kustomize v0.3.1 h1:wmb5D9e1+Rr3/5O3235ERuj+h2VKUArVf
|
|||||||
github.com/fluxcd/pkg/apis/kustomize v0.3.1/go.mod h1:k2HSRd68UwgNmOYBPOd6WbX6a2MH2X/Jeh7e3s3PFPc=
|
github.com/fluxcd/pkg/apis/kustomize v0.3.1/go.mod h1:k2HSRd68UwgNmOYBPOd6WbX6a2MH2X/Jeh7e3s3PFPc=
|
||||||
github.com/fluxcd/pkg/apis/meta v0.10.2 h1:pnDBBEvfs4HaKiVAYgz+e/AQ8dLvcgmVfSeBroZ/KKI=
|
github.com/fluxcd/pkg/apis/meta v0.10.2 h1:pnDBBEvfs4HaKiVAYgz+e/AQ8dLvcgmVfSeBroZ/KKI=
|
||||||
github.com/fluxcd/pkg/apis/meta v0.10.2/go.mod h1:KQ2er9xa6koy7uoPMZjIjNudB5p4tXs+w0GO6fRcy7I=
|
github.com/fluxcd/pkg/apis/meta v0.10.2/go.mod h1:KQ2er9xa6koy7uoPMZjIjNudB5p4tXs+w0GO6fRcy7I=
|
||||||
|
github.com/fluxcd/pkg/kustomize v0.0.2 h1:ipvQrxSeuGZDsPZrVUL6tYMlTR5xqYTZp6G0Tdy2hVs=
|
||||||
|
github.com/fluxcd/pkg/kustomize v0.0.2/go.mod h1:AFwnf3OqQmpTCuwCARTGpPRMBf0ZFJNGCvW63KbgK04=
|
||||||
github.com/fluxcd/pkg/runtime v0.12.3 h1:h21AZ3YG5MAP7DxFF9hfKrP+vFzys2L7CkUbPFjbP/0=
|
github.com/fluxcd/pkg/runtime v0.12.3 h1:h21AZ3YG5MAP7DxFF9hfKrP+vFzys2L7CkUbPFjbP/0=
|
||||||
github.com/fluxcd/pkg/runtime v0.12.3/go.mod h1:imJ2xYy/d4PbSinX2IefmZk+iS2c1P5fY0js8mCE4SM=
|
github.com/fluxcd/pkg/runtime v0.12.3/go.mod h1:imJ2xYy/d4PbSinX2IefmZk+iS2c1P5fY0js8mCE4SM=
|
||||||
github.com/fluxcd/pkg/ssa v0.10.0 h1:dhgWDeqz0/zAs5guzmPx/DMPCkzZdlEiPvCs1NChAQM=
|
github.com/fluxcd/pkg/ssa v0.11.0 h1:ejEMlHPsbXMP8BJQx3+0PwoBgJur0mHiPcMNKcFwoOE=
|
||||||
github.com/fluxcd/pkg/ssa v0.10.0/go.mod h1:S+qig7BTOxop0c134y8Yv8/iQST4Kt7S2xXiFkP4VMA=
|
github.com/fluxcd/pkg/ssa v0.11.0/go.mod h1:S+qig7BTOxop0c134y8Yv8/iQST4Kt7S2xXiFkP4VMA=
|
||||||
github.com/fluxcd/pkg/ssh v0.3.1 h1:iQw07bkX2rScactX8WYv+uMDsufFOlg8M3fV2TNs244=
|
github.com/fluxcd/pkg/ssh v0.3.1 h1:iQw07bkX2rScactX8WYv+uMDsufFOlg8M3fV2TNs244=
|
||||||
github.com/fluxcd/pkg/ssh v0.3.1/go.mod h1:Sebfv4Um51PvomuYdMvKRncQW5dtKhZ5J5TA+wiHNSQ=
|
github.com/fluxcd/pkg/ssh v0.3.1/go.mod h1:Sebfv4Um51PvomuYdMvKRncQW5dtKhZ5J5TA+wiHNSQ=
|
||||||
github.com/fluxcd/pkg/untar v0.0.5 h1:UGI3Ch1UIEIaqQvMicmImL1s9npQa64DJ/ozqHKB7gk=
|
github.com/fluxcd/pkg/untar v0.0.5 h1:UGI3Ch1UIEIaqQvMicmImL1s9npQa64DJ/ozqHKB7gk=
|
||||||
|
|||||||
@@ -14,17 +14,19 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kustomization
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
|
||||||
|
"github.com/fluxcd/pkg/kustomize"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@@ -37,9 +39,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
controllerName = "kustomize-controller"
|
controllerName = "kustomize-controller"
|
||||||
controllerGroup = "kustomize.toolkit.fluxcd.io"
|
controllerGroup = "kustomize.toolkit.fluxcd.io"
|
||||||
mask string = "**SOPS**"
|
mask = "**SOPS**"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultTimeout = 80 * time.Second
|
var defaultTimeout = 80 * time.Second
|
||||||
@@ -53,6 +55,9 @@ type Builder struct {
|
|||||||
name string
|
name string
|
||||||
namespace string
|
namespace string
|
||||||
resourcesPath string
|
resourcesPath string
|
||||||
|
// mu is used to synchronize access to the kustomization file
|
||||||
|
mu sync.Mutex
|
||||||
|
action kustomize.Action
|
||||||
kustomization *kustomizev1.Kustomization
|
kustomization *kustomizev1.Kustomization
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
@@ -149,13 +154,15 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
|
|||||||
// generate kustomization.yaml if needed
|
// generate kustomization.yaml if needed
|
||||||
action, er := b.generate(*k, b.resourcesPath)
|
action, er := b.generate(*k, b.resourcesPath)
|
||||||
if er != nil {
|
if er != nil {
|
||||||
errf := CleanDirectory(b.resourcesPath, action)
|
errf := kustomize.CleanDirectory(b.resourcesPath, action)
|
||||||
err = fmt.Errorf("failed to generate kustomization.yaml: %w", fmt.Errorf("%v %v", er, errf))
|
err = fmt.Errorf("failed to generate kustomization.yaml: %w", fmt.Errorf("%v %v", er, errf))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b.action = action
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
errf := CleanDirectory(b.resourcesPath, action)
|
errf := b.Cancel()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = errf
|
err = errf
|
||||||
}
|
}
|
||||||
@@ -185,18 +192,28 @@ func (b *Builder) build() (m resmap.ResMap, err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath string) (action, error) {
|
func (b *Builder) generate(kustomization kustomizev1.Kustomization, dirPath string) (kustomize.Action, error) {
|
||||||
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization)
|
data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&kustomization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
gen := NewGenerator(unstructured.Unstructured{Object: data})
|
gen := kustomize.NewGenerator(unstructured.Unstructured{Object: data})
|
||||||
return gen.WriteFile(dirPath, WithSaveOriginalKustomization())
|
|
||||||
|
// acuire the lock
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
return gen.WriteFile(dirPath, kustomize.WithSaveOriginalKustomization())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, dirPath string) (resmap.ResMap, error) {
|
func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomization, dirPath string) (resmap.ResMap, error) {
|
||||||
fs := filesys.MakeFsOnDisk()
|
fs := filesys.MakeFsOnDisk()
|
||||||
m, err := BuildKustomization(fs, dirPath)
|
|
||||||
|
// acuire the lock
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
m, err := kustomize.BuildKustomization(fs, dirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("kustomize build failed: %w", err)
|
return nil, fmt.Errorf("kustomize build failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -208,7 +225,7 @@ func (b *Builder) do(ctx context.Context, kustomization kustomizev1.Kustomizatio
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
outRes, err := SubstituteVariables(ctx, b.client, unstructured.Unstructured{Object: data}, res)
|
outRes, err := kustomize.SubstituteVariables(ctx, b.client, unstructured.Unstructured{Object: data}, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err)
|
return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err)
|
||||||
}
|
}
|
||||||
@@ -263,3 +280,18 @@ func trimSopsData(res *resource.Resource) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cancel cancels the build
|
||||||
|
// It restores a clean reprository
|
||||||
|
func (b *Builder) Cancel() error {
|
||||||
|
// acuire the lock
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
err := kustomize.CleanDirectory(b.resourcesPath, b.action)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kustomization
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package kustomization
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/homeport/dyff/pkg/dyff"
|
"github.com/homeport/dyff/pkg/dyff"
|
||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
|
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
|
||||||
@@ -27,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (b *Builder) Manager() (*ssa.ResourceManager, error) {
|
func (b *Builder) Manager() (*ssa.ResourceManager, error) {
|
||||||
statusPoller := polling.NewStatusPoller(b.client, b.restMapper)
|
statusPoller := polling.NewStatusPoller(b.client, b.restMapper, nil)
|
||||||
owner := ssa.Owner{
|
owner := ssa.Owner{
|
||||||
Field: controllerName,
|
Field: controllerName,
|
||||||
Group: controllerGroup,
|
Group: controllerGroup,
|
||||||
@@ -53,8 +52,6 @@ func (b *Builder) Diff() (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceManager.SetOwnerLabels(objects, b.kustomization.GetName(), b.kustomization.GetNamespace())
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -65,12 +62,17 @@ func (b *Builder) Diff() (string, error) {
|
|||||||
// create an inventory of objects to be reconciled
|
// create an inventory of objects to be reconciled
|
||||||
newInventory := newInventory()
|
newInventory := newInventory()
|
||||||
for _, obj := range objects {
|
for _, obj := range objects {
|
||||||
change, liveObject, mergedObject, err := resourceManager.Diff(ctx, obj)
|
diffOptions := ssa.DiffOptions{
|
||||||
|
Exclusions: map[string]string{
|
||||||
|
"kustomize.toolkit.fluxcd.io/reconcile": "disabled",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
change, liveObject, mergedObject, err := resourceManager.Diff(ctx, obj, diffOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if b.kustomization.Spec.Force && isImmutableError(err) {
|
if b.kustomization.Spec.Force && ssa.IsImmutableError(err) {
|
||||||
output.WriteString(writeString(fmt.Sprintf("► %s created\n", obj.GetName()), bunt.Green))
|
output.WriteString(writeString(fmt.Sprintf("► %s created\n", obj.GetName()), bunt.Green))
|
||||||
} else {
|
} else {
|
||||||
output.WriteString(writeString(fmt.Sprint(`✗`, err), bunt.Red))
|
output.WriteString(writeString(fmt.Sprintf("✗ %v\n", err), bunt.Red))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -283,12 +285,3 @@ func addObjectsToInventory(inv *kustomizev1.ResourceInventory, entry *ssa.Change
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isImmutableError(err error) bool {
|
|
||||||
// Detect immutability like kubectl does
|
|
||||||
// https://github.com/kubernetes/kubectl/blob/8165f83007/pkg/cmd/apply/patcher.go#L201
|
|
||||||
if errors.IsConflict(err) || errors.IsInvalid(err) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2021 The Flux 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 kustomization
|
|
||||||
|
|
||||||
// SubstituteReference contains a reference to a resource containing
|
|
||||||
// the variables name and value.
|
|
||||||
type SubstituteReference struct {
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
@@ -1,532 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2022 The Flux 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 kustomization
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/krusty"
|
|
||||||
"sigs.k8s.io/kustomize/api/provider"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
kustypes "sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filesys"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/apis/kustomize"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
specField = "spec"
|
|
||||||
targetNSField = "targetNamespace"
|
|
||||||
patchesField = "patches"
|
|
||||||
patchesSMField = "patchesStrategicMerge"
|
|
||||||
patchesJson6902Field = "patchesJson6902"
|
|
||||||
imagesField = "images"
|
|
||||||
originalKustomizationFile = "kustomization.yaml.original"
|
|
||||||
)
|
|
||||||
|
|
||||||
type action string
|
|
||||||
|
|
||||||
const (
|
|
||||||
createdAction action = "created"
|
|
||||||
unchangedAction action = "unchanged"
|
|
||||||
)
|
|
||||||
|
|
||||||
type KustomizeGenerator struct {
|
|
||||||
kustomization unstructured.Unstructured
|
|
||||||
}
|
|
||||||
|
|
||||||
type SavingOptions func(dirPath, file string, action action) error
|
|
||||||
|
|
||||||
func NewGenerator(kustomization unstructured.Unstructured) *KustomizeGenerator {
|
|
||||||
return &KustomizeGenerator{
|
|
||||||
kustomization: kustomization,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithSaveOriginalKustomization() SavingOptions {
|
|
||||||
return func(dirPath, kfile string, action action) error {
|
|
||||||
// copy the original kustomization.yaml to the directory if we did not create it
|
|
||||||
if action != createdAction {
|
|
||||||
if err := copyFile(kfile, filepath.Join(dirPath, originalKustomizationFile)); err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return fmt.Errorf("%v %v", err, errf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFile generates a kustomization.yaml in the given directory if it does not exist.
|
|
||||||
// It apply the flux kustomize resources to the kustomization.yaml and then write the
|
|
||||||
// updated kustomization.yaml to the directory.
|
|
||||||
// It returns an action that indicates if the kustomization.yaml was created or not.
|
|
||||||
// It is the caller responsability to clean up the directory by use the provided function CleanDirectory.
|
|
||||||
// example:
|
|
||||||
// err := CleanDirectory(dirPath, action)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Fatal(err)
|
|
||||||
// }
|
|
||||||
func (kg *KustomizeGenerator) WriteFile(dirPath string, opts ...SavingOptions) (action, error) {
|
|
||||||
action, err := kg.generateKustomization(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("%v %v", err, errf)
|
|
||||||
}
|
|
||||||
|
|
||||||
kfile := filepath.Join(dirPath, konfig.DefaultKustomizationFileName())
|
|
||||||
|
|
||||||
data, err := os.ReadFile(kfile)
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("%w %s", err, errf)
|
|
||||||
}
|
|
||||||
|
|
||||||
kus := kustypes.Kustomization{
|
|
||||||
TypeMeta: kustypes.TypeMeta{
|
|
||||||
APIVersion: kustypes.KustomizationVersion,
|
|
||||||
Kind: kustypes.KustomizationKind,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := yaml.Unmarshal(data, &kus); err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("%v %v", err, errf)
|
|
||||||
}
|
|
||||||
|
|
||||||
tg, ok, err := kg.getNestedString(specField, targetNSField)
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("%v %v", err, errf)
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
kus.Namespace = tg
|
|
||||||
}
|
|
||||||
|
|
||||||
patches, err := kg.getPatches()
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("unable to get patches: %w", fmt.Errorf("%v %v", err, errf))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range patches {
|
|
||||||
kus.Patches = append(kus.Patches, kustypes.Patch{
|
|
||||||
Patch: p.Patch,
|
|
||||||
Target: adaptSelector(&p.Target),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
patchesSM, err := kg.getPatchesStrategicMerge()
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("unable to get patchesStrategicMerge: %w", fmt.Errorf("%v %v", err, errf))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range patchesSM {
|
|
||||||
kus.PatchesStrategicMerge = append(kus.PatchesStrategicMerge, kustypes.PatchStrategicMerge(p.Raw))
|
|
||||||
}
|
|
||||||
|
|
||||||
patchesJSON, err := kg.getPatchesJson6902()
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("unable to get patchesJson6902: %w", fmt.Errorf("%v %v", err, errf))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range patchesJSON {
|
|
||||||
patch, err := json.Marshal(p.Patch)
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("%v %v", err, errf)
|
|
||||||
}
|
|
||||||
kus.PatchesJson6902 = append(kus.PatchesJson6902, kustypes.Patch{
|
|
||||||
Patch: string(patch),
|
|
||||||
Target: adaptSelector(&p.Target),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
images, err := kg.getImages()
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("unable to get images: %w", fmt.Errorf("%v %v", err, errf))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, image := range images {
|
|
||||||
newImage := kustypes.Image{
|
|
||||||
Name: image.Name,
|
|
||||||
NewName: image.NewName,
|
|
||||||
NewTag: image.NewTag,
|
|
||||||
}
|
|
||||||
if exists, index := checkKustomizeImageExists(kus.Images, image.Name); exists {
|
|
||||||
kus.Images[index] = newImage
|
|
||||||
} else {
|
|
||||||
kus.Images = append(kus.Images, newImage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest, err := yaml.Marshal(kus)
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("%v %v", err, errf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the original kustomization.yaml to the directory if we did not create it
|
|
||||||
for _, opt := range opts {
|
|
||||||
if err := opt(dirPath, kfile, action); err != nil {
|
|
||||||
return action, fmt.Errorf("failed to save original kustomization.yaml: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile(kfile, manifest, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
errf := CleanDirectory(dirPath, action)
|
|
||||||
return action, fmt.Errorf("%v %v", err, errf)
|
|
||||||
}
|
|
||||||
|
|
||||||
return action, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kg *KustomizeGenerator) getPatches() ([]kustomize.Patch, error) {
|
|
||||||
patches, ok, err := kg.getNestedSlice(specField, patchesField)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultErr error
|
|
||||||
if ok {
|
|
||||||
res := make([]kustomize.Patch, 0, len(patches))
|
|
||||||
for k, p := range patches {
|
|
||||||
patch, ok := p.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("unable to convert patch %d to map[string]interface{}", k)
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
var kpatch kustomize.Patch
|
|
||||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(patch, &kpatch)
|
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
res = append(res, kpatch)
|
|
||||||
}
|
|
||||||
return res, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, resultErr
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kg *KustomizeGenerator) getPatchesStrategicMerge() ([]apiextensionsv1.JSON, error) {
|
|
||||||
patches, ok, err := kg.getNestedSlice(specField, patchesSMField)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultErr error
|
|
||||||
if ok {
|
|
||||||
res := make([]apiextensionsv1.JSON, 0, len(patches))
|
|
||||||
for k, p := range patches {
|
|
||||||
patch, ok := p.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("unable to convert patch %d to map[string]interface{}", k)
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
var kpatch apiextensionsv1.JSON
|
|
||||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(patch, &kpatch)
|
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
res = append(res, kpatch)
|
|
||||||
}
|
|
||||||
return res, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, resultErr
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kg *KustomizeGenerator) getPatchesJson6902() ([]kustomize.JSON6902Patch, error) {
|
|
||||||
patches, ok, err := kg.getNestedSlice(specField, patchesJson6902Field)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultErr error
|
|
||||||
if ok {
|
|
||||||
res := make([]kustomize.JSON6902Patch, 0, len(patches))
|
|
||||||
for k, p := range patches {
|
|
||||||
patch, ok := p.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("unable to convert patch %d to map[string]interface{}", k)
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
var kpatch kustomize.JSON6902Patch
|
|
||||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(patch, &kpatch)
|
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
res = append(res, kpatch)
|
|
||||||
}
|
|
||||||
return res, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, resultErr
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kg *KustomizeGenerator) getImages() ([]kustomize.Image, error) {
|
|
||||||
img, ok, err := kg.getNestedSlice(specField, imagesField)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultErr error
|
|
||||||
if ok {
|
|
||||||
res := make([]kustomize.Image, 0, len(img))
|
|
||||||
for k, i := range img {
|
|
||||||
im, ok := i.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("unable to convert patch %d to map[string]interface{}", k)
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
var image kustomize.Image
|
|
||||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(im, &image)
|
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
res = append(res, image)
|
|
||||||
}
|
|
||||||
return res, resultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, resultErr
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkKustomizeImageExists(images []kustypes.Image, imageName string) (bool, int) {
|
|
||||||
for i, image := range images {
|
|
||||||
if imageName == image.Name {
|
|
||||||
return true, i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kg *KustomizeGenerator) getNestedString(fields ...string) (string, bool, error) {
|
|
||||||
val, ok, err := unstructured.NestedString(kg.kustomization.Object, fields...)
|
|
||||||
if err != nil {
|
|
||||||
return "", ok, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, ok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kg *KustomizeGenerator) getNestedSlice(fields ...string) ([]interface{}, bool, error) {
|
|
||||||
val, ok, err := unstructured.NestedSlice(kg.kustomization.Object, fields...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ok, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, ok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kg *KustomizeGenerator) generateKustomization(dirPath string) (action, error) {
|
|
||||||
fs := filesys.MakeFsOnDisk()
|
|
||||||
|
|
||||||
// Determine if there already is a Kustomization file at the root,
|
|
||||||
// as this means we do not have to generate one.
|
|
||||||
for _, kfilename := range konfig.RecognizedKustomizationFileNames() {
|
|
||||||
if kpath := filepath.Join(dirPath, kfilename); fs.Exists(kpath) && !fs.IsDir(kpath) {
|
|
||||||
return unchangedAction, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scan := func(base string) ([]string, error) {
|
|
||||||
var paths []string
|
|
||||||
pvd := provider.NewDefaultDepProvider()
|
|
||||||
rf := pvd.GetResourceFactory()
|
|
||||||
err := fs.Walk(base, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if path == base {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
// If a sub-directory contains an existing kustomization file add the
|
|
||||||
// directory as a resource and do not decend into it.
|
|
||||||
for _, kfilename := range konfig.RecognizedKustomizationFileNames() {
|
|
||||||
if kpath := filepath.Join(path, kfilename); fs.Exists(kpath) && !fs.IsDir(kpath) {
|
|
||||||
paths = append(paths, path)
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
extension := filepath.Ext(path)
|
|
||||||
if extension != ".yaml" && extension != ".yml" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fContents, err := fs.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := rf.SliceFromBytes(fContents); err != nil {
|
|
||||||
return fmt.Errorf("failed to decode Kubernetes YAML from %s: %w", path, err)
|
|
||||||
}
|
|
||||||
paths = append(paths, path)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return paths, err
|
|
||||||
}
|
|
||||||
|
|
||||||
abs, err := filepath.Abs(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return unchangedAction, err
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := scan(abs)
|
|
||||||
if err != nil {
|
|
||||||
return unchangedAction, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kfile := filepath.Join(dirPath, konfig.DefaultKustomizationFileName())
|
|
||||||
f, err := fs.Create(kfile)
|
|
||||||
if err != nil {
|
|
||||||
return unchangedAction, err
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
kus := kustypes.Kustomization{
|
|
||||||
TypeMeta: kustypes.TypeMeta{
|
|
||||||
APIVersion: kustypes.KustomizationVersion,
|
|
||||||
Kind: kustypes.KustomizationKind,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var resources []string
|
|
||||||
for _, file := range files {
|
|
||||||
resources = append(resources, strings.Replace(file, abs, ".", 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
kus.Resources = resources
|
|
||||||
kd, err := yaml.Marshal(kus)
|
|
||||||
if err != nil {
|
|
||||||
// delete the kustomization file
|
|
||||||
errf := CleanDirectory(dirPath, createdAction)
|
|
||||||
return unchangedAction, fmt.Errorf("%v %v", err, errf)
|
|
||||||
}
|
|
||||||
|
|
||||||
return createdAction, os.WriteFile(kfile, kd, os.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adaptSelector(selector *kustomize.Selector) (output *kustypes.Selector) {
|
|
||||||
if selector != nil {
|
|
||||||
output = &kustypes.Selector{}
|
|
||||||
output.Gvk.Group = selector.Group
|
|
||||||
output.Gvk.Kind = selector.Kind
|
|
||||||
output.Gvk.Version = selector.Version
|
|
||||||
output.Name = selector.Name
|
|
||||||
output.Namespace = selector.Namespace
|
|
||||||
output.LabelSelector = selector.LabelSelector
|
|
||||||
output.AnnotationSelector = selector.AnnotationSelector
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove mutex when kustomize fixes the concurrent map read/write panic
|
|
||||||
var kustomizeBuildMutex sync.Mutex
|
|
||||||
|
|
||||||
// BuildKustomization wraps krusty.MakeKustomizer with the following settings:
|
|
||||||
// - load files from outside the kustomization.yaml root
|
|
||||||
// - disable plugins except for the builtin ones
|
|
||||||
func BuildKustomization(fs filesys.FileSystem, dirPath string) (resmap.ResMap, error) {
|
|
||||||
// temporary workaround for concurrent map read and map write bug
|
|
||||||
// https://github.com/kubernetes-sigs/kustomize/issues/3659
|
|
||||||
kustomizeBuildMutex.Lock()
|
|
||||||
defer kustomizeBuildMutex.Unlock()
|
|
||||||
|
|
||||||
buildOptions := &krusty.Options{
|
|
||||||
LoadRestrictions: kustypes.LoadRestrictionsNone,
|
|
||||||
PluginConfig: kustypes.DisabledPluginConfig(),
|
|
||||||
}
|
|
||||||
|
|
||||||
k := krusty.MakeKustomizer(buildOptions)
|
|
||||||
return k.Run(fs, dirPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanDirectory removes the kustomization.yaml file from the given directory.
|
|
||||||
func CleanDirectory(dirPath string, action action) error {
|
|
||||||
kfile := filepath.Join(dirPath, konfig.DefaultKustomizationFileName())
|
|
||||||
originalFile := filepath.Join(dirPath, originalKustomizationFile)
|
|
||||||
|
|
||||||
// restore old file if it exists
|
|
||||||
if _, err := os.Stat(originalFile); err == nil {
|
|
||||||
err := os.Rename(originalFile, kfile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to cleanup repository: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if action == createdAction {
|
|
||||||
return os.Remove(kfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyFile copies the contents of the file named src to the file named
|
|
||||||
// by dst. The file will be created if it does not already exist or else trucnated.
|
|
||||||
func copyFile(src, dst string) (err error) {
|
|
||||||
in, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer in.Close()
|
|
||||||
|
|
||||||
out, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
errf := out.Close()
|
|
||||||
if err == nil {
|
|
||||||
err = errf
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err = io.Copy(out, in); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2021 The Flux 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 kustomization
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/drone/envsubst"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// varsubRegex is the regular expression used to validate
|
|
||||||
// the var names before substitution
|
|
||||||
varsubRegex = "^[_[:alpha:]][_[:alpha:][:digit:]]*$"
|
|
||||||
DisabledValue = "disabled"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SubstituteVariables replaces the vars with their values in the specified resource.
|
|
||||||
// If a resource is labeled or annotated with
|
|
||||||
// 'kustomize.toolkit.fluxcd.io/substitute: disabled' the substitution is skipped.
|
|
||||||
func SubstituteVariables(
|
|
||||||
ctx context.Context,
|
|
||||||
kubeClient client.Client,
|
|
||||||
kustomization unstructured.Unstructured,
|
|
||||||
res *resource.Resource) (*resource.Resource, error) {
|
|
||||||
resData, err := res.AsYAML()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf("%s/substitute", kustomization.GetObjectKind().GroupVersionKind().Group)
|
|
||||||
|
|
||||||
if res.GetLabels()[key] == DisabledValue || res.GetAnnotations()[key] == DisabledValue {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// load vars from ConfigMaps and Secrets data keys
|
|
||||||
vars, err := loadVars(ctx, kubeClient, kustomization)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// load in-line vars (overrides the ones from resources)
|
|
||||||
substitute, ok, err := unstructured.NestedStringMap(kustomization.Object, "spec", "postBuild", "substitute")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
for k, v := range substitute {
|
|
||||||
vars[k] = strings.Replace(v, "\n", "", -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run bash variable substitutions
|
|
||||||
if len(vars) > 0 {
|
|
||||||
jsonData, err := varSubstitution(resData, vars)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("YAMLToJSON: %w", err)
|
|
||||||
}
|
|
||||||
err = res.UnmarshalJSON(jsonData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("UnmarshalJSON: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadVars(ctx context.Context, kubeClient client.Client, kustomization unstructured.Unstructured) (map[string]string, error) {
|
|
||||||
vars := make(map[string]string)
|
|
||||||
substituteFrom, err := getSubstituteFrom(kustomization)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to get subsituteFrom: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, reference := range substituteFrom {
|
|
||||||
namespacedName := types.NamespacedName{Namespace: kustomization.GetNamespace(), Name: reference.Name}
|
|
||||||
switch reference.Kind {
|
|
||||||
case "ConfigMap":
|
|
||||||
resource := &corev1.ConfigMap{}
|
|
||||||
if err := kubeClient.Get(ctx, namespacedName, resource); err != nil {
|
|
||||||
return nil, fmt.Errorf("substitute from 'ConfigMap/%s' error: %w", reference.Name, err)
|
|
||||||
}
|
|
||||||
for k, v := range resource.Data {
|
|
||||||
vars[k] = strings.Replace(v, "\n", "", -1)
|
|
||||||
}
|
|
||||||
case "Secret":
|
|
||||||
resource := &corev1.Secret{}
|
|
||||||
if err := kubeClient.Get(ctx, namespacedName, resource); err != nil {
|
|
||||||
return nil, fmt.Errorf("substitute from 'Secret/%s' error: %w", reference.Name, err)
|
|
||||||
}
|
|
||||||
for k, v := range resource.Data {
|
|
||||||
vars[k] = strings.Replace(string(v), "\n", "", -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return vars, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func varSubstitution(data []byte, vars map[string]string) ([]byte, error) {
|
|
||||||
r, _ := regexp.Compile(varsubRegex)
|
|
||||||
for v := range vars {
|
|
||||||
if !r.MatchString(v) {
|
|
||||||
return nil, fmt.Errorf("'%s' var name is invalid, must match '%s'", v, varsubRegex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := envsubst.Eval(string(data), func(s string) string {
|
|
||||||
return vars[s]
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("variable substitution failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonData, err := yaml.YAMLToJSON([]byte(output))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("YAMLToJSON: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSubstituteFrom(kustomization unstructured.Unstructured) ([]SubstituteReference, error) {
|
|
||||||
substituteFrom, ok, err := unstructured.NestedSlice(kustomization.Object, "spec", "postBuild", "substituteFrom")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultErr error
|
|
||||||
if ok {
|
|
||||||
res := make([]SubstituteReference, 0, len(substituteFrom))
|
|
||||||
for k, s := range substituteFrom {
|
|
||||||
sub, ok := s.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("unable to convert patch %d to map[string]interface{}", k)
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
var substitute SubstituteReference
|
|
||||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(sub, &substitute)
|
|
||||||
if err != nil {
|
|
||||||
resultErr = multierror.Append(resultErr, err)
|
|
||||||
}
|
|
||||||
res = append(res, substitute)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, resultErr
|
|
||||||
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user