Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package google-osconfig-agent for
openSUSE:Factory checked in at 2026-07-14 13:48:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/google-osconfig-agent (Old)
and /work/SRC/openSUSE:Factory/.google-osconfig-agent.new.1991 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "google-osconfig-agent"
Tue Jul 14 13:48:39 2026 rev:57 rq:1365363 version:20260708.00
Changes:
--------
---
/work/SRC/openSUSE:Factory/google-osconfig-agent/google-osconfig-agent.changes
2026-07-07 21:08:06.984492215 +0200
+++
/work/SRC/openSUSE:Factory/.google-osconfig-agent.new.1991/google-osconfig-agent.changes
2026-07-14 13:49:07.329317925 +0200
@@ -1,0 +2,16 @@
+Thu Jul 9 13:29:35 UTC 2026 - John Paul Adrian Glaubitz
<[email protected]>
+
+- Update to version 20260708.00
+ * Add unit tests for policies/recipes/steps.go PART 2 (#975)
+- from version 20260707.00
+ * Bump golang.org/x/net (#1017)
+- from version 20260703.00
+ * upgrade x/net package. (#1018)
+- from version 20260702.00
+ * Add test cases for config/file_resource.go (#988)
+- from version 20260701.00
+ * Add unit tests for policies/recipes/artifacts.go (#985)
+- from version 20260630.00
+ * Add unit tests for policies/recipes/steps.go PART 1 (#974)
+
+-------------------------------------------------------------------
Old:
----
osconfig-20260625.00.tar.gz
New:
----
osconfig-20260708.00.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ google-osconfig-agent.spec ++++++
--- /var/tmp/diff_new_pack.uxc8Jz/_old 2026-07-14 13:49:09.521392867 +0200
+++ /var/tmp/diff_new_pack.uxc8Jz/_new 2026-07-14 13:49:09.525393004 +0200
@@ -19,7 +19,7 @@
%define shortname osconfig
Name: google-osconfig-agent
-Version: 20260625.00
+Version: 20260708.00
Release: 0
Summary: Google Cloud Guest Agent
License: Apache-2.0
++++++ _service ++++++
--- /var/tmp/diff_new_pack.uxc8Jz/_old 2026-07-14 13:49:09.601395602 +0200
+++ /var/tmp/diff_new_pack.uxc8Jz/_new 2026-07-14 13:49:09.633396696 +0200
@@ -3,8 +3,8 @@
<param name="url">https://github.com/GoogleCloudPlatform/osconfig</param>
<param name="scm">git</param>
<param name="exclude">.git</param>
- <param name="versionformat">20260625.00</param>
- <param name="revision">20260625.00</param>
+ <param name="versionformat">20260708.00</param>
+ <param name="revision">20260708.00</param>
<param name="changesgenerate">enable</param>
</service>
<service name="recompress" mode="disabled">
@@ -15,7 +15,7 @@
<param name="basename">osconfig</param>
</service>
<service name="go_modules" mode="disabled">
- <param name="archive">osconfig-20260625.00.tar.gz</param>
+ <param name="archive">osconfig-20260708.00.tar.gz</param>
</service>
</services>
++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.uxc8Jz/_old 2026-07-14 13:49:09.745400525 +0200
+++ /var/tmp/diff_new_pack.uxc8Jz/_new 2026-07-14 13:49:09.757400935 +0200
@@ -1,6 +1,6 @@
<servicedata>
<service name="tar_scm">
<param
name="url">https://github.com/GoogleCloudPlatform/osconfig</param>
- <param
name="changesrevision">66ece98bab1d62c2366c52bbc8058ef5463db58c</param></service></servicedata>
+ <param
name="changesrevision">c0faeb26694ede5df709110d496c6255eb216662</param></service></servicedata>
(No newline at EOF)
++++++ osconfig-20260625.00.tar.gz -> osconfig-20260708.00.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osconfig-20260625.00/config/file_resource_test.go
new/osconfig-20260708.00/config/file_resource_test.go
--- old/osconfig-20260625.00/config/file_resource_test.go 2026-06-25
16:31:39.000000000 +0200
+++ new/osconfig-20260708.00/config/file_resource_test.go 2026-07-08
10:05:56.000000000 +0200
@@ -16,12 +16,17 @@
import (
"context"
+ "errors"
+ "fmt"
"io/ioutil"
+ "net/url"
"os"
"path/filepath"
+ "strconv"
"testing"
"github.com/GoogleCloudPlatform/osconfig/util"
+ "github.com/GoogleCloudPlatform/osconfig/util/utiltest"
"github.com/google/go-cmp/cmp"
"cloud.google.com/go/osconfig/agentendpoint/apiv1/agentendpointpb"
@@ -39,37 +44,42 @@
t.Fatal(err)
}
+ _, errLocalPath := os.Open("does_not_exist")
+
var tests = []struct {
- name string
- frpb *agentendpointpb.OSPolicy_Resource_FileResource
- wantMR ManagedFile
+ name string
+ fileResourcePB *agentendpointpb.OSPolicy_Resource_FileResource
+ wantManagedFile ManagedFile
+ wantErr error
}{
{
- "Absent",
- &agentendpointpb.OSPolicy_Resource_FileResource{
+ name: "state absent, want absent managed file",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
Path: tmpFile,
State:
agentendpointpb.OSPolicy_Resource_FileResource_ABSENT,
},
- ManagedFile{
+ wantManagedFile: ManagedFile{
Path: tmpFile,
State:
agentendpointpb.OSPolicy_Resource_FileResource_ABSENT,
},
+ wantErr: nil,
},
{
- "Present",
- &agentendpointpb.OSPolicy_Resource_FileResource{
+ name: "state present, want present managed file",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
Path: tmpFile,
State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
},
- ManagedFile{
+ wantManagedFile: ManagedFile{
Path: tmpFile,
State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
Permisions: defaultFilePerms,
},
+ wantErr: nil,
},
{
- "ContentsMatch",
- &agentendpointpb.OSPolicy_Resource_FileResource{
+ name: "state contents match and local path source, want
contents match managed file with checksum",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
Path: tmpFile,
Source:
&agentendpointpb.OSPolicy_Resource_FileResource_File{
File:
&agentendpointpb.OSPolicy_Resource_File{
@@ -80,30 +90,32 @@
},
State:
agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH,
},
- ManagedFile{
+ wantManagedFile: ManagedFile{
Path: tmpFile,
source: tmpFile,
checksum:
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
State:
agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH,
Permisions: defaultFilePerms,
},
+ wantErr: nil,
},
{
- "Permissions",
- &agentendpointpb.OSPolicy_Resource_FileResource{
+ name: "state present and custom permissions, want
present managed file with custom permissions",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
Path: tmpFile,
State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
Permissions: "0777",
},
- ManagedFile{
+ wantManagedFile: ManagedFile{
Path: tmpFile,
State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
Permisions: 0777,
},
+ wantErr: nil,
},
{
- "LocalPath",
- &agentendpointpb.OSPolicy_Resource_FileResource{
+ name: "state present and local path source, want
present managed file with checksum",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
Path: tmpFile,
Source:
&agentendpointpb.OSPolicy_Resource_FileResource_File{
File:
&agentendpointpb.OSPolicy_Resource_File{
@@ -114,13 +126,70 @@
},
State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
},
- ManagedFile{
+ wantManagedFile: ManagedFile{
Path: tmpFile,
State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
Permisions: defaultFilePerms,
checksum:
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
source: tmpFile,
},
+ wantErr: nil,
+ },
+ {
+ name: "invalid state, expect unrecognized desired state
error",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_DESIRED_STATE_UNSPECIFIED,
+ },
+ wantErr: fmt.Errorf("unrecognized DesiredState for
FileResource: %q",
agentendpointpb.OSPolicy_Resource_FileResource_DESIRED_STATE_UNSPECIFIED),
+ },
+ {
+ name: "invalid permissions, expect fail to parse
permissions",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
+ Permissions: "invalid",
+ },
+ wantErr: fmt.Errorf("can't parse permissions %q: %v",
"invalid", &strconv.NumError{Func: "ParseUint", Num: "invalid", Err:
strconv.ErrSyntax}),
+ },
+ {
+ name: "local file does not exist, expect fail to open
file",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
+ Path: "some/path",
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
+ Source:
&agentendpointpb.OSPolicy_Resource_FileResource_File{
+ File:
&agentendpointpb.OSPolicy_Resource_File{
+ Type:
&agentendpointpb.OSPolicy_Resource_File_LocalPath{
+ LocalPath:
"does_not_exist",
+ },
+ },
+ },
+ },
+ wantErr: errLocalPath,
+ },
+ {
+ name: "unrecognized source, expect unrecognized source
type error",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
+ Path: "some/path",
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH,
+ Source: nil,
+ },
+ wantErr: errors.New("unrecognized Source type for
FileResource: %!q(<nil>)"),
+ },
+ {
+ name: "remote download error, expect fail to fetch
remote object",
+ fileResourcePB:
&agentendpointpb.OSPolicy_Resource_FileResource{
+ Path: "some/path",
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH,
+ Source:
&agentendpointpb.OSPolicy_Resource_FileResource_File{
+ File:
&agentendpointpb.OSPolicy_Resource_File{
+ Type:
&agentendpointpb.OSPolicy_Resource_File_Remote_{
+ Remote:
&agentendpointpb.OSPolicy_Resource_File_Remote{
+ Uri:
"doesnot/exist",
+ },
+ },
+ },
+ },
+ },
+ wantErr: &url.Error{Op: "Get", URL: "doesnot/exist",
Err: errors.New("unsupported protocol scheme \"\"")},
},
}
for _, tt := range tests {
@@ -128,18 +197,17 @@
pr := &OSPolicyResource{
OSPolicy_Resource:
&agentendpointpb.OSPolicy_Resource{
ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{
- File: tt.frpb,
+ File: tt.fileResourcePB,
},
},
}
- if err := pr.Validate(ctx); err != nil {
- t.Fatalf("Unexpected error: %v", err)
- }
+ gotErr := pr.Validate(ctx)
+ utiltest.AssertErrorMatchAndSkip(t, gotErr, tt.wantErr)
- if diff := cmp.Diff(pr.ManagedResources(),
&ManagedResources{Files: []ManagedFile{tt.wantMR}},
cmp.AllowUnexported(ManagedFile{})); diff != "" {
+ if diff := cmp.Diff(pr.ManagedResources(),
&ManagedResources{Files: []ManagedFile{tt.wantManagedFile}},
cmp.AllowUnexported(ManagedFile{})); diff != "" {
t.Errorf("OSPolicyResource does not match
expectation: (-got +want)\n%s", diff)
}
- if diff :=
cmp.Diff(pr.resource.(*fileResource).managedFile, tt.wantMR,
cmp.AllowUnexported(ManagedFile{})); diff != "" {
+ if diff :=
cmp.Diff(pr.resource.(*fileResource).managedFile, tt.wantManagedFile,
cmp.AllowUnexported(ManagedFile{})); diff != "" {
t.Errorf("fileResource does not match
expectation: (-got +want)\n%s", diff)
}
})
@@ -164,7 +232,7 @@
var tests = []struct {
name string
- frpb
*agentendpointpb.OSPolicy_Resource_FileResource
+ fileResourcePB
*agentendpointpb.OSPolicy_Resource_FileResource
wantInDesiredState bool
}{
{
@@ -279,7 +347,7 @@
pr := &OSPolicyResource{
OSPolicy_Resource:
&agentendpointpb.OSPolicy_Resource{
ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{
- File: tt.frpb,
+ File: tt.fileResourcePB,
},
},
}
@@ -310,13 +378,13 @@
t.Fatal(err)
}
- frpb := &agentendpointpb.OSPolicy_Resource_FileResource{
+ fileResourcePB := &agentendpointpb.OSPolicy_Resource_FileResource{
Path: tmpFile,
State: agentendpointpb.OSPolicy_Resource_FileResource_ABSENT,
}
pr := &OSPolicyResource{
OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{
- ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{File: frpb},
+ ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{File: fileResourcePB},
},
}
if err := pr.Validate(ctx); err != nil {
@@ -345,7 +413,7 @@
}
wantFile := filepath.Join(tmpDir, "bar")
- frpb := &agentendpointpb.OSPolicy_Resource_FileResource{
+ fileResourcePB := &agentendpointpb.OSPolicy_Resource_FileResource{
Path: wantFile,
Source: &agentendpointpb.OSPolicy_Resource_FileResource_File{
File: &agentendpointpb.OSPolicy_Resource_File{
@@ -358,7 +426,7 @@
}
pr := &OSPolicyResource{
OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{
- ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{File: frpb},
+ ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{File: fileResourcePB},
},
}
if err := pr.Validate(ctx); err != nil {
@@ -377,3 +445,154 @@
t.Fatal("Repo file contents do not match after enforcement")
}
}
+
+func TestFileResourceEnforceStateErrors(t *testing.T) {
+ ctx := t.Context()
+ missingPath := filepath.Join(t.TempDir(), "does_not_exist")
+ errRemove := os.Remove(missingPath)
+ tests := []struct {
+ name string
+ setup func(t *testing.T) *OSPolicyResource
+ wantErr error
+ }{
+ {
+ name: "invalid state, expect unrecognized desired state
error",
+ setup: func(t *testing.T) *OSPolicyResource {
+ fileResourcePB :=
&agentendpointpb.OSPolicy_Resource_FileResource{
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_DESIRED_STATE_UNSPECIFIED,
+ }
+ return &OSPolicyResource{
+ OSPolicy_Resource:
&agentendpointpb.OSPolicy_Resource{
+ ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{
+ File: fileResourcePB,
+ },
+ },
+ resource: &fileResource{
+ OSPolicy_Resource_FileResource:
fileResourcePB,
+ managedFile: ManagedFile{
+ State:
fileResourcePB.GetState(),
+ },
+ },
+ }
+ },
+ wantErr: fmt.Errorf("unrecognized DesiredState for
FileResource: %q",
agentendpointpb.OSPolicy_Resource_FileResource_DESIRED_STATE_UNSPECIFIED),
+ },
+ {
+ name: "invalid path remove, expect failed to remove
path error",
+ setup: func(t *testing.T) *OSPolicyResource {
+ fileResourcePB :=
&agentendpointpb.OSPolicy_Resource_FileResource{
+ Path: missingPath,
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_ABSENT,
+ }
+ return &OSPolicyResource{
+ OSPolicy_Resource:
&agentendpointpb.OSPolicy_Resource{
+ ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{
+ File: fileResourcePB,
+ },
+ },
+ resource: &fileResource{
+ OSPolicy_Resource_FileResource:
fileResourcePB,
+ managedFile: ManagedFile{
+ Path:
fileResourcePB.GetPath(),
+ State:
fileResourcePB.GetState(),
+ },
+ },
+ }
+ },
+ wantErr: fmt.Errorf("error removing %q: %v",
missingPath, errRemove),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pr := tt.setup(t)
+ gotErr := pr.EnforceState(ctx)
+ utiltest.AssertErrorMatch(t, gotErr, tt.wantErr)
+ })
+ }
+}
+
+func TestFileResourceEnforceStateDownload(t *testing.T) {
+ ctx := t.Context()
+ tmpDir := t.TempDir()
+
+ dst := filepath.Join(tmpDir, "dst")
+
+ tests := []struct {
+ name string
+ setup func(t *testing.T) (*OSPolicyResource, string)
+ wantErr error
+ wantContent string
+ }{
+ {
+ name: "download fails during enforce state, expect
unrecognized source type error",
+ setup: func(t *testing.T) (*OSPolicyResource, string) {
+ path := filepath.Join(tmpDir, "some_path")
+ fileResourcePB :=
&agentendpointpb.OSPolicy_Resource_FileResource{
+ Path: path,
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
+ Source: nil,
+ }
+ pr := &OSPolicyResource{
+ OSPolicy_Resource:
&agentendpointpb.OSPolicy_Resource{
+ ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{
+ File: fileResourcePB,
+ },
+ },
+ resource: &fileResource{
+ OSPolicy_Resource_FileResource:
fileResourcePB,
+ managedFile: ManagedFile{
+ Path:
fileResourcePB.GetPath(),
+ State:
fileResourcePB.GetState(),
+ Permisions:
defaultFilePerms,
+ },
+ },
+ }
+ t.Cleanup(func() { pr.Cleanup(ctx) })
+ return pr, path
+ },
+ wantErr: errors.New("unrecognized Source type for
FileResource: %!q(<nil>)"),
+ },
+ {
+ name: "download succeeds during enforce state, expect
success",
+ setup: func(t *testing.T) (*OSPolicyResource, string) {
+ fileResourcePB :=
&agentendpointpb.OSPolicy_Resource_FileResource{
+ Path: dst,
+ State:
agentendpointpb.OSPolicy_Resource_FileResource_PRESENT,
+ Source:
&agentendpointpb.OSPolicy_Resource_FileResource_Content{
+ Content: "test content",
+ },
+ }
+ pr := &OSPolicyResource{
+ OSPolicy_Resource:
&agentendpointpb.OSPolicy_Resource{
+ ResourceType:
&agentendpointpb.OSPolicy_Resource_File_{
+ File: fileResourcePB,
+ },
+ },
+ resource: &fileResource{
+ OSPolicy_Resource_FileResource:
fileResourcePB,
+ managedFile: ManagedFile{
+ Path:
fileResourcePB.GetPath(),
+ State:
fileResourcePB.GetState(),
+ Permisions:
defaultFilePerms,
+ },
+ },
+ }
+ t.Cleanup(func() { pr.Cleanup(ctx) })
+ return pr, dst
+ },
+ wantErr: nil,
+ wantContent: "test content",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pr, path := tt.setup(t)
+ gotErr := pr.EnforceState(ctx)
+
+ utiltest.AssertErrorMatchAndSkip(t, gotErr, tt.wantErr)
+ utiltest.AssertEquals(t, pr.InDesiredState(), true)
+ utiltest.AssertFileContents(t, path, tt.wantContent)
+ })
+ }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osconfig-20260625.00/e2e_tests/go.mod
new/osconfig-20260708.00/e2e_tests/go.mod
--- old/osconfig-20260625.00/e2e_tests/go.mod 2026-06-25 16:31:39.000000000
+0200
+++ new/osconfig-20260708.00/e2e_tests/go.mod 2026-07-08 10:05:56.000000000
+0200
@@ -50,11 +50,11 @@
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
- golang.org/x/crypto v0.49.0 // indirect
- golang.org/x/net v0.52.0 // indirect
+ golang.org/x/crypto v0.51.0 // indirect
+ golang.org/x/net v0.55.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
- golang.org/x/sys v0.42.0 // indirect
- golang.org/x/text v0.35.0 // indirect
+ golang.org/x/sys v0.45.0 // indirect
+ golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.15.0 // indirect
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osconfig-20260625.00/e2e_tests/go.sum
new/osconfig-20260708.00/e2e_tests/go.sum
--- old/osconfig-20260625.00/e2e_tests/go.sum 2026-06-25 16:31:39.000000000
+0200
+++ new/osconfig-20260708.00/e2e_tests/go.sum 2026-07-08 10:05:56.000000000
+0200
@@ -375,8 +375,8 @@
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0/go.mod
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
-golang.org/x/crypto v0.49.0/go.mod
h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
+golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
+golang.org/x/crypto v0.51.0/go.mod
h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod
h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -456,8 +456,8 @@
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod
h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
-golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
+golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -554,8 +554,8 @@
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
-golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
+golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod
h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -565,8 +565,8 @@
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
-golang.org/x/text v0.35.0/go.mod
h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
+golang.org/x/text v0.37.0/go.mod
h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod
h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod
h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod
h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osconfig-20260625.00/go.mod
new/osconfig-20260708.00/go.mod
--- old/osconfig-20260625.00/go.mod 2026-06-25 16:31:39.000000000 +0200
+++ new/osconfig-20260708.00/go.mod 2026-07-08 10:05:56.000000000 +0200
@@ -185,7 +185,7 @@
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/mod v0.35.0 // indirect
- golang.org/x/net v0.54.0 // indirect
+ golang.org/x/net v0.55.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect
golang.org/x/text v0.37.0 // indirect
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osconfig-20260625.00/go.sum
new/osconfig-20260708.00/go.sum
--- old/osconfig-20260625.00/go.sum 2026-06-25 16:31:39.000000000 +0200
+++ new/osconfig-20260708.00/go.sum 2026-07-08 10:05:56.000000000 +0200
@@ -653,8 +653,8 @@
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod
h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod
h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
-golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
+golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
+golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/osconfig-20260625.00/policies/recipes/artifacts_test.go
new/osconfig-20260708.00/policies/recipes/artifacts_test.go
--- old/osconfig-20260625.00/policies/recipes/artifacts_test.go 2026-06-25
16:31:39.000000000 +0200
+++ new/osconfig-20260708.00/policies/recipes/artifacts_test.go 2026-07-08
10:05:56.000000000 +0200
@@ -16,17 +16,204 @@
import (
"context"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
"net/url"
"strings"
"testing"
+
+ "github.com/GoogleCloudPlatform/osconfig/util/utiltest"
+
+ "cloud.google.com/go/osconfig/agentendpoint/apiv1beta/agentendpointpb"
)
-func TestFetchArtifacts_http_InvalidURL(t *testing.T) {
- uri := "ftp://google.com/agent.deb"
- u, err := url.Parse(uri)
- _, err = getHTTPArtifact(context.Background(), nil, *u)
- if err == nil || !strings.Contains(err.Error(), "unsupported protocol
scheme") {
- t.Errorf("expected error (unsupported protocol); got(%v)", err)
+// TestGetHTTPArtifact verifies downloading artifacts via HTTP/HTTPS and
handles various error scenarios.
+func TestGetHTTPArtifact(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r
*http.Request) {
+ fmt.Fprint(w, "test data")
+ }))
+ defer ts.Close()
+
+ ts404 := httptest.NewServer(http.HandlerFunc(func(w
http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ }))
+ defer ts404.Close()
+
+ tests := []struct {
+ name string
+ uri string
+ client *http.Client
+ want string
+ wantErr error
+ }{
+ {
+ name: "valid URL, want test data and nil error",
+ uri: ts.URL,
+ client: ts.Client(),
+ want: "test data",
+ wantErr: nil,
+ },
+ {
+ name: "unsupported protocol ftp, want unsupported
protocol scheme error",
+ uri: "ftp://google.com/agent.deb",
+ client: http.DefaultClient,
+ want: "",
+ wantErr: errors.New("error, unsupported protocol scheme
ftp"),
+ },
+ {
+ name: "http status 404, want got http status 404
error",
+ uri: ts404.URL,
+ client: ts404.Client(),
+ want: "",
+ wantErr: errors.New("got http status 404 when
attempting to download artifact"),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ u, _ := url.Parse(tt.uri)
+ reader, err := getHTTPArtifact(context.Background(),
tt.client, *u)
+
+ utiltest.AssertErrorMatchAndSkip(t, err, tt.wantErr)
+
+ defer reader.Close()
+ data, _ := io.ReadAll(reader)
+ utiltest.AssertEquals(t, string(data), tt.want)
+ })
+ }
+}
+
+// setupFetchArtifactsTest sets up a mock HTTP server and GCS emulator
environment for fetchArtifacts tests.
+func setupFetchArtifactsTest(t *testing.T) *httptest.Server {
+ t.Helper()
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r
*http.Request) {
+ if r.Method == "GET" {
+ if strings.Contains(r.URL.Path,
"test-bucket/test-object") {
+ fmt.Fprint(w, "gcs data")
+ return
+ }
+ if strings.Contains(r.URL.Path, "remote-artifact") {
+ fmt.Fprint(w, "remote data")
+ return
+ }
+ fmt.Fprint(w, "data")
+ return
+ }
+ w.WriteHeader(http.StatusNotFound)
+ }))
+ t.Cleanup(ts.Close)
+
+ t.Setenv("STORAGE_EMULATOR_HOST", ts.URL)
+ return ts
+}
+
+// TestFetchArtifacts verifies that single or multiple artifacts can be
correctly downloaded from HTTP/HTTPS sources and GCS.
+func TestFetchArtifacts(t *testing.T) {
+ ts := setupFetchArtifactsTest(t)
+
+ tests := []struct {
+ name string
+ artifacts []*agentendpointpb.SoftwareRecipe_Artifact
+ want map[string]string
+ wantErr error
+ }{
+ {
+ name: "remote artifact, want remote data and nil error",
+ artifacts: []*agentendpointpb.SoftwareRecipe_Artifact{
+ {
+ Id: "remote-art",
+ Artifact:
&agentendpointpb.SoftwareRecipe_Artifact_Remote_{
+ Remote:
&agentendpointpb.SoftwareRecipe_Artifact_Remote{
+ Uri: ts.URL +
"/remote-artifact",
+ },
+ },
+ },
+ },
+ want: map[string]string{
+ "remote-art": "remote data",
+ },
+ },
+ {
+ name: "GCS artifact, want gcs data and nil error",
+ artifacts: []*agentendpointpb.SoftwareRecipe_Artifact{
+ {
+ Id: "gcs-art",
+ Artifact:
&agentendpointpb.SoftwareRecipe_Artifact_Gcs_{
+ Gcs:
&agentendpointpb.SoftwareRecipe_Artifact_Gcs{
+ Bucket: "test-bucket",
+ Object: "test-object",
+ },
+ },
+ },
+ },
+ want: map[string]string{
+ "gcs-art": "gcs data",
+ },
+ },
+ {
+ name: "multiple artifacts, want data and nil error",
+ artifacts: []*agentendpointpb.SoftwareRecipe_Artifact{
+ {
+ Id: "art1",
+ Artifact:
&agentendpointpb.SoftwareRecipe_Artifact_Remote_{
+ Remote:
&agentendpointpb.SoftwareRecipe_Artifact_Remote{
+ Uri: ts.URL,
+ },
+ },
+ },
+ {
+ Id: "art2",
+ Artifact:
&agentendpointpb.SoftwareRecipe_Artifact_Remote_{
+ Remote:
&agentendpointpb.SoftwareRecipe_Artifact_Remote{
+ Uri: ts.URL,
+ },
+ },
+ },
+ },
+ want: map[string]string{
+ "art1": "data",
+ "art2": "data",
+ },
+ },
+ {
+ name: "remote artifact unsupported protocol, want
protocol error",
+ artifacts: []*agentendpointpb.SoftwareRecipe_Artifact{
+ {
+ Id: "ftp-art",
+ Artifact:
&agentendpointpb.SoftwareRecipe_Artifact_Remote_{
+ Remote:
&agentendpointpb.SoftwareRecipe_Artifact_Remote{
+ Uri: "ftp://localhost",
+ },
+ },
+ },
+ },
+ wantErr: fmt.Errorf("error fetching artifact %q: error,
unsupported protocol scheme ftp", "ftp-art"),
+ },
+ {
+ name: "unknown artifact type, want unknown artifact
error",
+ artifacts: []*agentendpointpb.SoftwareRecipe_Artifact{
+ {
+ Id: "unknown-art",
+ },
+ },
+ wantErr: fmt.Errorf("unknown artifact type for artifact
unknown-art"),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tmpDir := t.TempDir()
+ gotArtifacts, gotErr :=
fetchArtifacts(context.Background(), tt.artifacts, tmpDir)
+ utiltest.AssertErrorMatch(t, gotErr, tt.wantErr)
+ utiltest.AssertEquals(t, len(gotArtifacts),
len(tt.want))
+ for id, wantContent := range tt.want {
+ path, _ := gotArtifacts[id]
+ utiltest.AssertFileContents(t, path,
wantContent)
+ }
+ })
}
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osconfig-20260625.00/policies/recipes/steps_test.go
new/osconfig-20260708.00/policies/recipes/steps_test.go
--- old/osconfig-20260625.00/policies/recipes/steps_test.go 2026-06-25
16:31:39.000000000 +0200
+++ new/osconfig-20260708.00/policies/recipes/steps_test.go 2026-07-08
10:05:56.000000000 +0200
@@ -3,15 +3,23 @@
import (
"archive/tar"
"archive/zip"
+ "bytes"
+ "compress/gzip"
"context"
"fmt"
+ "io"
"os"
"path/filepath"
"regexp"
+ "strconv"
"testing"
"time"
+ "github.com/ulikunitz/xz"
+ "github.com/ulikunitz/xz/lzma"
+
"cloud.google.com/go/osconfig/agentendpoint/apiv1beta/agentendpointpb"
+ "github.com/GoogleCloudPlatform/osconfig/util/utiltest"
)
type fileEntry struct {
@@ -216,3 +224,372 @@
t.Errorf("unable to close file: %s", err)
}
}
+
+// Test_parsePermissions verifies that octal permission strings are correctly
parsed into os.FileMode.
+func Test_parsePermissions(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ wantPerm os.FileMode
+ wantErr error
+ }{
+ {
+ name: "empty string, want 0755",
+ input: "",
+ wantPerm: 0755,
+ wantErr: nil,
+ },
+ {
+ name: "valid octal 0644, want 0644",
+ input: "0644",
+ wantPerm: 0644,
+ wantErr: nil,
+ },
+ {
+ name: "valid octal 755, want 0755",
+ input: "755",
+ wantPerm: 0755,
+ wantErr: nil,
+ },
+ {
+ name: "invalid octal, want parse error",
+ input: "888",
+ wantErr: &strconv.NumError{Func: "ParseUint", Num:
"888", Err: strconv.ErrSyntax},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotPerm, gotErr := parsePermissions(tt.input)
+ utiltest.AssertErrorMatchAndSkip(t, gotErr, tt.wantErr)
+ utiltest.AssertEquals(t, gotPerm, tt.wantPerm)
+ })
+ }
+}
+
+func setupSymlinkTest(t *testing.T) (dir, linkInside, linkOutside string) {
+ tmpDir := t.TempDir()
+ dir = filepath.Join(tmpDir, "dir")
+ otherDir := filepath.Join(tmpDir, "other")
+ if err := os.Mkdir(dir, 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Mkdir(otherDir, 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ file := filepath.Join(dir, "file")
+ if err := os.WriteFile(file, []byte("test"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ otherFile := filepath.Join(otherDir, "other_file")
+ if err := os.WriteFile(otherFile, []byte("test"), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ linkInside = filepath.Join(dir, "link_inside")
+ if err := os.Symlink(file, linkInside); err != nil {
+ t.Fatal(err)
+ }
+ linkOutside = filepath.Join(dir, "link_outside")
+ if err := os.Symlink(otherFile, linkOutside); err != nil {
+ t.Fatal(err)
+ }
+
+ return dir, linkInside, linkOutside
+}
+
+// Test_ensureSymlinkBelongsToDir ensures that symlinks do not point to
locations outside the designated directory.
+func Test_ensureSymlinkBelongsToDir(t *testing.T) {
+ dir, linkInside, linkOutside := setupSymlinkTest(t)
+
+ tests := []struct {
+ name string
+ link string
+ wantErr error
+ }{
+ {
+ name: "link target inside dir, want nil error",
+ link: linkInside,
+ wantErr: nil,
+ },
+ {
+ name: "link target outside dir, want outside link
error",
+ link: linkOutside,
+ wantErr: fmt.Errorf("symlink %s, does not belongs to
dir %s, rel ../other/other_file", linkOutside, dir),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := ensureSymlinkBelongsToDir(dir, tt.link)
+ utiltest.AssertErrorMatch(t, err, tt.wantErr)
+ })
+ }
+}
+
+// Test_stepCopyFile verifies the file copying logic, including artifact
resolution, overwrite behavior, and permission setting.
+func Test_stepCopyFile(t *testing.T) {
+ tmpDir := t.TempDir()
+ artifactPath := filepath.Join(tmpDir, "artifact")
+ content := "artifact content"
+ if err := os.WriteFile(artifactPath, []byte(content), 0644); err != nil
{
+ t.Fatal(err)
+ }
+
+ destPath := filepath.Join(tmpDir, "dest")
+
+ tests := []struct {
+ name string
+ step *agentendpointpb.SoftwareRecipe_Step_CopyFile
+ artifacts map[string]string
+ setupFunc func()
+ wantErr error
+ wantContent string
+ wantPerm os.FileMode
+ }{
+ {
+ name: "successful copy, want nil error and 0644
permission",
+ step: &agentendpointpb.SoftwareRecipe_Step_CopyFile{
+ ArtifactId: "art1",
+ Destination: destPath,
+ Permissions: "0644",
+ },
+ artifacts: map[string]string{"art1": artifactPath},
+ setupFunc: func() {},
+ wantContent: content,
+ wantPerm: 0644,
+ },
+ {
+ name: "file already exists and overwrite false, want
file exists error",
+ step: &agentendpointpb.SoftwareRecipe_Step_CopyFile{
+ ArtifactId: "art1",
+ Destination: destPath,
+ Overwrite: false,
+ },
+ artifacts: map[string]string{"art1": artifactPath},
+ setupFunc: func() { os.WriteFile(destPath,
[]byte("old content"), 0644) },
+ wantErr: fmt.Errorf("file already exists at path %q
and Overwrite = false", destPath),
+ wantContent: "old content",
+ wantPerm: 0644,
+ },
+ {
+ name: "file already exists and overwrite true, want nil
error and 0755",
+ step: &agentendpointpb.SoftwareRecipe_Step_CopyFile{
+ ArtifactId: "art1",
+ Destination: destPath,
+ Overwrite: true,
+ Permissions: "0755",
+ },
+ artifacts: map[string]string{"art1": artifactPath},
+ setupFunc: func() { os.WriteFile(destPath,
[]byte("old content"), 0644) },
+ wantContent: content,
+ wantPerm: 0755,
+ },
+ {
+ name: "invalid permissions, want parse error",
+ step: &agentendpointpb.SoftwareRecipe_Step_CopyFile{
+ ArtifactId: "art1",
+ Destination: destPath,
+ Permissions: "888",
+ },
+ artifacts: map[string]string{"art1": artifactPath},
+ setupFunc: func() {},
+ wantErr: &strconv.NumError{Func: "ParseUint", Num:
"888", Err: strconv.ErrSyntax},
+ wantContent: "",
+ wantPerm: 0,
+ },
+ {
+ name: "artifact not found, want find error",
+ step: &agentendpointpb.SoftwareRecipe_Step_CopyFile{
+ ArtifactId: "unknown",
+ Destination: destPath,
+ },
+ artifacts: map[string]string{"art1": artifactPath},
+ setupFunc: func() {},
+ wantErr: fmt.Errorf("could not find location for
artifact \"unknown\""),
+ wantContent: "",
+ wantPerm: 0,
+ },
+ {
+ name: "artifact file missing, want no file error",
+ step: &agentendpointpb.SoftwareRecipe_Step_CopyFile{
+ ArtifactId: "art2",
+ Destination: destPath,
+ },
+ artifacts: map[string]string{"art2":
filepath.Join(tmpDir, "missing")},
+ setupFunc: func() {},
+ wantErr: &os.PathError{Op: "open", Path:
filepath.Join(tmpDir, "missing"), Err: fmt.Errorf("no such file or directory")},
+ wantContent: "",
+ wantPerm: 0,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Cleanup(func() {
+ os.Remove(destPath)
+ })
+ tt.setupFunc()
+
+ gotErr := stepCopyFile(tt.step, tt.artifacts, nil, "")
+ utiltest.AssertErrorMatch(t, gotErr, tt.wantErr)
+ utiltest.AssertFileExistsAndContents(t, destPath,
tt.wantContent)
+ info, err := os.Stat(destPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ utiltest.AssertEquals(t, info.Mode().Perm(),
tt.wantPerm)
+ })
+ }
+}
+
+// Test_checkForConflicts verifies that archive extraction does not overwrite
existing files.
+func Test_checkForConflicts(t *testing.T) {
+ existingFilePath := utiltest.WriteToTempFileMust(t, "exists.txt",
[]byte("content"))
+ tmpDir := filepath.Dir(existingFilePath)
+
+ tests := []struct {
+ name string
+ files []string
+ wantErr error
+ }{
+ {
+ name: "no conflicts, want nil error",
+ files: []string{"new.txt", "another.txt"},
+ },
+ {
+ name: "conflict with existing file, want file exists
error",
+ files: []string{"exists.txt"},
+ wantErr: fmt.Errorf("file exists: %s",
existingFilePath),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ buf := createTarArchive(t, tt.files)
+ gotTar := tar.NewReader(buf)
+
+ gotErr := checkForConflicts(gotTar, tmpDir)
+ utiltest.AssertErrorMatch(t, gotErr, tt.wantErr)
+ })
+ }
+}
+
+func setupTestDecompress(t *testing.T, content []byte) (gzipData, xzData,
lzmaData, bzip2Data []byte) {
+ // GZIP
+ var gzipBuf bytes.Buffer
+ gw := gzip.NewWriter(&gzipBuf)
+ if _, err := gw.Write(content); err != nil {
+ t.Fatal(err)
+ }
+ gw.Close()
+
+ // XZ
+ var xzBuf bytes.Buffer
+ xw, err := xz.NewWriter(&xzBuf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := xw.Write(content); err != nil {
+ t.Fatal(err)
+ }
+ xw.Close()
+
+ // LZMA
+ var lzmaBuf bytes.Buffer
+ lw, err := lzma.NewWriter2(&lzmaBuf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := lw.Write(content); err != nil {
+ t.Fatal(err)
+ }
+ lw.Close()
+
+ // BZIP2 (pre-generated hex for "dummy content")
+ bzip2Data = []byte{
+ 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59,
0x7f, 0x40, 0x3b, 0xb8, 0x00, 0x00,
+ 0x01, 0x11, 0x80, 0x40, 0x00, 0x0e, 0x03, 0x86, 0x20, 0x20,
0x00, 0x22, 0x00, 0x69, 0xea, 0x10,
+ 0x03, 0x02, 0x39, 0x84, 0x84, 0x31, 0x9e, 0x2e, 0xe4, 0x8a,
0x70, 0xa1, 0x20, 0xfe, 0x80, 0x77,
+ 0x70,
+ }
+
+ return gzipBuf.Bytes(), xzBuf.Bytes(), lzmaBuf.Bytes(), bzip2Data
+}
+
+// Test_decompress verifies that the decompress function correctly identifies
different archive formats.
+func Test_decompress(t *testing.T) {
+ content := []byte("dummy content")
+ gzipData, xzData, lzmaData, bzip2Data := setupTestDecompress(t, content)
+
+ tests := []struct {
+ name string
+ archiveType
agentendpointpb.SoftwareRecipe_Step_ExtractArchive_ArchiveType
+ data []byte
+ wantErr error
+ }{
+ {
+ name: "archive type TAR, want nil",
+ archiveType:
agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR,
+ data: content,
+ wantErr: nil,
+ },
+ {
+ name: "archive type TAR_GZIP, want nil",
+ archiveType:
agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_GZIP,
+ data: gzipData,
+ wantErr: nil,
+ },
+ {
+ name: "archive type TAR_BZIP, want nil",
+ archiveType:
agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_BZIP,
+ data: bzip2Data,
+ wantErr: nil,
+ },
+ {
+ name: "archive type TAR_LZMA, want nil",
+ archiveType:
agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_LZMA,
+ data: lzmaData,
+ wantErr: nil,
+ },
+ {
+ name: "archive type TAR_XZ, want nil",
+ archiveType:
agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_XZ,
+ data: xzData,
+ wantErr: nil,
+ },
+ {
+ name: "unrecognized archive type, want error",
+ archiveType: 999,
+ data: content,
+ wantErr: fmt.Errorf("Unrecognized archive type
\"999\" when trying to decompress tar"),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ reader := bytes.NewReader(tt.data)
+ gotReader, gotErr := decompress(reader, tt.archiveType)
+
+ utiltest.AssertErrorMatchAndSkip(t, gotErr, tt.wantErr)
+ gotContent, err := io.ReadAll(gotReader)
+ if err != nil {
+ t.Fatalf("failed to read decompressed content:
%v", err)
+ }
+ utiltest.AssertEquals(t, gotContent, content)
+ })
+ }
+}
+
+func createTarArchive(t *testing.T, files []string) *bytes.Buffer {
+ t.Helper()
+ var buf bytes.Buffer
+ tw := tar.NewWriter(&buf)
+ for _, f := range files {
+ if err := tw.WriteHeader(&tar.Header{Name: f}); err != nil {
+ t.Fatalf("failed to write tar header for %q: %v", f,
err)
+ }
+ }
+ if err := tw.Close(); err != nil {
+ t.Fatalf("failed to close tar writer: %v", err)
+ }
+ return &buf
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osconfig-20260625.00/util/utiltest/utiltest.go
new/osconfig-20260708.00/util/utiltest/utiltest.go
--- old/osconfig-20260625.00/util/utiltest/utiltest.go 2026-06-25
16:31:39.000000000 +0200
+++ new/osconfig-20260708.00/util/utiltest/utiltest.go 2026-07-08
10:05:56.000000000 +0200
@@ -184,6 +184,20 @@
}
}
+// AssertFileExistsAndContents verifies that the file at filePath exists and
matches wantContent.
+// If the file does not exist and wantContent is empty, the test is skipped.
+func AssertFileExistsAndContents(t *testing.T, filePath string, wantContent
string) {
+ t.Helper()
+ _, err := os.Stat(filePath)
+ if os.IsNotExist(err) {
+ if wantContent == "" {
+ t.SkipNow()
+ }
+ t.Fatalf("Failed to read file %q: %v", filePath, err)
+ }
+ AssertFileContents(t, filePath, wantContent)
+}
+
// OverrideVariable overrides the value of a variable and returns a function
to restore it.
func OverrideVariable[T any](t *testing.T, ptr *T, val T) {
original := *ptr
++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/google-osconfig-agent/vendor.tar.gz
/work/SRC/openSUSE:Factory/.google-osconfig-agent.new.1991/vendor.tar.gz
differ: char 122, line 1