mirror of https://github.com/fluxcd/flux2.git
Compare commits
201 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
94e9af6b2a | 4 days ago |
|
|
3fb05a604f | 4 days ago |
|
|
9b76ba19a8 | 3 weeks ago |
|
|
1e7dd5dfd8 | 3 weeks ago |
|
|
d6dec730d8 | 3 weeks ago |
|
|
0ba28f3f91 | 3 weeks ago |
|
|
55936e9366 | 4 weeks ago |
|
|
6ecad4783f | 4 weeks ago |
|
|
5759d08473 | 4 weeks ago |
|
|
5048de80f0 | 4 weeks ago |
|
|
97a437d059 | 4 weeks ago |
|
|
cfb28ffdc0 | 4 weeks ago |
|
|
ae9ef62f39 | 4 weeks ago |
|
|
69feb7214a | 4 weeks ago |
|
|
e95da82f5a | 1 month ago |
|
|
7c5f9befb4 | 1 month ago |
|
|
26a8d0c1c7 | 1 month ago |
|
|
833815c71d | 1 month ago |
|
|
31287b9b27 | 2 months ago |
|
|
28f5b553a2 | 2 months ago |
|
|
b33f173670 | 2 months ago |
|
|
d8c6ee167c | 2 months ago |
|
|
e288cb2771 | 2 months ago |
|
|
5f2a6ebc2b | 2 months ago |
|
|
cdc37c304a | 2 months ago |
|
|
60e4d99b57 | 2 months ago |
|
|
8229ffb674 | 2 months ago |
|
|
9b944da896 | 2 months ago |
|
|
5b37a6b04b | 2 months ago |
|
|
9f18062d43 | 2 months ago |
|
|
1055f28524 | 2 months ago |
|
|
7b0021c1a8 | 2 months ago |
|
|
ba997449aa | 2 months ago |
|
|
ca2f0205c4 | 2 months ago |
|
|
058525fe37 | 2 months ago |
|
|
686ee31f8a | 2 months ago |
|
|
767f235f94 | 2 months ago |
|
|
d5a2c66746 | 2 months ago |
|
|
f2ff083b8e | 2 months ago |
|
|
8c45f25f33 | 2 months ago |
|
|
f85cbfa9c8 | 2 months ago |
|
|
71a3dad213 | 2 months ago |
|
|
72e0535958 | 2 months ago |
|
|
4f2d1c3a2a | 2 months ago |
|
|
8e99cf7c93 | 2 months ago |
|
|
2bb7f38603 | 2 months ago |
|
|
0fe4449870 | 2 months ago |
|
|
7c5fb2297c | 2 months ago |
|
|
f4a811fbd3 | 2 months ago |
|
|
bb3726bb87 | 2 months ago |
|
|
333c8fe704 | 2 months ago |
|
|
83213ce83f | 2 months ago |
|
|
69718599ac | 2 months ago |
|
|
0255957dd7 | 2 months ago |
|
|
69b4b85cd9 | 2 months ago |
|
|
a9b5be7ff4 | 2 months ago |
|
|
1b46056e7d | 2 months ago |
|
|
039d79b3c2 | 2 months ago |
|
|
66b8aca399 | 2 months ago |
|
|
41c413e178 | 2 months ago |
|
|
d5f8720c4d | 2 months ago |
|
|
e6eb9d79e3 | 2 months ago |
|
|
b90d1738a9 | 3 months ago |
|
|
f9e66dee9e | 3 months ago |
|
|
f251e8e8a9 | 3 months ago |
|
|
44f0d50dbf | 3 months ago |
|
|
4664d49e29 | 3 months ago |
|
|
2997645ea3 | 3 months ago |
|
|
3247a46654 | 3 months ago |
|
|
b5ecb9bc56 | 3 months ago |
|
|
550260638d | 3 months ago |
|
|
b52d76d6e6 | 3 months ago |
|
|
95b2d855cb | 3 months ago |
|
|
52e0c9815b | 3 months ago |
|
|
154069893b | 3 months ago |
|
|
6185366b8a | 3 months ago |
|
|
f7665f4b47 | 3 months ago |
|
|
b20eb0ca22 | 3 months ago |
|
|
8000a41015 | 3 months ago |
|
|
4601a304dd | 3 months ago |
|
|
2fc09963e8 | 3 months ago |
|
|
a2b4edc2f3 | 3 months ago |
|
|
55bb3fe643 | 3 months ago |
|
|
7060770258 | 3 months ago |
|
|
c3eadad983 | 3 months ago |
|
|
e56dfcacf2 | 3 months ago |
|
|
56e73ae03c | 3 months ago |
|
|
7a2f77ffe0 | 3 months ago |
|
|
c1b2c7cae8 | 3 months ago |
|
|
79186a0055 | 3 months ago |
|
|
e7f1faea01 | 3 months ago |
|
|
74edb12bd1 | 3 months ago |
|
|
48d509d838 | 3 months ago |
|
|
948ed45f10 | 3 months ago |
|
|
6f47ae0f2f | 3 months ago |
|
|
a1f366933b | 3 months ago |
|
|
99b51ad525 | 3 months ago |
|
|
b6e0e8fd63 | 3 months ago |
|
|
9056ec029c | 3 months ago |
|
|
9caea521ea | 3 months ago |
|
|
a317f7c445 | 3 months ago |
|
|
698a68424f | 3 months ago |
|
|
5556a5cc9a | 3 months ago |
|
|
c416671ec4 | 3 months ago |
|
|
f719d2bf76 | 3 months ago |
|
|
46aa068fda | 3 months ago |
|
|
3542d61afd | 3 months ago |
|
|
0a87ed5a42 | 3 months ago |
|
|
e4dcc4bd5f | 3 months ago |
|
|
b4bc0d4932 | 3 months ago |
|
|
6cc446af00 | 3 months ago |
|
|
8db628cc90 | 3 months ago |
|
|
e765897df7 | 3 months ago |
|
|
210b2aa458 | 3 months ago |
|
|
a8e0ea495d | 3 months ago |
|
|
8fb1ccebfa | 3 months ago |
|
|
664423230d | 3 months ago |
|
|
0c8cfcdc85 | 3 months ago |
|
|
89d4467a50 | 3 months ago |
|
|
bef6f36755 | 3 months ago |
|
|
6125991b78 | 3 months ago |
|
|
64bfa02db4 | 3 months ago |
|
|
1e662e5ed9 | 3 months ago |
|
|
df57392f48 | 3 months ago |
|
|
19cd02e548 | 3 months ago |
|
|
8bc7822fe5 | 3 months ago |
|
|
e97da26435 | 3 months ago |
|
|
1a89fa419e | 3 months ago |
|
|
7c0e70b9cc | 3 months ago |
|
|
ed9ee95dbe | 3 months ago |
|
|
63a38ab228 | 3 months ago |
|
|
c2a883e25a | 3 months ago |
|
|
24ae50cfd5 | 3 months ago |
|
|
0573138e38 | 4 months ago |
|
|
2f14313646 | 4 months ago |
|
|
e135336aae | 4 months ago |
|
|
64eeda58e6 | 4 months ago |
|
|
acdf523c54 | 4 months ago |
|
|
e2abf8e358 | 4 months ago |
|
|
d340f80d75 | 4 months ago |
|
|
76d36cb429 | 4 months ago |
|
|
a7fadcd344 | 4 months ago |
|
|
f19f8611f4 | 4 months ago |
|
|
8cccb90f90 | 4 months ago |
|
|
1408bb8294 | 4 months ago |
|
|
45837d2d1b | 4 months ago |
|
|
ccb9d12927 | 5 months ago |
|
|
8b95a09319 | 5 months ago |
|
|
8176d88801 | 5 months ago |
|
|
2f850743fa | 5 months ago |
|
|
4e53b6cb8d | 5 months ago |
|
|
0bb2e3929f | 5 months ago |
|
|
82b38dfa68 | 5 months ago |
|
|
b3b404ed30 | 5 months ago |
|
|
45990633e6 | 5 months ago |
|
|
97937c55bf | 5 months ago |
|
|
f79c44ee0a | 5 months ago |
|
|
16eb212609 | 5 months ago |
|
|
5da5186b3b | 5 months ago |
|
|
158618e632 | 5 months ago |
|
|
81bd619abd | 5 months ago |
|
|
d2aa9fb996 | 5 months ago |
|
|
315dad8682 | 5 months ago |
|
|
600ec37524 | 5 months ago |
|
|
1af7e08f07 | 5 months ago |
|
|
61a19cac84 | 5 months ago |
|
|
fa8ef5b9d1 | 5 months ago |
|
|
eb5904fb9d | 6 months ago |
|
|
fda72a014c | 6 months ago |
|
|
f4d6934a6f | 6 months ago |
|
|
545b338004 | 6 months ago |
|
|
a8425f50bd | 6 months ago |
|
|
24bf751d4d | 6 months ago |
|
|
cf157ad8a3 | 6 months ago |
|
|
5a4bc9410b | 6 months ago |
|
|
de594183bd | 6 months ago |
|
|
4c343893c5 | 6 months ago |
|
|
8ae0aaa46c | 6 months ago |
|
|
6b3a1134bd | 6 months ago |
|
|
40a9b495b2 | 6 months ago |
|
|
1d34e5355b | 6 months ago |
|
|
00d0e1af25 | 6 months ago |
|
|
9f29702f54 | 6 months ago |
|
|
7626cd0c86 | 6 months ago |
|
|
5291902fd7 | 6 months ago |
|
|
1757d964c0 | 6 months ago |
|
|
999f61c02e | 6 months ago |
|
|
5eb43e4566 | 6 months ago |
|
|
ec3804cc6f | 6 months ago |
|
|
4c3aed9faf | 6 months ago |
|
|
06e3047a2f | 6 months ago |
|
|
99e6791f4b | 6 months ago |
|
|
9cad95dda5 | 6 months ago |
|
|
76c584e751 | 6 months ago |
|
|
cd4244ae65 | 6 months ago |
|
|
1d6137d39d | 7 months ago |
|
|
be8acc0cfb | 7 months ago |
|
|
2f5f40d593 | 7 months ago |
|
|
4172a8a7f9 | 7 months ago |
|
|
4addf8a528 | 7 months ago |
|
|
1df7697811 | 7 months ago |
@ -1,34 +1,13 @@
|
||||
name: backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed, labeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
permissions: read-all
|
||||
jobs:
|
||||
pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
backport:
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Create backport PRs
|
||||
uses: korthout/backport-action@436145e922f9561fc5ea157ff406f21af2d6b363 # v3.2.0
|
||||
# xref: https://github.com/korthout/backport-action#inputs
|
||||
with:
|
||||
# Use token to allow workflows to be triggered for the created PR
|
||||
github_token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
# Match labels with a pattern `backport:<target-branch>`
|
||||
label_pattern: '^backport:([^ ]+)$'
|
||||
# A bit shorter pull-request title than the default
|
||||
pull_title: '[${target_branch}] ${pull_title}'
|
||||
# Simpler PR description than default
|
||||
pull_description: |-
|
||||
Automated backport to `${target_branch}`, triggered by a label in #${pull_number}.
|
||||
contents: write # for reading and creating branches.
|
||||
pull-requests: write # for creating pull requests against release branches.
|
||||
uses: fluxcd/gha-workflows/.github/workflows/backport.yaml@v0.4.0
|
||||
secrets:
|
||||
github-token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
|
||||
)
|
||||
|
||||
// swapi.ArtifactGenerator
|
||||
|
||||
var artifactGeneratorType = apiType{
|
||||
kind: swapi.ArtifactGeneratorKind,
|
||||
humanKind: "artifactgenerator",
|
||||
groupVersion: swapi.GroupVersion,
|
||||
}
|
||||
|
||||
type artifactGeneratorAdapter struct {
|
||||
*swapi.ArtifactGenerator
|
||||
}
|
||||
|
||||
func (h artifactGeneratorAdapter) asClientObject() client.Object {
|
||||
return h.ArtifactGenerator
|
||||
}
|
||||
|
||||
func (h artifactGeneratorAdapter) deepCopyClientObject() client.Object {
|
||||
return h.ArtifactGenerator.DeepCopy()
|
||||
}
|
||||
|
||||
// swapi.ArtifactGeneratorList
|
||||
|
||||
type artifactGeneratorListAdapter struct {
|
||||
*swapi.ArtifactGeneratorList
|
||||
}
|
||||
|
||||
func (h artifactGeneratorListAdapter) asClientList() client.ObjectList {
|
||||
return h.ArtifactGeneratorList
|
||||
}
|
||||
|
||||
func (h artifactGeneratorListAdapter) len() int {
|
||||
return len(h.ArtifactGeneratorList.Items)
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
//go:build e2e
|
||||
// +build e2e
|
||||
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateTenant(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
assert assertFunc
|
||||
}{
|
||||
{
|
||||
name: "no args",
|
||||
args: "create tenant",
|
||||
assert: assertError("name is required"),
|
||||
},
|
||||
{
|
||||
name: "no namespace",
|
||||
args: "create tenant dev-team --cluster-role=cluster-admin",
|
||||
assert: assertError("with-namespace is required"),
|
||||
},
|
||||
{
|
||||
name: "basic tenant",
|
||||
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --export",
|
||||
assert: assertGoldenFile("./testdata/create_tenant/tenant-basic.yaml"),
|
||||
},
|
||||
{
|
||||
name: "tenant with custom serviceaccount",
|
||||
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --with-service-account=flux-tenant --export",
|
||||
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-service-account.yaml"),
|
||||
},
|
||||
{
|
||||
name: "tenant with custom cluster role",
|
||||
args: "create tenant dev-team --with-namespace=apps --cluster-role=custom-role --export",
|
||||
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-cluster-role.yaml"),
|
||||
},
|
||||
{
|
||||
name: "tenant with skip namespace",
|
||||
args: "create tenant dev-team --with-namespace=apps --cluster-role=cluster-admin --skip-namespace --export",
|
||||
assert: assertGoldenFile("./testdata/create_tenant/tenant-with-skip-namespace.yaml"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := cmdTestCase{
|
||||
args: tt.args,
|
||||
assert: tt.assert,
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var exportArtifactCmd = &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Export artifact objects",
|
||||
Long: `The export artifact sub-commands export artifacts objects in YAML format.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportCmd.AddCommand(exportArtifactCmd)
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
|
||||
)
|
||||
|
||||
var exportArtifactGeneratorCmd = &cobra.Command{
|
||||
Use: "generator [name]",
|
||||
Short: "Export ArtifactGenerator resources in YAML format",
|
||||
Long: "The export artifact generator command exports one or all ArtifactGenerator resources in YAML format.",
|
||||
Example: ` # Export all ArtifactGenerator resources
|
||||
flux export artifact generator --all > artifact-generators.yaml
|
||||
|
||||
# Export a specific generator
|
||||
flux export artifact generator my-generator > my-generator.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),
|
||||
RunE: exportCommand{
|
||||
object: artifactGeneratorAdapter{&swapi.ArtifactGenerator{}},
|
||||
list: artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportArtifactCmd.AddCommand(exportArtifactGeneratorCmd)
|
||||
}
|
||||
|
||||
// Export returns an ArtifactGenerator value which has
|
||||
// extraneous information stripped out.
|
||||
func exportArtifactGenerator(item *swapi.ArtifactGenerator) interface{} {
|
||||
gvk := swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)
|
||||
export := swapi.ArtifactGenerator{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: gvk.Kind,
|
||||
APIVersion: gvk.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: item.Name,
|
||||
Namespace: item.Namespace,
|
||||
Labels: item.Labels,
|
||||
Annotations: item.Annotations,
|
||||
},
|
||||
Spec: item.Spec,
|
||||
}
|
||||
return export
|
||||
}
|
||||
|
||||
func (ex artifactGeneratorAdapter) export() interface{} {
|
||||
return exportArtifactGenerator(ex.ArtifactGenerator)
|
||||
}
|
||||
|
||||
func (ex artifactGeneratorListAdapter) exportItem(i int) interface{} {
|
||||
return exportArtifactGenerator(&ex.ArtifactGeneratorList.Items[i])
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
)
|
||||
|
||||
var exportSourceExternalCmd = &cobra.Command{
|
||||
Use: "external [name]",
|
||||
Short: "Export ExternalArtifact sources in YAML format",
|
||||
Long: "The export source external command exports one or all ExternalArtifact sources in YAML format.",
|
||||
Example: ` # Export all ExternalArtifact sources
|
||||
flux export source external --all > sources.yaml
|
||||
|
||||
# Export a specific ExternalArtifact
|
||||
flux export source external my-artifact > source.yaml`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)),
|
||||
RunE: exportWithSecretCommand{
|
||||
list: externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},
|
||||
object: externalArtifactAdapter{&sourcev1.ExternalArtifact{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportSourceCmd.AddCommand(exportSourceExternalCmd)
|
||||
}
|
||||
|
||||
func exportExternalArtifact(source *sourcev1.ExternalArtifact) any {
|
||||
gvk := sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)
|
||||
export := sourcev1.ExternalArtifact{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: gvk.Kind,
|
||||
APIVersion: gvk.GroupVersion().String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: source.Name,
|
||||
Namespace: source.Namespace,
|
||||
Labels: source.Labels,
|
||||
Annotations: source.Annotations,
|
||||
},
|
||||
Spec: source.Spec,
|
||||
}
|
||||
return export
|
||||
}
|
||||
|
||||
func getExternalArtifactSecret(source *sourcev1.ExternalArtifact) *types.NamespacedName {
|
||||
// ExternalArtifact does not have a secretRef in its spec, this satisfies the interface
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ex externalArtifactAdapter) secret() *types.NamespacedName {
|
||||
return getExternalArtifactSecret(ex.ExternalArtifact)
|
||||
}
|
||||
|
||||
func (ex externalArtifactListAdapter) secretItem(i int) *types.NamespacedName {
|
||||
return getExternalArtifactSecret(&ex.ExternalArtifactList.Items[i])
|
||||
}
|
||||
|
||||
func (ex externalArtifactAdapter) export() any {
|
||||
return exportExternalArtifact(ex.ExternalArtifact)
|
||||
}
|
||||
|
||||
func (ex externalArtifactListAdapter) exportItem(i int) any {
|
||||
return exportExternalArtifact(&ex.ExternalArtifactList.Items[i])
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var getArtifactCmd = &cobra.Command{
|
||||
Use: "artifacts",
|
||||
Aliases: []string{"artifact"},
|
||||
Short: "Get artifact object status",
|
||||
Long: `The get artifact sub-commands print the status of artifact objects.`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
getCmd.AddCommand(getArtifactCmd)
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
swapi "github.com/fluxcd/source-watcher/api/v2/v1beta1"
|
||||
)
|
||||
|
||||
var getArtifactGeneratorCmd = &cobra.Command{
|
||||
Use: "generators",
|
||||
Aliases: []string{"generator"},
|
||||
Short: "Get artifact generator statuses",
|
||||
Long: `The get artifact generator command prints the statuses of the resources.`,
|
||||
Example: ` # List all ArtifactGenerators and their status
|
||||
flux get artifact generators`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(swapi.GroupVersion.WithKind(swapi.ArtifactGeneratorKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: receiverType,
|
||||
list: artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{}},
|
||||
funcMap: make(typeMap),
|
||||
}
|
||||
|
||||
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
|
||||
o, ok := obj.(*swapi.ArtifactGenerator)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("impossible to cast type %#v generator", obj)
|
||||
}
|
||||
|
||||
sink := artifactGeneratorListAdapter{&swapi.ArtifactGeneratorList{
|
||||
Items: []swapi.ArtifactGenerator{
|
||||
*o,
|
||||
}}}
|
||||
return sink, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := get.run(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
getArtifactCmd.AddCommand(getArtifactGeneratorCmd)
|
||||
}
|
||||
|
||||
func (s artifactGeneratorListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := s.Items[i]
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
cases.Title(language.English).String(strconv.FormatBool(item.IsDisabled())), status, msg)
|
||||
}
|
||||
|
||||
func (s artifactGeneratorListAdapter) headers(includeNamespace bool) []string {
|
||||
headers := []string{"Name", "Suspended", "Ready", "Message"}
|
||||
if includeNamespace {
|
||||
return append(namespaceHeader, headers...)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func (s artifactGeneratorListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
|
||||
item := s.Items[i]
|
||||
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
)
|
||||
|
||||
var getSourceExternalCmd = &cobra.Command{
|
||||
Use: "external",
|
||||
Short: "Get ExternalArtifact source statuses",
|
||||
Long: `The get sources external command prints the status of the ExternalArtifact sources.`,
|
||||
Example: ` # List all ExternalArtifacts and their status
|
||||
flux get sources external
|
||||
|
||||
# List ExternalArtifacts from all namespaces
|
||||
flux get sources external --all-namespaces`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.ExternalArtifactKind)),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
get := getCommand{
|
||||
apiType: externalArtifactType,
|
||||
list: &externalArtifactListAdapter{&sourcev1.ExternalArtifactList{}},
|
||||
funcMap: make(typeMap),
|
||||
}
|
||||
|
||||
err := get.funcMap.registerCommand(get.apiType.kind, func(obj runtime.Object) (summarisable, error) {
|
||||
o, ok := obj.(*sourcev1.ExternalArtifact)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("impossible to cast type %#v to ExternalArtifact", obj)
|
||||
}
|
||||
|
||||
sink := &externalArtifactListAdapter{&sourcev1.ExternalArtifactList{
|
||||
Items: []sourcev1.ExternalArtifact{
|
||||
*o,
|
||||
}}}
|
||||
return sink, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := get.run(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
getSourceCmd.AddCommand(getSourceExternalCmd)
|
||||
}
|
||||
|
||||
func (a *externalArtifactListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
|
||||
item := a.Items[i]
|
||||
var revision string
|
||||
if item.Status.Artifact != nil {
|
||||
revision = item.Status.Artifact.Revision
|
||||
}
|
||||
status, msg := statusAndMessage(item.Status.Conditions)
|
||||
revision = utils.TruncateHex(revision)
|
||||
msg = utils.TruncateHex(msg)
|
||||
|
||||
var source string
|
||||
if item.Spec.SourceRef != nil {
|
||||
source = fmt.Sprintf("%s/%s/%s",
|
||||
item.Spec.SourceRef.Kind,
|
||||
item.Spec.SourceRef.Namespace,
|
||||
item.Spec.SourceRef.Name)
|
||||
}
|
||||
return append(nameColumns(&item, includeNamespace, includeKind),
|
||||
revision, source, status, msg)
|
||||
}
|
||||
|
||||
func (a externalArtifactListAdapter) headers(includeNamespace bool) []string {
|
||||
headers := []string{"Name", "Revision", "Source", "Ready", "Message"}
|
||||
if includeNamespace {
|
||||
headers = append([]string{"Namespace"}, headers...)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func (a externalArtifactListAdapter) statusSelectorMatches(i int, conditionType, conditionStatus string) bool {
|
||||
item := a.Items[i]
|
||||
return statusMatches(conditionType, conditionStatus, item.Status.Conditions)
|
||||
}
|
||||
@ -0,0 +1,691 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fluxcd/pkg/ssa"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/cobra"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
imageautov1 "github.com/fluxcd/image-automation-controller/api/v1"
|
||||
imageautov1b2 "github.com/fluxcd/image-automation-controller/api/v1beta2"
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
imagev1b2 "github.com/fluxcd/image-reflector-controller/api/v1beta2"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
|
||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1"
|
||||
notificationv1b3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1"
|
||||
swv1b1 "github.com/fluxcd/source-watcher/api/v2/v1beta1"
|
||||
|
||||
"github.com/fluxcd/flux2/v2/internal/utils"
|
||||
)
|
||||
|
||||
// APIVersions holds the mapping of GroupKinds to their respective
|
||||
// latest API versions for a specific Flux version.
|
||||
type APIVersions struct {
|
||||
FluxVersion string
|
||||
LatestVersions map[schema.GroupKind]string
|
||||
}
|
||||
|
||||
// TODO: Update this mapping when new Flux minor versions are released!
|
||||
// latestAPIVersions contains the latest API versions for each GroupKind
|
||||
// for each supported Flux version. We maintain the latest two minor versions.
|
||||
var latestAPIVersions = []APIVersions{
|
||||
{
|
||||
FluxVersion: "2.7",
|
||||
LatestVersions: map[schema.GroupKind]string{
|
||||
// source-controller
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,
|
||||
|
||||
// kustomize-controller
|
||||
{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,
|
||||
|
||||
// helm-controller
|
||||
{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,
|
||||
|
||||
// notification-controller
|
||||
{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}: notificationv1.GroupVersion.Version,
|
||||
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}: notificationv1b3.GroupVersion.Version,
|
||||
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,
|
||||
|
||||
// image-reflector-controller
|
||||
{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImageRepositoryKind}: imagev1.GroupVersion.Version,
|
||||
{Group: imagev1.GroupVersion.Group, Kind: imagev1.ImagePolicyKind}: imagev1.GroupVersion.Version,
|
||||
|
||||
// image-automation-controller
|
||||
{Group: imageautov1.GroupVersion.Group, Kind: imageautov1.ImageUpdateAutomationKind}: imageautov1.GroupVersion.Version,
|
||||
|
||||
// source-watcher
|
||||
{Group: swv1b1.GroupVersion.Group, Kind: swv1b1.ArtifactGeneratorKind}: swv1b1.GroupVersion.Version,
|
||||
},
|
||||
},
|
||||
{
|
||||
FluxVersion: "2.6",
|
||||
LatestVersions: map[schema.GroupKind]string{
|
||||
// source-controller
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.BucketKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.GitRepositoryKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.OCIRepositoryKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmRepositoryKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.HelmChartKind}: sourcev1.GroupVersion.Version,
|
||||
{Group: sourcev1.GroupVersion.Group, Kind: sourcev1.ExternalArtifactKind}: sourcev1.GroupVersion.Version,
|
||||
|
||||
// kustomize-controller
|
||||
{Group: kustomizev1.GroupVersion.Group, Kind: kustomizev1.KustomizationKind}: kustomizev1.GroupVersion.Version,
|
||||
|
||||
// helm-controller
|
||||
{Group: helmv2.GroupVersion.Group, Kind: helmv2.HelmReleaseKind}: helmv2.GroupVersion.Version,
|
||||
|
||||
// notification-controller
|
||||
{Group: notificationv1.GroupVersion.Group, Kind: notificationv1.ReceiverKind}: notificationv1.GroupVersion.Version,
|
||||
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.AlertKind}: notificationv1b3.GroupVersion.Version,
|
||||
{Group: notificationv1b3.GroupVersion.Group, Kind: notificationv1b3.ProviderKind}: notificationv1b3.GroupVersion.Version,
|
||||
|
||||
// image-reflector-controller
|
||||
{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImageRepositoryKind}: imagev1b2.GroupVersion.Version,
|
||||
{Group: imagev1b2.GroupVersion.Group, Kind: imagev1b2.ImagePolicyKind}: imagev1b2.GroupVersion.Version,
|
||||
|
||||
// image-automation-controller
|
||||
{Group: imageautov1b2.GroupVersion.Group, Kind: imageautov1b2.ImageUpdateAutomationKind}: imageautov1b2.GroupVersion.Version,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Args: cobra.NoArgs,
|
||||
Short: "Migrate the Flux custom resources to their latest API version",
|
||||
Long: `The migrate command must be run before a Flux minor version upgrade.
|
||||
|
||||
The command has two modes of operation:
|
||||
|
||||
- Cluster mode (default): migrates all the Flux custom resources stored in Kubernetes etcd to their latest API version.
|
||||
- File system mode (-f): migrates the Flux custom resources defined in the manifests located in the specified path.
|
||||
`,
|
||||
Example: ` # Migrate all the Flux custom resources in the cluster.
|
||||
# This uses the current kubeconfig context and requires cluster-admin permissions.
|
||||
flux migrate
|
||||
|
||||
# Migrate all the Flux custom resources in a Git repository
|
||||
# checked out in the current working directory.
|
||||
flux migrate -f .
|
||||
|
||||
# Migrate all Flux custom resources defined in YAML and Helm YAML template files.
|
||||
flux migrate -f . --extensions=.yml,.yaml,.tpl
|
||||
|
||||
# Migrate the Flux custom resources to the latest API versions of Flux 2.6.
|
||||
flux migrate -f . --version=2.6
|
||||
|
||||
# Migrate the Flux custom resources defined in a multi-document YAML manifest file.
|
||||
flux migrate -f path/to/manifest.yaml
|
||||
|
||||
# Simulate the migration without making any changes.
|
||||
flux migrate -f . --dry-run
|
||||
|
||||
# Run the migration skipping confirmation prompts.
|
||||
flux migrate -f . --yes
|
||||
`,
|
||||
RunE: runMigrateCmd,
|
||||
}
|
||||
|
||||
var migrateFlags struct {
|
||||
yes bool
|
||||
dryRun bool
|
||||
path string
|
||||
version string
|
||||
extensions []string
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(migrateCmd)
|
||||
|
||||
migrateCmd.Flags().StringVarP(&migrateFlags.path, "path", "f", "",
|
||||
"the path to the directory containing the manifests to migrate")
|
||||
migrateCmd.Flags().StringSliceVarP(&migrateFlags.extensions, "extensions", "e", []string{".yaml", ".yml"},
|
||||
"the file extensions to consider when migrating manifests, only applicable with --path")
|
||||
migrateCmd.Flags().StringVarP(&migrateFlags.version, "version", "v", "",
|
||||
"the target Flux minor version to migrate manifests to, only applicable with --path (defaults to the version of the CLI)")
|
||||
migrateCmd.Flags().BoolVarP(&migrateFlags.yes, "yes", "y", false,
|
||||
"skip confirmation prompts when migrating manifests, only applicable with --path")
|
||||
migrateCmd.Flags().BoolVar(&migrateFlags.dryRun, "dry-run", false,
|
||||
"simulate the migration of manifests without making any changes, only applicable with --path")
|
||||
}
|
||||
|
||||
func runMigrateCmd(*cobra.Command, []string) error {
|
||||
if migrateFlags.path == "" {
|
||||
return migrateCluster()
|
||||
}
|
||||
return migrateFileSystem()
|
||||
}
|
||||
|
||||
func migrateCluster() error {
|
||||
logger.Actionf("starting migration of custom resources")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the Kubernetes client initialization failed: %w", err)
|
||||
}
|
||||
|
||||
kubeClient, err := client.New(cfg, client.Options{Scheme: utils.NewScheme()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrator := NewClusterMigrator(kubeClient, client.MatchingLabels{
|
||||
"app.kubernetes.io/part-of": "flux",
|
||||
})
|
||||
|
||||
if err := migrator.Run(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Successf("custom resources migrated successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateFileSystem() error {
|
||||
pathRoot, err := os.OpenRoot(".")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open filesystem at the current working directory: %w", err)
|
||||
}
|
||||
defer pathRoot.Close()
|
||||
|
||||
fileSystem := &osFS{pathRoot.FS()}
|
||||
yes := migrateFlags.yes
|
||||
dryRun := migrateFlags.dryRun
|
||||
path := migrateFlags.path
|
||||
extensions := migrateFlags.extensions
|
||||
var latestVersions map[schema.GroupKind]string
|
||||
|
||||
// Determine latest API versions based on the Flux version.
|
||||
if migrateFlags.version == "" {
|
||||
latestVersions = latestAPIVersions[0].LatestVersions
|
||||
} else {
|
||||
supportedVersions := make([]string, 0, len(latestAPIVersions))
|
||||
for _, v := range latestAPIVersions {
|
||||
if v.FluxVersion == migrateFlags.version {
|
||||
latestVersions = v.LatestVersions
|
||||
break
|
||||
}
|
||||
supportedVersions = append(supportedVersions, v.FluxVersion)
|
||||
}
|
||||
if latestVersions == nil {
|
||||
return fmt.Errorf("version %s is not supported, supported versions are: %s",
|
||||
migrateFlags.version, strings.Join(supportedVersions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
return NewFileSystemMigrator(fileSystem, yes, dryRun, path, extensions, latestVersions).Run()
|
||||
}
|
||||
|
||||
// ClusterMigrator migrates all the CRs in the cluster for the CRDs matching the label selector.
|
||||
type ClusterMigrator struct {
|
||||
labelSelector client.MatchingLabels
|
||||
kubeClient client.Client
|
||||
}
|
||||
|
||||
// NewClusterMigrator creates a new ClusterMigrator instance with the specified label selector.
|
||||
func NewClusterMigrator(kubeClient client.Client, labelSelector client.MatchingLabels) *ClusterMigrator {
|
||||
return &ClusterMigrator{
|
||||
labelSelector: labelSelector,
|
||||
kubeClient: kubeClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClusterMigrator) Run(ctx context.Context) error {
|
||||
crdList := &apiextensionsv1.CustomResourceDefinitionList{}
|
||||
|
||||
if err := c.kubeClient.List(ctx, crdList, c.labelSelector); err != nil {
|
||||
return fmt.Errorf("failed to list CRDs: %w", err)
|
||||
}
|
||||
|
||||
for _, crd := range crdList.Items {
|
||||
if err := c.migrateCRD(ctx, crd.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClusterMigrator) migrateCRD(ctx context.Context, name string) error {
|
||||
crd := &apiextensionsv1.CustomResourceDefinition{}
|
||||
|
||||
if err := c.kubeClient.Get(ctx, client.ObjectKey{Name: name}, crd); err != nil {
|
||||
return fmt.Errorf("failed to get CRD %s: %w", name, err)
|
||||
}
|
||||
|
||||
// get the latest storage version for the CRD
|
||||
storageVersion := c.getStorageVersion(crd)
|
||||
if storageVersion == "" {
|
||||
return fmt.Errorf("no storage version found for CRD %s", name)
|
||||
}
|
||||
|
||||
// migrate all the resources for the CRD
|
||||
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
return c.migrateCR(ctx, crd, storageVersion)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to migrate resources for CRD %s: %w", name, err)
|
||||
}
|
||||
|
||||
// set the CRD status to contain only the latest storage version
|
||||
if len(crd.Status.StoredVersions) > 1 || crd.Status.StoredVersions[0] != storageVersion {
|
||||
crd.Status.StoredVersions = []string{storageVersion}
|
||||
if err := c.kubeClient.Status().Update(ctx, crd); err != nil {
|
||||
return fmt.Errorf("failed to update CRD %s status: %w", crd.Name, err)
|
||||
}
|
||||
logger.Successf("%s migrated to storage version %s", crd.Name, storageVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateCR migrates all CRs for the given CRD to the specified version by patching them.
|
||||
func (c *ClusterMigrator) migrateCR(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition, version string) error {
|
||||
list := &unstructured.UnstructuredList{}
|
||||
|
||||
apiVersion := crd.Spec.Group + "/" + version
|
||||
listKind := crd.Spec.Names.ListKind
|
||||
|
||||
list.SetAPIVersion(apiVersion)
|
||||
list.SetKind(listKind)
|
||||
|
||||
err := c.kubeClient.List(ctx, list, client.InNamespace(""))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list resources for CRD %s: %w", crd.Name, err)
|
||||
}
|
||||
|
||||
if len(list.Items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, item := range list.Items {
|
||||
patches, err := ssa.PatchMigrateToVersion(&item, apiVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create migration patch for %s/%s/%s: %w",
|
||||
item.GetKind(), item.GetNamespace(), item.GetName(), err)
|
||||
}
|
||||
|
||||
if len(patches) == 0 {
|
||||
// patch the resource with an empty patch to update the version
|
||||
if err := c.kubeClient.Patch(
|
||||
ctx,
|
||||
&item,
|
||||
client.RawPatch(client.Merge.Type(), []byte("{}")),
|
||||
); err != nil && !apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf(" %s/%s/%s failed to migrate: %w",
|
||||
item.GetKind(), item.GetNamespace(), item.GetName(), err)
|
||||
}
|
||||
} else {
|
||||
// patch the resource to migrate the managed fields to the latest apiVersion
|
||||
rawPatch, err := json.Marshal(patches)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal migration patch for %s/%s/%s: %w",
|
||||
item.GetKind(), item.GetNamespace(), item.GetName(), err)
|
||||
}
|
||||
if err := c.kubeClient.Patch(
|
||||
ctx,
|
||||
&item,
|
||||
client.RawPatch(types.JSONPatchType, rawPatch),
|
||||
); err != nil && !apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf(" %s/%s/%s failed to migrate managed fields: %w",
|
||||
item.GetKind(), item.GetNamespace(), item.GetName(), err)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Successf("%s/%s/%s migrated to version %s",
|
||||
item.GetKind(), item.GetNamespace(), item.GetName(), version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStorageVersion retrieves the storage version of a CustomResourceDefinition.
|
||||
func (c *ClusterMigrator) getStorageVersion(crd *apiextensionsv1.CustomResourceDefinition) string {
|
||||
var version string
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if v.Storage {
|
||||
version = v.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// WritableFS extends fs.FS with a WriteFile method.
|
||||
type WritableFS interface {
|
||||
fs.FS
|
||||
WriteFile(name string, data []byte, perm os.FileMode) error
|
||||
}
|
||||
|
||||
// osFS is a WritableFS implementation that uses the file system of the OS.
|
||||
type osFS struct {
|
||||
fs.FS
|
||||
}
|
||||
|
||||
func (o *osFS) WriteFile(name string, data []byte, perm os.FileMode) error {
|
||||
return os.WriteFile(name, data, perm)
|
||||
}
|
||||
|
||||
// FileSystemMigrator migrates all the CRs found in the manifests located in the specified path.
|
||||
type FileSystemMigrator struct {
|
||||
fileSystem WritableFS
|
||||
yes bool
|
||||
dryRun bool
|
||||
path string
|
||||
extensions []string
|
||||
latestVersions map[schema.GroupKind]string
|
||||
}
|
||||
|
||||
// FileAPIUpgrades represents the API upgrades detected in a specific manifest file.
|
||||
type FileAPIUpgrades struct {
|
||||
File string
|
||||
Upgrades []APIUpgrade
|
||||
}
|
||||
|
||||
// APIUpgrade represents an upgrade of a specific API version in a manifest file.
|
||||
type APIUpgrade struct {
|
||||
Line int
|
||||
Kind string
|
||||
OldVersion string
|
||||
NewVersion string
|
||||
}
|
||||
|
||||
// NewFileSystemMigrator creates a new FileSystemMigrator instance with the specified flags.
|
||||
func NewFileSystemMigrator(fileSystem WritableFS, yes, dryRun bool, path string,
|
||||
extensions []string, latestVersions map[schema.GroupKind]string) *FileSystemMigrator {
|
||||
return &FileSystemMigrator{
|
||||
fileSystem: fileSystem,
|
||||
yes: yes,
|
||||
dryRun: dryRun,
|
||||
path: filepath.Clean(path), // convert dir/ to dir to avoid error when walking
|
||||
extensions: extensions,
|
||||
latestVersions: latestVersions,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) Run() error {
|
||||
logger.Actionf("starting migration of custom resources")
|
||||
|
||||
// List and filter files.
|
||||
files, err := f.listFiles()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Detect upgrades.
|
||||
upgrades, err := f.detectUpgrades(files)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(upgrades) == 0 {
|
||||
logger.Successf("no custom resources found that require migration")
|
||||
return nil
|
||||
}
|
||||
if f.dryRun {
|
||||
logger.Successf("dry-run mode enabled, no changes will be made")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Confirm upgrades.
|
||||
if !f.yes {
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Are you sure you want to proceed with the above upgrades", // Already prints "? [y/N]"
|
||||
IsConfirm: true,
|
||||
}
|
||||
if _, err := prompt.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate files.
|
||||
for _, fileUpgrades := range upgrades {
|
||||
if err := f.migrateFile(&fileUpgrades); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Successf("file %s migrated successfully", fileUpgrades.File)
|
||||
}
|
||||
|
||||
logger.Successf("custom resources migrated successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) listFiles() ([]string, error) {
|
||||
fileInfo, err := fs.Stat(f.fileSystem, f.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to stat path %s: %w", f.path, err)
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
return f.listDirectoryFiles()
|
||||
}
|
||||
if err := f.validateSingleFile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []string{f.path}, nil
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) listDirectoryFiles() ([]string, error) {
|
||||
var files []string
|
||||
err := fs.WalkDir(f.fileSystem, f.path, func(path string, dirEntry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !f.matchesExtensions(path) {
|
||||
return nil
|
||||
}
|
||||
fileInfo, err := dirEntry.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fileInfo.Mode().IsRegular() {
|
||||
files = append(files, path)
|
||||
} else if !fileInfo.IsDir() {
|
||||
logger.Warningf("skipping irregular file %s", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to walk directory %s: %w", f.path, err)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) validateSingleFile() error {
|
||||
if !f.matchesExtensions(f.path) {
|
||||
return fmt.Errorf("file %s does not match the specified extensions: %v",
|
||||
f.path, strings.Join(f.extensions, ", "))
|
||||
}
|
||||
|
||||
// Check if it's irregular by walking the parent directory.
|
||||
var irregular bool
|
||||
err := fs.WalkDir(f.fileSystem, filepath.Dir(f.path), func(path string, dirEntry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if path != f.path {
|
||||
return nil
|
||||
}
|
||||
fileInfo, err := dirEntry.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fileInfo.Mode().IsRegular() {
|
||||
irregular = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate file %s: %w", f.path, err)
|
||||
}
|
||||
if irregular {
|
||||
return fmt.Errorf("file %s is irregular", f.path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) matchesExtensions(file string) bool {
|
||||
for _, ext := range f.extensions {
|
||||
if strings.HasSuffix(file, ext) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) detectUpgrades(files []string) ([]FileAPIUpgrades, error) {
|
||||
var upgrades []FileAPIUpgrades
|
||||
for _, file := range files {
|
||||
fileUpgrades, err := f.detectFileUpgrades(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(fileUpgrades) == 0 {
|
||||
continue
|
||||
}
|
||||
fu := FileAPIUpgrades{
|
||||
File: file,
|
||||
Upgrades: fileUpgrades,
|
||||
}
|
||||
upgrades = append(upgrades, fu)
|
||||
f.printDetectedUpgrades(&fu)
|
||||
}
|
||||
return upgrades, nil
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) detectFileUpgrades(file string) ([]APIUpgrade, error) {
|
||||
b, err := fs.ReadFile(f.fileSystem, file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file %s: %w", file, err)
|
||||
}
|
||||
lines := strings.Split(string(b), "\n")
|
||||
|
||||
var fileUpgrades []APIUpgrade
|
||||
for line, apiVersionLine := range lines {
|
||||
// Parse apiVersion.
|
||||
const apiVersionPrefix = "apiVersion: "
|
||||
idx := strings.Index(apiVersionLine, apiVersionPrefix)
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
apiVersionValuePrefix := strings.TrimSpace(apiVersionLine[idx+len(apiVersionPrefix):])
|
||||
apiVersion := strings.Split(apiVersionValuePrefix, " ")[0]
|
||||
gv, err := schema.ParseGroupVersion(apiVersion)
|
||||
if err != nil {
|
||||
logger.Warningf("%s:%d: %v", file, line+1, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse kind.
|
||||
if line+1 >= len(lines) {
|
||||
continue
|
||||
}
|
||||
kindLine := lines[line+1]
|
||||
const kindPrefix = "kind: "
|
||||
idx = strings.Index(kindLine, kindPrefix)
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
kindValuePrefix := strings.TrimSpace(kindLine[idx+len(kindPrefix):])
|
||||
kind := strings.Split(kindValuePrefix, " ")[0]
|
||||
|
||||
// Build GroupKind.
|
||||
gk := schema.GroupKind{
|
||||
Group: gv.Group,
|
||||
Kind: kind,
|
||||
}
|
||||
|
||||
// Check if there's a newer version for the GroupKind.
|
||||
latestVersion, ok := f.latestVersions[gk]
|
||||
if !ok || latestVersion == gv.Version {
|
||||
continue
|
||||
}
|
||||
|
||||
// Record the upgrade.
|
||||
fileUpgrades = append(fileUpgrades, APIUpgrade{
|
||||
Line: line,
|
||||
Kind: kind,
|
||||
OldVersion: gv.Version,
|
||||
NewVersion: latestVersion,
|
||||
})
|
||||
}
|
||||
return fileUpgrades, nil
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) printDetectedUpgrades(fileUpgrades *FileAPIUpgrades) {
|
||||
for _, upgrade := range fileUpgrades.Upgrades {
|
||||
logger.Generatef("%s:%d: %s %s -> %s",
|
||||
fileUpgrades.File,
|
||||
upgrade.Line+1,
|
||||
upgrade.Kind,
|
||||
upgrade.OldVersion,
|
||||
upgrade.NewVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileSystemMigrator) migrateFile(fileUpgrades *FileAPIUpgrades) error {
|
||||
// Read file and map lines.
|
||||
b, err := fs.ReadFile(f.fileSystem, fileUpgrades.File)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file %s: %w", fileUpgrades.File, err)
|
||||
}
|
||||
lines := strings.Split(string(b), "\n")
|
||||
|
||||
// Apply upgrades to lines.
|
||||
for _, upgrade := range fileUpgrades.Upgrades {
|
||||
line := lines[upgrade.Line]
|
||||
line = strings.Replace(line, upgrade.OldVersion, upgrade.NewVersion, 1)
|
||||
lines[upgrade.Line] = line
|
||||
}
|
||||
|
||||
// Read file info to preserve permissions.
|
||||
fileInfo, err := fs.Stat(f.fileSystem, fileUpgrades.File)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stat file %s: %w", fileUpgrades.File, err)
|
||||
}
|
||||
|
||||
// Write file with preserved permissions.
|
||||
b = []byte(strings.Join(lines, "\n"))
|
||||
if err := f.fileSystem.WriteFile(fileUpgrades.File, b, fileInfo.Mode()); err != nil {
|
||||
return fmt.Errorf("failed to write file %s: %w", fileUpgrades.File, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/fs"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type writeToMemoryFS struct {
|
||||
fs.FS
|
||||
|
||||
writtenFiles map[string][]byte
|
||||
}
|
||||
|
||||
func (m *writeToMemoryFS) WriteFile(name string, data []byte, perm os.FileMode) error {
|
||||
m.writtenFiles[name] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
type writtenFile struct {
|
||||
file string
|
||||
goldenFile string
|
||||
}
|
||||
|
||||
func TestFileSystemMigrator(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
path string
|
||||
outputGolden string
|
||||
writtenFiles []writtenFile
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "errors out for single file that is a symlink",
|
||||
path: "testdata/migrate/file-system/single-file-link.yaml",
|
||||
err: "file testdata/migrate/file-system/single-file-link.yaml is irregular",
|
||||
},
|
||||
{
|
||||
name: "errors out for single file with wrong extension",
|
||||
path: "testdata/migrate/file-system/single-file-wrong-ext.json",
|
||||
err: "file testdata/migrate/file-system/single-file-wrong-ext.json does not match the specified extensions: .yaml, .yml",
|
||||
},
|
||||
{
|
||||
name: "migrate single file",
|
||||
path: "testdata/migrate/file-system/single-file.yaml",
|
||||
outputGolden: "testdata/migrate/file-system/single-file.yaml.output.golden",
|
||||
writtenFiles: []writtenFile{
|
||||
{
|
||||
file: "testdata/migrate/file-system/single-file.yaml",
|
||||
goldenFile: "testdata/migrate/file-system/single-file.yaml.golden",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "migrate files in directory",
|
||||
path: "testdata/migrate/file-system/dir",
|
||||
outputGolden: "testdata/migrate/file-system/dir.output.golden",
|
||||
writtenFiles: []writtenFile{
|
||||
{
|
||||
file: "testdata/migrate/file-system/dir/some-dir/another-file.yaml",
|
||||
goldenFile: "testdata/migrate/file-system/dir.golden/some-dir/another-file.yaml",
|
||||
},
|
||||
{
|
||||
file: "testdata/migrate/file-system/dir/some-dir/another-file.yml",
|
||||
goldenFile: "testdata/migrate/file-system/dir.golden/some-dir/another-file.yml",
|
||||
},
|
||||
{
|
||||
file: "testdata/migrate/file-system/dir/some-file.yaml",
|
||||
goldenFile: "testdata/migrate/file-system/dir.golden/some-file.yaml",
|
||||
},
|
||||
{
|
||||
file: "testdata/migrate/file-system/dir/some-file.yml",
|
||||
goldenFile: "testdata/migrate/file-system/dir.golden/some-file.yml",
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
// Store logger, replace with test logger, and restore at the end of the test.
|
||||
var testLogger bytes.Buffer
|
||||
oldLogger := logger
|
||||
logger = stderrLogger{&testLogger}
|
||||
t.Cleanup(func() { logger = oldLogger })
|
||||
|
||||
// Open current working directory as root and build write-to-memory filesystem.
|
||||
pathRoot, err := os.OpenRoot(".")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
t.Cleanup(func() { pathRoot.Close() })
|
||||
fileSystem := &writeToMemoryFS{
|
||||
FS: pathRoot.FS(),
|
||||
writtenFiles: make(map[string][]byte),
|
||||
}
|
||||
|
||||
// Prepare other inputs.
|
||||
const yes = true
|
||||
const dryRun = false
|
||||
extensions := []string{".yaml", ".yml"}
|
||||
latestVersions := map[schema.GroupKind]string{
|
||||
{Group: "image.toolkit.fluxcd.io", Kind: "ImageRepository"}: "v1",
|
||||
{Group: "image.toolkit.fluxcd.io", Kind: "ImagePolicy"}: "v1",
|
||||
{Group: "image.toolkit.fluxcd.io", Kind: "ImageUpdateAutomation"}: "v1",
|
||||
}
|
||||
|
||||
// Run migration.
|
||||
err = NewFileSystemMigrator(fileSystem, yes, dryRun, tt.path, extensions, latestVersions).Run()
|
||||
if tt.err != "" {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(Equal(tt.err))
|
||||
return
|
||||
}
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Assert logger output.
|
||||
b, err := os.ReadFile(tt.outputGolden)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(string(b)).To(Equal(testLogger.String()),
|
||||
"logger output does not match golden file %s", tt.outputGolden)
|
||||
|
||||
// Assert which files were written.
|
||||
writtenFiles := make([]string, 0, len(fileSystem.writtenFiles))
|
||||
for name := range fileSystem.writtenFiles {
|
||||
writtenFiles = append(writtenFiles, name)
|
||||
}
|
||||
expectedWrittenFiles := make([]string, 0, len(tt.writtenFiles))
|
||||
for _, wf := range tt.writtenFiles {
|
||||
expectedWrittenFiles = append(expectedWrittenFiles, wf.file)
|
||||
}
|
||||
g.Expect(writtenFiles).To(ConsistOf(expectedWrittenFiles))
|
||||
|
||||
// Assert contents of written files.
|
||||
for _, wf := range tt.writtenFiles {
|
||||
b, err := os.ReadFile(wf.goldenFile)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(string(fileSystem.writtenFiles[wf.file])).To(Equal(string(b)),
|
||||
"file %s does not match golden file %s", wf.file, wf.goldenFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var reconcileImagePolicyCmd = &cobra.Command{
|
||||
Use: "policy [name]",
|
||||
Short: "Reconcile an ImagePolicy",
|
||||
Long: `The reconcile image policy command triggers a reconciliation of an ImagePolicy resource and waits for it to finish.`,
|
||||
Example: `
|
||||
# Trigger a reconciliation for an existing image policy called 'alpine'
|
||||
flux reconcile image policy alpine`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||
RunE: reconcileCommand{
|
||||
apiType: imagePolicyType,
|
||||
object: imagePolicyAdapter{&imagev1.ImagePolicy{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
reconcileImageCmd.AddCommand(reconcileImagePolicyCmd)
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var resumeImagePolicyCmd = &cobra.Command{
|
||||
Use: "policy [name]",
|
||||
Short: "Resume an ImagePolicy",
|
||||
Long: `The resume image policy command resumes a suspended ImagePolicy resource.`,
|
||||
Example: `
|
||||
# Resume a suspended image policy called 'alpine'
|
||||
flux resume image policy alpine`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||
RunE: resumeCommand{
|
||||
apiType: imagePolicyType,
|
||||
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
resumeImageCmd.AddCommand(resumeImagePolicyCmd)
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2025 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 main
|
||||
|
||||
import (
|
||||
imagev1 "github.com/fluxcd/image-reflector-controller/api/v1"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var suspendImagePolicyCmd = &cobra.Command{
|
||||
Use: "policy [name]",
|
||||
Short: "Suspend an ImagePolicy",
|
||||
Long: `The suspend image policy command suspends the reconciliation of an ImagePolicy resource.`,
|
||||
ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
|
||||
RunE: suspendCommand{
|
||||
apiType: imagePolicyType,
|
||||
list: imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
|
||||
}.run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
suspendImageCmd.AddCommand(suspendImagePolicyCmd)
|
||||
}
|
||||
@ -1,3 +1,3 @@
|
||||
► checking prerequisites
|
||||
✔ Kubernetes {{ .serverVersion }} >=1.31.0-0
|
||||
✔ Kubernetes {{ .serverVersion }} >=1.32.0-0
|
||||
✔ prerequisites checks passed
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue