15
15
package commands
16
16
17
17
import (
18
+ "archive/tar"
19
+ "context"
18
20
"fmt"
21
+ "io"
19
22
"os"
20
23
"path/filepath"
21
- "time"
22
-
23
- "github.com/google/go-containerregistry/pkg/name"
24
- v1 "github.com/google/go-containerregistry/pkg/v1"
25
- "github.com/google/go-containerregistry/pkg/v1/empty"
26
- "github.com/google/go-containerregistry/pkg/v1/mutate"
27
- "github.com/google/go-containerregistry/pkg/v1/static"
28
- "github.com/google/go-containerregistry/pkg/v1/tarball"
29
- "github.com/google/go-containerregistry/pkg/v1/types"
24
+ "strings"
25
+
26
+ ocispec "github.com/opencontainers/image-spec/specs-go/v1"
30
27
"github.com/spf13/cobra"
28
+ oras "oras.land/oras-go/v2"
29
+ "oras.land/oras-go/v2/content/oci"
31
30
"sigs.k8s.io/yaml"
32
31
33
32
"github.com/kro-run/kro/api/v1alpha1"
@@ -42,7 +41,7 @@ var packageConfig = &PackageConfig{}
42
41
43
42
func init () {
44
43
packageRGDCmd .PersistentFlags ().StringVarP (& packageConfig .resourceGraphDefinitionFile ,
45
- "file" , "f" , "" ,
44
+ "file" , "f" , "rgd.yaml " ,
46
45
"Path to the ResourceGraphDefinition file" ,
47
46
)
48
47
@@ -55,7 +54,7 @@ var packageRGDCmd = &cobra.Command{
55
54
Use : "package" ,
56
55
Short : "Create an OCI Image packaging the ResourceGraphDefinition" ,
57
56
Long : "Package command packages the ResourceGraphDefinition" +
58
- "file into an OCI image, which can be used for distribution and deployment." ,
57
+ "file into an OCI image bundle , which can be used for distribution and deployment." ,
59
58
RunE : func (cmd * cobra.Command , args []string ) error {
60
59
if packageConfig .resourceGraphDefinitionFile == "" {
61
60
return fmt .Errorf ("ResourceGraphDefinition file is required" )
@@ -72,61 +71,142 @@ var packageRGDCmd = &cobra.Command{
72
71
}
73
72
74
73
basename := filepath .Base (packageConfig .resourceGraphDefinitionFile )
75
- extension := filepath .Ext (basename )
76
- nameWithoutExt := basename [:len (basename )- len (extension )]
77
- outputFile := nameWithoutExt + ".tar"
74
+ ext := filepath .Ext (basename )
75
+ nameWithoutExt := basename [:len (basename )- len (ext )]
76
+ outputTar := nameWithoutExt + ".tar"
78
77
79
- if err = packageRGD (outputFile , data , & rgd ); err != nil {
78
+ if err : = packageRGD (outputTar , data , & rgd ); err != nil {
80
79
return fmt .Errorf ("failed to package ResourceGraphDefinition: %w" , err )
81
80
}
82
81
83
- fmt .Println ("Successfully packaged ResourceGraphDefinition to" , outputFile )
84
-
82
+ fmt .Println ("Successfully packaged ResourceGraphDefinition to" , outputTar )
85
83
return nil
86
84
},
87
85
}
88
86
89
- func packageRGD (outputFile string , data []byte , rgd * v1alpha1.ResourceGraphDefinition ) error {
90
- layer := static .NewLayer (data , types .MediaType ("application/vnd.kro.resourcegraphdefinition.v1alpha1+yaml" ))
91
-
92
- img := empty .Image
93
-
94
- img , err := mutate .AppendLayers (img , layer )
87
+ func packageRGD (outputTar string , data []byte , rgd * v1alpha1.ResourceGraphDefinition ) error {
88
+ ctx := context .Background ()
95
89
90
+ tempDir , err := os .MkdirTemp ("" , "kro-oci-*" )
96
91
if err != nil {
97
- return fmt .Errorf ("failed to append layer : %w" , err )
92
+ return fmt .Errorf ("failed to create temp dir : %w" , err )
98
93
}
94
+ defer func () {
95
+ if err := os .RemoveAll (tempDir ); err != nil {
96
+ fmt .Println ("failed to remove temp dir" , tempDir , ":" , err )
97
+ }
98
+ }()
99
99
100
- configFile , err := img . ConfigFile ( )
100
+ store , err := oci . New ( tempDir )
101
101
if err != nil {
102
- return fmt .Errorf ("failed to get config file : %w" , err )
102
+ return fmt .Errorf ("failed to create OCI layout store : %w" , err )
103
103
}
104
104
105
- now := time .Now ()
106
- configFile .Created = v1.Time {Time : now }
105
+ mediaType := "application/vnd.kro.resourcegraphdefinition"
106
+ blobDesc , err := oras .PushBytes (ctx , store , mediaType , data )
107
+ if err != nil {
108
+ return fmt .Errorf ("failed to push RGD blob: %w" , err )
109
+ }
107
110
108
- configFile .Config .Labels = map [string ]string {
109
- "kro.run/type" : "resourcegraphdefinition" ,
110
- "kro.run/name" : rgd .Name ,
111
+ layer := blobDesc
112
+ if layer .Annotations == nil {
113
+ layer .Annotations = map [string ]string {}
114
+ }
115
+ layer .Annotations [ocispec .AnnotationTitle ] = filepath .Base (packageConfig .resourceGraphDefinitionFile )
116
+
117
+ artifactType := "application/vnd.kro.resourcegraphdefinition"
118
+ packOpts := oras.PackManifestOptions {
119
+ Layers : []ocispec.Descriptor {layer },
120
+ ManifestAnnotations : map [string ]string {
121
+ "kro.run/type" : "resourcegraphdefinition" ,
122
+ "kro.run/name" : rgd .Name ,
123
+ },
111
124
}
112
125
113
- img , err = mutate . ConfigFile ( img , configFile )
126
+ manifestDesc , err := oras . PackManifest ( ctx , store , oras . PackManifestVersion1_1 , artifactType , packOpts )
114
127
if err != nil {
115
- return fmt .Errorf ("failed to update image config : %w" , err )
128
+ return fmt .Errorf ("failed to pack manifest : %w" , err )
116
129
}
117
130
118
- ref , err := name . ParseReference ( fmt .Sprintf ("kro.run/rgd/ %s:%s" , rgd .Name , packageConfig .tag ) )
119
- if err != nil {
120
- return fmt .Errorf ("failed to parse image reference : %w" , err )
131
+ tag := fmt .Sprintf ("%s:%s" , rgd .Name , packageConfig .tag )
132
+ if err := store . Tag ( ctx , manifestDesc , tag ); err != nil {
133
+ return fmt .Errorf ("failed to tag manifest in layout : %w" , err )
121
134
}
122
135
123
- if err := tarball .WriteToFile (outputFile , ref , img ); err != nil {
124
- return fmt .Errorf ("failed to write image to file: %w" , err )
136
+ if err := os .RemoveAll (filepath .Join (tempDir , "ingest" )); err != nil {
137
+ fmt .Println ("failed to remove ingest folder: " , err )
138
+ }
139
+
140
+ if err := tarDir (tempDir , outputTar ); err != nil {
141
+ return fmt .Errorf ("failed to write OCI layout tar: %w" , err )
125
142
}
126
143
127
144
return nil
128
145
}
129
146
147
+ func tarDir (srcDir , tarPath string ) error {
148
+ out , err := os .Create (tarPath )
149
+ if err != nil {
150
+ return err
151
+ }
152
+ defer func () {
153
+ if err := out .Close (); err != nil {
154
+ fmt .Println ("failed to close tar file: " , err )
155
+ }
156
+ }()
157
+
158
+ tw := tar .NewWriter (out )
159
+ defer func () {
160
+ if err := tw .Close (); err != nil {
161
+ fmt .Println ("failed to close tar writer: " , err )
162
+ }
163
+ }()
164
+
165
+ return filepath .WalkDir (srcDir , func (path string , d os.DirEntry , err error ) error {
166
+ if err != nil || path == srcDir {
167
+ return err
168
+ }
169
+
170
+ info , _ := d .Info ()
171
+ rel , _ := filepath .Rel (srcDir , path )
172
+ rel = filepath .ToSlash (rel )
173
+
174
+ switch {
175
+ case strings .HasPrefix (rel , "ingest/" ),
176
+ strings .HasPrefix (rel , "blobs/sha256/" ) && info .Size () == 0 ,
177
+ filepath .Clean (path ) == filepath .Clean (tarPath ):
178
+ return nil
179
+ }
180
+
181
+ hdr , err := tar .FileInfoHeader (info , "" )
182
+ if err != nil {
183
+ return err
184
+ }
185
+ hdr .Name = rel
186
+
187
+ if err := tw .WriteHeader (hdr ); err != nil {
188
+ return err
189
+ }
190
+
191
+ if ! info .Mode ().IsRegular () {
192
+ return nil
193
+ }
194
+
195
+ f , err := os .Open (path )
196
+ if err != nil {
197
+ return err
198
+ }
199
+ defer func () {
200
+ if err := f .Close (); err != nil {
201
+ fmt .Println ("failed to close file " , path , ":" , err )
202
+ }
203
+ }()
204
+
205
+ _ , err = io .Copy (tw , f )
206
+ return err
207
+ })
208
+ }
209
+
130
210
func AddPackageCommand (rootCmd * cobra.Command ) {
131
211
rootCmd .AddCommand (packageRGDCmd )
132
212
}
0 commit comments