Implement Size field on archived artifacts

This adds a Size field to Artifacts, which reflects the number of bytes
written to the artifact when it's being archived.

Signed-off-by: Kevin McDermott <bigkevmcd@gmail.com>
This commit is contained in:
Kevin McDermott 2022-02-22 10:30:58 +00:00 committed by Hidde Beydals
parent 25e6e16a75
commit f7105ea736
14 changed files with 95 additions and 5 deletions

View File

@ -51,6 +51,10 @@ type Artifact struct {
// artifact. // artifact.
// +required // +required
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"` LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
// Size is the number of bytes in the file.
// +optional
Size *int64 `json:"size,omitempty"`
} }
// HasRevision returns true if the given revision matches the current Revision // HasRevision returns true if the given revision matches the current Revision

View File

@ -32,6 +32,11 @@ import (
func (in *Artifact) DeepCopyInto(out *Artifact) { func (in *Artifact) DeepCopyInto(out *Artifact) {
*out = *in *out = *in
in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
if in.Size != nil {
in, out := &in.Size, &out.Size
*out = new(int64)
**out = **in
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Artifact. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Artifact.

View File

@ -391,6 +391,10 @@ spec:
in the origin source system. It can be a Git commit SHA, Git in the origin source system. It can be a Git commit SHA, Git
tag, a Helm index timestamp, a Helm chart version, etc. tag, a Helm index timestamp, a Helm chart version, etc.
type: string type: string
size:
description: Size is the number of bytes in the file.
format: int64
type: integer
url: url:
description: URL is the HTTP address of this artifact. It is used description: URL is the HTTP address of this artifact. It is used
by the consumers of the artifacts to fetch and use the artifacts. by the consumers of the artifacts to fetch and use the artifacts.

View File

@ -559,6 +559,10 @@ spec:
in the origin source system. It can be a Git commit SHA, Git in the origin source system. It can be a Git commit SHA, Git
tag, a Helm index timestamp, a Helm chart version, etc. tag, a Helm index timestamp, a Helm chart version, etc.
type: string type: string
size:
description: Size is the number of bytes in the file.
format: int64
type: integer
url: url:
description: URL is the HTTP address of this artifact. It is used description: URL is the HTTP address of this artifact. It is used
by the consumers of the artifacts to fetch and use the artifacts. by the consumers of the artifacts to fetch and use the artifacts.
@ -663,6 +667,10 @@ spec:
in the origin source system. It can be a Git commit SHA, Git in the origin source system. It can be a Git commit SHA, Git
tag, a Helm index timestamp, a Helm chart version, etc. tag, a Helm index timestamp, a Helm chart version, etc.
type: string type: string
size:
description: Size is the number of bytes in the file.
format: int64
type: integer
url: url:
description: URL is the HTTP address of this artifact. It is description: URL is the HTTP address of this artifact. It is
used by the consumers of the artifacts to fetch and use the used by the consumers of the artifacts to fetch and use the

View File

@ -438,6 +438,10 @@ spec:
in the origin source system. It can be a Git commit SHA, Git in the origin source system. It can be a Git commit SHA, Git
tag, a Helm index timestamp, a Helm chart version, etc. tag, a Helm index timestamp, a Helm chart version, etc.
type: string type: string
size:
description: Size is the number of bytes in the file.
format: int64
type: integer
url: url:
description: URL is the HTTP address of this artifact. It is used description: URL is the HTTP address of this artifact. It is used
by the consumers of the artifacts to fetch and use the artifacts. by the consumers of the artifacts to fetch and use the artifacts.

View File

@ -364,6 +364,10 @@ spec:
in the origin source system. It can be a Git commit SHA, Git in the origin source system. It can be a Git commit SHA, Git
tag, a Helm index timestamp, a Helm chart version, etc. tag, a Helm index timestamp, a Helm chart version, etc.
type: string type: string
size:
description: Size is the number of bytes in the file.
format: int64
type: integer
url: url:
description: URL is the HTTP address of this artifact. It is used description: URL is the HTTP address of this artifact. It is used
by the consumers of the artifacts to fetch and use the artifacts. by the consumers of the artifacts to fetch and use the artifacts.

View File

@ -54,6 +54,9 @@ func (m matchArtifact) Match(actual interface{}) (success bool, err error) {
if ok, err = Equal(m.expected.Checksum).Match(actualArtifact.Checksum); !ok { if ok, err = Equal(m.expected.Checksum).Match(actualArtifact.Checksum); !ok {
return ok, err return ok, err
} }
if ok, err = Equal(m.expected.Size).Match(actualArtifact.Size); !ok {
return ok, err
}
return ok, err return ok, err
} }

View File

@ -200,6 +200,7 @@ func TestBucketReconciler_reconcileStorage(t *testing.T) {
Revision: "c", Revision: "c",
Checksum: "2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6", Checksum: "2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6",
URL: testStorage.Hostname + "/reconcile-storage/c.txt", URL: testStorage.Hostname + "/reconcile-storage/c.txt",
Size: int64p(int64(len("c"))),
}, },
assertPaths: []string{ assertPaths: []string{
"/reconcile-storage/c.txt", "/reconcile-storage/c.txt",
@ -251,6 +252,7 @@ func TestBucketReconciler_reconcileStorage(t *testing.T) {
Revision: "f", Revision: "f",
Checksum: "3b9c358f36f0a31b6ad3e14f309c7cf198ac9246e8316f9ce543d5b19ac02b80", Checksum: "3b9c358f36f0a31b6ad3e14f309c7cf198ac9246e8316f9ce543d5b19ac02b80",
URL: testStorage.Hostname + "/reconcile-storage/hostname.txt", URL: testStorage.Hostname + "/reconcile-storage/hostname.txt",
Size: int64p(int64(len("file"))),
}, },
}, },
} }

View File

@ -19,6 +19,7 @@ package controllers
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@ -818,6 +819,16 @@ func TestGitRepositoryReconciler_reconcileArtifact(t *testing.T) {
wantErr: true, wantErr: true,
}, },
} }
artifactSize := func(g *WithT, artifactURL string) *int64 {
if artifactURL == "" {
return nil
}
res, err := http.Get(artifactURL)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(res.StatusCode).To(Equal(http.StatusOK))
defer res.Body.Close()
return &res.ContentLength
}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -850,6 +861,10 @@ func TestGitRepositoryReconciler_reconcileArtifact(t *testing.T) {
g.Expect(err != nil).To(Equal(tt.wantErr)) g.Expect(err != nil).To(Equal(tt.wantErr))
g.Expect(got).To(Equal(tt.want)) g.Expect(got).To(Equal(tt.want))
if obj.Status.Artifact != nil {
g.Expect(obj.Status.Artifact.Size).To(Equal(artifactSize(g, obj.Status.Artifact.URL)))
}
if tt.afterFunc != nil { if tt.afterFunc != nil {
tt.afterFunc(g, obj) tt.afterFunc(g, obj)
} }

View File

@ -198,6 +198,7 @@ func TestHelmChartReconciler_reconcileStorage(t *testing.T) {
Revision: "c", Revision: "c",
Checksum: "2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6", Checksum: "2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6",
URL: testStorage.Hostname + "/reconcile-storage/c.txt", URL: testStorage.Hostname + "/reconcile-storage/c.txt",
Size: int64p(int64(len("c"))),
}, },
assertPaths: []string{ assertPaths: []string{
"/reconcile-storage/c.txt", "/reconcile-storage/c.txt",
@ -250,6 +251,7 @@ func TestHelmChartReconciler_reconcileStorage(t *testing.T) {
Revision: "f", Revision: "f",
Checksum: "3b9c358f36f0a31b6ad3e14f309c7cf198ac9246e8316f9ce543d5b19ac02b80", Checksum: "3b9c358f36f0a31b6ad3e14f309c7cf198ac9246e8316f9ce543d5b19ac02b80",
URL: testStorage.Hostname + "/reconcile-storage/hostname.txt", URL: testStorage.Hostname + "/reconcile-storage/hostname.txt",
Size: int64p(int64(len("file"))),
}, },
}, },
} }

View File

@ -166,6 +166,7 @@ func TestHelmRepositoryReconciler_reconcileStorage(t *testing.T) {
Revision: "c", Revision: "c",
Checksum: "2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6", Checksum: "2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6",
URL: testStorage.Hostname + "/reconcile-storage/c.txt", URL: testStorage.Hostname + "/reconcile-storage/c.txt",
Size: int64p(int64(len("c"))),
}, },
assertPaths: []string{ assertPaths: []string{
"/reconcile-storage/c.txt", "/reconcile-storage/c.txt",
@ -218,6 +219,7 @@ func TestHelmRepositoryReconciler_reconcileStorage(t *testing.T) {
Revision: "f", Revision: "f",
Checksum: "3b9c358f36f0a31b6ad3e14f309c7cf198ac9246e8316f9ce543d5b19ac02b80", Checksum: "3b9c358f36f0a31b6ad3e14f309c7cf198ac9246e8316f9ce543d5b19ac02b80",
URL: testStorage.Hostname + "/reconcile-storage/hostname.txt", URL: testStorage.Hostname + "/reconcile-storage/hostname.txt",
Size: int64p(int64(len("file"))),
}, },
}, },
} }

View File

@ -194,7 +194,8 @@ func (s *Storage) Archive(artifact *sourcev1.Artifact, dir string, filter Archiv
}() }()
h := newHash() h := newHash()
mw := io.MultiWriter(h, tf) sz := &writeCounter{}
mw := io.MultiWriter(h, tf, sz)
gw := gzip.NewWriter(mw) gw := gzip.NewWriter(mw)
tw := tar.NewWriter(gw) tw := tar.NewWriter(gw)
@ -286,6 +287,8 @@ func (s *Storage) Archive(artifact *sourcev1.Artifact, dir string, filter Archiv
artifact.Checksum = fmt.Sprintf("%x", h.Sum(nil)) artifact.Checksum = fmt.Sprintf("%x", h.Sum(nil))
artifact.LastUpdateTime = metav1.Now() artifact.LastUpdateTime = metav1.Now()
artifact.Size = &sz.written
return nil return nil
} }
@ -305,7 +308,8 @@ func (s *Storage) AtomicWriteFile(artifact *sourcev1.Artifact, reader io.Reader,
}() }()
h := newHash() h := newHash()
mw := io.MultiWriter(h, tf) sz := &writeCounter{}
mw := io.MultiWriter(h, tf, sz)
if _, err := io.Copy(mw, reader); err != nil { if _, err := io.Copy(mw, reader); err != nil {
tf.Close() tf.Close()
@ -325,6 +329,8 @@ func (s *Storage) AtomicWriteFile(artifact *sourcev1.Artifact, reader io.Reader,
artifact.Checksum = fmt.Sprintf("%x", h.Sum(nil)) artifact.Checksum = fmt.Sprintf("%x", h.Sum(nil))
artifact.LastUpdateTime = metav1.Now() artifact.LastUpdateTime = metav1.Now()
artifact.Size = &sz.written
return nil return nil
} }
@ -344,7 +350,8 @@ func (s *Storage) Copy(artifact *sourcev1.Artifact, reader io.Reader) (err error
}() }()
h := newHash() h := newHash()
mw := io.MultiWriter(h, tf) sz := &writeCounter{}
mw := io.MultiWriter(h, tf, sz)
if _, err := io.Copy(mw, reader); err != nil { if _, err := io.Copy(mw, reader); err != nil {
tf.Close() tf.Close()
@ -360,6 +367,8 @@ func (s *Storage) Copy(artifact *sourcev1.Artifact, reader io.Reader) (err error
artifact.Checksum = fmt.Sprintf("%x", h.Sum(nil)) artifact.Checksum = fmt.Sprintf("%x", h.Sum(nil))
artifact.LastUpdateTime = metav1.Now() artifact.LastUpdateTime = metav1.Now()
artifact.Size = &sz.written
return nil return nil
} }
@ -471,3 +480,15 @@ func (s *Storage) LocalPath(artifact sourcev1.Artifact) string {
func newHash() hash.Hash { func newHash() hash.Hash {
return sha256.New() return sha256.New()
} }
// writecounter is an implementation of io.Writer that only records the number
// of bytes written.
type writeCounter struct {
written int64
}
func (wc *writeCounter) Write(p []byte) (int, error) {
n := len(p)
wc.written += int64(n)
return n, nil
}

View File

@ -193,3 +193,7 @@ func randStringRunes(n int) string {
} }
return string(b) return string(b)
} }
func int64p(i int64) *int64 {
return &i
}

View File

@ -931,6 +931,18 @@ Kubernetes meta/v1.Time
artifact.</p> artifact.</p>
</td> </td>
</tr> </tr>
<tr>
<td>
<code>size</code><br>
<em>
int64
</em>
</td>
<td>
<em>(Optional)</em>
<p>Size is the number of bytes in the file.</p>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -1568,8 +1580,8 @@ Artifact
<td> <td>
<code>includedArtifacts</code><br> <code>includedArtifacts</code><br>
<em> <em>
<a href="#source.toolkit.fluxcd.io/v1beta2.*github.com/fluxcd/source-controller/api/v1beta2.Artifact"> <a href="#source.toolkit.fluxcd.io/v1beta2.*./api/v1beta2.Artifact">
[]*github.com/fluxcd/source-controller/api/v1beta2.Artifact []*./api/v1beta2.Artifact
</a> </a>
</em> </em>
</td> </td>