@@ -17,10 +17,12 @@ limitations under the License.
17
17
package controllers
18
18
19
19
import (
20
+ "bytes"
20
21
"context"
21
22
"errors"
22
23
"fmt"
23
24
"io"
25
+ "io/ioutil"
24
26
"net/http"
25
27
"os"
26
28
"path/filepath"
@@ -31,6 +33,8 @@ import (
31
33
32
34
"github.com/darkowlzz/controller-check/status"
33
35
. "github.com/onsi/gomega"
36
+ "helm.sh/helm/v3/pkg/chart/loader"
37
+ "helm.sh/helm/v3/pkg/registry"
34
38
corev1 "k8s.io/api/core/v1"
35
39
apierrors "k8s.io/apimachinery/pkg/api/errors"
36
40
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -45,12 +49,12 @@ import (
45
49
"github.com/fluxcd/pkg/runtime/conditions"
46
50
"github.com/fluxcd/pkg/runtime/patch"
47
51
"github.com/fluxcd/pkg/testserver"
48
-
49
52
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
50
53
serror "github.com/fluxcd/source-controller/internal/error"
51
54
"github.com/fluxcd/source-controller/internal/helm/chart"
52
55
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
53
56
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
57
+ hchart "helm.sh/helm/v3/pkg/chart"
54
58
)
55
59
56
60
func TestHelmChartReconciler_Reconcile (t * testing.T ) {
@@ -776,6 +780,217 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
776
780
}
777
781
}
778
782
783
+ func TestHelmChartReconciler_buildFromOCIHelmRepository (t * testing.T ) {
784
+ g := NewWithT (t )
785
+
786
+ tmpDir := t .TempDir ()
787
+
788
+ const (
789
+ chartPath = "testdata/charts/helmchart-0.1.0.tgz"
790
+ )
791
+
792
+ // Login to the registry
793
+ err := testRegistryserver .RegistryClient .Login (testRegistryserver .DockerRegistryHost ,
794
+ registry .LoginOptBasicAuth (testUsername , testPassword ),
795
+ registry .LoginOptInsecure (true ))
796
+ g .Expect (err ).NotTo (HaveOccurred ())
797
+
798
+ // Load a test chart
799
+ chartData , err := ioutil .ReadFile (chartPath )
800
+ g .Expect (err ).NotTo (HaveOccurred ())
801
+ metadata , err := extractChartMeta (chartData )
802
+ g .Expect (err ).NotTo (HaveOccurred ())
803
+
804
+ // Upload the test chart
805
+ ref := fmt .Sprintf ("%s/testrepo/%s:%s" , testRegistryserver .DockerRegistryHost , metadata .Name , metadata .Version )
806
+ _ , err = testRegistryserver .RegistryClient .Push (chartData , ref )
807
+ g .Expect (err ).NotTo (HaveOccurred ())
808
+
809
+ storage , err := NewStorage (tmpDir , "example.com" , retentionTTL , retentionRecords )
810
+ g .Expect (err ).ToNot (HaveOccurred ())
811
+
812
+ cachedArtifact := & sourcev1.Artifact {
813
+ Revision : "0.1.0" ,
814
+ Path : metadata .Name + "-" + metadata .Version + ".tgz" ,
815
+ }
816
+ g .Expect (storage .CopyFromPath (cachedArtifact , "testdata/charts/helmchart-0.1.0.tgz" )).To (Succeed ())
817
+
818
+ tests := []struct {
819
+ name string
820
+ secret * corev1.Secret
821
+ beforeFunc func (obj * sourcev1.HelmChart , repository * sourcev1.HelmRepository )
822
+ want sreconcile.Result
823
+ wantErr error
824
+ assertFunc func (g * WithT , obj * sourcev1.HelmChart , build chart.Build )
825
+ cleanFunc func (g * WithT , build * chart.Build )
826
+ }{
827
+ {
828
+ name : "Reconciles chart build with repository credentials" ,
829
+ secret : & corev1.Secret {
830
+ ObjectMeta : metav1.ObjectMeta {
831
+ Name : "auth" ,
832
+ },
833
+ Data : map [string ][]byte {
834
+ "username" : []byte (testUsername ),
835
+ "password" : []byte (testPassword ),
836
+ },
837
+ },
838
+ beforeFunc : func (obj * sourcev1.HelmChart , repository * sourcev1.HelmRepository ) {
839
+ obj .Spec .Chart = metadata .Name
840
+ obj .Spec .Version = metadata .Version
841
+ repository .Spec .SecretRef = & meta.LocalObjectReference {Name : "auth" }
842
+ },
843
+ want : sreconcile .ResultSuccess ,
844
+ assertFunc : func (g * WithT , _ * sourcev1.HelmChart , build chart.Build ) {
845
+ g .Expect (build .Name ).To (Equal (metadata .Name ))
846
+ g .Expect (build .Version ).To (Equal (metadata .Version ))
847
+ g .Expect (build .Path ).ToNot (BeEmpty ())
848
+ g .Expect (build .Path ).To (BeARegularFile ())
849
+ },
850
+ cleanFunc : func (g * WithT , build * chart.Build ) {
851
+ g .Expect (os .Remove (build .Path )).To (Succeed ())
852
+ },
853
+ },
854
+ {
855
+ name : "Uses artifact as build cache" ,
856
+ beforeFunc : func (obj * sourcev1.HelmChart , repository * sourcev1.HelmRepository ) {
857
+ obj .Spec .Chart = metadata .Name
858
+ obj .Spec .Version = metadata .Version
859
+ obj .Status .Artifact = & sourcev1.Artifact {Path : metadata .Name + "-" + metadata .Version + ".tgz" }
860
+ },
861
+ want : sreconcile .ResultSuccess ,
862
+ assertFunc : func (g * WithT , obj * sourcev1.HelmChart , build chart.Build ) {
863
+ g .Expect (build .Name ).To (Equal (metadata .Name ))
864
+ g .Expect (build .Version ).To (Equal (metadata .Version ))
865
+ g .Expect (build .Path ).To (Equal (storage .LocalPath (* cachedArtifact .DeepCopy ())))
866
+ g .Expect (build .Path ).To (BeARegularFile ())
867
+ },
868
+ },
869
+ {
870
+ name : "Forces build on generation change" ,
871
+ beforeFunc : func (obj * sourcev1.HelmChart , repository * sourcev1.HelmRepository ) {
872
+ obj .Generation = 3
873
+ obj .Spec .Chart = metadata .Name
874
+ obj .Spec .Version = metadata .Version
875
+
876
+ obj .Status .ObservedGeneration = 2
877
+ obj .Status .Artifact = & sourcev1.Artifact {Path : metadata .Name + "-" + metadata .Version + ".tgz" }
878
+ },
879
+ want : sreconcile .ResultSuccess ,
880
+ assertFunc : func (g * WithT , obj * sourcev1.HelmChart , build chart.Build ) {
881
+ g .Expect (build .Name ).To (Equal (metadata .Name ))
882
+ g .Expect (build .Version ).To (Equal (metadata .Version ))
883
+ fmt .Println ("buildpath" , build .Path )
884
+ fmt .Println ("storage Path" , storage .LocalPath (* cachedArtifact .DeepCopy ()))
885
+ g .Expect (build .Path ).ToNot (Equal (storage .LocalPath (* cachedArtifact .DeepCopy ())))
886
+ g .Expect (build .Path ).To (BeARegularFile ())
887
+ },
888
+ cleanFunc : func (g * WithT , build * chart.Build ) {
889
+ g .Expect (os .Remove (build .Path )).To (Succeed ())
890
+ },
891
+ },
892
+ {
893
+ name : "Event on unsuccessful secret retrieval" ,
894
+ beforeFunc : func (_ * sourcev1.HelmChart , repository * sourcev1.HelmRepository ) {
895
+ repository .Spec .SecretRef = & meta.LocalObjectReference {
896
+ Name : "invalid" ,
897
+ }
898
+ },
899
+ want : sreconcile .ResultEmpty ,
900
+ wantErr : & serror.Event {Err : errors .New ("failed to get secret 'invalid'" )},
901
+ assertFunc : func (g * WithT , obj * sourcev1.HelmChart , build chart.Build ) {
902
+ g .Expect (build .Complete ()).To (BeFalse ())
903
+
904
+ g .Expect (obj .Status .Conditions ).To (conditions .MatchConditions ([]metav1.Condition {
905
+ * conditions .TrueCondition (sourcev1 .FetchFailedCondition , sourcev1 .AuthenticationFailedReason , "failed to get secret 'invalid'" ),
906
+ }))
907
+ },
908
+ },
909
+ {
910
+ name : "Stalling on invalid client options" ,
911
+ beforeFunc : func (obj * sourcev1.HelmChart , repository * sourcev1.HelmRepository ) {
912
+ repository .Spec .URL = "https://unsupported" // Unsupported protocol
913
+ },
914
+ want : sreconcile .ResultEmpty ,
915
+ wantErr : & serror.Stalling {Err : errors .New ("failed to construct Helm client: invalid OCI registry URL: https://unsupported" )},
916
+ assertFunc : func (g * WithT , obj * sourcev1.HelmChart , build chart.Build ) {
917
+ g .Expect (build .Complete ()).To (BeFalse ())
918
+
919
+ g .Expect (obj .Status .Conditions ).To (conditions .MatchConditions ([]metav1.Condition {
920
+ * conditions .TrueCondition (sourcev1 .FetchFailedCondition , meta .FailedReason , "failed to construct Helm client" ),
921
+ }))
922
+ },
923
+ },
924
+ {
925
+ name : "BuildError on temporary build error" ,
926
+ beforeFunc : func (obj * sourcev1.HelmChart , _ * sourcev1.HelmRepository ) {
927
+ obj .Spec .Chart = "invalid"
928
+ },
929
+ want : sreconcile .ResultEmpty ,
930
+ wantErr : & chart.BuildError {Err : errors .New ("failed to get chart version for remote reference" )},
931
+ },
932
+ }
933
+ for _ , tt := range tests {
934
+ t .Run (tt .name , func (t * testing.T ) {
935
+ g := NewWithT (t )
936
+
937
+ testRegistryClient , err = registry .NewClient (registry .ClientOptWriter (os .Stdout ))
938
+ g .Expect (err ).To (BeNil ())
939
+
940
+ clientBuilder := fake .NewClientBuilder ()
941
+ if tt .secret != nil {
942
+ clientBuilder .WithObjects (tt .secret .DeepCopy ())
943
+ }
944
+
945
+ r := & HelmChartReconciler {
946
+ Client : clientBuilder .Build (),
947
+ EventRecorder : record .NewFakeRecorder (32 ),
948
+ Getters : testGetters ,
949
+ Storage : storage ,
950
+ RegistryClient : testRegistryClient ,
951
+ }
952
+
953
+ repository := & sourcev1.HelmRepository {
954
+ ObjectMeta : metav1.ObjectMeta {
955
+ GenerateName : "helmrepository-" ,
956
+ },
957
+ Spec : sourcev1.HelmRepositorySpec {
958
+ URL : fmt .Sprintf ("oci://%s/testrepo" , testRegistryserver .DockerRegistryHost ),
959
+ Timeout : & metav1.Duration {Duration : timeout },
960
+ Type : sourcev1 .HelmRepositoryTypeOCI ,
961
+ },
962
+ }
963
+ obj := & sourcev1.HelmChart {
964
+ ObjectMeta : metav1.ObjectMeta {
965
+ GenerateName : "helmrepository-" ,
966
+ },
967
+ Spec : sourcev1.HelmChartSpec {},
968
+ }
969
+
970
+ if tt .beforeFunc != nil {
971
+ tt .beforeFunc (obj , repository )
972
+ }
973
+
974
+ var b chart.Build
975
+ if tt .cleanFunc != nil {
976
+ defer tt .cleanFunc (g , & b )
977
+ }
978
+ got , err := r .buildFromHelmRepository (context .TODO (), obj , repository , & b )
979
+
980
+ g .Expect (err != nil ).To (Equal (tt .wantErr != nil ))
981
+ if tt .wantErr != nil {
982
+ g .Expect (reflect .TypeOf (err ).String ()).To (Equal (reflect .TypeOf (tt .wantErr ).String ()))
983
+ g .Expect (err .Error ()).To (ContainSubstring (tt .wantErr .Error ()))
984
+ }
985
+ g .Expect (got ).To (Equal (tt .want ))
986
+
987
+ if tt .assertFunc != nil {
988
+ tt .assertFunc (g , obj , b )
989
+ }
990
+ })
991
+ }
992
+ }
993
+
779
994
func TestHelmChartReconciler_buildFromTarballArtifact (t * testing.T ) {
780
995
g := NewWithT (t )
781
996
@@ -1690,3 +1905,12 @@ func TestHelmChartReconciler_notify(t *testing.T) {
1690
1905
})
1691
1906
}
1692
1907
}
1908
+
1909
+ // extractChartMeta is used to extract a chart metadata from a byte array
1910
+ func extractChartMeta (chartData []byte ) (* hchart.Metadata , error ) {
1911
+ ch , err := loader .LoadArchive (bytes .NewReader (chartData ))
1912
+ if err != nil {
1913
+ return nil , err
1914
+ }
1915
+ return ch .Metadata , nil
1916
+ }
0 commit comments