diff --git a/docs/resources/bootstrap_git.md b/docs/resources/bootstrap_git.md index b181f1bd..40ea0964 100644 --- a/docs/resources/bootstrap_git.md +++ b/docs/resources/bootstrap_git.md @@ -41,6 +41,7 @@ resource "flux_bootstrap_git" "this" { - `recurse_submodules` (Boolean) Configures the GitRepository source to initialize and include Git submodules in the artifact it produces. - `registry` (String) Container registry where the toolkit images are published. Defaults to `ghcr.io/fluxcd`. - `secret_name` (String) Name of the secret the sync credentials can be found in or stored to. Defaults to `flux-system`. +- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) - `toleration_keys` (Set of String) List of toleration keys used to schedule the components pods onto nodes with matching taints. - `version` (String) Flux version. Defaults to `v0.41.2`. - `watch_all_namespaces` (Boolean) If true watch for custom resources in all namespaces. Defaults to `true`. @@ -50,6 +51,16 @@ resource "flux_bootstrap_git" "this" { - `id` (String) The ID of this resource. - `repository_files` (Map of String) Git repository files created and managed by the provider. + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) +- `delete` (String) +- `read` (String) +- `update` (String) + ### Customizing Flux The Flux controller deployments, container command arguments, node affinity, etc can be customized using [Kustomize strategic merge patches and JSON patches](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/patchMultipleObjects.md). diff --git a/go.mod b/go.mod index 28cb10ab..6c674caf 100644 --- a/go.mod +++ b/go.mod @@ -31,9 +31,11 @@ require ( github.com/google/go-containerregistry v0.13.0 github.com/hashicorp/terraform-plugin-docs v0.13.0 github.com/hashicorp/terraform-plugin-framework v1.2.0 + github.com/hashicorp/terraform-plugin-framework-timeouts v0.3.1 github.com/hashicorp/terraform-plugin-framework-validators v0.9.0 github.com/hashicorp/terraform-plugin-go v0.14.3 github.com/hashicorp/terraform-plugin-log v0.8.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1 github.com/hashicorp/terraform-plugin-testing v1.1.0 github.com/mitchellh/go-homedir v1.1.0 github.com/stretchr/testify v1.8.2 @@ -118,11 +120,10 @@ require ( github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hc-install v0.5.0 // indirect - github.com/hashicorp/hcl/v2 v2.16.0 // indirect + github.com/hashicorp/hcl/v2 v2.16.2 // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-exec v0.17.3 // indirect - github.com/hashicorp/terraform-json v0.14.0 // indirect - github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 // indirect + github.com/hashicorp/terraform-exec v0.18.1 // indirect + github.com/hashicorp/terraform-json v0.16.0 // indirect github.com/hashicorp/terraform-registry-address v0.1.0 // indirect github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -178,7 +179,7 @@ require ( github.com/xanzy/go-gitlab v0.78.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xlab/treeprint v1.1.0 // indirect - github.com/zclconf/go-cty v1.12.1 // indirect + github.com/zclconf/go-cty v1.13.1 // indirect go.starlark.net v0.0.0-20221205180719-3fd0dac74452 // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.8.0 // indirect diff --git a/go.sum b/go.sum index 5357a118..c2c60501 100644 --- a/go.sum +++ b/go.sum @@ -38,7 +38,6 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= @@ -224,7 +223,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k= @@ -274,31 +272,32 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.5.0 h1:D9bl4KayIYKEeJ4vUDe9L5huqxZXczKaykSRcmQ0xY0= github.com/hashicorp/hc-install v0.5.0/go.mod h1:JyzMfbzfSBSjoDCRPna1vi/24BEDxFaCPfdHtM5SCdo= -github.com/hashicorp/hcl/v2 v2.16.0 h1:MPq1q615H+9wBAdE3EbwEd6imSohElrIguuasbQruB0= -github.com/hashicorp/hcl/v2 v2.16.0/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= +github.com/hashicorp/hcl/v2 v2.16.2 h1:mpkHZh/Tv+xet3sy3F9Ld4FyI2tUpWe9x3XtPx9f1a0= +github.com/hashicorp/hcl/v2 v2.16.2/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-exec v0.17.3 h1:MX14Kvnka/oWGmIkyuyvL6POx25ZmKrjlaclkx3eErU= -github.com/hashicorp/terraform-exec v0.17.3/go.mod h1:+NELG0EqQekJzhvikkeQsOAZpsw0cv/03rbeQJqscAI= -github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= -github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= +github.com/hashicorp/terraform-exec v0.18.1 h1:LAbfDvNQU1l0NOQlTuudjczVhHj061fNX5H8XZxHlH4= +github.com/hashicorp/terraform-exec v0.18.1/go.mod h1:58wg4IeuAJ6LVsLUeD2DWZZoc/bYi6dzhLHzxM41980= +github.com/hashicorp/terraform-json v0.16.0 h1:UKkeWRWb23do5LNAFlh/K3N0ymn1qTOO8c+85Albo3s= +github.com/hashicorp/terraform-json v0.16.0/go.mod h1:v0Ufk9jJnk6tcIZvScHvetlKfiNTC+WS21mnXIlc0B0= github.com/hashicorp/terraform-plugin-docs v0.13.0 h1:6e+VIWsVGb6jYJewfzq2ok2smPzZrt1Wlm9koLeKazY= github.com/hashicorp/terraform-plugin-docs v0.13.0/go.mod h1:W0oCmHAjIlTHBbvtppWHe8fLfZ2BznQbuv8+UD8OucQ= github.com/hashicorp/terraform-plugin-framework v1.2.0 h1:MZjFFfULnFq8fh04FqrKPcJ/nGpHOvX4buIygT3MSNY= github.com/hashicorp/terraform-plugin-framework v1.2.0/go.mod h1:nToI62JylqXDq84weLJ/U3umUsBhZAaTmU0HXIVUOcw= +github.com/hashicorp/terraform-plugin-framework-timeouts v0.3.1 h1:5GhozvHUsrqxqku+yd0UIRTkmDLp2QPX5paL1Kq5uUA= +github.com/hashicorp/terraform-plugin-framework-timeouts v0.3.1/go.mod h1:ThtYDU8p6sJ9+SI+TYxXrw28vXxgBwYOpoPv1EojSJI= github.com/hashicorp/terraform-plugin-framework-validators v0.9.0 h1:LYz4bXh3t7bTEydXOmPDPupRRnA480B/9+jV8yZvxBA= github.com/hashicorp/terraform-plugin-framework-validators v0.9.0/go.mod h1:+BVERsnfdlhYR2YkXMBtPnmn9UsL19U3qUtSZ+Y/5MY= github.com/hashicorp/terraform-plugin-go v0.14.3 h1:nlnJ1GXKdMwsC8g1Nh05tK2wsC3+3BL/DBBxFEki+j0= github.com/hashicorp/terraform-plugin-go v0.14.3/go.mod h1:7ees7DMZ263q8wQ6E4RdIdR6nHHJtrdt4ogX5lPkX1A= github.com/hashicorp/terraform-plugin-log v0.8.0 h1:pX2VQ/TGKu+UU1rCay0OlzosNKe4Nz1pepLXj95oyy0= github.com/hashicorp/terraform-plugin-log v0.8.0/go.mod h1:1myFrhVsBLeylQzYYEV17VVjtG8oYPRFdaZs7xdW2xs= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 h1:zHcMbxY0+rFO9gY99elV/XC/UnQVg7FhRCbj1i5b7vM= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1/go.mod h1:+tNlb0wkfdsDJ7JEiERLz4HzM19HyiuIoGzTsM7rPpw= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1 h1:G9WAfb8LHeCxu7Ae8nc1agZlQOSCUWsb610iAogBhCs= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1/go.mod h1:xcOSYlRVdPLmDUoqPhO9fiO/YCN/l6MGYeTzGt5jgkQ= github.com/hashicorp/terraform-plugin-testing v1.1.0 h1:l5UuTAt7yQcThGe0dFGSCOHR4M1k0VVTqW60K2+q6AE= github.com/hashicorp/terraform-plugin-testing v1.1.0/go.mod h1:D52zIrX/2hgLsUYMj3tfiLAOFJzhGf8GDv/8nCCtPKA= github.com/hashicorp/terraform-registry-address v0.1.0 h1:W6JkV9wbum+m516rCl5/NjKxCyTVaaUBbzYcMzBDO3U= @@ -433,7 +432,6 @@ github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3V github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= @@ -497,11 +495,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= -github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty v1.13.1 h1:0a6bRwuiSHtAmqCqNOE+c2oHgepv0ctoxU4FUe43kwc= +github.com/zclconf/go-cty v1.13.1/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.starlark.net v0.0.0-20221205180719-3fd0dac74452 h1:JZtNuL6LPB+scU5yaQ6hqRlJFRiddZm2FwRt2AQqtHA= go.starlark.net v0.0.0-20221205180719-3fd0dac74452/go.mod h1:kIVgS18CjmEC3PqMd5kaJSGEifyV/CeB9x506ZJ1Vbk= diff --git a/internal/provider/resource_bootstrap_git.go b/internal/provider/resource_bootstrap_git.go index 7b457985..237942e9 100644 --- a/internal/provider/resource_bootstrap_git.go +++ b/internal/provider/resource_bootstrap_git.go @@ -61,6 +61,8 @@ import ( "github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/git/repository" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" customtypes "github.com/fluxcd/terraform-provider-flux/internal/framework/types" "github.com/fluxcd/terraform-provider-flux/internal/framework/validators" @@ -68,6 +70,11 @@ import ( ) const ( + defaultCreateTimeout = 15 * time.Minute + defaultReadTimeout = 5 * time.Minute + defaultUpdateTimeout = 15 * time.Minute + defaultDeleteTimeout = 5 * time.Minute + rfc1123LabelRegex = `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` rfc1123LabelError = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character" rfc1123DomainRegex = `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` @@ -95,6 +102,7 @@ type bootstrapGitResourceData struct { RecurseSubmodules types.Bool `tfsdk:"recurse_submodules"` KustomizationOverride types.String `tfsdk:"kustomization_override"` RepositoryFiles types.Map `tfsdk:"repository_files"` + Timeouts timeouts.Value `tfsdk:"timeouts"` } // Ensure provider defined types fully satisfy framework interfaces @@ -278,6 +286,7 @@ func (r *bootstrapGitResource) Schema(ctx context.Context, req resource.SchemaRe Description: "Git repository files created and managed by the provider.", Computed: true, }, + "timeouts": timeouts.AttributesAll(ctx), }, } } @@ -324,6 +333,14 @@ func (r *bootstrapGitResource) Create(ctx context.Context, req resource.CreateRe return } + timeout, diags := data.Timeouts.Create(ctx, defaultCreateTimeout) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + kubeClient, err := r.prd.GetKubernetesClient() if err != nil { resp.Diagnostics.AddError("Kubernetes Client", err.Error()) @@ -378,7 +395,7 @@ func (r *bootstrapGitResource) Create(ctx context.Context, req resource.CreateRe } manifestsBase := "" - err = bootstrap.Run(ctx, b, manifestsBase, installOpts, secretOpts, syncOpts, 2*time.Second, 10*time.Minute) + err = bootstrap.Run(ctx, b, manifestsBase, installOpts, secretOpts, syncOpts, 2*time.Second, timeout) if err != nil { resp.Diagnostics.AddError("Bootstrap run error", err.Error()) return @@ -418,6 +435,14 @@ func (r *bootstrapGitResource) Read(ctx context.Context, req resource.ReadReques return } + timeout, diags := data.Timeouts.Read(ctx, defaultCreateTimeout) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + gitClient, err := r.prd.GetGitClient(ctx) if err != nil { resp.Diagnostics.AddError("Git Client", err.Error()) @@ -460,70 +485,80 @@ func (r bootstrapGitResource) Update(ctx context.Context, req resource.UpdateReq return } - gitClient, err := r.prd.GetGitClient(ctx) - if err != nil { - resp.Diagnostics.AddError("Git Client", err.Error()) + timeout, diags := data.Timeouts.Update(ctx, defaultUpdateTimeout) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { return } - defer os.RemoveAll(gitClient.Path()) + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() - // Files should be removed if they are present in the state but not the plan. previousRepositoryFiles := types.MapNull(types.StringType) diags = req.State.GetAttribute(ctx, path.Root("repository_files"), &previousRepositoryFiles) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - for k := range previousRepositoryFiles.Elements() { - _, ok := data.RepositoryFiles.Elements()[k] - if ok { - continue - } - path := filepath.Join(gitClient.Path(), k) - _, err := os.Stat(path) - if errors.Is(err, os.ErrNotExist) { - tflog.Debug(ctx, "Skipping removing no longer tracked file as it does not exist", map[string]interface{}{"path": path}) - continue - } - if err != nil { - resp.Diagnostics.AddError("Could not stat no longer tracked file", err.Error()) - return - } - err = os.Remove(path) - if err != nil { - resp.Diagnostics.AddError("Could not remove no longer tracked file", err.Error()) - return - } - } - - // Write expected file contents to repo repositoryFiles := map[string]string{} diags = data.RepositoryFiles.ElementsAs(ctx, &repositoryFiles, false) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - files := map[string]io.Reader{} - for k, v := range repositoryFiles { - files[k] = strings.NewReader(v) - } - commit, signer, err := r.prd.CreateCommit("Update Flux") - if err != nil { - resp.Diagnostics.AddError("Unable to create commit", err.Error()) - return - } - _, err = gitClient.Commit(commit, signer, repository.WithFiles(files)) - if err != nil && !errors.Is(err, git.ErrNoStagedFiles) { - resp.Diagnostics.AddError("Unable to commit updated files", err.Error()) - return - } - // Only push if changes are committed - if err == nil { + + err := retry.RetryContext(ctx, timeout, func() *retry.RetryError { + gitClient, err := r.prd.GetGitClient(ctx) + if err != nil { + return retry.NonRetryableError(err) + } + defer os.RemoveAll(gitClient.Path()) + + // Files should be removed if they are present in the state but not the plan. + for k := range previousRepositoryFiles.Elements() { + _, ok := data.RepositoryFiles.Elements()[k] + if ok { + continue + } + path := filepath.Join(gitClient.Path(), k) + _, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + tflog.Debug(ctx, "Skipping removing no longer tracked file as it does not exist", map[string]interface{}{"path": path}) + continue + } + if err != nil { + retry.NonRetryableError(fmt.Errorf("Could not stat no longer tracked file: %w", err)) + } + err = os.Remove(path) + if err != nil { + return retry.NonRetryableError(fmt.Errorf("Could not remove no longer tracked file: %w", err)) + } + } + + // Write expected file contents to repo + files := map[string]io.Reader{} + for k, v := range repositoryFiles { + files[k] = strings.NewReader(v) + } + commit, signer, err := r.prd.CreateCommit("Update Flux") + if err != nil { + return retry.NonRetryableError(fmt.Errorf("Unable to create commit: %w", err)) + } + _, err = gitClient.Commit(commit, signer, repository.WithFiles(files)) + if err != nil && !errors.Is(err, git.ErrNoStagedFiles) { + return retry.NonRetryableError(fmt.Errorf("Unable to commit updated files: %w", err)) + } + // Skip pushing if no changes have been made + if err != nil { + return nil + } err = gitClient.Push(ctx) if err != nil { - resp.Diagnostics.AddError("Unable to push updated files", err.Error()) - return + return retry.RetryableError(fmt.Errorf("Unable to push file update: %w", err)) } + return nil + }) + if err != nil { + resp.Diagnostics.AddError("Could not update Flux", err.Error()) } diags = resp.State.Set(ctx, &data) @@ -538,19 +573,19 @@ func (r bootstrapGitResource) Delete(ctx context.Context, req resource.DeleteReq return } - kubeClient, err := r.prd.GetKubernetesClient() - if err != nil { - resp.Diagnostics.AddError("Kubernetes Client", err.Error()) + timeout, diags := data.Timeouts.Delete(ctx, defaultDeleteTimeout) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { return } + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() - gitClient, err := r.prd.GetGitClient(ctx) + kubeClient, err := r.prd.GetKubernetesClient() if err != nil { - resp.Diagnostics.AddError("Git Client", err.Error()) + resp.Diagnostics.AddError("Kubernetes Client", err.Error()) return } - defer os.RemoveAll(gitClient.Path()) - // TODO: Uninstall fails when flux-system namespace does not exist err = uninstall.Components(ctx, log.NopLogger{}, kubeClient, data.Namespace.ValueString(), false) if err != nil { @@ -573,35 +608,43 @@ func (r bootstrapGitResource) Delete(ctx context.Context, req resource.DeleteReq return } - // Remove all tracked files from git - for k := range data.RepositoryFiles.Elements() { - path := filepath.Join(gitClient.Path(), k) - if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { - tflog.Debug(ctx, "Skipping file removal as the file does not exist", map[string]interface{}{"path": path}) - continue + err = retry.RetryContext(ctx, timeout, func() *retry.RetryError { + gitClient, err := r.prd.GetGitClient(ctx) + if err != nil { + return retry.NonRetryableError(err) + } + defer os.RemoveAll(gitClient.Path()) + + // Remove all tracked files from git + for k := range data.RepositoryFiles.Elements() { + path := filepath.Join(gitClient.Path(), k) + if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { + tflog.Debug(ctx, "Skipping file removal as the file does not exist", map[string]interface{}{"path": path}) + continue + } + err := os.Remove(path) + if err != nil { + return retry.NonRetryableError(fmt.Errorf("Could not remove file from git repository: %w", err)) + } } - err := os.Remove(path) + // TODO: If no files are removed we should not commit anything. + commit, signer, err := r.prd.CreateCommit("Uninstall Flux") if err != nil { - resp.Diagnostics.AddError("Could not remove file from git repository", err.Error()) - return + return retry.NonRetryableError(fmt.Errorf("Unable to create commit: %w", err)) } - } - // TODO: If no files are removed we should not commit anything. - commit, signer, err := r.prd.CreateCommit("Uninstall Flux") - if err != nil { - resp.Diagnostics.AddError("Unable to create commit", err.Error()) - return - } - // TODO: If all files are removed from the repository delete will fail. This needs a test and to be fixed. - _, err = gitClient.Commit(commit, signer) - if err != nil { - resp.Diagnostics.AddError("Unable to commit removed file(s)", err.Error()) - return - } - err = gitClient.Push(ctx) + // TODO: If all files are removed from the repository delete will fail. This needs a test and to be fixed. + _, err = gitClient.Commit(commit, signer) + if err != nil { + return retry.NonRetryableError(fmt.Errorf("Unable to commit removed file(s): %w", err)) + } + err = gitClient.Push(ctx) + if err != nil { + return retry.RetryableError(fmt.Errorf("Unable to psuh removed file(s): %w", err)) + } + return nil + }) if err != nil { - resp.Diagnostics.AddError("Unable to push removed file(s)", err.Error()) - return + resp.Diagnostics.AddError("Could not delete Flux", err.Error()) } } @@ -614,6 +657,14 @@ func (r *bootstrapGitResource) ImportState(ctx context.Context, req resource.Imp } data := bootstrapGitResourceData{} + data.Timeouts = timeouts.Value{ + Object: types.ObjectNull(map[string]attr.Type{ + "create": types.StringType, + "delete": types.StringType, + "read": types.StringType, + "update": types.StringType, + }), + } data.ID = types.StringValue(req.ID) data.Namespace = data.ID