From 7ff69b1e8f84bb8408d2b7aed29454ca6ba2938d Mon Sep 17 00:00:00 2001 From: Deepti Vaidyanathan Date: Mon, 6 Jan 2025 14:37:10 -0800 Subject: [PATCH 1/7] Adds error clarification codes --- pkg/errorutil/errorclarificationcodes.go | 49 ++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 pkg/errorutil/errorclarificationcodes.go diff --git a/pkg/errorutil/errorclarificationcodes.go b/pkg/errorutil/errorclarificationcodes.go new file mode 100644 index 0000000..cad4274 --- /dev/null +++ b/pkg/errorutil/errorclarificationcodes.go @@ -0,0 +1,49 @@ +package errorutil + +import ( + "math" +) + +const ( + // System errors + fileDownload_badRequest int = -41 + fileDownload_unknownError int = -40 + + imds_internalMsiError int = -30 + + internal_badConfig int = -21 + internal_couldNotFindCertificate int = -20 + + storage_internalServerError int = -1 + systemError int = 0 // CRP interprets anything > 0 as user errors + + // User errors + commandExecution_failedUnknownError int = 1 + commandExecution_failureExitCode int = 2 + commandExecution_interruptedByVmShutdown int = 3 + + customerInput_commandToExecuteSpecifiedInTwoPlaces int = 20 + customerInput_ignoreRelativePathForFileDownloadsSpecifiedInTwoPlaces int = 21 + customerInput_fileUrisSpecifiedInTwoPlaces int = 22 + customerInput_commandToExecuteNotSpecified int = 23 + customerInput_fileUriContainsNull int = 24 + customerInput_invalidFileUris int = 25 + customerInput_storageCredsAndMIBothSpecified int = 26 + customerInput_clientIdObjectIdBothSpecified int = 27 + + fileDownload_unableToCreateDownloadDirectory int = 50 + fileDownload_sasExpired int = 51 + fileDownload_accessDenied int = 52 + fileDownload_doesNotExist int = 53 + fileDownload_networkingError int = 54 + fileDownload_genericError int = 55 + fileDownload_exceededTimeout int = 56 + + msi_notFound int = 70 + msi_doesNotHaveRightPermissions int = 71 + msi_GenericRetrievalError int = 72 + + // No Error - used as a placeholder value + // when representing an "empty" ErrorWithClarification + noError int = math.MaxInt +) From 3acd0b5d20cfe2969895f94b9d25786ab5462d58 Mon Sep 17 00:00:00 2001 From: Deepti Vaidyanathan Date: Tue, 7 Jan 2025 14:02:43 -0800 Subject: [PATCH 2/7] Initial integration of user error clarifications --- go.mod | 28 +++++++-- go.sum | 79 ++++++++++++++++++++++++ main/cmds.go | 44 +++++++------ main/exec.go | 8 ++- main/files.go | 37 ++++++----- main/handlersettings.go | 38 +++++++----- main/main.go | 10 +-- main/status.go | 24 +++++++ pkg/download/downloader.go | 31 ++++++++-- pkg/download/retry.go | 18 +++--- pkg/download/save.go | 14 +++-- pkg/errorutil/errorclarificationcodes.go | 18 +++--- 12 files changed, 261 insertions(+), 88 deletions(-) diff --git a/go.mod b/go.mod index 4564042..44f9d88 100644 --- a/go.mod +++ b/go.mod @@ -1,31 +1,47 @@ module github.com/Azure/custom-script-extension-linux -go 1.21 +go 1.23 + +toolchain go1.23.4 require ( github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187 - github.com/Azure/azure-extension-platform v0.0.0-20230218002700-ca684482c954 - github.com/Azure/azure-sdk-for-go v3.1.0-beta.0.20160802173609-87de771fcdf5+incompatible + github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6 + github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1 - github.com/go-kit/kit v0.12.0 - github.com/google/uuid v1.3.0 + github.com/go-kit/kit v0.13.0 + github.com/google/uuid v1.6.0 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.2 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/text v0.9.0 + golang.org/x/text v0.21.0 ) require ( + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.29 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-stack/stack v1.8.1 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4 // indirect github.com/gorilla/mux v0.0.0-20160605233521-9fa818a44c2b // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/sys v0.29.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/go-kit/kit => github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2 + +// //DELETE after testing +// replace "github.com/Azure/custom-script-extension-linux" => "../custom-script-extension-linux" + diff --git a/go.sum b/go.sum index 6a10423..5ac8ee9 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,27 @@ github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187 h github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187/go.mod h1:a0BFq9UoWBHvBS7iagvjFqBjYfxtBsmqvCLWIHRq9b0= github.com/Azure/azure-extension-platform v0.0.0-20230218002700-ca684482c954 h1:Jnedpc6riCirNEUQGZ7I7gH1PZym71sKFnbP9Lb32oA= github.com/Azure/azure-extension-platform v0.0.0-20230218002700-ca684482c954/go.mod h1:1hEkO8M1zN/SQpdFOTDDMTNfeE1Q2tCHmEXXiHrWTgo= +github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6 h1:RDJpiDBkLFa711zwULKd1bhIoWpaslIwlfz5CBHn+eI= +github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY= github.com/Azure/azure-sdk-for-go v3.1.0-beta.0.20160802173609-87de771fcdf5+incompatible h1:7CctKV2SGUVFq3a+WNHypGRKQzaPCNVEAMMAlEJXrWc= github.com/Azure/azure-sdk-for-go v3.1.0-beta.0.20160802173609-87de771fcdf5+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= +github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= +github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= +github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4= +github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1 h1:/sPElPBMLSi6+bV0o0fPN4U24qQCNHs1i/BjnO+GqLc= github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1/go.mod h1:Rg55S63lgqSBCawY/oTm7jdFSySp6jwIqgHMB2IeHK8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -15,8 +34,16 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4 h1:3nOfQt8sRPYbXORD5tJ8YyQ3HlL2Jt3LJ2U17CbNh6I= github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v0.0.0-20160605233521-9fa818a44c2b h1:OFvZV3a+25cGJH9dETHw0nk0wV6hLZI7IJijOkXEFS0= @@ -45,8 +72,60 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/main/cmds.go b/main/cmds.go index 354f815..8ef85fd 100644 --- a/main/cmds.go +++ b/main/cmds.go @@ -13,6 +13,8 @@ import ( "time" utils "github.com/Azure/azure-extension-platform/pkg/utils" + vmextension "github.com/Azure/azure-extension-platform/vmextension" + github.com/Azure/custom-script-extension-linux/pkg/errorutil "github.com/Azure/custom-script-extension-linux/pkg/seqnum" "github.com/go-kit/kit/log" "github.com/pkg/errors" @@ -22,7 +24,7 @@ const ( maxScriptSize = 256 * 1024 ) -type cmdFunc func(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) (msg string, err error) +type cmdFunc func(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) (msg string, ewc vmextension.ErrorWithClarification) type preFunc func(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) error type cmd struct { @@ -55,14 +57,14 @@ var ( } ) -func noop(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) { +func noop(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { ctx.Log("event", "noop") - return "", nil + return "", vmextension.NewErrorWithClarification(errorutil.noError, nil) } -func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) { +func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { if err := os.MkdirAll(dataDir, 0755); err != nil { - return "", errors.Wrap(err, "failed to create data dir") + return "", vmextension.NewErrorWithClarification(errorutil.systemError, errors.Wrap(err, "failed to create data dir")) } // If the file mrseq does not exists it is for two possible reasons. @@ -74,10 +76,10 @@ func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) ctx.Log("event", "created data dir", "path", dataDir) ctx.Log("event", "installed") - return "", nil + return "", vmextension.NewErrorWithClarification(errorutil.noError, nil) } -func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) { +func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextensionErrorWithClarification) { { // a new context scope with path ctx = ctx.With("path", dataDir) ctx.Log("event", "removing data dir", "path", dataDir) @@ -87,7 +89,7 @@ func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, erro ctx.Log("event", "removed data dir") } ctx.Log("event", "uninstalled") - return "", nil + return "", vmextension.NewErrorWithClarification(errorutil.noError, nil) } func enablePre(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) error { @@ -110,16 +112,18 @@ func min(a, b int) int { return b } -func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, error) { +func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextensionErrorWithClarification) { // parse the extension handler settings (not available prior to 'enable') - cfg, err := parseAndValidateSettings(ctx, h.HandlerEnvironment.ConfigFolder, seqNum) - if err != nil { - return "", errors.Wrap(err, "failed to get configuration") + cfg, ewc := parseAndValidateSettings(ctx, h.HandlerEnvironment.ConfigFolder, seqNum) + if ewc.Err != nil { + ewc.Err = errors.Wrap(ewc.Err, "failed to get configuration") + return "", ewc } dir := filepath.Join(dataDir, downloadDir, fmt.Sprintf("%d", seqNum)) - if err := downloadFiles(ctx, dir, cfg); err != nil { - return "", errors.Wrap(err, "processing file downloads failed") + if ewc := downloadFiles(ctx, dir, cfg); ewc.Err != nil { + ewc.Err = errors.Wrap(ewc.Err, "processing file downloads failed") + return "", ewc } // execute the command, save its error @@ -175,12 +179,12 @@ func checkAndSaveSeqNum(ctx log.Logger, seq int, mrseqPath string) (shouldExit b // downloadFiles downloads the files specified in cfg into dir (creates if does // not exist) and takes storage credentials specified in cfg into account. -func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) error { +func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) vmextension.ErrorWithClarification { // - prepare the output directory for files and the command output // - create the directory if missing ctx.Log("event", "creating output directory", "path", dir) if err := os.MkdirAll(dir, 0700); err != nil { - return errors.Wrap(err, "failed to prepare output directory") + return vmextension.NewErrorWithClarification(errorutil.fileDownload_unableToCreateDownloadDirectory, errors.Wrap(err, "failed to prepare output directory")) } ctx.Log("event", "created output directory") @@ -200,13 +204,13 @@ func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) error { for i, f := range cfg.fileUrls() { ctx := ctx.With("file", i) ctx.Log("event", "download start") - if err := downloadAndProcessURL(ctx, f, dir, &cfg); err != nil { - ctx.Log("event", "download failed", "error", err) - return errors.Wrapf(err, "failed to download file[%d]", i) + if ewc := downloadAndProcessURL(ctx, f, dir, &cfg); ewc.Err != nil { + ctx.Log("event", "download failed", "error", ewc.Err) + return vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download file[%d]", i)) } ctx.Log("event", "download complete", "output", dir) } - return nil + return vmextension.NewErrorWithClarification(errorutil.noError, nil) } // runCmd runs the command (extracted from cfg) in the given dir (assumed to exist). diff --git a/main/exec.go b/main/exec.go index ed367ca..235c06b 100644 --- a/main/exec.go +++ b/main/exec.go @@ -9,6 +9,9 @@ import ( "syscall" "github.com/pkg/errors" + vmextension "github.com/Azure/azure-extension-platform/vmextension" + github.com/Azure/custom-script-extension-linux/pkg/errorutil + ) // Exec runs the given cmd in /bin/sh, saves its stdout/stderr streams to @@ -16,7 +19,7 @@ import ( // // On error, an exit code may be returned if it is an exit code error. // Given stdout and stderr will be closed upon returning. -func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, error) { +func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, vmextension.ErrorWithClarification) { defer stdout.Close() defer stderr.Close() @@ -30,6 +33,9 @@ func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, error) { if ok { if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { code := status.ExitStatus() + if code != 0 { + return code, vmextension.NewErrorWithClarification(errorutil.commandExecution_failureExitCode, fmt.Errorf("command terminated with exit status=%d", code)) + } return code, fmt.Errorf("command terminated with exit status=%d", code) } } diff --git a/main/files.go b/main/files.go index 8e57090..3bf686f 100644 --- a/main/files.go +++ b/main/files.go @@ -9,48 +9,53 @@ import ( "os" + "github.com/Azure/azure-extension-platform/vmextension" "github.com/Azure/custom-script-extension-linux/pkg/blobutil" "github.com/Azure/custom-script-extension-linux/pkg/download" "github.com/Azure/custom-script-extension-linux/pkg/preprocess" "github.com/Azure/custom-script-extension-linux/pkg/urlutil" "github.com/go-kit/kit/log" "github.com/pkg/errors" + + github.com/Azure/custom-script-extension-linux/pkg/errorutil + ) // downloadAndProcessURL downloads using the specified downloader and saves it to the // specified existing directory, which must be the path to the saved file. Then // it post-processes file based on heuristics. -func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handlerSettings) error { +func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handlerSettings) vmextension.ErrorWithClarification { fn, err := urlToFileName(url) if err != nil { - return err + return vmextension.NewErrorWithClarification(errorutil.customerInput_invalidFileUris, err) } if !urlutil.IsValidUrl(url) { - return fmt.Errorf("[REDACTED] is not a valid url") + return vmextension.NewErrorWithClarification(errorutil.customerInput_invalidFileUris, + fmt.Errorf("[REDACTED] is not a valid url")) } - dl, err := getDownloaders(url, cfg.StorageAccountName, cfg.StorageAccountKey, cfg.ManagedIdentity) - if err != nil { - return err + dl, ewc := getDownloaders(url, cfg.StorageAccountName, cfg.StorageAccountKey, cfg.ManagedIdentity) + if ewc.Err != nil { + return ewc } fp := filepath.Join(downloadDir, fn) const mode = 0500 // we assume users download scripts to execute - if _, err := download.SaveTo(ctx, dl, fp, mode); err != nil { - return err + if _, ewc := download.SaveTo(ctx, dl, fp, mode); ewc.Err != nil { + return ewc } if cfg.SkipDos2Unix == false { err = postProcessFile(fp) } - return errors.Wrapf(err, "failed to post-process '%s'", fn) + return vmextension.NewErrorWithClarification(errorutil.systemError, errors.Wrapf(err, "failed to post-process '%s'", fn)) } // getDownloader returns a downloader for the given URL based on whether the // storage credentials are empty or not. func getDownloaders(fileURL string, storageAccountName, storageAccountKey string, managedIdentity *clientOrObjectId) ( - []download.Downloader, error) { + []download.Downloader, vmextension.ErrorWithClarification) { if storageAccountName == "" || storageAccountKey == "" { // storage account name and key cannot be specified with managed identity, handler settings validation won't allow that // handler settings validation will also not allow storageAccountName XOR storageAccountKey == 1 @@ -67,27 +72,27 @@ func getDownloaders(fileURL string, storageAccountName, storageAccountKey string case managedIdentity.ClientId == "" && managedIdentity.ObjectId != "": msiProvider = download.GetMsiProviderForStorageAccountsWithObjectId(fileURL, managedIdentity.ObjectId) default: - return nil, fmt.Errorf("unexpected combination of ClientId and ObjectId found") + return nil, vmextension.NewErrorWithClarification(errorutil.customerInput_clientIdObjectIdBothSpecified, fmt.Errorf("unexpected combination of ClientId and ObjectId found")) } return []download.Downloader{ // try downloading without MSI token first, but attempt with MSI if the download fails download.NewURLDownload(fileURL), download.NewBlobWithMsiDownload(fileURL, msiProvider), - }, nil + }, vmextension.NewErrorWithClarification(errorutil.noError, nil) } else { // do not use MSI downloader if the uri is not azure storage blob, or managedIdentity isn't specified - return []download.Downloader{download.NewURLDownload(fileURL)}, nil + return []download.Downloader{download.NewURLDownload(fileURL)}, vmextension.NewErrorWithClarification(errorutil.noError, nil) } } else { // if storage name account and key are specified, use that for all files // this preserves old behavior blob, err := blobutil.ParseBlobURL(fileURL) if err != nil { - return nil, err + return nil, vmextension.NewErrorWithClarification(errorutil.customerInput_invalidFileUris, err) } return []download.Downloader{download.NewBlobDownload( - storageAccountName, storageAccountKey, - blob)}, nil + storageAccountName, storageAccountKey, blob)}, + vmextension.NewErrorWithClarification(errorutil.noError, nil) } } diff --git a/main/handlersettings.go b/main/handlersettings.go index a15dee5..c23ad8f 100644 --- a/main/handlersettings.go +++ b/main/handlersettings.go @@ -7,12 +7,15 @@ import ( "github.com/go-kit/kit/log" "github.com/pkg/errors" + "github.com/Azure/azure-extension-platform/vmextension" + github.com/Azure/custom-script-extension-linux/pkg/errorutil ) var ( errStoragePartialCredentials = errors.New("both 'storageAccountName' and 'storageAccountKey' must be specified") errCmdTooMany = errors.New("'commandToExecute' was specified both in public and protected settings; it must be specified only once") errScriptTooMany = errors.New("'script' was specified both in public and protected settings; it must be specified only once") + errFileUrisTooMany = errors.New("'fileUris' were specified both in public and protected settings; it must be specified only once") errCmdAndScript = errors.New("'commandToExecute' and 'script' were both specified, but only one is validate at a time") errCmdMissing = errors.New("'commandToExecute' is not specified") errUsingBothKeyAndMsi = errors.New("'storageAccountName' or 'storageAccountKey' must not be specified with 'managedServiceIdentity'") @@ -48,34 +51,38 @@ func (s *handlerSettings) fileUrls() []string { // validate makes logical validation on the handlerSettings which already passed // the schema validation. -func (h handlerSettings) validate() error { +func (h handlerSettings) validate() vmextension.ErrorWithClarification { if h.commandToExecute() == "" && h.script() == "" { - return errCmdMissing + return vmextension.NewErrorWithClarification(errorutil.customerInput_commandToExecuteAndScriptNotSpecified, errCmdMissing) } if h.publicSettings.CommandToExecute != "" && h.protectedSettings.CommandToExecute != "" { - return errCmdTooMany + return vmextension.NewErrorWithClarification(errorutil.customerInput_commandToExecuteSpecifiedInTwoPlaces, errCmdTooMany) } if h.publicSettings.Script != "" && h.protectedSettings.Script != "" { - return errScriptTooMany + return vmextension.NewErrorWithClarification(errorutil.customerInput_scriptSpecifiedInTwoPlaces, errScriptTooMany) + } + + if (h.publicSettings.FileURLs != nil && len(h.publicSettings.FileURLs) > 0) && (h.protectedSettings.FileURLs != nil && len(h.privateSettings.FileURLs) > 0) { + return vmextension.NewErrorWithClarification(errorutil.customerInput_fileUrisSpecifiedInTwoPlaces, errFileUrisTooMany) } if h.commandToExecute() != "" && h.script() != "" { - return errCmdAndScript + return vmextension.NewErrorWithClarification(errorutil.customerInput_commandToExecuteAndScriptBothSpecified, errCmdAndScript) } if (h.protectedSettings.StorageAccountName != "") != (h.protectedSettings.StorageAccountKey != "") { - return errStoragePartialCredentials + return vmextension.NewErrorWithClarification(errorutil.customerInput_incompleteStorageCreds, errStoragePartialCredentials) } if (h.protectedSettings.StorageAccountKey != "" || h.protectedSettings.StorageAccountName != "") && h.protectedSettings.ManagedIdentity != nil { - return errUsingBothKeyAndMsi + return vmextension.NewErrorWithClarification(errorutil.customerInput_storageCredsAndMIBothSpecified, errUsingBothKeyAndMsi) } if h.protectedSettings.ManagedIdentity != nil { if h.protectedSettings.ManagedIdentity.ClientId != "" && h.protectedSettings.ManagedIdentity.ObjectId != "" { - return errUsingBothClientIdAndObjectId + return vmextension.NewErrorWithClarification(errorutil.customerInput_clientIdObjectIdBothSpecified, errUsingBothClientIdAndObjectId) } } @@ -113,32 +120,33 @@ func (self *clientOrObjectId) isEmpty() bool { // parseAndValidateSettings reads configuration from configFolder, decrypts it, // runs JSON-schema and logical validation on it and returns it back. -func parseAndValidateSettings(ctx *log.Context, configFolder string, seqNum int) (h handlerSettings, _ error) { +func parseAndValidateSettings(ctx *log.Context, configFolder string, seqNum int) (h handlerSettings, _ vmextension.ErrorWithClarification) { ctx.Log("event", "reading configuration") pubJSON, protJSON, err := readSettings(configFolder, seqNum) if err != nil { - return h, err + return h, vmextension.NewErrorWithClarification(errorutil.internal_badConfig, err) } ctx.Log("event", "read configuration") ctx.Log("event", "validating json schema") if err := validateSettingsSchema(pubJSON, protJSON); err != nil { - return h, errors.Wrap(err, "json validation error") + return h, vmextension.NewErrorWithClarification(errorutil.internal_badConfig, errors.Wrap(err, "json validation error")) } ctx.Log("event", "json schema valid") ctx.Log("event", "parsing configuration json") if err := UnmarshalHandlerSettings(pubJSON, protJSON, &h.publicSettings, &h.protectedSettings); err != nil { - return h, errors.Wrap(err, "json parsing error") + return h, vmextension.NewErrorWithClarification(errorutil.internal_badConfig, errors.Wrap(err, "json parsing error")) } ctx.Log("event", "parsed configuration json") ctx.Log("event", "validating configuration logically") - if err := h.validate(); err != nil { - return h, errors.Wrap(err, "invalid configuration") + if ewc := h.validate(); err != nil { + ewc.Err = errors.Wrap(ewc.Err, "invalid configuration") + return h, ewc } ctx.Log("event", "validated configuration") - return h, nil + return h, vmextension.NewErrorWithClarification(errorutil.noError, nil) } // readSettings uses specified configFolder (comes from HandlerEnvironment) to diff --git a/main/main.go b/main/main.go index cad6dfe..990e5ad 100644 --- a/main/main.go +++ b/main/main.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/go-kit/kit/log" + "github.com/pkg/errors" ) var ( @@ -78,10 +79,11 @@ func main() { } // execute the subcommand reportStatus(ctx, hEnv, seqNum, StatusTransitioning, cmd, "") - msg, err := cmd.f(ctx, hEnv, seqNum) - if err != nil { - ctx.Log("event", "failed to handle", "error", err) - reportStatus(ctx, hEnv, seqNum, StatusError, cmd, err.Error()+msg) + msg, ewc := cmd.f(ctx, hEnv, seqNum) + if ewc.Err != nil { + ctx.Log("event", "failed to handle", "error", ewc.Error()) + ewc.Err = errors.Wrap(ewc.Err, ewc.Error()+msg) + reportErrorStatus(ctx, hEnv, seqNum, StatusError, cmd, ewc) os.Exit(cmd.failExitCode) } reportStatus(ctx, hEnv, seqNum, StatusSuccess, cmd, msg) diff --git a/main/status.go b/main/status.go index 196feb4..5505598 100644 --- a/main/status.go +++ b/main/status.go @@ -8,6 +8,8 @@ import ( "path/filepath" "time" + status "github.com/Azure/azure-extension-platform/pkg/status" + vmextension "github.com/Azure/azure-extension-platform/vmextension" "github.com/go-kit/kit/log" "github.com/pkg/errors" ) @@ -102,6 +104,28 @@ func reportStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, return nil } +// reportStatus saves operation status to the status file for the extension +// handler with the optional given message, if the given cmd requires reporting +// status. +// +// If an error occurs reporting the status, it will be logged and returned. +func reportErrorStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, c cmd, err vmextension.ErrorWithClarification) error { + if !c.shouldReportStatus { + ctx.Log("status", "not reported for operation (by design)") + return nil + } + ewc := status.ErrorClarification{ + Code: err.ErrorCode, + Message: err.Error(), + } + s := status.NewError(c.name, ewc) + if err := s.Save(hEnv.HandlerEnvironment.StatusFolder, uint(seqNum)); err != nil { + ctx.Log("event", "failed to save handler status", "error", err) + return errors.Wrap(err, "failed to save handler status") + } + return nil +} + // readStatus loads current status file in StatusReport func readStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) (Type, error) { fileName := fmt.Sprintf("%d.status", seqNum) diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index cbec58a..39143fc 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -6,7 +6,10 @@ import ( "net" "net/http" "time" + "url" + "github.com/Azure/azure-extension-platform/vmextension" + github.com/Azure/custom-script-extension-linux/pkg/errorutil "github.com/Azure/custom-script-extension-linux/pkg/urlutil" "github.com/go-kit/kit/log" @@ -44,10 +47,11 @@ var ( // Download retrieves a response body and checks the response status code to see // if it is 200 OK and then returns the response body. It issues a new request // every time called. It is caller's responsibility to close the response body. -func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, error) { +func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.ErrorWithClarification) { req, err := d.GetRequest() if err != nil { - return -1, nil, errors.Wrapf(err, "failed to create http request") + return -1, nil, vmextension.NewErrorWithClarification(errorutil.fileDownload_genericError, + errors.Wrapf(err, "failed to create http request")) } requestID := req.Header.Get(xMsClientRequestIdHeaderName) if len(requestID) > 0 { @@ -55,23 +59,35 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, error) { } resp, err := httpClient.Do(req) if err != nil { + if ((url.Error)err.Timeout()) { + err = urlutil.RemoveUrlFromErr(err) + return -1, nil, vmextension.NewErrorWithClarification(errorutil.fileDownload_exceededTimeout, + errors.Wrapf(err, "http request timed out")) + } err = urlutil.RemoveUrlFromErr(err) - return -1, nil, errors.Wrapf(err, "http request failed") + return -1, nil, vmextension.NewErrorWithClarification(errorutil.fileDownload_unknownError, + errors.Wrapf(err, "http request failed")) } if resp.StatusCode == http.StatusOK { - return resp.StatusCode, resp.Body, nil + // We're setting the errorCode to MaxInt because we're only checking whether the internal error is nil + return resp.StatusCode, resp.Body, vmextension.NewErrorWithClarification(errorutil.noError, nil) } errString := "" + errClarificationCode := 0 requestId := resp.Header.Get(xMsServiceRequestIdHeaderName) switch d.(type) { case *blobWithMsiToken: switch resp.StatusCode { case http.StatusNotFound: errString = MsiDownload404ErrorString + errClarificationCode = errorutil.msi_notFound case http.StatusForbidden: errString = MsiDownload403ErrorString + errClarificationCode = errorutil.msi_doesNotHaveRightPermissions + default: + errClarificationCode = errorutil.msi_GenericRetrievalError } break default: @@ -81,28 +97,33 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, error) { errString = fmt.Sprintf("CustomScript failed to download the file from %s because access was denied. Please fix the blob permissions and try again, the response code and message returned were: %q", hostname, resp.Status) + errClarificationCode = errorutil.fileDownload_accessDenied case http.StatusNotFound: errString = fmt.Sprintf("CustomScript failed to download the file from %s because it does not exist. Please create the blob and try again, the response code and message returned were: %q", hostname, resp.Status) + errClarificationCode = errorutil.fileDownload_doesNotExist case http.StatusBadRequest: errString = fmt.Sprintf("CustomScript failed to download the file from %s because parts of the request were incorrectly formatted, missing, and/or invalid. The response code and message returned were: %q", hostname, resp.Status) + errClarificationCode = errorutil.fileDownload_badRequest case http.StatusInternalServerError: errString = fmt.Sprintf("CustomScript failed to download the file from %s due to an issue with storage. The response code and message returned were: %q", hostname, resp.Status) + errClarificationCode = errorutil.storage_internalServerError default: errString = fmt.Sprintf("CustomScript failed to download the file from %s because the server returned a response code and message of %q Please verify the machine has network connectivity.", hostname, resp.Status) + errClarificationCode = errorutil.fileDownload_networkingError } } if len(requestId) > 0 { errString += fmt.Sprintf(" (Service request ID: %s)", requestId) } - return resp.StatusCode, nil, fmt.Errorf(errString) + return resp.StatusCode, nil, vmextension.NewErrorWithClarification(errClarificationCode, fmt.Errorf(errString)) } diff --git a/pkg/download/retry.go b/pkg/download/retry.go index 6999119..f248064 100644 --- a/pkg/download/retry.go +++ b/pkg/download/retry.go @@ -8,7 +8,11 @@ import ( "os" "time" - "github.com/go-kit/kit/log" + "github.com/Azure/azure-extension-platform/vmextension" + "github.com/go-kit/kit/log" + + github.com/Azure/custom-script-extension-linux/pkg/errorutil + ) // SleepFunc pauses the execution for at least duration d. @@ -33,17 +37,17 @@ const ( // closed on failures). If the retries do not succeed, the last error is returned. // // It sleeps in exponentially increasing durations between retries. -func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf SleepFunc) (int64, error) { - var lastErr error +func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf SleepFunc) (int64, vmextension.ErrorWithClarification) { + var lastErr vmextension.ErrorWithClarification for _, d := range downloaders { for n := 0; n < expRetryN; n++ { ctx := ctx.With("retry", n) // reset the last error before each retry - lastErr = nil + lastErr = vmextension.NewErrorWithClarification(errorutil.noError, nil) start := time.Now() status, out, err := Download(ctx, d) - if err == nil { + if err.Err == nil { // server returned status code 200 OK // we have a response body, copy it to the file nBytes, innerErr := io.CopyBuffer(f, out, make([]byte, writeBufSize)) @@ -53,7 +57,7 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee out.Close() end := time.Since(start) ctx.Log("info", fmt.Sprintf("file download sucessful: downloaded and saved %d bytes in %d milliseconds", nBytes, end.Milliseconds())) - return nBytes, nil + return nBytes, vmextension.NewErrorWithClarification(errorutil.noError, nil) } else { // we failed to download the response body and write it to file // because either connection was closed prematurely or file write operation failed @@ -62,7 +66,7 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee // clear out the contents of the file so as to not leave a partial file f.Truncate(0) // cache the inner error - lastErr = innerErr + lastErr = vmextension.NewErrorWithClarification(errorutil.fileDownload_genericError, innerErr) } } else { // cache the outer error diff --git a/pkg/download/save.go b/pkg/download/save.go index 7273138..f45c46d 100644 --- a/pkg/download/save.go +++ b/pkg/download/save.go @@ -3,25 +3,27 @@ package download import ( "os" + "github.com/Azure/azure-extension-platform/vmextension" "github.com/go-kit/kit/log" "github.com/pkg/errors" + github.com/Azure/custom-script-extension-linux/pkg/errorutil ) // SaveTo uses given downloader to fetch the resource with retries and saves the // given file. Directory of dst is not created by this function. If a file at // dst exists, it will be truncated. If a new file is created, mode is used to // set the permission bits. Written number of bytes are returned on success. -func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, error) { +func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, vmextension.ErrorWithClarification) { f, err := os.OpenFile(dst, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, mode) if err != nil { - return 0, errors.Wrap(err, "failed to open file for writing") + return 0, vmextension.NewErrorWithClarification(errorutil.unknownError, errors.Wrap(err, "failed to open file for writing")) } defer f.Close() - n, err := WithRetries(ctx, f, d, ActualSleep) - if err != nil { - return n, errors.Wrapf(err, "failed to download response and write to file: %s", dst) + n, ewc := WithRetries(ctx, f, d, ActualSleep) + if ewc.Err != nil { + return n, vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download response and write to file: %s", dst)) } - return n, nil + return n, vmextension.NewErrorWithClarification(errorutil.noError, nil) } diff --git a/pkg/errorutil/errorclarificationcodes.go b/pkg/errorutil/errorclarificationcodes.go index cad4274..a501c1b 100644 --- a/pkg/errorutil/errorclarificationcodes.go +++ b/pkg/errorutil/errorclarificationcodes.go @@ -22,14 +22,16 @@ const ( commandExecution_failureExitCode int = 2 commandExecution_interruptedByVmShutdown int = 3 - customerInput_commandToExecuteSpecifiedInTwoPlaces int = 20 - customerInput_ignoreRelativePathForFileDownloadsSpecifiedInTwoPlaces int = 21 - customerInput_fileUrisSpecifiedInTwoPlaces int = 22 - customerInput_commandToExecuteNotSpecified int = 23 - customerInput_fileUriContainsNull int = 24 - customerInput_invalidFileUris int = 25 - customerInput_storageCredsAndMIBothSpecified int = 26 - customerInput_clientIdObjectIdBothSpecified int = 27 + customerInput_commandToExecuteSpecifiedInTwoPlaces int = 20 + customerInput_fileUrisSpecifiedInTwoPlaces int = 22 + customerInput_commandToExecuteAndScriptNotSpecified int = 23 + customerInput_fileUriContainsNull int = 24 + customerInput_invalidFileUris int = 25 + customerInput_storageCredsAndMIBothSpecified int = 26 + customerInput_clientIdObjectIdBothSpecified int = 27 + customerInput_scriptSpecifiedInTwoPlaces int = 28 + customerInput_commandToExecuteAndScriptBothSpecified int = 29 + customerInput_incompleteStorageCreds int = 30 fileDownload_unableToCreateDownloadDirectory int = 50 fileDownload_sasExpired int = 51 From ba346da4299b545d1fbd23fff86bbde2641a4591 Mon Sep 17 00:00:00 2001 From: Deepti Vaidyanathan Date: Wed, 8 Jan 2025 23:43:52 +0000 Subject: [PATCH 3/7] Fixes error clarification codes --- go.mod | 27 +++++----- go.sum | 38 +++++++------ main/cmds.go | 39 +++++++------- main/exec.go | 26 ++++----- main/files.go | 18 +++---- main/handlersettings.go | 30 +++++------ pkg/download/downloader.go | 38 ++++++------- pkg/download/downloader_test.go | 33 ++++++++---- pkg/download/retry.go | 24 +++++---- pkg/download/retry_test.go | 10 ++-- pkg/download/save.go | 15 +++--- pkg/errorutil/errorclarificationcodes.go | 69 ++++++++++++------------ 12 files changed, 192 insertions(+), 175 deletions(-) diff --git a/go.mod b/go.mod index 44f9d88..4836129 100644 --- a/go.mod +++ b/go.mod @@ -1,47 +1,48 @@ module github.com/Azure/custom-script-extension-linux -go 1.23 - -toolchain go1.23.4 +go 1.23.4 require ( - github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187 github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6 - github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1 + github.com/ahmetalpbalkan/go-httpbin v0.0.0-20240315150752-da45896c98cb github.com/go-kit/kit v0.13.0 - github.com/google/uuid v1.6.0 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.2 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/text v0.21.0 ) require ( + github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187 // indirect + github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-kit/log v0.2.1 github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect - github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4 // indirect - github.com/gorilla/mux v0.0.0-20160605233521-9fa818a44c2b // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect golang.org/x/crypto v0.32.0 // indirect golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v2 v2.2.8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/go-kit/kit => github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2 - // //DELETE after testing -// replace "github.com/Azure/custom-script-extension-linux" => "../custom-script-extension-linux" +//require github.com/Azure/custom-script-extension-linux/pkg v0.0.0 +//replace github.com/Azure/custom-script-extension-linux/pkg => ../custom-script-extension-linux/pkg + +replace github.com/go-kit/kit => github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2 diff --git a/go.sum b/go.sum index 5ac8ee9..ffaaa0a 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,7 @@ github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187 h1:C4S32XsUvctWzdWDEYlvhfcgH1iGvSD62II7Dd7F6B8= github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187/go.mod h1:a0BFq9UoWBHvBS7iagvjFqBjYfxtBsmqvCLWIHRq9b0= -github.com/Azure/azure-extension-platform v0.0.0-20230218002700-ca684482c954 h1:Jnedpc6riCirNEUQGZ7I7gH1PZym71sKFnbP9Lb32oA= -github.com/Azure/azure-extension-platform v0.0.0-20230218002700-ca684482c954/go.mod h1:1hEkO8M1zN/SQpdFOTDDMTNfeE1Q2tCHmEXXiHrWTgo= github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6 h1:RDJpiDBkLFa711zwULKd1bhIoWpaslIwlfz5CBHn+eI= github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY= -github.com/Azure/azure-sdk-for-go v3.1.0-beta.0.20160802173609-87de771fcdf5+incompatible h1:7CctKV2SGUVFq3a+WNHypGRKQzaPCNVEAMMAlEJXrWc= -github.com/Azure/azure-sdk-for-go v3.1.0-beta.0.20160802173609-87de771fcdf5+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -18,18 +14,29 @@ github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2 github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1 h1:/sPElPBMLSi6+bV0o0fPN4U24qQCNHs1i/BjnO+GqLc= -github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1/go.mod h1:Rg55S63lgqSBCawY/oTm7jdFSySp6jwIqgHMB2IeHK8= +github.com/ahmetalpbalkan/go-httpbin v0.0.0-20240315150752-da45896c98cb h1:DSAtit+Eq3K2/kzZsSBYSh3jfCBiWm+FMJkFF0Ffy+I= +github.com/ahmetalpbalkan/go-httpbin v0.0.0-20240315150752-da45896c98cb/go.mod h1:Rg55S63lgqSBCawY/oTm7jdFSySp6jwIqgHMB2IeHK8= +github.com/ahmetb/go-httpbin v0.0.0-20240315150752-da45896c98cb h1:od9/PvyZ6X+dCU04fTRyrYS8HKVW1SxprFvLYjzUI0U= +github.com/ahmetb/go-httpbin v0.0.0-20240315150752-da45896c98cb/go.mod h1:iB3NbHoh0P/9AZepPBcH+gM1PhQJGmsres+ZHf72M3k= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2 h1:awXynDTA1TiAp1SA/o/xoU6oRHE3xKCokck9l4/poMc= github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= @@ -40,14 +47,10 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4 h1:3nOfQt8sRPYbXORD5tJ8YyQ3HlL2Jt3LJ2U17CbNh6I= -github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v0.0.0-20160605233521-9fa818a44c2b h1:OFvZV3a+25cGJH9dETHw0nk0wV6hLZI7IJijOkXEFS0= -github.com/gorilla/mux v0.0.0-20160605233521-9fa818a44c2b/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -72,14 +75,14 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -90,6 +93,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -102,8 +107,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -116,7 +119,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= @@ -129,6 +131,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main/cmds.go b/main/cmds.go index 8ef85fd..d118505 100644 --- a/main/cmds.go +++ b/main/cmds.go @@ -14,7 +14,7 @@ import ( utils "github.com/Azure/azure-extension-platform/pkg/utils" vmextension "github.com/Azure/azure-extension-platform/vmextension" - github.com/Azure/custom-script-extension-linux/pkg/errorutil + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/Azure/custom-script-extension-linux/pkg/seqnum" "github.com/go-kit/kit/log" "github.com/pkg/errors" @@ -59,12 +59,12 @@ var ( func noop(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { ctx.Log("event", "noop") - return "", vmextension.NewErrorWithClarification(errorutil.noError, nil) + return "", vmextension.NewErrorWithClarification(errorutil.NoError, nil) } func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { if err := os.MkdirAll(dataDir, 0755); err != nil { - return "", vmextension.NewErrorWithClarification(errorutil.systemError, errors.Wrap(err, "failed to create data dir")) + return "", vmextension.NewErrorWithClarification(errorutil.SystemError, errors.Wrap(err, "failed to create data dir")) } // If the file mrseq does not exists it is for two possible reasons. @@ -76,20 +76,20 @@ func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmexte ctx.Log("event", "created data dir", "path", dataDir) ctx.Log("event", "installed") - return "", vmextension.NewErrorWithClarification(errorutil.noError, nil) + return "", vmextension.NewErrorWithClarification(errorutil.NoError, nil) } -func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextensionErrorWithClarification) { +func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { { // a new context scope with path ctx = ctx.With("path", dataDir) ctx.Log("event", "removing data dir", "path", dataDir) if err := os.RemoveAll(dataDir); err != nil { - return "", errors.Wrap(err, "failed to delete data directory") + return "", vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrap(err, "failed to delete data directory")) } ctx.Log("event", "removed data dir") } ctx.Log("event", "uninstalled") - return "", vmextension.NewErrorWithClarification(errorutil.noError, nil) + return "", vmextension.NewErrorWithClarification(errorutil.NoError, nil) } func enablePre(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) error { @@ -112,7 +112,7 @@ func min(a, b int) int { return b } -func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextensionErrorWithClarification) { +func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { // parse the extension handler settings (not available prior to 'enable') cfg, ewc := parseAndValidateSettings(ctx, h.HandlerEnvironment.ConfigFolder, seqNum) if ewc.Err != nil { @@ -140,7 +140,7 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmexten ctx.Log("message", "error tailing stderr logs", "error", err) } - isSuccess := runErr == nil + isSuccess := runErr.Err == nil telemetry("Output", "-- stdout/stderr omitted from telemetry pipeline --", isSuccess, 0) if isSuccess { @@ -184,7 +184,7 @@ func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) vmextensio // - create the directory if missing ctx.Log("event", "creating output directory", "path", dir) if err := os.MkdirAll(dir, 0700); err != nil { - return vmextension.NewErrorWithClarification(errorutil.fileDownload_unableToCreateDownloadDirectory, errors.Wrap(err, "failed to prepare output directory")) + return vmextension.NewErrorWithClarification(errorutil.FileDownload_unableToCreateDownloadDirectory, errors.Wrap(err, "failed to prepare output directory")) } ctx.Log("event", "created output directory") @@ -210,15 +210,16 @@ func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) vmextensio } ctx.Log("event", "download complete", "output", dir) } - return vmextension.NewErrorWithClarification(errorutil.noError, nil) + return vmextension.NewErrorWithClarification(errorutil.NoError, nil) } // runCmd runs the command (extracted from cfg) in the given dir (assumed to exist). -func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (err error) { +func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (ewc vmextension.ErrorWithClarification) { ctx.Log("event", "executing command", "output", dir) var cmd string var scenario string var scenarioInfo string + var err error // So many ways to execute a command! if cfg.publicSettings.CommandToExecute != "" { @@ -232,30 +233,30 @@ func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (err error) { } else if cfg.publicSettings.Script != "" { ctx.Log("event", "executing public script", "output", dir) if cmd, scenarioInfo, err = writeTempScript(cfg.publicSettings.Script, dir, cfg.publicSettings.SkipDos2Unix); err != nil { - return + return vmextension.NewErrorWithClarification(errorutil.NoError, err) } scenario = fmt.Sprintf("public-script;%s", scenarioInfo) } else if cfg.protectedSettings.Script != "" { ctx.Log("event", "executing protected script", "output", dir) if cmd, scenarioInfo, err = writeTempScript(cfg.protectedSettings.Script, dir, cfg.publicSettings.SkipDos2Unix); err != nil { - return + return vmextension.NewErrorWithClarification(errorutil.NoError, err) } scenario = fmt.Sprintf("protected-script;%s", scenarioInfo) } begin := time.Now() - err = ExecCmdInDir(cmd, dir) + ewc = ExecCmdInDir(cmd, dir) elapsed := time.Now().Sub(begin) - isSuccess := err == nil + isSuccess := ewc.Err == nil telemetry("scenario", scenario, isSuccess, elapsed) - if err != nil { + if ewc.Err != nil { ctx.Log("event", "failed to execute command", "error", err, "output", dir) - return errors.Wrap(err, "failed to execute command") + return vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrap(err, "failed to execute command")) } ctx.Log("event", "executed command", "output", dir) - return nil + return vmextension.NewErrorWithClarification(errorutil.NoError, nil) } func writeTempScript(script, dir string, skipDosToUnix bool) (string, string, error) { diff --git a/main/exec.go b/main/exec.go index 235c06b..9b97375 100644 --- a/main/exec.go +++ b/main/exec.go @@ -8,10 +8,9 @@ import ( "path/filepath" "syscall" - "github.com/pkg/errors" vmextension "github.com/Azure/azure-extension-platform/vmextension" - github.com/Azure/custom-script-extension-linux/pkg/errorutil - + errorutil "github.com/Azure/custom-script-extension-linux/pkg/errorutil" + "github.com/pkg/errors" ) // Exec runs the given cmd in /bin/sh, saves its stdout/stderr streams to @@ -33,13 +32,14 @@ func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, vmextension. if ok { if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { code := status.ExitStatus() - if code != 0 { - return code, vmextension.NewErrorWithClarification(errorutil.commandExecution_failureExitCode, fmt.Errorf("command terminated with exit status=%d", code)) - } - return code, fmt.Errorf("command terminated with exit status=%d", code) + return code, vmextension.NewErrorWithClarification(errorutil.CommandExecution_failedUnknownError, fmt.Errorf("command terminated with exit status=%d", code)) } } - return 0, errors.Wrapf(err, "failed to execute command") + if err == nil { + return 0, vmextension.NewErrorWithClarification(errorutil.NoError, nil) + } + return 0, vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to execute command")) + } // ExecCmdInDir executes the given command in given directory and saves output @@ -48,20 +48,20 @@ func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, vmextension. // // Ideally, we execute commands only once per sequence number in custom-script-extension, // and save their output under /var/lib/waagent//download//*. -func ExecCmdInDir(cmd, workdir string) error { +func ExecCmdInDir(cmd, workdir string) vmextension.ErrorWithClarification { outFn, errFn := logPaths(workdir) outF, err := os.OpenFile(outFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - return errors.Wrapf(err, "failed to open stdout file") + return vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to open stdout file")) } errF, err := os.OpenFile(errFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - return errors.Wrapf(err, "failed to open stderr file") + return vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to open stderr file")) } - _, err = Exec(cmd, workdir, outF, errF) - return err + _, ewc := Exec(cmd, workdir, outF, errF) + return ewc } // logPaths returns stdout and stderr file paths for the specified output diff --git a/main/files.go b/main/files.go index 3bf686f..a513642 100644 --- a/main/files.go +++ b/main/files.go @@ -17,7 +17,7 @@ import ( "github.com/go-kit/kit/log" "github.com/pkg/errors" - github.com/Azure/custom-script-extension-linux/pkg/errorutil + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" ) @@ -27,11 +27,11 @@ import ( func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handlerSettings) vmextension.ErrorWithClarification { fn, err := urlToFileName(url) if err != nil { - return vmextension.NewErrorWithClarification(errorutil.customerInput_invalidFileUris, err) + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, err) } if !urlutil.IsValidUrl(url) { - return vmextension.NewErrorWithClarification(errorutil.customerInput_invalidFileUris, + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, fmt.Errorf("[REDACTED] is not a valid url")) } @@ -49,7 +49,7 @@ func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handl if cfg.SkipDos2Unix == false { err = postProcessFile(fp) } - return vmextension.NewErrorWithClarification(errorutil.systemError, errors.Wrapf(err, "failed to post-process '%s'", fn)) + return vmextension.NewErrorWithClarification(errorutil.SystemError, errors.Wrapf(err, "failed to post-process '%s'", fn)) } // getDownloader returns a downloader for the given URL based on whether the @@ -72,27 +72,27 @@ func getDownloaders(fileURL string, storageAccountName, storageAccountKey string case managedIdentity.ClientId == "" && managedIdentity.ObjectId != "": msiProvider = download.GetMsiProviderForStorageAccountsWithObjectId(fileURL, managedIdentity.ObjectId) default: - return nil, vmextension.NewErrorWithClarification(errorutil.customerInput_clientIdObjectIdBothSpecified, fmt.Errorf("unexpected combination of ClientId and ObjectId found")) + return nil, vmextension.NewErrorWithClarification(errorutil.CustomerInput_clientIdObjectIdBothSpecified, fmt.Errorf("unexpected combination of ClientId and ObjectId found")) } return []download.Downloader{ // try downloading without MSI token first, but attempt with MSI if the download fails download.NewURLDownload(fileURL), download.NewBlobWithMsiDownload(fileURL, msiProvider), - }, vmextension.NewErrorWithClarification(errorutil.noError, nil) + }, vmextension.NewErrorWithClarification(errorutil.NoError, nil) } else { // do not use MSI downloader if the uri is not azure storage blob, or managedIdentity isn't specified - return []download.Downloader{download.NewURLDownload(fileURL)}, vmextension.NewErrorWithClarification(errorutil.noError, nil) + return []download.Downloader{download.NewURLDownload(fileURL)}, vmextension.NewErrorWithClarification(errorutil.NoError, nil) } } else { // if storage name account and key are specified, use that for all files // this preserves old behavior blob, err := blobutil.ParseBlobURL(fileURL) if err != nil { - return nil, vmextension.NewErrorWithClarification(errorutil.customerInput_invalidFileUris, err) + return nil, vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, err) } return []download.Downloader{download.NewBlobDownload( storageAccountName, storageAccountKey, blob)}, - vmextension.NewErrorWithClarification(errorutil.noError, nil) + vmextension.NewErrorWithClarification(errorutil.NoError, nil) } } diff --git a/main/handlersettings.go b/main/handlersettings.go index c23ad8f..7899cb6 100644 --- a/main/handlersettings.go +++ b/main/handlersettings.go @@ -8,7 +8,7 @@ import ( "github.com/go-kit/kit/log" "github.com/pkg/errors" "github.com/Azure/azure-extension-platform/vmextension" - github.com/Azure/custom-script-extension-linux/pkg/errorutil + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" ) var ( @@ -53,40 +53,40 @@ func (s *handlerSettings) fileUrls() []string { // the schema validation. func (h handlerSettings) validate() vmextension.ErrorWithClarification { if h.commandToExecute() == "" && h.script() == "" { - return vmextension.NewErrorWithClarification(errorutil.customerInput_commandToExecuteAndScriptNotSpecified, errCmdMissing) + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteAndScriptNotSpecified, errCmdMissing) } if h.publicSettings.CommandToExecute != "" && h.protectedSettings.CommandToExecute != "" { - return vmextension.NewErrorWithClarification(errorutil.customerInput_commandToExecuteSpecifiedInTwoPlaces, errCmdTooMany) + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteSpecifiedInTwoPlaces, errCmdTooMany) } if h.publicSettings.Script != "" && h.protectedSettings.Script != "" { - return vmextension.NewErrorWithClarification(errorutil.customerInput_scriptSpecifiedInTwoPlaces, errScriptTooMany) + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_scriptSpecifiedInTwoPlaces, errScriptTooMany) } - if (h.publicSettings.FileURLs != nil && len(h.publicSettings.FileURLs) > 0) && (h.protectedSettings.FileURLs != nil && len(h.privateSettings.FileURLs) > 0) { - return vmextension.NewErrorWithClarification(errorutil.customerInput_fileUrisSpecifiedInTwoPlaces, errFileUrisTooMany) + if (h.publicSettings.FileURLs != nil && len(h.publicSettings.FileURLs) > 0) && (h.protectedSettings.FileURLs != nil && len(h.protectedSettings.FileURLs) > 0) { + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_fileUrisSpecifiedInTwoPlaces, errFileUrisTooMany) } if h.commandToExecute() != "" && h.script() != "" { - return vmextension.NewErrorWithClarification(errorutil.customerInput_commandToExecuteAndScriptBothSpecified, errCmdAndScript) + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteAndScriptBothSpecified, errCmdAndScript) } if (h.protectedSettings.StorageAccountName != "") != (h.protectedSettings.StorageAccountKey != "") { - return vmextension.NewErrorWithClarification(errorutil.customerInput_incompleteStorageCreds, errStoragePartialCredentials) + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_incompleteStorageCreds, errStoragePartialCredentials) } if (h.protectedSettings.StorageAccountKey != "" || h.protectedSettings.StorageAccountName != "") && h.protectedSettings.ManagedIdentity != nil { - return vmextension.NewErrorWithClarification(errorutil.customerInput_storageCredsAndMIBothSpecified, errUsingBothKeyAndMsi) + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_storageCredsAndMIBothSpecified, errUsingBothKeyAndMsi) } if h.protectedSettings.ManagedIdentity != nil { if h.protectedSettings.ManagedIdentity.ClientId != "" && h.protectedSettings.ManagedIdentity.ObjectId != "" { - return vmextension.NewErrorWithClarification(errorutil.customerInput_clientIdObjectIdBothSpecified, errUsingBothClientIdAndObjectId) + return vmextension.NewErrorWithClarification(errorutil.CustomerInput_clientIdObjectIdBothSpecified, errUsingBothClientIdAndObjectId) } } - return nil + return vmextension.NewErrorWithClarification(errorutil.NoError, nil) } // publicSettings is the type deserialized from public configuration section of @@ -124,19 +124,19 @@ func parseAndValidateSettings(ctx *log.Context, configFolder string, seqNum int) ctx.Log("event", "reading configuration") pubJSON, protJSON, err := readSettings(configFolder, seqNum) if err != nil { - return h, vmextension.NewErrorWithClarification(errorutil.internal_badConfig, err) + return h, vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, err) } ctx.Log("event", "read configuration") ctx.Log("event", "validating json schema") if err := validateSettingsSchema(pubJSON, protJSON); err != nil { - return h, vmextension.NewErrorWithClarification(errorutil.internal_badConfig, errors.Wrap(err, "json validation error")) + return h, vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, errors.Wrap(err, "json validation error")) } ctx.Log("event", "json schema valid") ctx.Log("event", "parsing configuration json") if err := UnmarshalHandlerSettings(pubJSON, protJSON, &h.publicSettings, &h.protectedSettings); err != nil { - return h, vmextension.NewErrorWithClarification(errorutil.internal_badConfig, errors.Wrap(err, "json parsing error")) + return h, vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, errors.Wrap(err, "json parsing error")) } ctx.Log("event", "parsed configuration json") @@ -146,7 +146,7 @@ func parseAndValidateSettings(ctx *log.Context, configFolder string, seqNum int) return h, ewc } ctx.Log("event", "validated configuration") - return h, vmextension.NewErrorWithClarification(errorutil.noError, nil) + return h, vmextension.NewErrorWithClarification(errorutil.NoError, nil) } // readSettings uses specified configFolder (comes from HandlerEnvironment) to diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index 39143fc..cb29f01 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -6,10 +6,9 @@ import ( "net" "net/http" "time" - "url" + "net/url" - "github.com/Azure/azure-extension-platform/vmextension" - github.com/Azure/custom-script-extension-linux/pkg/errorutil + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/Azure/custom-script-extension-linux/pkg/urlutil" "github.com/go-kit/kit/log" @@ -47,11 +46,10 @@ var ( // Download retrieves a response body and checks the response status code to see // if it is 200 OK and then returns the response body. It issues a new request // every time called. It is caller's responsibility to close the response body. -func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.ErrorWithClarification) { +func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, int, error) { req, err := d.GetRequest() if err != nil { - return -1, nil, vmextension.NewErrorWithClarification(errorutil.fileDownload_genericError, - errors.Wrapf(err, "failed to create http request")) + return -1, nil, errorutil.FileDownload_genericError, errors.Wrapf(err, "failed to create http request") } requestID := req.Header.Get(xMsClientRequestIdHeaderName) if len(requestID) > 0 { @@ -59,19 +57,17 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.E } resp, err := httpClient.Do(req) if err != nil { - if ((url.Error)err.Timeout()) { + if ((err.(*url.Error)).Timeout()) { err = urlutil.RemoveUrlFromErr(err) - return -1, nil, vmextension.NewErrorWithClarification(errorutil.fileDownload_exceededTimeout, - errors.Wrapf(err, "http request timed out")) + return -1, nil, errorutil.FileDownload_exceededTimeout, errors.Wrapf(err, "http request timed out") } err = urlutil.RemoveUrlFromErr(err) - return -1, nil, vmextension.NewErrorWithClarification(errorutil.fileDownload_unknownError, - errors.Wrapf(err, "http request failed")) + return -1, nil, errorutil.FileDownload_unknownError, errors.Wrapf(err, "http request failed") } if resp.StatusCode == http.StatusOK { // We're setting the errorCode to MaxInt because we're only checking whether the internal error is nil - return resp.StatusCode, resp.Body, vmextension.NewErrorWithClarification(errorutil.noError, nil) + return resp.StatusCode, resp.Body, errorutil.NoError, nil } errString := "" @@ -82,12 +78,12 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.E switch resp.StatusCode { case http.StatusNotFound: errString = MsiDownload404ErrorString - errClarificationCode = errorutil.msi_notFound + errClarificationCode = errorutil.Msi_notFound case http.StatusForbidden: errString = MsiDownload403ErrorString - errClarificationCode = errorutil.msi_doesNotHaveRightPermissions + errClarificationCode = errorutil.Msi_doesNotHaveRightPermissions default: - errClarificationCode = errorutil.msi_GenericRetrievalError + errClarificationCode = errorutil.Msi_GenericRetrievalError } break default: @@ -97,33 +93,33 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.E errString = fmt.Sprintf("CustomScript failed to download the file from %s because access was denied. Please fix the blob permissions and try again, the response code and message returned were: %q", hostname, resp.Status) - errClarificationCode = errorutil.fileDownload_accessDenied + errClarificationCode = errorutil.FileDownload_accessDenied case http.StatusNotFound: errString = fmt.Sprintf("CustomScript failed to download the file from %s because it does not exist. Please create the blob and try again, the response code and message returned were: %q", hostname, resp.Status) - errClarificationCode = errorutil.fileDownload_doesNotExist + errClarificationCode = errorutil.FileDownload_doesNotExist case http.StatusBadRequest: errString = fmt.Sprintf("CustomScript failed to download the file from %s because parts of the request were incorrectly formatted, missing, and/or invalid. The response code and message returned were: %q", hostname, resp.Status) - errClarificationCode = errorutil.fileDownload_badRequest + errClarificationCode = errorutil.FileDownload_badRequest case http.StatusInternalServerError: errString = fmt.Sprintf("CustomScript failed to download the file from %s due to an issue with storage. The response code and message returned were: %q", hostname, resp.Status) - errClarificationCode = errorutil.storage_internalServerError + errClarificationCode = errorutil.Storage_internalServerError default: errString = fmt.Sprintf("CustomScript failed to download the file from %s because the server returned a response code and message of %q Please verify the machine has network connectivity.", hostname, resp.Status) - errClarificationCode = errorutil.fileDownload_networkingError + errClarificationCode = errorutil.FileDownload_networkingError } } if len(requestId) > 0 { errString += fmt.Sprintf(" (Service request ID: %s)", requestId) } - return resp.StatusCode, nil, vmextension.NewErrorWithClarification(errClarificationCode, fmt.Errorf(errString)) + return resp.StatusCode, nil, errClarificationCode, fmt.Errorf(errString) } diff --git a/pkg/download/downloader_test.go b/pkg/download/downloader_test.go index 7c2bf2d..7657154 100644 --- a/pkg/download/downloader_test.go +++ b/pkg/download/downloader_test.go @@ -3,7 +3,7 @@ package download_test import ( "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/go-kit/kit/log" "github.com/Azure/custom-script-extension-linux/pkg/download" + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/ahmetalpbalkan/go-httpbin" "github.com/stretchr/testify/require" ) @@ -29,13 +30,15 @@ func (b *badDownloader) GetRequest() (*http.Request, error) { } func TestDownload_wrapsGetRequestError(t *testing.T) { - _, _, err := download.Download(testctx, new(badDownloader)) + _, _, errCode, err := download.Download(testctx, new(badDownloader)) + require.Equal(t, errCode, errorutil.FileDownload_genericError) require.NotNil(t, err) require.EqualError(t, err, "failed to create http request: expected error") } func TestDownload_wrapsHTTPError(t *testing.T) { - _, _, err := download.Download(testctx, download.NewURLDownload("bad url")) + _, _, errCode, err := download.Download(testctx, download.NewURLDownload("bad url")) + require.Equal(t, errCode, errorutil.FileDownload_unknownError) require.NotNil(t, err) require.Contains(t, err.Error(), "http request failed:") } @@ -53,19 +56,24 @@ func TestDownload_wrapsCommonErrorCodes(t *testing.T) { http.StatusBadRequest, http.StatusUnauthorized, } { - respCode, _, err := download.Download(testctx, download.NewURLDownload(fmt.Sprintf("%s/status/%d", srv.URL, code))) + respCode, _, errCode, err := download.Download(testctx, download.NewURLDownload(fmt.Sprintf("%s/status/%d", srv.URL, code))) require.NotNil(t, err, "not failed for code:%d", code) require.Equal(t, code, respCode) switch respCode { case http.StatusNotFound: + require.Equal(t, errCode, errorutil.FileDownload_doesNotExist) require.Contains(t, err.Error(), "because it does not exist") case http.StatusForbidden: + require.Equal(t, errCode, errorutil.FileDownload_networkingError) require.Contains(t, err.Error(), "Please verify the machine has network connectivity") case http.StatusInternalServerError: + require.Equal(t, errCode, errorutil.Storage_internalServerError) require.Contains(t, err.Error(), "due to an issue with storage") case http.StatusBadRequest: + require.Equal(t, errCode, errorutil.FileDownload_badRequest) require.Contains(t, err.Error(), "because parts of the request were incorrectly formatted, missing, and/or invalid") case http.StatusUnauthorized: + require.Equal(t, errCode, errorutil.FileDownload_accessDenied) require.Contains(t, err.Error(), "because access was denied") } } @@ -75,7 +83,8 @@ func TestDownload_statusOKSucceeds(t *testing.T) { srv := httptest.NewServer(httpbin.GetMux()) defer srv.Close() - _, body, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/status/200")) + _, body, errCode, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/status/200")) + require.Equal(t, errCode, errorutil.NoError) require.Nil(t, err) defer body.Close() require.NotNil(t, body) @@ -90,13 +99,15 @@ func TestDowload_msiDownloaderErrorMessage(t *testing.T) { msiDownloader404 := download.NewBlobWithMsiDownload(srv.URL+"/status/404", mockMsiProvider) - returnCode, body, err := download.Download(testctx, msiDownloader404) + returnCode, body, errCode, err := download.Download(testctx, msiDownloader404) + require.Equal(t, errCode, errorutil.Msi_notFound) require.True(t, strings.Contains(err.Error(), download.MsiDownload404ErrorString), "error string doesn't contain the correct message") require.Nil(t, body, "body is not nil for failed download") require.Equal(t, 404, returnCode, "return code was not 404") msiDownloader403 := download.NewBlobWithMsiDownload(srv.URL+"/status/403", mockMsiProvider) - returnCode, body, err = download.Download(testctx, msiDownloader403) + returnCode, body, errCode, err = download.Download(testctx, msiDownloader403) + require.Equal(t, errCode, errorutil.Msi_doesNotHaveRightPermissions) require.True(t, strings.Contains(err.Error(), download.MsiDownload403ErrorString), "error string doesn't contain the correct message") require.Nil(t, body, "body is not nil for failed download") require.Equal(t, 403, returnCode, "return code was not 403") @@ -107,10 +118,11 @@ func TestDownload_retrievesBody(t *testing.T) { srv := httptest.NewServer(httpbin.GetMux()) defer srv.Close() - _, body, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/bytes/65536")) + _, body, errCode, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/bytes/65536")) + require.Equal(t, errCode, errorutil.NoError) require.Nil(t, err) defer body.Close() - b, err := ioutil.ReadAll(body) + b, err := io.ReadAll(body) require.Nil(t, err) require.EqualValues(t, 65536, len(b)) } @@ -119,7 +131,8 @@ func TestDownload_bodyClosesWithoutError(t *testing.T) { srv := httptest.NewServer(httpbin.GetMux()) defer srv.Close() - _, body, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/get")) + _, body, errCode, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/get")) + require.Equal(t, errCode, errorutil.NoError) require.Nil(t, err) require.Nil(t, body.Close(), "body should close fine") } diff --git a/pkg/download/retry.go b/pkg/download/retry.go index f248064..74f5a35 100644 --- a/pkg/download/retry.go +++ b/pkg/download/retry.go @@ -8,11 +8,9 @@ import ( "os" "time" - "github.com/Azure/azure-extension-platform/vmextension" "github.com/go-kit/kit/log" - github.com/Azure/custom-script-extension-linux/pkg/errorutil - + errorutil "github.com/Azure/custom-script-extension-linux/pkg/errorutil" ) // SleepFunc pauses the execution for at least duration d. @@ -37,17 +35,19 @@ const ( // closed on failures). If the retries do not succeed, the last error is returned. // // It sleeps in exponentially increasing durations between retries. -func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf SleepFunc) (int64, vmextension.ErrorWithClarification) { - var lastErr vmextension.ErrorWithClarification +func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf SleepFunc) (int64, int, error) { + var lastErr error + var lastErrCode int for _, d := range downloaders { for n := 0; n < expRetryN; n++ { ctx := ctx.With("retry", n) // reset the last error before each retry - lastErr = vmextension.NewErrorWithClarification(errorutil.noError, nil) + lastErr = nil + lastErrCode = errorutil.NoError start := time.Now() - status, out, err := Download(ctx, d) - if err.Err == nil { + status, out, errCode, err := Download(ctx, d) + if err == nil { // server returned status code 200 OK // we have a response body, copy it to the file nBytes, innerErr := io.CopyBuffer(f, out, make([]byte, writeBufSize)) @@ -57,7 +57,7 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee out.Close() end := time.Since(start) ctx.Log("info", fmt.Sprintf("file download sucessful: downloaded and saved %d bytes in %d milliseconds", nBytes, end.Milliseconds())) - return nBytes, vmextension.NewErrorWithClarification(errorutil.noError, nil) + return nBytes, lastErrCode, lastErr } else { // we failed to download the response body and write it to file // because either connection was closed prematurely or file write operation failed @@ -66,11 +66,13 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee // clear out the contents of the file so as to not leave a partial file f.Truncate(0) // cache the inner error - lastErr = vmextension.NewErrorWithClarification(errorutil.fileDownload_genericError, innerErr) + lastErrCode = errorutil.FileDownload_genericError + lastErr = innerErr } } else { // cache the outer error lastErr = err + lastErrCode = errCode } // we are here because either server returned a non-200 status code @@ -98,7 +100,7 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee } } } - return 0, lastErr + return 0, lastErrCode, lastErr } func isTransientHttpStatusCode(statusCode int) bool { diff --git a/pkg/download/retry_test.go b/pkg/download/retry_test.go index aeb6b69..605e2d0 100644 --- a/pkg/download/retry_test.go +++ b/pkg/download/retry_test.go @@ -2,7 +2,7 @@ package download_test import ( "fmt" - "io/ioutil" + // "io/ioutil" "net/http" "net/http/httptest" "os" @@ -168,8 +168,8 @@ func TestRetriesWith_LargeFileThatTimesOutWhileDownloading(t *testing.T) { largeFileDownloader := mockDownloader{0, srv.URL + "/bytes/" + fmt.Sprintf("%d", size)} sr := new(sleepRecorder) - n, err := download.WithRetries(nopLog(), file, []download.Downloader{&largeFileDownloader}, sr.Sleep) - require.NotNil(t, err, "download with retries should fail because of server timeout") + n, ewc := download.WithRetries(nopLog(), file, []download.Downloader{&largeFileDownloader}, sr.Sleep) + require.NotNil(t, ewc.Err, "download with retries should fail because of server timeout") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") fi, err := file.Stat() @@ -178,8 +178,8 @@ func TestRetriesWith_LargeFileThatTimesOutWhileDownloading(t *testing.T) { } func CreateTestFile(t *testing.T) (string, *os.File) { - dir, err := ioutil.TempDir("", "") - require.Nil(t, err) + dir := os.TempDir() + // require.Nil(t, err) path := filepath.Join(dir, "test-file") diff --git a/pkg/download/save.go b/pkg/download/save.go index f45c46d..a7cc06c 100644 --- a/pkg/download/save.go +++ b/pkg/download/save.go @@ -3,27 +3,26 @@ package download import ( "os" - "github.com/Azure/azure-extension-platform/vmextension" "github.com/go-kit/kit/log" "github.com/pkg/errors" - github.com/Azure/custom-script-extension-linux/pkg/errorutil + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" ) // SaveTo uses given downloader to fetch the resource with retries and saves the // given file. Directory of dst is not created by this function. If a file at // dst exists, it will be truncated. If a new file is created, mode is used to // set the permission bits. Written number of bytes are returned on success. -func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, vmextension.ErrorWithClarification) { +func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, int, error) { f, err := os.OpenFile(dst, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, mode) if err != nil { - return 0, vmextension.NewErrorWithClarification(errorutil.unknownError, errors.Wrap(err, "failed to open file for writing")) + return 0, errorutil.FileDownload_unknownError, errors.Wrap(err, "failed to open file for writing") } defer f.Close() - n, ewc := WithRetries(ctx, f, d, ActualSleep) - if ewc.Err != nil { - return n, vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download response and write to file: %s", dst)) + n, errCode, err := WithRetries(ctx, f, d, ActualSleep) + if err != nil { + return n, errCode, errors.Wrapf(err, "failed to download response and write to file: %s", dst) } - return n, vmextension.NewErrorWithClarification(errorutil.noError, nil) + return n, errorutil.NoError, nil } diff --git a/pkg/errorutil/errorclarificationcodes.go b/pkg/errorutil/errorclarificationcodes.go index a501c1b..11341d3 100644 --- a/pkg/errorutil/errorclarificationcodes.go +++ b/pkg/errorutil/errorclarificationcodes.go @@ -6,46 +6,47 @@ import ( const ( // System errors - fileDownload_badRequest int = -41 - fileDownload_unknownError int = -40 + FileDownload_badRequest int = -41 + FileDownload_unknownError int = -40 - imds_internalMsiError int = -30 + Imds_internalMsiError int = -30 - internal_badConfig int = -21 - internal_couldNotFindCertificate int = -20 + Internal_badConfig int = -21 + Internal_couldNotFindCertificate int = -20 - storage_internalServerError int = -1 - systemError int = 0 // CRP interprets anything > 0 as user errors + Storage_internalServerError int = -1 + SystemError int = 0 // CRP interprets anything > 0 as user errors // User errors - commandExecution_failedUnknownError int = 1 - commandExecution_failureExitCode int = 2 - commandExecution_interruptedByVmShutdown int = 3 - - customerInput_commandToExecuteSpecifiedInTwoPlaces int = 20 - customerInput_fileUrisSpecifiedInTwoPlaces int = 22 - customerInput_commandToExecuteAndScriptNotSpecified int = 23 - customerInput_fileUriContainsNull int = 24 - customerInput_invalidFileUris int = 25 - customerInput_storageCredsAndMIBothSpecified int = 26 - customerInput_clientIdObjectIdBothSpecified int = 27 - customerInput_scriptSpecifiedInTwoPlaces int = 28 - customerInput_commandToExecuteAndScriptBothSpecified int = 29 - customerInput_incompleteStorageCreds int = 30 - - fileDownload_unableToCreateDownloadDirectory int = 50 - fileDownload_sasExpired int = 51 - fileDownload_accessDenied int = 52 - fileDownload_doesNotExist int = 53 - fileDownload_networkingError int = 54 - fileDownload_genericError int = 55 - fileDownload_exceededTimeout int = 56 - - msi_notFound int = 70 - msi_doesNotHaveRightPermissions int = 71 - msi_GenericRetrievalError int = 72 + CommandExecution_failedUnknownError int = 1 + CommandExecution_failureExitCode int = 2 + CommandExecution_interruptedByVmShutdown int = 3 + + CustomerInput_commandToExecuteSpecifiedInTwoPlaces int = 20 + CustomerInput_fileUrisSpecifiedInTwoPlaces int = 22 + CustomerInput_commandToExecuteAndScriptNotSpecified int = 23 + CustomerInput_fileUriContainsNull int = 24 + CustomerInput_invalidFileUris int = 25 + CustomerInput_storageCredsAndMIBothSpecified int = 26 + CustomerInput_clientIdObjectIdBothSpecified int = 27 + CustomerInput_scriptSpecifiedInTwoPlaces int = 28 + CustomerInput_commandToExecuteAndScriptBothSpecified int = 29 + CustomerInput_incompleteStorageCreds int = 30 + + FileDownload_unableToCreateDownloadDirectory int = 50 + FileDownload_sasExpired int = 51 + FileDownload_accessDenied int = 52 + FileDownload_doesNotExist int = 53 + FileDownload_networkingError int = 54 + FileDownload_genericError int = 55 + FileDownload_exceededTimeout int = 56 + + Msi_notFound int = 70 + Msi_doesNotHaveRightPermissions int = 71 + Msi_GenericRetrievalError int = 72 // No Error - used as a placeholder value // when representing an "empty" ErrorWithClarification - noError int = math.MaxInt + // or when the error can be treated without the clarification + NoError int = math.MaxInt ) From 11302c25ae973fe1c8861f6cf0453bb1d66bf80d Mon Sep 17 00:00:00 2001 From: Deepti Vaidyanathan Date: Wed, 12 Feb 2025 23:51:28 +0000 Subject: [PATCH 4/7] Fixes tests --- go.mod | 40 +++------- go.sum | 111 ++++---------------------- main/cmds.go | 2 +- main/cmds_test.go | 14 ++-- main/exec.go | 4 +- main/exec_test.go | 35 ++++---- main/files_test.go | 16 ++-- main/handlersettings_test.go | 32 ++++---- main/status.go | 22 ++--- main/status_test.go | 20 +++++ pkg/download/blobwithmsitoken_test.go | 4 +- pkg/download/downloader.go | 19 +++-- pkg/download/downloader_test.go | 84 +++++++++++-------- pkg/download/retry.go | 16 ++-- pkg/download/retry_test.go | 16 ++-- pkg/download/save.go | 15 ++-- pkg/download/save_test.go | 16 ++-- 17 files changed, 216 insertions(+), 250 deletions(-) diff --git a/go.mod b/go.mod index 4836129..9340f73 100644 --- a/go.mod +++ b/go.mod @@ -1,48 +1,34 @@ module github.com/Azure/custom-script-extension-linux -go 1.23.4 +go 1.23 + +toolchain go1.23.5 require ( - github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6 - github.com/ahmetalpbalkan/go-httpbin v0.0.0-20240315150752-da45896c98cb - github.com/go-kit/kit v0.13.0 + github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187 + github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f + github.com/Azure/azure-sdk-for-go v3.1.0-beta.0.20160802173609-87de771fcdf5+incompatible + github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1 + github.com/go-kit/kit v0.12.0 + github.com/google/uuid v1.3.0 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.2 github.com/xeipuuv/gojsonschema v1.2.0 + golang.org/x/text v0.9.0 ) require ( - github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187 // indirect - github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.29 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/andybalholm/brotli v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-kit/log v0.2.1 github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4 // indirect + github.com/gorilla/mux v0.0.0-20160605233521-9fa818a44c2b // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.5.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v2 v2.2.8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -// //DELETE after testing -//require github.com/Azure/custom-script-extension-linux/pkg v0.0.0 - -//replace github.com/Azure/custom-script-extension-linux/pkg => ../custom-script-extension-linux/pkg - replace github.com/go-kit/kit => github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2 diff --git a/go.sum b/go.sum index ffaaa0a..2800b39 100644 --- a/go.sum +++ b/go.sum @@ -1,56 +1,28 @@ github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187 h1:C4S32XsUvctWzdWDEYlvhfcgH1iGvSD62II7Dd7F6B8= github.com/Azure/azure-extension-foundation v0.0.0-20230404211847-9858bdd5c187/go.mod h1:a0BFq9UoWBHvBS7iagvjFqBjYfxtBsmqvCLWIHRq9b0= -github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6 h1:RDJpiDBkLFa711zwULKd1bhIoWpaslIwlfz5CBHn+eI= -github.com/Azure/azure-extension-platform v0.0.0-20241219234143-33858f5985a6/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY= -github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= -github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= -github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= -github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= -github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4= -github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= -github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/ahmetalpbalkan/go-httpbin v0.0.0-20240315150752-da45896c98cb h1:DSAtit+Eq3K2/kzZsSBYSh3jfCBiWm+FMJkFF0Ffy+I= -github.com/ahmetalpbalkan/go-httpbin v0.0.0-20240315150752-da45896c98cb/go.mod h1:Rg55S63lgqSBCawY/oTm7jdFSySp6jwIqgHMB2IeHK8= -github.com/ahmetb/go-httpbin v0.0.0-20240315150752-da45896c98cb h1:od9/PvyZ6X+dCU04fTRyrYS8HKVW1SxprFvLYjzUI0U= -github.com/ahmetb/go-httpbin v0.0.0-20240315150752-da45896c98cb/go.mod h1:iB3NbHoh0P/9AZepPBcH+gM1PhQJGmsres+ZHf72M3k= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/Azure/azure-extension-platform v0.0.0-20230218002700-ca684482c954 h1:Jnedpc6riCirNEUQGZ7I7gH1PZym71sKFnbP9Lb32oA= +github.com/Azure/azure-extension-platform v0.0.0-20230218002700-ca684482c954/go.mod h1:1hEkO8M1zN/SQpdFOTDDMTNfeE1Q2tCHmEXXiHrWTgo= +github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f h1:ddsUz/suc9txCMz/xWOslqNMvzhbWFMTflUrbcMNoSw= +github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY= +github.com/Azure/azure-sdk-for-go v3.1.0-beta.0.20160802173609-87de771fcdf5+incompatible h1:7CctKV2SGUVFq3a+WNHypGRKQzaPCNVEAMMAlEJXrWc= +github.com/Azure/azure-sdk-for-go v3.1.0-beta.0.20160802173609-87de771fcdf5+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1 h1:/sPElPBMLSi6+bV0o0fPN4U24qQCNHs1i/BjnO+GqLc= +github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1/go.mod h1:Rg55S63lgqSBCawY/oTm7jdFSySp6jwIqgHMB2IeHK8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2 h1:awXynDTA1TiAp1SA/o/xoU6oRHE3xKCokck9l4/poMc= github.com/go-kit/kit v0.1.1-0.20160721083846-b076b44dbec2/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4 h1:3nOfQt8sRPYbXORD5tJ8YyQ3HlL2Jt3LJ2U17CbNh6I= +github.com/gorilla/context v0.0.0-20160525203319-aed02d124ae4/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v0.0.0-20160605233521-9fa818a44c2b h1:OFvZV3a+25cGJH9dETHw0nk0wV6hLZI7IJijOkXEFS0= +github.com/gorilla/mux v0.0.0-20160605233521-9fa818a44c2b/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -75,64 +47,13 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main/cmds.go b/main/cmds.go index d118505..581a377 100644 --- a/main/cmds.go +++ b/main/cmds.go @@ -253,7 +253,7 @@ func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (ewc vmextension.Er if ewc.Err != nil { ctx.Log("event", "failed to execute command", "error", err, "output", dir) - return vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrap(err, "failed to execute command")) + return vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrap(ewc.Err, "failed to execute command")) } ctx.Log("event", "executed command", "output", dir) return vmextension.NewErrorWithClarification(errorutil.NoError, nil) diff --git a/main/cmds_test.go b/main/cmds_test.go index d6294df..14c7920 100644 --- a/main/cmds_test.go +++ b/main/cmds_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "testing" + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/ahmetalpbalkan/go-httpbin" "github.com/go-kit/kit/log" "github.com/stretchr/testify/require" @@ -84,7 +85,7 @@ func Test_runCmd_success(t *testing.T) { require.Nil(t, runCmd(log.NewNopLogger(), dir, handlerSettings{ publicSettings: publicSettings{CommandToExecute: "date"}, - }), "command should run successfully") + }).Err, "command should run successfully") // check stdout stderr files _, err = os.Stat(filepath.Join(dir, "stdout")) @@ -98,11 +99,12 @@ func Test_runCmd_fail(t *testing.T) { require.Nil(t, err) defer os.RemoveAll(dir) - err = runCmd(log.NewNopLogger(), dir, handlerSettings{ + ewc := runCmd(log.NewNopLogger(), dir, handlerSettings{ publicSettings: publicSettings{CommandToExecute: "non-existing-cmd"}, }) - require.NotNil(t, err, "command terminated with exit status") - require.Contains(t, err.Error(), "failed to execute command") + require.Equal(t, errorutil.CommandExecution_failureExitCode, ewc.ErrorCode) + require.NotNil(t, ewc.Err, "command terminated with exit status") + require.Contains(t, ewc.Err.Error(), "failed to execute command") } func Test_downloadFiles(t *testing.T) { @@ -113,7 +115,7 @@ func Test_downloadFiles(t *testing.T) { srv := httptest.NewServer(httpbin.GetMux()) defer srv.Close() - err = downloadFiles(log.NewContext(log.NewNopLogger()), + ewc := downloadFiles(log.NewContext(log.NewNopLogger()), dir, handlerSettings{ publicSettings: publicSettings{ @@ -123,7 +125,7 @@ func Test_downloadFiles(t *testing.T) { srv.URL + "/bytes/1000", }}, }) - require.Nil(t, err) + require.Nil(t, ewc.Err) // check the files f := []string{"10", "100", "1000"} diff --git a/main/exec.go b/main/exec.go index 9b97375..96ed557 100644 --- a/main/exec.go +++ b/main/exec.go @@ -32,13 +32,13 @@ func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, vmextension. if ok { if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { code := status.ExitStatus() - return code, vmextension.NewErrorWithClarification(errorutil.CommandExecution_failedUnknownError, fmt.Errorf("command terminated with exit status=%d", code)) + return code, vmextension.NewErrorWithClarification(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command terminated with exit status=%d", code)) } } if err == nil { return 0, vmextension.NewErrorWithClarification(errorutil.NoError, nil) } - return 0, vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to execute command")) + return 0, vmextension.NewErrorWithClarification(errorutil.CommandExecution_failedUnknownError, errors.Wrapf(err, "failed to execute command")) } diff --git a/main/exec_test.go b/main/exec_test.go index e01b34c..cb98e5b 100644 --- a/main/exec_test.go +++ b/main/exec_test.go @@ -8,13 +8,14 @@ import ( "path/filepath" "testing" + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/stretchr/testify/require" ) func TestExec_success(t *testing.T) { v := new(mockFile) ec, err := Exec("date", "/", v, v) - require.Nil(t, err, "err: %v -- out: %s", err, v.b.Bytes()) + require.Nil(t, err.Err, "err: %v -- out: %s", err.Err, v.b.Bytes()) require.EqualValues(t, 0, ec) } @@ -24,7 +25,7 @@ func TestExec_success_redirectsStdStreams_closesFds(t *testing.T) { require.False(t, e.closed, "stderr open") _, err := Exec("/bin/echo 'I am stdout!'>&1; /bin/echo 'I am stderr!'>&2", "/", o, e) - require.Nil(t, err, "err: %v -- stderr: %s", err, e.b.Bytes()) + require.Nil(t, err.Err, "err: %v -- stderr: %s", err.Err, e.b.Bytes()) require.Equal(t, "I am stdout!\n", string(o.b.Bytes())) require.Equal(t, "I am stderr!\n", string(e.b.Bytes())) require.True(t, o.closed, "stdout closed") @@ -33,15 +34,17 @@ func TestExec_success_redirectsStdStreams_closesFds(t *testing.T) { func TestExec_failure_exitError(t *testing.T) { ec, err := Exec("exit 12", "/", new(mockFile), new(mockFile)) - require.NotNil(t, err) - require.EqualError(t, err, "command terminated with exit status=12") // error is customized + require.Equal(t, err.ErrorCode, errorutil.CommandExecution_failureExitCode) + require.NotNil(t, err.Err) + require.EqualError(t, err.Err, "command terminated with exit status=12") // error is customized require.EqualValues(t, 12, ec) } func TestExec_failure_genericError(t *testing.T) { _, err := Exec("date", "/non-existing-path", new(mockFile), new(mockFile)) - require.NotNil(t, err) - require.Contains(t, err.Error(), "failed to execute command:") // error is wrapped + require.Equal(t, err.ErrorCode, errorutil.CommandExecution_failedUnknownError) + require.NotNil(t, err.Err) + require.Contains(t, err.Err.Error(), "failed to execute command:") // error is wrapped } func TestExec_failure_fdClosed(t *testing.T) { @@ -49,8 +52,9 @@ func TestExec_failure_fdClosed(t *testing.T) { require.Nil(t, out.Close()) _, err := Exec("date", "/", out, out) - require.NotNil(t, err) - require.Contains(t, err.Error(), "file closed") // error is wrapped + require.Equal(t, err.ErrorCode, errorutil.CommandExecution_failedUnknownError) + require.NotNil(t, err.Err) + require.Contains(t, err.Err.Error(), "file closed") // error is wrapped } func TestExec_failure_redirectsStdStreams_closesFds(t *testing.T) { @@ -59,7 +63,8 @@ func TestExec_failure_redirectsStdStreams_closesFds(t *testing.T) { require.False(t, e.closed, "stderr open") _, err := Exec(`/bin/echo 'I am stdout!'>&1; /bin/echo 'I am stderr!'>&2; exit 12`, "/", o, e) - require.NotNil(t, err) + require.Equal(t, err.ErrorCode, errorutil.CommandExecution_failureExitCode) + require.NotNil(t, err.Err) require.Equal(t, "I am stdout!\n", string(o.b.Bytes())) require.Equal(t, "I am stderr!\n", string(e.b.Bytes())) require.True(t, o.closed, "stdout closed") @@ -71,8 +76,8 @@ func TestExecCmdInDir(t *testing.T) { require.Nil(t, err) defer os.RemoveAll(dir) - err = ExecCmdInDir("/bin/echo 'Hello world'", dir) - require.Nil(t, err) + ewc := ExecCmdInDir("/bin/echo 'Hello world'", dir) + require.Nil(t, ewc.Err) require.True(t, fileExists(t, filepath.Join(dir, "stdout")), "stdout file should be created") require.True(t, fileExists(t, filepath.Join(dir, "stderr")), "stderr file should be created") @@ -87,7 +92,9 @@ func TestExecCmdInDir(t *testing.T) { func TestExecCmdInDir_cantOpenError(t *testing.T) { err := ExecCmdInDir("/bin/echo 'Hello world'", "/non-existing-dir") - require.Contains(t, err.Error(), "failed to open stdout file") + require.Equal(t, err.ErrorCode, errorutil.NoError) + require.NotNil(t, err.Err) + require.Contains(t, err.Err.Error(), "failed to open stdout file") } func TestExecCmdInDir_truncates(t *testing.T) { @@ -95,8 +102,8 @@ func TestExecCmdInDir_truncates(t *testing.T) { require.Nil(t, err) defer os.RemoveAll(dir) - require.Nil(t, ExecCmdInDir("/bin/echo '1:out'; /bin/echo '1:err'>&2", dir)) - require.Nil(t, ExecCmdInDir("/bin/echo '2:out'; /bin/echo '2:err'>&2", dir)) + require.Nil(t, ExecCmdInDir("/bin/echo '1:out'; /bin/echo '1:err'>&2", dir).Err) + require.Nil(t, ExecCmdInDir("/bin/echo '2:out'; /bin/echo '2:err'>&2", dir).Err) b, err := ioutil.ReadFile(filepath.Join(dir, "stdout")) require.Nil(t, err) diff --git a/main/files_test.go b/main/files_test.go index 72e60a6..0abc412 100644 --- a/main/files_test.go +++ b/main/files_test.go @@ -16,11 +16,11 @@ import ( func Test_getDownloader_azureBlob(t *testing.T) { // error condition _, err := getDownloaders("http://acct.blob.core.windows.net/", "acct", "key", nil) - require.NotNil(t, err) + require.NotNil(t, err.Err) // valid input d, err := getDownloaders("http://acct.blob.core.windows.net/container/blob", "acct", "key", nil) - require.Nil(t, err) + require.Nil(t, err.Err) require.NotNil(t, d) require.Equal(t, 1, len(d)) require.Equal(t, "download.blobDownload", fmt.Sprintf("%T", d[0]), "got wrong type") @@ -28,14 +28,14 @@ func Test_getDownloader_azureBlob(t *testing.T) { func Test_getDownloader_externalUrl(t *testing.T) { d, err := getDownloaders("http://acct.blob.core.windows.net/", "", "", nil) - require.Nil(t, err) + require.Nil(t, err.Err) require.NotNil(t, d) require.NotEmpty(t, d) require.Equal(t, 1, len(d)) require.Equal(t, "download.urlDownload", fmt.Sprintf("%T", d[0]), "got wrong type") d, err = getDownloaders("http://acct.blob.core.windows.net/", "", "", &clientOrObjectId{"", "dummyclientid"}) - require.Nil(t, err) + require.Nil(t, err.Err) require.NotNil(t, d) require.NotEmpty(t, d) require.Equal(t, 2, len(d)) @@ -43,13 +43,13 @@ func Test_getDownloader_externalUrl(t *testing.T) { require.Equal(t, "*download.blobWithMsiToken", fmt.Sprintf("%T", d[1]), "got wrong type") d, err = getDownloaders("http://acct.blob.core.windows.net/", "foo", "", nil) - require.Nil(t, err) + require.Nil(t, err.Err) require.NotNil(t, d) require.Equal(t, 1, len(d)) require.Equal(t, "download.urlDownload", fmt.Sprintf("%T", d[0]), "got wrong type") d, err = getDownloaders("http://acct.blob.core.windows.net/", "", "bar", nil) - require.Nil(t, err) + require.Nil(t, err.Err) require.NotNil(t, d) require.Equal(t, 1, len(d)) require.Equal(t, "download.urlDownload", fmt.Sprintf("%T", d[0]), "got wrong type") @@ -124,8 +124,8 @@ func Test_downloadAndProcessURL(t *testing.T) { defer os.RemoveAll(tmpDir) cfg := handlerSettings{publicSettings{}, protectedSettings{StorageAccountName: "", StorageAccountKey: ""}} - err = downloadAndProcessURL(log.NewContext(log.NewNopLogger()), srv.URL+"/bytes/256", tmpDir, &cfg) - require.Nil(t, err) + ewc := downloadAndProcessURL(log.NewContext(log.NewNopLogger()), srv.URL+"/bytes/256", tmpDir, &cfg) + require.Nil(t, ewc.Err) fp := filepath.Join(tmpDir, "256") fi, err := os.Stat(fp) diff --git a/main/handlersettings_test.go b/main/handlersettings_test.go index 3addd72..7097aa0 100644 --- a/main/handlersettings_test.go +++ b/main/handlersettings_test.go @@ -11,30 +11,30 @@ func Test_handlerSettingsValidate(t *testing.T) { require.Equal(t, errCmdMissing, handlerSettings{ publicSettings{}, protectedSettings{}, - }.validate()) + }.validate().Err) // commandToExecute specified twice require.Equal(t, errCmdTooMany, handlerSettings{ publicSettings{CommandToExecute: "foo"}, protectedSettings{CommandToExecute: "foo"}, - }.validate()) + }.validate().Err) // script specified twice require.Equal(t, errScriptTooMany, handlerSettings{ publicSettings{Script: "foo"}, protectedSettings{Script: "foo"}, - }.validate()) + }.validate().Err) // commandToExecute and script both specified require.Equal(t, errCmdAndScript, handlerSettings{ publicSettings{CommandToExecute: "foo"}, protectedSettings{Script: "foo"}, - }.validate()) + }.validate().Err) require.Equal(t, errCmdAndScript, handlerSettings{ publicSettings{Script: "foo"}, protectedSettings{CommandToExecute: "foo"}, - }.validate()) + }.validate().Err) // storageAccount name specified; but not key require.Equal(t, errStoragePartialCredentials, handlerSettings{ @@ -42,7 +42,7 @@ func Test_handlerSettingsValidate(t *testing.T) { CommandToExecute: "date", StorageAccountName: "foo", StorageAccountKey: ""}, - }.validate()) + }.validate().Err) // storageAccount key specified; but not name require.Equal(t, errStoragePartialCredentials, handlerSettings{ @@ -50,7 +50,7 @@ func Test_handlerSettingsValidate(t *testing.T) { CommandToExecute: "date", StorageAccountName: "", StorageAccountKey: "foo"}, - }.validate()) + }.validate().Err) } func Test_commandToExecutePrivateIfNotPublic(t *testing.T) { @@ -96,14 +96,14 @@ func Test_managedIdentityVerification(t *testing.T) { ManagedIdentity: &clientOrObjectId{ ClientId: "31b403aa-c364-4240-a7ff-d85fb6cd7232", }, - }}.validate(), "validation failed for settings with MSI") + }}.validate().Err, "validation failed for settings with MSI") require.NoError(t, handlerSettings{publicSettings{}, protectedSettings{ CommandToExecute: "echo hi", ManagedIdentity: &clientOrObjectId{ ObjectId: "31b403aa-c364-4240-a7ff-d85fb6cd7232", }, - }}.validate(), "validation failed for settings with MSI") + }}.validate().Err, "validation failed for settings with MSI") require.Equal(t, errUsingBothKeyAndMsi, handlerSettings{publicSettings{}, @@ -114,7 +114,7 @@ func Test_managedIdentityVerification(t *testing.T) { ManagedIdentity: &clientOrObjectId{ ObjectId: "31b403aa-c364-4240-a7ff-d85fb6cd7232", }, - }}.validate(), "validation didn't fail for settings with both MSI and storage account") + }}.validate().Err, "validation didn't fail for settings with both MSI and storage account") require.Equal(t, errUsingBothClientIdAndObjectId, handlerSettings{publicSettings{}, @@ -124,7 +124,7 @@ func Test_managedIdentityVerification(t *testing.T) { ObjectId: "31b403aa-c364-4240-a7ff-d85fb6cd7232", ClientId: "31b403aa-c364-4240-a7ff-d85fb6cd7232", }, - }}.validate(), "validation didn't fail for settings with both MSI and storage account") + }}.validate().Err, "validation didn't fail for settings with both MSI and storage account") } func Test_toJSON_empty(t *testing.T) { @@ -148,7 +148,7 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.NoError(t, err, "error while deserializing json") require.Nil(t, protSettings.ManagedIdentity, "ProtectedSettings.ManagedIdentity was expected to be nil") h := handlerSettings{publicSettings{}, *protSettings} - require.NoError(t, h.validate(), "settings should be valid") + require.NoError(t, h.validate().Err, "settings should be valid") testString = `{"commandToExecute" : "echo hello", "fileUris":["https://a.com/file.txt"], "managedIdentity": { }}` require.NoError(t, validateProtectedSettings(testString), "protected settings should be valid") @@ -159,7 +159,7 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.Equal(t, protSettings.ManagedIdentity.ClientId, "") require.Equal(t, protSettings.ManagedIdentity.ObjectId, "") h = handlerSettings{publicSettings{}, *protSettings} - require.NoError(t, h.validate(), "settings should be valid") + require.NoError(t, h.validate().Err, "settings should be valid") testString = `{"commandToExecute" : "echo hello", "fileUris":["https://a.com/file.txt", "https://b.com/file2.txt"], "managedIdentity": { "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232"}}` require.NoError(t, validateProtectedSettings(testString), "protected settings should be valid") @@ -170,7 +170,7 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.Equal(t, protSettings.ManagedIdentity.ClientId, "31b403aa-c364-4240-a7ff-d85fb6cd7232") require.Equal(t, protSettings.ManagedIdentity.ObjectId, "") h = handlerSettings{publicSettings{}, *protSettings} - require.NoError(t, h.validate(), "settings should be valid") + require.NoError(t, h.validate().Err, "settings should be valid") testString = `{"commandToExecute" : "echo hello", "fileUris":["https://a.com/file.txt"], "managedIdentity": { "objectId": "31b403aa-c364-4240-a7ff-d85fb6cd7232"}}` require.NoError(t, validateProtectedSettings(testString), "protected settings should be valid") @@ -181,7 +181,7 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.Equal(t, protSettings.ManagedIdentity.ObjectId, "31b403aa-c364-4240-a7ff-d85fb6cd7232") require.Equal(t, protSettings.ManagedIdentity.ClientId, "") h = handlerSettings{publicSettings{}, *protSettings} - require.NoError(t, h.validate(), "settings should be valid") + require.NoError(t, h.validate().Err, "settings should be valid") testString = `{"commandToExecute" : "echo hello", "fileUris":["https://a.com/file.txt", "https://b.com/file2.txt"], "managedIdentity": { "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232", "objectId": "41b403aa-c364-4240-a7ff-d85fb6cd7232"}}` require.NoError(t, validateProtectedSettings(testString), "protected settings should be valid") @@ -192,5 +192,5 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.Equal(t, protSettings.ManagedIdentity.ClientId, "31b403aa-c364-4240-a7ff-d85fb6cd7232") require.Equal(t, protSettings.ManagedIdentity.ObjectId, "41b403aa-c364-4240-a7ff-d85fb6cd7232") h = handlerSettings{publicSettings{}, *protSettings} - require.Error(t, h.validate(), "settings should be invalid") + require.Error(t, h.validate().Err, "settings should be invalid") } diff --git a/main/status.go b/main/status.go index 5505598..0395e08 100644 --- a/main/status.go +++ b/main/status.go @@ -10,6 +10,7 @@ import ( status "github.com/Azure/azure-extension-platform/pkg/status" vmextension "github.com/Azure/azure-extension-platform/vmextension" + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/go-kit/kit/log" "github.com/pkg/errors" ) @@ -104,22 +105,25 @@ func reportStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, return nil } -// reportStatus saves operation status to the status file for the extension -// handler with the optional given message, if the given cmd requires reporting -// status. +// reportErrorStatus saves the error(s) that occurred during the operation +// to the status file for the extension handler with clarification messages and codes, +// if the given cmd requires reporting status. // // If an error occurs reporting the status, it will be logged and returned. -func reportErrorStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, c cmd, err vmextension.ErrorWithClarification) error { +func reportErrorStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, c cmd, ewc vmextension.ErrorWithClarification) error { if !c.shouldReportStatus { ctx.Log("status", "not reported for operation (by design)") return nil } - ewc := status.ErrorClarification{ - Code: err.ErrorCode, - Message: err.Error(), + var err error + if ewc.ErrorCode == errorutil.NoError { + s := NewStatus(t, c.name, statusMsg(c, t, ewc.Err.Error())) + err = s.Save(hEnv.HandlerEnvironment.StatusFolder, seqNum) + } else { + s := status.NewError(c.name, status.ErrorClarification{Code: ewc.ErrorCode, Message: ewc.Error()}) + err = s.Save(hEnv.HandlerEnvironment.StatusFolder, uint(seqNum)) } - s := status.NewError(c.name, ewc) - if err := s.Save(hEnv.HandlerEnvironment.StatusFolder, uint(seqNum)); err != nil { + if err != nil { ctx.Log("event", "failed to save handler status", "error", err) return errors.Wrap(err, "failed to save handler status") } diff --git a/main/status_test.go b/main/status_test.go index 770180d..c13c921 100644 --- a/main/status_test.go +++ b/main/status_test.go @@ -1,11 +1,14 @@ package main import ( + "fmt" "io/ioutil" "os" "path/filepath" "testing" + vmextension "github.com/Azure/azure-extension-platform/vmextension" + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/go-kit/kit/log" "github.com/stretchr/testify/require" ) @@ -46,6 +49,23 @@ func Test_reportStatus_fileExists(t *testing.T) { require.NotEqual(t, 0, len(b), ".status file not empty") } +func Test_reportErrorStatus_fileExists(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + + fakeEnv := HandlerEnvironment{} + fakeEnv.HandlerEnvironment.StatusFolder = tmpDir + ewc := vmextension.NewErrorWithClarification(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command failed with exit code = 1")) + + require.Nil(t, reportErrorStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusError, cmdEnable, ewc)) + + path := filepath.Join(tmpDir, "1.status") + b, err := ioutil.ReadFile(path) + require.Nil(t, err, ".status file exists") + require.NotEqual(t, 0, len(b), ".status file not empty") +} + func Test_reportStatus_checksIfShouldBeReported(t *testing.T) { for _, c := range cmds { tmpDir, err := ioutil.TempDir("", "status-"+c.name) diff --git a/pkg/download/blobwithmsitoken_test.go b/pkg/download/blobwithmsitoken_test.go index ec33db0..7134cda 100644 --- a/pkg/download/blobwithmsitoken_test.go +++ b/pkg/download/blobwithmsitoken_test.go @@ -46,8 +46,8 @@ func Test_realDownloadBlobWithMsiToken(t *testing.T) { err := json.Unmarshal([]byte(msiJson), &msi) return msi, err }} - _, stream, err := Download(testctx, &downloader) - require.NoError(t, err, "File download failed") + _, stream, ewc := Download(testctx, &downloader) + require.NoError(t, ewc.Err, "File download failed") defer stream.Close() bytes, err := ioutil.ReadAll(stream) diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index cb29f01..92228f9 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -8,6 +8,7 @@ import ( "time" "net/url" + "github.com/Azure/azure-extension-platform/vmextension" "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/Azure/custom-script-extension-linux/pkg/urlutil" "github.com/go-kit/kit/log" @@ -24,6 +25,8 @@ type Downloader interface { const ( MsiDownload404ErrorString = "please ensure that the blob location in the fileUri setting exists, and the specified Managed Identity has read permissions to the storage blob" MsiDownload403ErrorString = "please ensure that the specified Managed Identity has read permissions to the storage blob" + MsiDownloadGenericErrorString = "unable to download the MSI. This may be due to firewall rules or a networking error" + MsiDownload500ErrorString = "the IMDS service returned a00 upon requesting the MSI" ) var ( @@ -46,10 +49,10 @@ var ( // Download retrieves a response body and checks the response status code to see // if it is 200 OK and then returns the response body. It issues a new request // every time called. It is caller's responsibility to close the response body. -func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, int, error) { +func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.ErrorWithClarification) { req, err := d.GetRequest() if err != nil { - return -1, nil, errorutil.FileDownload_genericError, errors.Wrapf(err, "failed to create http request") + return -1, nil, vmextension.NewErrorWithClarification(errorutil.FileDownload_genericError, errors.Wrapf(err, "failed to create http request")) } requestID := req.Header.Get(xMsClientRequestIdHeaderName) if len(requestID) > 0 { @@ -59,15 +62,15 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, int, error) { if err != nil { if ((err.(*url.Error)).Timeout()) { err = urlutil.RemoveUrlFromErr(err) - return -1, nil, errorutil.FileDownload_exceededTimeout, errors.Wrapf(err, "http request timed out") + return -1, nil, vmextension.NewErrorWithClarification(errorutil.FileDownload_exceededTimeout, errors.Wrapf(err, "http request timed out")) } err = urlutil.RemoveUrlFromErr(err) - return -1, nil, errorutil.FileDownload_unknownError, errors.Wrapf(err, "http request failed") + return -1, nil, vmextension.NewErrorWithClarification(errorutil.FileDownload_unknownError, errors.Wrapf(err, "http request failed")) } if resp.StatusCode == http.StatusOK { // We're setting the errorCode to MaxInt because we're only checking whether the internal error is nil - return resp.StatusCode, resp.Body, errorutil.NoError, nil + return resp.StatusCode, resp.Body, vmextension.NewErrorWithClarification(errorutil.NoError, nil) } errString := "" @@ -82,7 +85,11 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, int, error) { case http.StatusForbidden: errString = MsiDownload403ErrorString errClarificationCode = errorutil.Msi_doesNotHaveRightPermissions + case http.StatusInternalServerError: + errString = MsiDownload500ErrorString + errClarificationCode = errorutil.Imds_internalMsiError default: + errString = MsiDownloadGenericErrorString errClarificationCode = errorutil.Msi_GenericRetrievalError } break @@ -121,5 +128,5 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, int, error) { if len(requestId) > 0 { errString += fmt.Sprintf(" (Service request ID: %s)", requestId) } - return resp.StatusCode, nil, errClarificationCode, fmt.Errorf(errString) + return resp.StatusCode, nil, vmextension.NewErrorWithClarification(errClarificationCode, fmt.Errorf(errString)) } diff --git a/pkg/download/downloader_test.go b/pkg/download/downloader_test.go index 7657154..f784000 100644 --- a/pkg/download/downloader_test.go +++ b/pkg/download/downloader_test.go @@ -30,17 +30,17 @@ func (b *badDownloader) GetRequest() (*http.Request, error) { } func TestDownload_wrapsGetRequestError(t *testing.T) { - _, _, errCode, err := download.Download(testctx, new(badDownloader)) - require.Equal(t, errCode, errorutil.FileDownload_genericError) - require.NotNil(t, err) - require.EqualError(t, err, "failed to create http request: expected error") + _, _, ewc:= download.Download(testctx, new(badDownloader)) + require.Equal(t, ewc.ErrorCode, errorutil.FileDownload_genericError) + require.NotNil(t, ewc.Err) + require.EqualError(t, ewc.Err, "failed to create http request: expected error") } func TestDownload_wrapsHTTPError(t *testing.T) { - _, _, errCode, err := download.Download(testctx, download.NewURLDownload("bad url")) - require.Equal(t, errCode, errorutil.FileDownload_unknownError) - require.NotNil(t, err) - require.Contains(t, err.Error(), "http request failed:") + _, _, ewc := download.Download(testctx, download.NewURLDownload("bad url")) + require.Equal(t, ewc.ErrorCode, errorutil.FileDownload_unknownError) + require.NotNil(t, ewc.Err) + require.Contains(t, ewc.Err.Error(), "http request failed:") } // This test is only to make sure that formatting of error messages for specific codes is correct @@ -56,25 +56,25 @@ func TestDownload_wrapsCommonErrorCodes(t *testing.T) { http.StatusBadRequest, http.StatusUnauthorized, } { - respCode, _, errCode, err := download.Download(testctx, download.NewURLDownload(fmt.Sprintf("%s/status/%d", srv.URL, code))) - require.NotNil(t, err, "not failed for code:%d", code) + respCode, _, ewc:= download.Download(testctx, download.NewURLDownload(fmt.Sprintf("%s/status/%d", srv.URL, code))) + require.NotNil(t, ewc.Err, "not failed for code:%d", code) require.Equal(t, code, respCode) switch respCode { case http.StatusNotFound: - require.Equal(t, errCode, errorutil.FileDownload_doesNotExist) - require.Contains(t, err.Error(), "because it does not exist") + require.Equal(t, ewc.ErrorCode, errorutil.FileDownload_doesNotExist) + require.Contains(t, ewc.Err.Error(), "because it does not exist") case http.StatusForbidden: - require.Equal(t, errCode, errorutil.FileDownload_networkingError) - require.Contains(t, err.Error(), "Please verify the machine has network connectivity") + require.Equal(t, ewc.ErrorCode, errorutil.FileDownload_networkingError) + require.Contains(t, ewc.Err.Error(), "Please verify the machine has network connectivity") case http.StatusInternalServerError: - require.Equal(t, errCode, errorutil.Storage_internalServerError) - require.Contains(t, err.Error(), "due to an issue with storage") + require.Equal(t, ewc.ErrorCode, errorutil.Storage_internalServerError) + require.Contains(t, ewc.Err.Error(), "due to an issue with storage") case http.StatusBadRequest: - require.Equal(t, errCode, errorutil.FileDownload_badRequest) - require.Contains(t, err.Error(), "because parts of the request were incorrectly formatted, missing, and/or invalid") + require.Equal(t, ewc.ErrorCode, errorutil.FileDownload_badRequest) + require.Contains(t, ewc.Err.Error(), "because parts of the request were incorrectly formatted, missing, and/or invalid") case http.StatusUnauthorized: - require.Equal(t, errCode, errorutil.FileDownload_accessDenied) - require.Contains(t, err.Error(), "because access was denied") + require.Equal(t, ewc.ErrorCode, errorutil.FileDownload_accessDenied) + require.Contains(t, ewc.Err.Error(), "because access was denied") } } } @@ -83,9 +83,9 @@ func TestDownload_statusOKSucceeds(t *testing.T) { srv := httptest.NewServer(httpbin.GetMux()) defer srv.Close() - _, body, errCode, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/status/200")) - require.Equal(t, errCode, errorutil.NoError) - require.Nil(t, err) + _, body, ewc := download.Download(testctx, download.NewURLDownload(srv.URL+"/status/200")) + require.Equal(t, ewc.ErrorCode, errorutil.NoError) + require.Nil(t, ewc.Err) defer body.Close() require.NotNil(t, body) } @@ -99,28 +99,42 @@ func TestDowload_msiDownloaderErrorMessage(t *testing.T) { msiDownloader404 := download.NewBlobWithMsiDownload(srv.URL+"/status/404", mockMsiProvider) - returnCode, body, errCode, err := download.Download(testctx, msiDownloader404) - require.Equal(t, errCode, errorutil.Msi_notFound) - require.True(t, strings.Contains(err.Error(), download.MsiDownload404ErrorString), "error string doesn't contain the correct message") + returnCode, body, ewc := download.Download(testctx, msiDownloader404) + require.Equal(t, ewc.ErrorCode, errorutil.Msi_notFound) + require.True(t, strings.Contains(ewc.Err.Error(), download.MsiDownload404ErrorString), "error string doesn't contain the correct message") require.Nil(t, body, "body is not nil for failed download") require.Equal(t, 404, returnCode, "return code was not 404") msiDownloader403 := download.NewBlobWithMsiDownload(srv.URL+"/status/403", mockMsiProvider) - returnCode, body, errCode, err = download.Download(testctx, msiDownloader403) - require.Equal(t, errCode, errorutil.Msi_doesNotHaveRightPermissions) - require.True(t, strings.Contains(err.Error(), download.MsiDownload403ErrorString), "error string doesn't contain the correct message") + returnCode, body, ewc = download.Download(testctx, msiDownloader403) + require.Equal(t, ewc.ErrorCode, errorutil.Msi_doesNotHaveRightPermissions) + require.True(t, strings.Contains(ewc.Err.Error(), download.MsiDownload403ErrorString), "error string doesn't contain the correct message") require.Nil(t, body, "body is not nil for failed download") require.Equal(t, 403, returnCode, "return code was not 403") + msiDownloade500 := download.NewBlobWithMsiDownload(srv.URL+"/status/500", mockMsiProvider) + returnCode, body, ewc = download.Download(testctx, msiDownloade500) + require.Equal(t, ewc.ErrorCode, errorutil.Imds_internalMsiError) + require.True(t, strings.Contains(ewc.Err.Error(), download.MsiDownload500ErrorString), "error string doesn't contain the correct message") + require.Nil(t, body, "body is not nil for failed download") + require.Equal(t, 500, returnCode, "return code was not 500") + + msiDownloader400 := download.NewBlobWithMsiDownload(srv.URL+"/status/400", mockMsiProvider) + returnCode, body, ewc = download.Download(testctx, msiDownloader400) + require.Equal(t, ewc.ErrorCode, errorutil.Msi_GenericRetrievalError) + require.True(t, strings.Contains(ewc.Err.Error(), download.MsiDownloadGenericErrorString), "error string doesn't contain the correct message") + require.Nil(t, body, "body is not nil for failed download") + require.Equal(t, 400, returnCode, "return code was not 400") + } func TestDownload_retrievesBody(t *testing.T) { srv := httptest.NewServer(httpbin.GetMux()) defer srv.Close() - _, body, errCode, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/bytes/65536")) - require.Equal(t, errCode, errorutil.NoError) - require.Nil(t, err) + _, body, ewc := download.Download(testctx, download.NewURLDownload(srv.URL+"/bytes/65536")) + require.Equal(t, ewc.ErrorCode, errorutil.NoError) + require.Nil(t, ewc.Err) defer body.Close() b, err := io.ReadAll(body) require.Nil(t, err) @@ -131,8 +145,8 @@ func TestDownload_bodyClosesWithoutError(t *testing.T) { srv := httptest.NewServer(httpbin.GetMux()) defer srv.Close() - _, body, errCode, err := download.Download(testctx, download.NewURLDownload(srv.URL+"/get")) - require.Equal(t, errCode, errorutil.NoError) - require.Nil(t, err) + _, body, ewc := download.Download(testctx, download.NewURLDownload(srv.URL+"/get")) + require.Equal(t, ewc.ErrorCode, errorutil.NoError) + require.Nil(t, ewc.Err) require.Nil(t, body.Close(), "body should close fine") } diff --git a/pkg/download/retry.go b/pkg/download/retry.go index 74f5a35..fd8bab3 100644 --- a/pkg/download/retry.go +++ b/pkg/download/retry.go @@ -9,6 +9,8 @@ import ( "time" "github.com/go-kit/kit/log" + "github.com/Azure/azure-extension-platform/vmextension" + errorutil "github.com/Azure/custom-script-extension-linux/pkg/errorutil" ) @@ -35,7 +37,7 @@ const ( // closed on failures). If the retries do not succeed, the last error is returned. // // It sleeps in exponentially increasing durations between retries. -func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf SleepFunc) (int64, int, error) { +func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf SleepFunc) (int64, vmextension.ErrorWithClarification) { var lastErr error var lastErrCode int for _, d := range downloaders { @@ -46,8 +48,8 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee lastErr = nil lastErrCode = errorutil.NoError start := time.Now() - status, out, errCode, err := Download(ctx, d) - if err == nil { + status, out, ewc := Download(ctx, d) + if ewc.Err == nil { // server returned status code 200 OK // we have a response body, copy it to the file nBytes, innerErr := io.CopyBuffer(f, out, make([]byte, writeBufSize)) @@ -57,7 +59,7 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee out.Close() end := time.Since(start) ctx.Log("info", fmt.Sprintf("file download sucessful: downloaded and saved %d bytes in %d milliseconds", nBytes, end.Milliseconds())) - return nBytes, lastErrCode, lastErr + return nBytes, vmextension.NewErrorWithClarification(lastErrCode, lastErr) } else { // we failed to download the response body and write it to file // because either connection was closed prematurely or file write operation failed @@ -71,8 +73,8 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee } } else { // cache the outer error - lastErr = err - lastErrCode = errCode + lastErr = ewc.Err + lastErrCode = ewc.ErrorCode } // we are here because either server returned a non-200 status code @@ -100,7 +102,7 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee } } } - return 0, lastErrCode, lastErr + return 0, vmextension.NewErrorWithClarification(lastErrCode, lastErr) } func isTransientHttpStatusCode(statusCode int) bool { diff --git a/pkg/download/retry_test.go b/pkg/download/retry_test.go index 605e2d0..7bfe869 100644 --- a/pkg/download/retry_test.go +++ b/pkg/download/retry_test.go @@ -48,7 +48,7 @@ func TestWithRetries_noRetries(t *testing.T) { sr := new(sleepRecorder) n, err := download.WithRetries(nopLog(), file, []download.Downloader{d}, sr.Sleep) - require.Nil(t, err, "should not fail") + require.Nil(t, err.Err, "should not fail") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, []time.Duration(nil), []time.Duration(*sr), "sleep should not be called") } @@ -65,7 +65,7 @@ func TestWithRetries_failing_validateNumberOfCalls(t *testing.T) { sr := new(sleepRecorder) n, err := download.WithRetries(nopLog(), file, []download.Downloader{bd}, sr.Sleep) - require.Contains(t, err.Error(), "expected error", "error is preserved") + require.Contains(t, err.Err.Error(), "expected error", "error is preserved") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.EqualValues(t, 7, bd.calls, "calls exactly expRetryN times") } @@ -82,8 +82,8 @@ func TestWithRetries_failingBadStatusCode_validateSleeps(t *testing.T) { sr := new(sleepRecorder) n, err := download.WithRetries(nopLog(), file, []download.Downloader{d}, sr.Sleep) - require.Contains(t, err.Error(), "429 Too Many Requests") - require.Contains(t, err.Error(), "Please verify the machine has network connectivity") + require.Contains(t, err.Err.Error(), "429 Too Many Requests") + require.Contains(t, err.Err.Error(), "Please verify the machine has network connectivity") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, sleepSchedule, []time.Duration(*sr)) } @@ -100,7 +100,7 @@ func TestWithRetries_healingServer(t *testing.T) { sr := new(sleepRecorder) n, err := download.WithRetries(nopLog(), file, []download.Downloader{d}, sr.Sleep) - require.Nil(t, err, "should eventually succeed") + require.Nil(t, err.Err, "should eventually succeed") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, sleepSchedule[:3], []time.Duration(*sr)) } @@ -118,7 +118,7 @@ func TestRetriesWith_SwitchDownloaderOn404(t *testing.T) { d200 := mockDownloader{0, hSvr.URL} n, err := download.WithRetries(nopLog(), file, []download.Downloader{&d404, &d200}, func(d time.Duration) { return }) - require.Nil(t, err, "should eventually succeed") + require.Nil(t, err.Err, "should eventually succeed") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, d404.timesCalled, 1) require.Equal(t, d200.timesCalled, 4) @@ -140,7 +140,7 @@ func TestRetriesWith_SwitchDownloaderThenFailWithCorretErrorMessage(t *testing.T msiDownloader403 := download.NewBlobWithMsiDownload(svr.URL+"/status/403", mockMsiProvider) n, err := download.WithRetries(nopLog(), file, []download.Downloader{&d404, msiDownloader403}, func(d time.Duration) { return }) - require.NotNil(t, err, "download with retries should fail") + require.NotNil(t, err.Err, "download with retries should fail") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, d404.timesCalled, 1) require.True(t, strings.Contains(err.Error(), download.MsiDownload403ErrorString), "error string doesn't contain the correct message") @@ -149,7 +149,7 @@ func TestRetriesWith_SwitchDownloaderThenFailWithCorretErrorMessage(t *testing.T msiDownloader404 := download.NewBlobWithMsiDownload(svr.URL+"/status/404", mockMsiProvider) n, err = download.WithRetries(nopLog(), file, []download.Downloader{&d404, msiDownloader404}, func(d time.Duration) { return }) - require.NotNil(t, err, "download with retries should fail") + require.NotNil(t, err.Err, "download with retries should fail") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, d404.timesCalled, 1) require.True(t, strings.Contains(err.Error(), download.MsiDownload404ErrorString), "error string doesn't contain the correct message") diff --git a/pkg/download/save.go b/pkg/download/save.go index a7cc06c..9a3dd89 100644 --- a/pkg/download/save.go +++ b/pkg/download/save.go @@ -3,6 +3,7 @@ package download import ( "os" + "github.com/Azure/azure-extension-platform/vmextension" "github.com/go-kit/kit/log" "github.com/pkg/errors" "github.com/Azure/custom-script-extension-linux/pkg/errorutil" @@ -12,17 +13,19 @@ import ( // given file. Directory of dst is not created by this function. If a file at // dst exists, it will be truncated. If a new file is created, mode is used to // set the permission bits. Written number of bytes are returned on success. -func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, int, error) { +func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, vmextension.ErrorWithClarification) { f, err := os.OpenFile(dst, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, mode) if err != nil { - return 0, errorutil.FileDownload_unknownError, errors.Wrap(err, "failed to open file for writing") + return 0, vmextension.NewErrorWithClarification(errorutil.FileDownload_unknownError, errors.Wrap(err, "failed to open file for writing")) + } defer f.Close() - n, errCode, err := WithRetries(ctx, f, d, ActualSleep) - if err != nil { - return n, errCode, errors.Wrapf(err, "failed to download response and write to file: %s", dst) + n, ewc := WithRetries(ctx, f, d, ActualSleep) + if ewc.Err != nil { + return n, vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download response and write to file: %s", dst)) + } - return n, errorutil.NoError, nil + return n, vmextension.NewErrorWithClarification(errorutil.NoError, nil) } diff --git a/pkg/download/save_test.go b/pkg/download/save_test.go index 3bbfb9f..3b16de3 100644 --- a/pkg/download/save_test.go +++ b/pkg/download/save_test.go @@ -33,8 +33,8 @@ func TestSave(t *testing.T) { d := download.NewURLDownload(srv.URL + "/bytes/65536") path := filepath.Join(dir, "test-file") - n, err := download.SaveTo(nopLog(), []download.Downloader{d}, path, 0600) - require.Nil(t, err) + n, ewc := download.SaveTo(nopLog(), []download.Downloader{d}, path, 0600) + require.Nil(t, ewc.Err) require.EqualValues(t, 65536, n) fi, err := os.Stat(path) @@ -52,10 +52,10 @@ func TestSave_truncates(t *testing.T) { defer os.RemoveAll(dir) path := filepath.Join(dir, "test-file") - _, err = download.SaveTo(nopLog(), []download.Downloader{download.NewURLDownload(srv.URL + "/bytes/65536")}, path, 0600) - require.Nil(t, err) - _, err = download.SaveTo(nopLog(), []download.Downloader{download.NewURLDownload(srv.URL + "/bytes/128")}, path, 0777) - require.Nil(t, err) + _, ewc := download.SaveTo(nopLog(), []download.Downloader{download.NewURLDownload(srv.URL + "/bytes/65536")}, path, 0600) + require.Nil(t, ewc.Err) + _, ewc = download.SaveTo(nopLog(), []download.Downloader{download.NewURLDownload(srv.URL + "/bytes/128")}, path, 0777) + require.Nil(t, ewc.Err) fi, err := os.Stat(path) require.Nil(t, err) @@ -74,8 +74,8 @@ func TestSave_largeFile(t *testing.T) { size := 1024 * 1024 * 128 // 128 mb path := filepath.Join(dir, "large-file") - n, err := download.SaveTo(nopLog(), []download.Downloader{download.NewURLDownload(srv.URL + "/bytes/" + fmt.Sprintf("%d", size))}, path, 0600) - require.Nil(t, err) + n, ewc := download.SaveTo(nopLog(), []download.Downloader{download.NewURLDownload(srv.URL + "/bytes/" + fmt.Sprintf("%d", size))}, path, 0600) + require.Nil(t, ewc.Err) require.EqualValues(t, size, n) fi, err := os.Stat(path) From c9531e89a1b477a84c2a9af02c3762424da9e975 Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Tue, 6 Jan 2026 15:20:07 -0800 Subject: [PATCH 5/7] Switches ErrorWithClarification to a pointer --- go.mod | 2 +- go.sum | 1 + main/cmds.go | 45 ++++++++++++++++++++---------------- main/exec.go | 18 +++++++++------ main/files.go | 32 ++++++++++++++++---------- main/handlersettings.go | 47 +++++++++++++++++++++++--------------- pkg/download/downloader.go | 32 +++++++++++++++++--------- pkg/download/retry.go | 20 ++++++++++------ pkg/download/save.go | 14 +++++++----- 9 files changed, 129 insertions(+), 82 deletions(-) diff --git a/go.mod b/go.mod index 9213f75..cdaf2a7 100644 --- a/go.mod +++ b/go.mod @@ -34,8 +34,8 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - golang.org/x/sys v0.5.0 // indirect golang.org/x/crypto v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 45b4d63..22e13b7 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/main/cmds.go b/main/cmds.go index 581a377..7fbbb0a 100644 --- a/main/cmds.go +++ b/main/cmds.go @@ -24,7 +24,7 @@ const ( maxScriptSize = 256 * 1024 ) -type cmdFunc func(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) (msg string, ewc vmextension.ErrorWithClarification) +type cmdFunc func(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) (msg string, ewc *vmextension.ErrorWithClarification) type preFunc func(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) error type cmd struct { @@ -57,14 +57,15 @@ var ( } ) -func noop(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { +func noop(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vmextension.ErrorWithClarification) { ctx.Log("event", "noop") - return "", vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return "", nil } -func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { +func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vmextension.ErrorWithClarification) { if err := os.MkdirAll(dataDir, 0755); err != nil { - return "", vmextension.NewErrorWithClarification(errorutil.SystemError, errors.Wrap(err, "failed to create data dir")) + ewc := vmextension.NewErrorWithClarification(errorutil.SystemError, errors.Wrap(err, "failed to create data dir")) + return "", &ewc } // If the file mrseq does not exists it is for two possible reasons. @@ -76,20 +77,21 @@ func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmexte ctx.Log("event", "created data dir", "path", dataDir) ctx.Log("event", "installed") - return "", vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return "", nil } -func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { +func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vmextension.ErrorWithClarification) { { // a new context scope with path ctx = ctx.With("path", dataDir) ctx.Log("event", "removing data dir", "path", dataDir) if err := os.RemoveAll(dataDir); err != nil { - return "", vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrap(err, "failed to delete data directory")) + ewc := vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrap(err, "failed to delete data directory")) + return "", &ewc } ctx.Log("event", "removed data dir") } ctx.Log("event", "uninstalled") - return "", vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return "", nil } func enablePre(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) error { @@ -112,10 +114,10 @@ func min(a, b int) int { return b } -func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, vmextension.ErrorWithClarification) { +func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vmextension.ErrorWithClarification) { // parse the extension handler settings (not available prior to 'enable') cfg, ewc := parseAndValidateSettings(ctx, h.HandlerEnvironment.ConfigFolder, seqNum) - if ewc.Err != nil { + if ewc != nil { ewc.Err = errors.Wrap(ewc.Err, "failed to get configuration") return "", ewc } @@ -179,12 +181,13 @@ func checkAndSaveSeqNum(ctx log.Logger, seq int, mrseqPath string) (shouldExit b // downloadFiles downloads the files specified in cfg into dir (creates if does // not exist) and takes storage credentials specified in cfg into account. -func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) vmextension.ErrorWithClarification { +func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) *vmextension.ErrorWithClarification { // - prepare the output directory for files and the command output // - create the directory if missing ctx.Log("event", "creating output directory", "path", dir) if err := os.MkdirAll(dir, 0700); err != nil { - return vmextension.NewErrorWithClarification(errorutil.FileDownload_unableToCreateDownloadDirectory, errors.Wrap(err, "failed to prepare output directory")) + ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_unableToCreateDownloadDirectory, errors.Wrap(err, "failed to prepare output directory")) + return &ewc } ctx.Log("event", "created output directory") @@ -206,15 +209,16 @@ func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) vmextensio ctx.Log("event", "download start") if ewc := downloadAndProcessURL(ctx, f, dir, &cfg); ewc.Err != nil { ctx.Log("event", "download failed", "error", ewc.Err) - return vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download file[%d]", i)) + ewc := vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download file[%d]", i)) + return &ewc } ctx.Log("event", "download complete", "output", dir) } - return vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return nil } // runCmd runs the command (extracted from cfg) in the given dir (assumed to exist). -func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (ewc vmextension.ErrorWithClarification) { +func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (ewc *vmextension.ErrorWithClarification) { ctx.Log("event", "executing command", "output", dir) var cmd string var scenario string @@ -233,13 +237,13 @@ func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (ewc vmextension.Er } else if cfg.publicSettings.Script != "" { ctx.Log("event", "executing public script", "output", dir) if cmd, scenarioInfo, err = writeTempScript(cfg.publicSettings.Script, dir, cfg.publicSettings.SkipDos2Unix); err != nil { - return vmextension.NewErrorWithClarification(errorutil.NoError, err) + return nil } scenario = fmt.Sprintf("public-script;%s", scenarioInfo) } else if cfg.protectedSettings.Script != "" { ctx.Log("event", "executing protected script", "output", dir) if cmd, scenarioInfo, err = writeTempScript(cfg.protectedSettings.Script, dir, cfg.publicSettings.SkipDos2Unix); err != nil { - return vmextension.NewErrorWithClarification(errorutil.NoError, err) + return nil } scenario = fmt.Sprintf("protected-script;%s", scenarioInfo) } @@ -253,10 +257,11 @@ func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (ewc vmextension.Er if ewc.Err != nil { ctx.Log("event", "failed to execute command", "error", err, "output", dir) - return vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrap(ewc.Err, "failed to execute command")) + ewc := vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrap(ewc.Err, "failed to execute command")) + return &ewc } ctx.Log("event", "executed command", "output", dir) - return vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return nil } func writeTempScript(script, dir string, skipDosToUnix bool) (string, string, error) { diff --git a/main/exec.go b/main/exec.go index 96ed557..049294b 100644 --- a/main/exec.go +++ b/main/exec.go @@ -18,7 +18,7 @@ import ( // // On error, an exit code may be returned if it is an exit code error. // Given stdout and stderr will be closed upon returning. -func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, vmextension.ErrorWithClarification) { +func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, *vmextension.ErrorWithClarification) { defer stdout.Close() defer stderr.Close() @@ -32,14 +32,16 @@ func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, vmextension. if ok { if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { code := status.ExitStatus() - return code, vmextension.NewErrorWithClarification(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command terminated with exit status=%d", code)) + ewc := vmextension.NewErrorWithClarification(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command terminated with exit status=%d", code)) + return code, &ewc } } if err == nil { - return 0, vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return 0, nil } - return 0, vmextension.NewErrorWithClarification(errorutil.CommandExecution_failedUnknownError, errors.Wrapf(err, "failed to execute command")) + ewc := vmextension.NewErrorWithClarification(errorutil.CommandExecution_failedUnknownError, errors.Wrapf(err, "failed to execute command")) + return 0, &ewc } // ExecCmdInDir executes the given command in given directory and saves output @@ -48,16 +50,18 @@ func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, vmextension. // // Ideally, we execute commands only once per sequence number in custom-script-extension, // and save their output under /var/lib/waagent//download//*. -func ExecCmdInDir(cmd, workdir string) vmextension.ErrorWithClarification { +func ExecCmdInDir(cmd, workdir string) *vmextension.ErrorWithClarification { outFn, errFn := logPaths(workdir) outF, err := os.OpenFile(outFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - return vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to open stdout file")) + ewc := vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to open stdout file")) + return &ewc } errF, err := os.OpenFile(errFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - return vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to open stderr file")) + ewc := vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to open stderr file")) + return &ewc } _, ewc := Exec(cmd, workdir, outF, errF) diff --git a/main/files.go b/main/files.go index a513642..3401535 100644 --- a/main/files.go +++ b/main/files.go @@ -18,21 +18,21 @@ import ( "github.com/pkg/errors" "github.com/Azure/custom-script-extension-linux/pkg/errorutil" - ) // downloadAndProcessURL downloads using the specified downloader and saves it to the // specified existing directory, which must be the path to the saved file. Then // it post-processes file based on heuristics. -func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handlerSettings) vmextension.ErrorWithClarification { +func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handlerSettings) *vmextension.ErrorWithClarification { fn, err := urlToFileName(url) if err != nil { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, err) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, err) + return &ewc } if !urlutil.IsValidUrl(url) { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, - fmt.Errorf("[REDACTED] is not a valid url")) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, fmt.Errorf("[REDACTED] is not a valid url")) + return &ewc } dl, ewc := getDownloaders(url, cfg.StorageAccountName, cfg.StorageAccountKey, cfg.ManagedIdentity) @@ -49,13 +49,19 @@ func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handl if cfg.SkipDos2Unix == false { err = postProcessFile(fp) } - return vmextension.NewErrorWithClarification(errorutil.SystemError, errors.Wrapf(err, "failed to post-process '%s'", fn)) + + if err != nil { + ewc := vmextension.NewErrorWithClarification(errorutil.SystemError, errors.Wrapf(err, "failed to post-process '%s'", fn)) + return &ewc + } + + return nil } // getDownloader returns a downloader for the given URL based on whether the // storage credentials are empty or not. func getDownloaders(fileURL string, storageAccountName, storageAccountKey string, managedIdentity *clientOrObjectId) ( - []download.Downloader, vmextension.ErrorWithClarification) { + []download.Downloader, *vmextension.ErrorWithClarification) { if storageAccountName == "" || storageAccountKey == "" { // storage account name and key cannot be specified with managed identity, handler settings validation won't allow that // handler settings validation will also not allow storageAccountName XOR storageAccountKey == 1 @@ -72,27 +78,29 @@ func getDownloaders(fileURL string, storageAccountName, storageAccountKey string case managedIdentity.ClientId == "" && managedIdentity.ObjectId != "": msiProvider = download.GetMsiProviderForStorageAccountsWithObjectId(fileURL, managedIdentity.ObjectId) default: - return nil, vmextension.NewErrorWithClarification(errorutil.CustomerInput_clientIdObjectIdBothSpecified, fmt.Errorf("unexpected combination of ClientId and ObjectId found")) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_clientIdObjectIdBothSpecified, fmt.Errorf("unexpected combination of ClientId and ObjectId found")) + return nil, &ewc } return []download.Downloader{ // try downloading without MSI token first, but attempt with MSI if the download fails download.NewURLDownload(fileURL), download.NewBlobWithMsiDownload(fileURL, msiProvider), - }, vmextension.NewErrorWithClarification(errorutil.NoError, nil) + }, nil } else { // do not use MSI downloader if the uri is not azure storage blob, or managedIdentity isn't specified - return []download.Downloader{download.NewURLDownload(fileURL)}, vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return []download.Downloader{download.NewURLDownload(fileURL)}, nil } } else { // if storage name account and key are specified, use that for all files // this preserves old behavior blob, err := blobutil.ParseBlobURL(fileURL) if err != nil { - return nil, vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, err) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, err) + return nil, &ewc } return []download.Downloader{download.NewBlobDownload( storageAccountName, storageAccountKey, blob)}, - vmextension.NewErrorWithClarification(errorutil.NoError, nil) + nil } } diff --git a/main/handlersettings.go b/main/handlersettings.go index 7899cb6..b5c3369 100644 --- a/main/handlersettings.go +++ b/main/handlersettings.go @@ -5,17 +5,17 @@ import ( "fmt" "path/filepath" - "github.com/go-kit/kit/log" - "github.com/pkg/errors" "github.com/Azure/azure-extension-platform/vmextension" "github.com/Azure/custom-script-extension-linux/pkg/errorutil" + "github.com/go-kit/kit/log" + "github.com/pkg/errors" ) var ( errStoragePartialCredentials = errors.New("both 'storageAccountName' and 'storageAccountKey' must be specified") errCmdTooMany = errors.New("'commandToExecute' was specified both in public and protected settings; it must be specified only once") errScriptTooMany = errors.New("'script' was specified both in public and protected settings; it must be specified only once") - errFileUrisTooMany = errors.New("'fileUris' were specified both in public and protected settings; it must be specified only once") + errFileUrisTooMany = errors.New("'fileUris' were specified both in public and protected settings; it must be specified only once") errCmdAndScript = errors.New("'commandToExecute' and 'script' were both specified, but only one is validate at a time") errCmdMissing = errors.New("'commandToExecute' is not specified") errUsingBothKeyAndMsi = errors.New("'storageAccountName' or 'storageAccountKey' must not be specified with 'managedServiceIdentity'") @@ -51,42 +51,50 @@ func (s *handlerSettings) fileUrls() []string { // validate makes logical validation on the handlerSettings which already passed // the schema validation. -func (h handlerSettings) validate() vmextension.ErrorWithClarification { +func (h handlerSettings) validate() *vmextension.ErrorWithClarification { if h.commandToExecute() == "" && h.script() == "" { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteAndScriptNotSpecified, errCmdMissing) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteAndScriptNotSpecified, errCmdMissing) + return &ewc } if h.publicSettings.CommandToExecute != "" && h.protectedSettings.CommandToExecute != "" { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteSpecifiedInTwoPlaces, errCmdTooMany) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteSpecifiedInTwoPlaces, errCmdTooMany) + return &ewc } if h.publicSettings.Script != "" && h.protectedSettings.Script != "" { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_scriptSpecifiedInTwoPlaces, errScriptTooMany) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_scriptSpecifiedInTwoPlaces, errScriptTooMany) + return &ewc } if (h.publicSettings.FileURLs != nil && len(h.publicSettings.FileURLs) > 0) && (h.protectedSettings.FileURLs != nil && len(h.protectedSettings.FileURLs) > 0) { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_fileUrisSpecifiedInTwoPlaces, errFileUrisTooMany) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_fileUrisSpecifiedInTwoPlaces, errFileUrisTooMany) + return &ewc } if h.commandToExecute() != "" && h.script() != "" { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteAndScriptBothSpecified, errCmdAndScript) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteAndScriptBothSpecified, errCmdAndScript) + return &ewc } if (h.protectedSettings.StorageAccountName != "") != (h.protectedSettings.StorageAccountKey != "") { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_incompleteStorageCreds, errStoragePartialCredentials) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_incompleteStorageCreds, errStoragePartialCredentials) + return &ewc } if (h.protectedSettings.StorageAccountKey != "" || h.protectedSettings.StorageAccountName != "") && h.protectedSettings.ManagedIdentity != nil { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_storageCredsAndMIBothSpecified, errUsingBothKeyAndMsi) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_storageCredsAndMIBothSpecified, errUsingBothKeyAndMsi) + return &ewc } if h.protectedSettings.ManagedIdentity != nil { if h.protectedSettings.ManagedIdentity.ClientId != "" && h.protectedSettings.ManagedIdentity.ObjectId != "" { - return vmextension.NewErrorWithClarification(errorutil.CustomerInput_clientIdObjectIdBothSpecified, errUsingBothClientIdAndObjectId) + ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_clientIdObjectIdBothSpecified, errUsingBothClientIdAndObjectId) + return &ewc } } - return vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return nil } // publicSettings is the type deserialized from public configuration section of @@ -120,23 +128,26 @@ func (self *clientOrObjectId) isEmpty() bool { // parseAndValidateSettings reads configuration from configFolder, decrypts it, // runs JSON-schema and logical validation on it and returns it back. -func parseAndValidateSettings(ctx *log.Context, configFolder string, seqNum int) (h handlerSettings, _ vmextension.ErrorWithClarification) { +func parseAndValidateSettings(ctx *log.Context, configFolder string, seqNum int) (h handlerSettings, _ *vmextension.ErrorWithClarification) { ctx.Log("event", "reading configuration") pubJSON, protJSON, err := readSettings(configFolder, seqNum) if err != nil { - return h, vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, err) + ewc := vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, err) + return h, &ewc } ctx.Log("event", "read configuration") ctx.Log("event", "validating json schema") if err := validateSettingsSchema(pubJSON, protJSON); err != nil { - return h, vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, errors.Wrap(err, "json validation error")) + ewc := vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, errors.Wrap(err, "json validation error")) + return h, &ewc } ctx.Log("event", "json schema valid") ctx.Log("event", "parsing configuration json") if err := UnmarshalHandlerSettings(pubJSON, protJSON, &h.publicSettings, &h.protectedSettings); err != nil { - return h, vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, errors.Wrap(err, "json parsing error")) + ewc := vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, errors.Wrap(err, "json parsing error")) + return h, &ewc } ctx.Log("event", "parsed configuration json") @@ -146,7 +157,7 @@ func parseAndValidateSettings(ctx *log.Context, configFolder string, seqNum int) return h, ewc } ctx.Log("event", "validated configuration") - return h, vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return h, nil } // readSettings uses specified configFolder (comes from HandlerEnvironment) to diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index 92228f9..7c8e718 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -5,8 +5,8 @@ import ( "io" "net" "net/http" - "time" "net/url" + "time" "github.com/Azure/azure-extension-platform/vmextension" "github.com/Azure/custom-script-extension-linux/pkg/errorutil" @@ -23,10 +23,10 @@ type Downloader interface { } const ( - MsiDownload404ErrorString = "please ensure that the blob location in the fileUri setting exists, and the specified Managed Identity has read permissions to the storage blob" - MsiDownload403ErrorString = "please ensure that the specified Managed Identity has read permissions to the storage blob" + MsiDownload404ErrorString = "please ensure that the blob location in the fileUri setting exists, and the specified Managed Identity has read permissions to the storage blob" + MsiDownload403ErrorString = "please ensure that the specified Managed Identity has read permissions to the storage blob" MsiDownloadGenericErrorString = "unable to download the MSI. This may be due to firewall rules or a networking error" - MsiDownload500ErrorString = "the IMDS service returned a00 upon requesting the MSI" + MsiDownload500ErrorString = "the IMDS service returned a00 upon requesting the MSI" ) var ( @@ -49,10 +49,11 @@ var ( // Download retrieves a response body and checks the response status code to see // if it is 200 OK and then returns the response body. It issues a new request // every time called. It is caller's responsibility to close the response body. -func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.ErrorWithClarification) { +func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, *vmextension.ErrorWithClarification) { req, err := d.GetRequest() if err != nil { - return -1, nil, vmextension.NewErrorWithClarification(errorutil.FileDownload_genericError, errors.Wrapf(err, "failed to create http request")) + ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_genericError, errors.Wrapf(err, "failed to create http request")) + return -1, nil, &ewc } requestID := req.Header.Get(xMsClientRequestIdHeaderName) if len(requestID) > 0 { @@ -60,17 +61,20 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.E } resp, err := httpClient.Do(req) if err != nil { - if ((err.(*url.Error)).Timeout()) { + if (err.(*url.Error)).Timeout() { err = urlutil.RemoveUrlFromErr(err) - return -1, nil, vmextension.NewErrorWithClarification(errorutil.FileDownload_exceededTimeout, errors.Wrapf(err, "http request timed out")) + ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_exceededTimeout, errors.Wrapf(err, "http request timed out")) + return -1, nil, &ewc } err = urlutil.RemoveUrlFromErr(err) - return -1, nil, vmextension.NewErrorWithClarification(errorutil.FileDownload_unknownError, errors.Wrapf(err, "http request failed")) + ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_unknownError, errors.Wrapf(err, "http request failed")) + return -1, nil, &ewc } if resp.StatusCode == http.StatusOK { // We're setting the errorCode to MaxInt because we're only checking whether the internal error is nil - return resp.StatusCode, resp.Body, vmextension.NewErrorWithClarification(errorutil.NoError, nil) + ewc := vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return resp.StatusCode, resp.Body, &ewc } errString := "" @@ -128,5 +132,11 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, vmextension.E if len(requestId) > 0 { errString += fmt.Sprintf(" (Service request ID: %s)", requestId) } - return resp.StatusCode, nil, vmextension.NewErrorWithClarification(errClarificationCode, fmt.Errorf(errString)) + + if errClarificationCode == 0 { + return resp.StatusCode, nil, nil + } + + ewc := vmextension.NewErrorWithClarification(errClarificationCode, fmt.Errorf(errString)) + return resp.StatusCode, nil, &ewc } diff --git a/pkg/download/retry.go b/pkg/download/retry.go index fd8bab3..d047810 100644 --- a/pkg/download/retry.go +++ b/pkg/download/retry.go @@ -8,11 +8,10 @@ import ( "os" "time" - "github.com/go-kit/kit/log" "github.com/Azure/azure-extension-platform/vmextension" + "github.com/go-kit/kit/log" - - errorutil "github.com/Azure/custom-script-extension-linux/pkg/errorutil" + errorutil "github.com/Azure/custom-script-extension-linux/pkg/errorutil" ) // SleepFunc pauses the execution for at least duration d. @@ -37,9 +36,9 @@ const ( // closed on failures). If the retries do not succeed, the last error is returned. // // It sleeps in exponentially increasing durations between retries. -func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf SleepFunc) (int64, vmextension.ErrorWithClarification) { +func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf SleepFunc) (int64, *vmextension.ErrorWithClarification) { var lastErr error - var lastErrCode int + var lastErrCode int for _, d := range downloaders { for n := 0; n < expRetryN; n++ { ctx := ctx.With("retry", n) @@ -59,7 +58,8 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee out.Close() end := time.Since(start) ctx.Log("info", fmt.Sprintf("file download sucessful: downloaded and saved %d bytes in %d milliseconds", nBytes, end.Milliseconds())) - return nBytes, vmextension.NewErrorWithClarification(lastErrCode, lastErr) + ewc := vmextension.NewErrorWithClarification(lastErrCode, lastErr) + return nBytes, &ewc } else { // we failed to download the response body and write it to file // because either connection was closed prematurely or file write operation failed @@ -102,7 +102,13 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee } } } - return 0, vmextension.NewErrorWithClarification(lastErrCode, lastErr) + + if lastErr == nil { + return 0, nil + } + + ewc := vmextension.NewErrorWithClarification(lastErrCode, lastErr) + return 0, &ewc } func isTransientHttpStatusCode(statusCode int) bool { diff --git a/pkg/download/save.go b/pkg/download/save.go index 9a3dd89..b0de719 100644 --- a/pkg/download/save.go +++ b/pkg/download/save.go @@ -4,28 +4,30 @@ import ( "os" "github.com/Azure/azure-extension-platform/vmextension" + "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/go-kit/kit/log" "github.com/pkg/errors" - "github.com/Azure/custom-script-extension-linux/pkg/errorutil" ) // SaveTo uses given downloader to fetch the resource with retries and saves the // given file. Directory of dst is not created by this function. If a file at // dst exists, it will be truncated. If a new file is created, mode is used to // set the permission bits. Written number of bytes are returned on success. -func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, vmextension.ErrorWithClarification) { +func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, *vmextension.ErrorWithClarification) { f, err := os.OpenFile(dst, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, mode) if err != nil { - return 0, vmextension.NewErrorWithClarification(errorutil.FileDownload_unknownError, errors.Wrap(err, "failed to open file for writing")) + ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_unknownError, errors.Wrap(err, "failed to open file for writing")) + return 0, &ewc } defer f.Close() n, ewc := WithRetries(ctx, f, d, ActualSleep) - if ewc.Err != nil { - return n, vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download response and write to file: %s", dst)) + if ewc != nil { + ewc := vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download response and write to file: %s", dst)) + return n, &ewc } - return n, vmextension.NewErrorWithClarification(errorutil.NoError, nil) + return n, nil } From 53b6716f754d390221bee6a8b7486c43a5cdf323 Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Tue, 6 Jan 2026 16:24:50 -0800 Subject: [PATCH 6/7] Fixes unit tests --- main/cmds.go | 14 +++++++------- main/cmds_test.go | 2 +- main/exec.go | 4 ++-- main/exec_test.go | 10 +++++----- main/files.go | 4 ++-- main/files_test.go | 14 +++++++------- main/handlersettings_test.go | 23 +++++++++++++---------- main/status.go | 9 ++++----- main/status_test.go | 2 +- pkg/download/downloader.go | 6 ++---- pkg/download/downloader_test.go | 13 +++++-------- pkg/download/retry.go | 7 +++---- pkg/download/retry_test.go | 10 +++++----- pkg/errorutil/errorclarificationcodes.go | 4 ++++ 14 files changed, 61 insertions(+), 61 deletions(-) diff --git a/main/cmds.go b/main/cmds.go index 7fbbb0a..7732cc3 100644 --- a/main/cmds.go +++ b/main/cmds.go @@ -12,7 +12,7 @@ import ( "strconv" "time" - utils "github.com/Azure/azure-extension-platform/pkg/utils" + "github.com/Azure/azure-extension-platform/pkg/utils" vmextension "github.com/Azure/azure-extension-platform/vmextension" "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/Azure/custom-script-extension-linux/pkg/seqnum" @@ -85,7 +85,7 @@ func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vme ctx = ctx.With("path", dataDir) ctx.Log("event", "removing data dir", "path", dataDir) if err := os.RemoveAll(dataDir); err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrap(err, "failed to delete data directory")) + ewc := vmextension.NewErrorWithClarification(errorutil.Os_FailedToDeleteDataDir, errors.Wrap(err, "failed to delete data directory")) return "", &ewc } ctx.Log("event", "removed data dir") @@ -123,7 +123,7 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vmexte } dir := filepath.Join(dataDir, downloadDir, fmt.Sprintf("%d", seqNum)) - if ewc := downloadFiles(ctx, dir, cfg); ewc.Err != nil { + if ewc := downloadFiles(ctx, dir, cfg); ewc != nil { ewc.Err = errors.Wrap(ewc.Err, "processing file downloads failed") return "", ewc } @@ -142,7 +142,7 @@ func enable(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vmexte ctx.Log("message", "error tailing stderr logs", "error", err) } - isSuccess := runErr.Err == nil + isSuccess := runErr == nil telemetry("Output", "-- stdout/stderr omitted from telemetry pipeline --", isSuccess, 0) if isSuccess { @@ -207,7 +207,7 @@ func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) *vmextensi for i, f := range cfg.fileUrls() { ctx := ctx.With("file", i) ctx.Log("event", "download start") - if ewc := downloadAndProcessURL(ctx, f, dir, &cfg); ewc.Err != nil { + if ewc := downloadAndProcessURL(ctx, f, dir, &cfg); ewc != nil { ctx.Log("event", "download failed", "error", ewc.Err) ewc := vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download file[%d]", i)) return &ewc @@ -251,11 +251,11 @@ func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (ewc *vmextension.E begin := time.Now() ewc = ExecCmdInDir(cmd, dir) elapsed := time.Now().Sub(begin) - isSuccess := ewc.Err == nil + isSuccess := ewc == nil telemetry("scenario", scenario, isSuccess, elapsed) - if ewc.Err != nil { + if ewc != nil { ctx.Log("event", "failed to execute command", "error", err, "output", dir) ewc := vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrap(ewc.Err, "failed to execute command")) return &ewc diff --git a/main/cmds_test.go b/main/cmds_test.go index 14c7920..8437e6f 100644 --- a/main/cmds_test.go +++ b/main/cmds_test.go @@ -125,7 +125,7 @@ func Test_downloadFiles(t *testing.T) { srv.URL + "/bytes/1000", }}, }) - require.Nil(t, ewc.Err) + require.Nil(t, ewc) // check the files f := []string{"10", "100", "1000"} diff --git a/main/exec.go b/main/exec.go index 049294b..07017ac 100644 --- a/main/exec.go +++ b/main/exec.go @@ -55,12 +55,12 @@ func ExecCmdInDir(cmd, workdir string) *vmextension.ErrorWithClarification { outF, err := os.OpenFile(outFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to open stdout file")) + ewc := vmextension.NewErrorWithClarification(errorutil.Os_FailedToOpenStdOut, errors.Wrapf(err, "failed to open stdout file")) return &ewc } errF, err := os.OpenFile(errFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.NoError, errors.Wrapf(err, "failed to open stderr file")) + ewc := vmextension.NewErrorWithClarification(errorutil.Os_FailedToOpenStdErr, errors.Wrapf(err, "failed to open stderr file")) return &ewc } diff --git a/main/exec_test.go b/main/exec_test.go index cb98e5b..5eabc88 100644 --- a/main/exec_test.go +++ b/main/exec_test.go @@ -25,7 +25,7 @@ func TestExec_success_redirectsStdStreams_closesFds(t *testing.T) { require.False(t, e.closed, "stderr open") _, err := Exec("/bin/echo 'I am stdout!'>&1; /bin/echo 'I am stderr!'>&2", "/", o, e) - require.Nil(t, err.Err, "err: %v -- stderr: %s", err.Err, e.b.Bytes()) + require.Nil(t, err, "err: %v -- stderr: %s", err.Err, e.b.Bytes()) require.Equal(t, "I am stdout!\n", string(o.b.Bytes())) require.Equal(t, "I am stderr!\n", string(e.b.Bytes())) require.True(t, o.closed, "stdout closed") @@ -77,7 +77,7 @@ func TestExecCmdInDir(t *testing.T) { defer os.RemoveAll(dir) ewc := ExecCmdInDir("/bin/echo 'Hello world'", dir) - require.Nil(t, ewc.Err) + require.Nil(t, ewc) require.True(t, fileExists(t, filepath.Join(dir, "stdout")), "stdout file should be created") require.True(t, fileExists(t, filepath.Join(dir, "stderr")), "stderr file should be created") @@ -92,7 +92,7 @@ func TestExecCmdInDir(t *testing.T) { func TestExecCmdInDir_cantOpenError(t *testing.T) { err := ExecCmdInDir("/bin/echo 'Hello world'", "/non-existing-dir") - require.Equal(t, err.ErrorCode, errorutil.NoError) + require.Equal(t, err.ErrorCode, errorutil.Os_FailedToOpenStdErr) require.NotNil(t, err.Err) require.Contains(t, err.Err.Error(), "failed to open stdout file") } @@ -102,8 +102,8 @@ func TestExecCmdInDir_truncates(t *testing.T) { require.Nil(t, err) defer os.RemoveAll(dir) - require.Nil(t, ExecCmdInDir("/bin/echo '1:out'; /bin/echo '1:err'>&2", dir).Err) - require.Nil(t, ExecCmdInDir("/bin/echo '2:out'; /bin/echo '2:err'>&2", dir).Err) + require.Nil(t, ExecCmdInDir("/bin/echo '1:out'; /bin/echo '1:err'>&2", dir)) + require.Nil(t, ExecCmdInDir("/bin/echo '2:out'; /bin/echo '2:err'>&2", dir)) b, err := ioutil.ReadFile(filepath.Join(dir, "stdout")) require.Nil(t, err) diff --git a/main/files.go b/main/files.go index 3401535..c561f66 100644 --- a/main/files.go +++ b/main/files.go @@ -36,13 +36,13 @@ func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handl } dl, ewc := getDownloaders(url, cfg.StorageAccountName, cfg.StorageAccountKey, cfg.ManagedIdentity) - if ewc.Err != nil { + if ewc != nil { return ewc } fp := filepath.Join(downloadDir, fn) const mode = 0500 // we assume users download scripts to execute - if _, ewc := download.SaveTo(ctx, dl, fp, mode); ewc.Err != nil { + if _, ewc := download.SaveTo(ctx, dl, fp, mode); ewc != nil { return ewc } diff --git a/main/files_test.go b/main/files_test.go index 0abc412..dd1bb05 100644 --- a/main/files_test.go +++ b/main/files_test.go @@ -16,11 +16,11 @@ import ( func Test_getDownloader_azureBlob(t *testing.T) { // error condition _, err := getDownloaders("http://acct.blob.core.windows.net/", "acct", "key", nil) - require.NotNil(t, err.Err) + require.NotNil(t, err) // valid input d, err := getDownloaders("http://acct.blob.core.windows.net/container/blob", "acct", "key", nil) - require.Nil(t, err.Err) + require.Nil(t, err) require.NotNil(t, d) require.Equal(t, 1, len(d)) require.Equal(t, "download.blobDownload", fmt.Sprintf("%T", d[0]), "got wrong type") @@ -28,14 +28,14 @@ func Test_getDownloader_azureBlob(t *testing.T) { func Test_getDownloader_externalUrl(t *testing.T) { d, err := getDownloaders("http://acct.blob.core.windows.net/", "", "", nil) - require.Nil(t, err.Err) + require.Nil(t, err) require.NotNil(t, d) require.NotEmpty(t, d) require.Equal(t, 1, len(d)) require.Equal(t, "download.urlDownload", fmt.Sprintf("%T", d[0]), "got wrong type") d, err = getDownloaders("http://acct.blob.core.windows.net/", "", "", &clientOrObjectId{"", "dummyclientid"}) - require.Nil(t, err.Err) + require.Nil(t, err) require.NotNil(t, d) require.NotEmpty(t, d) require.Equal(t, 2, len(d)) @@ -43,13 +43,13 @@ func Test_getDownloader_externalUrl(t *testing.T) { require.Equal(t, "*download.blobWithMsiToken", fmt.Sprintf("%T", d[1]), "got wrong type") d, err = getDownloaders("http://acct.blob.core.windows.net/", "foo", "", nil) - require.Nil(t, err.Err) + require.Nil(t, err) require.NotNil(t, d) require.Equal(t, 1, len(d)) require.Equal(t, "download.urlDownload", fmt.Sprintf("%T", d[0]), "got wrong type") d, err = getDownloaders("http://acct.blob.core.windows.net/", "", "bar", nil) - require.Nil(t, err.Err) + require.Nil(t, err) require.NotNil(t, d) require.Equal(t, 1, len(d)) require.Equal(t, "download.urlDownload", fmt.Sprintf("%T", d[0]), "got wrong type") @@ -125,7 +125,7 @@ func Test_downloadAndProcessURL(t *testing.T) { cfg := handlerSettings{publicSettings{}, protectedSettings{StorageAccountName: "", StorageAccountKey: ""}} ewc := downloadAndProcessURL(log.NewContext(log.NewNopLogger()), srv.URL+"/bytes/256", tmpDir, &cfg) - require.Nil(t, ewc.Err) + require.Nil(t, ewc) fp := filepath.Join(tmpDir, "256") fi, err := os.Stat(fp) diff --git a/main/handlersettings_test.go b/main/handlersettings_test.go index 7097aa0..53f149a 100644 --- a/main/handlersettings_test.go +++ b/main/handlersettings_test.go @@ -3,8 +3,9 @@ package main import ( "encoding/json" "testing" + + "github.com/stretchr/testify/require" ) -import "github.com/stretchr/testify/require" func Test_handlerSettingsValidate(t *testing.T) { // commandToExecute not specified @@ -90,20 +91,22 @@ func Test_skipDos2UnixDefaultsToFalse(t *testing.T) { } func Test_managedIdentityVerification(t *testing.T) { - require.NoError(t, handlerSettings{publicSettings{}, protectedSettings{ + err := handlerSettings{publicSettings{}, protectedSettings{ CommandToExecute: "echo hi", FileURLs: []string{"file1", "file2"}, ManagedIdentity: &clientOrObjectId{ ClientId: "31b403aa-c364-4240-a7ff-d85fb6cd7232", }, - }}.validate().Err, "validation failed for settings with MSI") + }}.validate() + require.Nil(t, err, "validation failed for settings with MSI") - require.NoError(t, handlerSettings{publicSettings{}, protectedSettings{ + err = handlerSettings{publicSettings{}, protectedSettings{ CommandToExecute: "echo hi", ManagedIdentity: &clientOrObjectId{ ObjectId: "31b403aa-c364-4240-a7ff-d85fb6cd7232", }, - }}.validate().Err, "validation failed for settings with MSI") + }}.validate() + require.Nil(t, err, "validation failed for settings with MSI") require.Equal(t, errUsingBothKeyAndMsi, handlerSettings{publicSettings{}, @@ -148,7 +151,7 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.NoError(t, err, "error while deserializing json") require.Nil(t, protSettings.ManagedIdentity, "ProtectedSettings.ManagedIdentity was expected to be nil") h := handlerSettings{publicSettings{}, *protSettings} - require.NoError(t, h.validate().Err, "settings should be valid") + require.Nil(t, h.validate(), "settings should be valid") testString = `{"commandToExecute" : "echo hello", "fileUris":["https://a.com/file.txt"], "managedIdentity": { }}` require.NoError(t, validateProtectedSettings(testString), "protected settings should be valid") @@ -159,7 +162,7 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.Equal(t, protSettings.ManagedIdentity.ClientId, "") require.Equal(t, protSettings.ManagedIdentity.ObjectId, "") h = handlerSettings{publicSettings{}, *protSettings} - require.NoError(t, h.validate().Err, "settings should be valid") + require.Nil(t, h.validate(), "settings should be valid") testString = `{"commandToExecute" : "echo hello", "fileUris":["https://a.com/file.txt", "https://b.com/file2.txt"], "managedIdentity": { "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232"}}` require.NoError(t, validateProtectedSettings(testString), "protected settings should be valid") @@ -170,7 +173,7 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.Equal(t, protSettings.ManagedIdentity.ClientId, "31b403aa-c364-4240-a7ff-d85fb6cd7232") require.Equal(t, protSettings.ManagedIdentity.ObjectId, "") h = handlerSettings{publicSettings{}, *protSettings} - require.NoError(t, h.validate().Err, "settings should be valid") + require.Nil(t, h.validate(), "settings should be valid") testString = `{"commandToExecute" : "echo hello", "fileUris":["https://a.com/file.txt"], "managedIdentity": { "objectId": "31b403aa-c364-4240-a7ff-d85fb6cd7232"}}` require.NoError(t, validateProtectedSettings(testString), "protected settings should be valid") @@ -181,7 +184,7 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.Equal(t, protSettings.ManagedIdentity.ObjectId, "31b403aa-c364-4240-a7ff-d85fb6cd7232") require.Equal(t, protSettings.ManagedIdentity.ClientId, "") h = handlerSettings{publicSettings{}, *protSettings} - require.NoError(t, h.validate().Err, "settings should be valid") + require.Nil(t, h.validate(), "settings should be valid") testString = `{"commandToExecute" : "echo hello", "fileUris":["https://a.com/file.txt", "https://b.com/file2.txt"], "managedIdentity": { "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232", "objectId": "41b403aa-c364-4240-a7ff-d85fb6cd7232"}}` require.NoError(t, validateProtectedSettings(testString), "protected settings should be valid") @@ -192,5 +195,5 @@ func Test_toJSONUmarshallForManagedIdentity(t *testing.T) { require.Equal(t, protSettings.ManagedIdentity.ClientId, "31b403aa-c364-4240-a7ff-d85fb6cd7232") require.Equal(t, protSettings.ManagedIdentity.ObjectId, "41b403aa-c364-4240-a7ff-d85fb6cd7232") h = handlerSettings{publicSettings{}, *protSettings} - require.Error(t, h.validate().Err, "settings should be invalid") + require.Error(t, h.validate(), "settings should be invalid") } diff --git a/main/status.go b/main/status.go index 0395e08..7f9fa03 100644 --- a/main/status.go +++ b/main/status.go @@ -10,7 +10,6 @@ import ( status "github.com/Azure/azure-extension-platform/pkg/status" vmextension "github.com/Azure/azure-extension-platform/vmextension" - "github.com/Azure/custom-script-extension-linux/pkg/errorutil" "github.com/go-kit/kit/log" "github.com/pkg/errors" ) @@ -105,18 +104,18 @@ func reportStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, return nil } -// reportErrorStatus saves the error(s) that occurred during the operation -// to the status file for the extension handler with clarification messages and codes, +// reportErrorStatus saves the error(s) that occurred during the operation +// to the status file for the extension handler with clarification messages and codes, // if the given cmd requires reporting status. // // If an error occurs reporting the status, it will be logged and returned. -func reportErrorStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, c cmd, ewc vmextension.ErrorWithClarification) error { +func reportErrorStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, c cmd, ewc *vmextension.ErrorWithClarification) error { if !c.shouldReportStatus { ctx.Log("status", "not reported for operation (by design)") return nil } var err error - if ewc.ErrorCode == errorutil.NoError { + if ewc == nil { s := NewStatus(t, c.name, statusMsg(c, t, ewc.Err.Error())) err = s.Save(hEnv.HandlerEnvironment.StatusFolder, seqNum) } else { diff --git a/main/status_test.go b/main/status_test.go index c13c921..8e7e47b 100644 --- a/main/status_test.go +++ b/main/status_test.go @@ -58,7 +58,7 @@ func Test_reportErrorStatus_fileExists(t *testing.T) { fakeEnv.HandlerEnvironment.StatusFolder = tmpDir ewc := vmextension.NewErrorWithClarification(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command failed with exit code = 1")) - require.Nil(t, reportErrorStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusError, cmdEnable, ewc)) + require.Nil(t, reportErrorStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusError, cmdEnable, &ewc)) path := filepath.Join(tmpDir, "1.status") b, err := ioutil.ReadFile(path) diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index 7c8e718..96ac406 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -72,9 +72,7 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, *vmextension. } if resp.StatusCode == http.StatusOK { - // We're setting the errorCode to MaxInt because we're only checking whether the internal error is nil - ewc := vmextension.NewErrorWithClarification(errorutil.NoError, nil) - return resp.StatusCode, resp.Body, &ewc + return resp.StatusCode, resp.Body, nil } errString := "" @@ -137,6 +135,6 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, *vmextension. return resp.StatusCode, nil, nil } - ewc := vmextension.NewErrorWithClarification(errClarificationCode, fmt.Errorf(errString)) + ewc := vmextension.NewErrorWithClarification(errClarificationCode, errors.New(errString)) return resp.StatusCode, nil, &ewc } diff --git a/pkg/download/downloader_test.go b/pkg/download/downloader_test.go index f784000..78ceda3 100644 --- a/pkg/download/downloader_test.go +++ b/pkg/download/downloader_test.go @@ -30,7 +30,7 @@ func (b *badDownloader) GetRequest() (*http.Request, error) { } func TestDownload_wrapsGetRequestError(t *testing.T) { - _, _, ewc:= download.Download(testctx, new(badDownloader)) + _, _, ewc := download.Download(testctx, new(badDownloader)) require.Equal(t, ewc.ErrorCode, errorutil.FileDownload_genericError) require.NotNil(t, ewc.Err) require.EqualError(t, ewc.Err, "failed to create http request: expected error") @@ -56,7 +56,7 @@ func TestDownload_wrapsCommonErrorCodes(t *testing.T) { http.StatusBadRequest, http.StatusUnauthorized, } { - respCode, _, ewc:= download.Download(testctx, download.NewURLDownload(fmt.Sprintf("%s/status/%d", srv.URL, code))) + respCode, _, ewc := download.Download(testctx, download.NewURLDownload(fmt.Sprintf("%s/status/%d", srv.URL, code))) require.NotNil(t, ewc.Err, "not failed for code:%d", code) require.Equal(t, code, respCode) switch respCode { @@ -84,8 +84,7 @@ func TestDownload_statusOKSucceeds(t *testing.T) { defer srv.Close() _, body, ewc := download.Download(testctx, download.NewURLDownload(srv.URL+"/status/200")) - require.Equal(t, ewc.ErrorCode, errorutil.NoError) - require.Nil(t, ewc.Err) + require.Nil(t, ewc) defer body.Close() require.NotNil(t, body) } @@ -133,8 +132,7 @@ func TestDownload_retrievesBody(t *testing.T) { defer srv.Close() _, body, ewc := download.Download(testctx, download.NewURLDownload(srv.URL+"/bytes/65536")) - require.Equal(t, ewc.ErrorCode, errorutil.NoError) - require.Nil(t, ewc.Err) + require.Nil(t, ewc) defer body.Close() b, err := io.ReadAll(body) require.Nil(t, err) @@ -146,7 +144,6 @@ func TestDownload_bodyClosesWithoutError(t *testing.T) { defer srv.Close() _, body, ewc := download.Download(testctx, download.NewURLDownload(srv.URL+"/get")) - require.Equal(t, ewc.ErrorCode, errorutil.NoError) - require.Nil(t, ewc.Err) + require.Nil(t, ewc) require.Nil(t, body.Close(), "body should close fine") } diff --git a/pkg/download/retry.go b/pkg/download/retry.go index d047810..5c11410 100644 --- a/pkg/download/retry.go +++ b/pkg/download/retry.go @@ -45,10 +45,10 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee // reset the last error before each retry lastErr = nil - lastErrCode = errorutil.NoError + lastErrCode = 0 start := time.Now() status, out, ewc := Download(ctx, d) - if ewc.Err == nil { + if ewc == nil { // server returned status code 200 OK // we have a response body, copy it to the file nBytes, innerErr := io.CopyBuffer(f, out, make([]byte, writeBufSize)) @@ -58,8 +58,7 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee out.Close() end := time.Since(start) ctx.Log("info", fmt.Sprintf("file download sucessful: downloaded and saved %d bytes in %d milliseconds", nBytes, end.Milliseconds())) - ewc := vmextension.NewErrorWithClarification(lastErrCode, lastErr) - return nBytes, &ewc + return nBytes, nil } else { // we failed to download the response body and write it to file // because either connection was closed prematurely or file write operation failed diff --git a/pkg/download/retry_test.go b/pkg/download/retry_test.go index 7bfe869..267ca1d 100644 --- a/pkg/download/retry_test.go +++ b/pkg/download/retry_test.go @@ -48,7 +48,7 @@ func TestWithRetries_noRetries(t *testing.T) { sr := new(sleepRecorder) n, err := download.WithRetries(nopLog(), file, []download.Downloader{d}, sr.Sleep) - require.Nil(t, err.Err, "should not fail") + require.Nil(t, err, "should not fail") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, []time.Duration(nil), []time.Duration(*sr), "sleep should not be called") } @@ -100,7 +100,7 @@ func TestWithRetries_healingServer(t *testing.T) { sr := new(sleepRecorder) n, err := download.WithRetries(nopLog(), file, []download.Downloader{d}, sr.Sleep) - require.Nil(t, err.Err, "should eventually succeed") + require.Nil(t, err, "should eventually succeed") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, sleepSchedule[:3], []time.Duration(*sr)) } @@ -118,7 +118,7 @@ func TestRetriesWith_SwitchDownloaderOn404(t *testing.T) { d200 := mockDownloader{0, hSvr.URL} n, err := download.WithRetries(nopLog(), file, []download.Downloader{&d404, &d200}, func(d time.Duration) { return }) - require.Nil(t, err.Err, "should eventually succeed") + require.Nil(t, err, "should eventually succeed") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, d404.timesCalled, 1) require.Equal(t, d200.timesCalled, 4) @@ -140,7 +140,7 @@ func TestRetriesWith_SwitchDownloaderThenFailWithCorretErrorMessage(t *testing.T msiDownloader403 := download.NewBlobWithMsiDownload(svr.URL+"/status/403", mockMsiProvider) n, err := download.WithRetries(nopLog(), file, []download.Downloader{&d404, msiDownloader403}, func(d time.Duration) { return }) - require.NotNil(t, err.Err, "download with retries should fail") + require.NotNil(t, err, "download with retries should fail") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, d404.timesCalled, 1) require.True(t, strings.Contains(err.Error(), download.MsiDownload403ErrorString), "error string doesn't contain the correct message") @@ -149,7 +149,7 @@ func TestRetriesWith_SwitchDownloaderThenFailWithCorretErrorMessage(t *testing.T msiDownloader404 := download.NewBlobWithMsiDownload(svr.URL+"/status/404", mockMsiProvider) n, err = download.WithRetries(nopLog(), file, []download.Downloader{&d404, msiDownloader404}, func(d time.Duration) { return }) - require.NotNil(t, err.Err, "download with retries should fail") + require.NotNil(t, err, "download with retries should fail") require.EqualValues(t, 0, n, "downloaded number of bytes should be zero") require.Equal(t, d404.timesCalled, 1) require.True(t, strings.Contains(err.Error(), download.MsiDownload404ErrorString), "error string doesn't contain the correct message") diff --git a/pkg/errorutil/errorclarificationcodes.go b/pkg/errorutil/errorclarificationcodes.go index 11341d3..e823017 100644 --- a/pkg/errorutil/errorclarificationcodes.go +++ b/pkg/errorutil/errorclarificationcodes.go @@ -14,6 +14,10 @@ const ( Internal_badConfig int = -21 Internal_couldNotFindCertificate int = -20 + Os_FailedToDeleteDataDir int = -50 + Os_FailedToOpenStdOut int = -51 + Os_FailedToOpenStdErr int = -52 + Storage_internalServerError int = -1 SystemError int = 0 // CRP interprets anything > 0 as user errors From f97313eb2876dc22ac0f645d985444ed26163dde Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Wed, 7 Jan 2026 13:21:02 -0800 Subject: [PATCH 7/7] Switches to pointer method --- go.mod | 2 +- go.sum | 2 ++ main/cmds.go | 15 +++++---------- main/exec.go | 12 ++++-------- main/files.go | 15 +++++---------- main/handlersettings.go | 33 +++++++++++---------------------- main/status_test.go | 4 ++-- pkg/download/downloader.go | 12 ++++-------- pkg/download/retry.go | 3 +-- pkg/download/save.go | 6 ++---- 10 files changed, 37 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index cdaf2a7..6609c3e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24 require ( github.com/Azure/azure-extension-foundation v0.0.0-20250620154556-caff9e3c3c5c - github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f + github.com/Azure/azure-extension-platform v0.0.0-20260107210613-2a62cc200c34 github.com/Azure/azure-sdk-for-go v63.2.0+incompatible github.com/ahmetalpbalkan/go-httpbin v0.0.0-20160706084156-8817b883dae1 github.com/go-kit/kit v0.12.0 diff --git a/go.sum b/go.sum index 22e13b7..82c287b 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/Azure/azure-extension-foundation v0.0.0-20250620154556-caff9e3c3c5c h github.com/Azure/azure-extension-foundation v0.0.0-20250620154556-caff9e3c3c5c/go.mod h1:sNC6lMTUkXwjrQ+nttr6GXhDfvSGT7t3UDq30BEYzu8= github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f h1:ddsUz/suc9txCMz/xWOslqNMvzhbWFMTflUrbcMNoSw= github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY= +github.com/Azure/azure-extension-platform v0.0.0-20260107210613-2a62cc200c34 h1:7bEC4DJC4w0gx7SBy7M7Q2qi6ckmHcnnlFJzo+X/gi4= +github.com/Azure/azure-extension-platform v0.0.0-20260107210613-2a62cc200c34/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY= github.com/Azure/azure-sdk-for-go v63.2.0+incompatible h1:OIqkK/zTGqVUuzpEvY0B1YSYDRAFC/j+y0w2GovCggI= github.com/Azure/azure-sdk-for-go v63.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= diff --git a/main/cmds.go b/main/cmds.go index 7732cc3..5bd856f 100644 --- a/main/cmds.go +++ b/main/cmds.go @@ -64,8 +64,7 @@ func noop(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vmextens func install(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vmextension.ErrorWithClarification) { if err := os.MkdirAll(dataDir, 0755); err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.SystemError, errors.Wrap(err, "failed to create data dir")) - return "", &ewc + return "", vmextension.NewErrorWithClarificationPtr(errorutil.SystemError, errors.Wrap(err, "failed to create data dir")) } // If the file mrseq does not exists it is for two possible reasons. @@ -85,8 +84,7 @@ func uninstall(ctx *log.Context, h HandlerEnvironment, seqNum int) (string, *vme ctx = ctx.With("path", dataDir) ctx.Log("event", "removing data dir", "path", dataDir) if err := os.RemoveAll(dataDir); err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.Os_FailedToDeleteDataDir, errors.Wrap(err, "failed to delete data directory")) - return "", &ewc + return "", vmextension.NewErrorWithClarificationPtr(errorutil.Os_FailedToDeleteDataDir, errors.Wrap(err, "failed to delete data directory")) } ctx.Log("event", "removed data dir") } @@ -186,8 +184,7 @@ func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) *vmextensi // - create the directory if missing ctx.Log("event", "creating output directory", "path", dir) if err := os.MkdirAll(dir, 0700); err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_unableToCreateDownloadDirectory, errors.Wrap(err, "failed to prepare output directory")) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.FileDownload_unableToCreateDownloadDirectory, errors.Wrap(err, "failed to prepare output directory")) } ctx.Log("event", "created output directory") @@ -209,8 +206,7 @@ func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) *vmextensi ctx.Log("event", "download start") if ewc := downloadAndProcessURL(ctx, f, dir, &cfg); ewc != nil { ctx.Log("event", "download failed", "error", ewc.Err) - ewc := vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download file[%d]", i)) - return &ewc + return vmextension.NewErrorWithClarificationPtr(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download file[%d]", i)) } ctx.Log("event", "download complete", "output", dir) } @@ -257,8 +253,7 @@ func runCmd(ctx log.Logger, dir string, cfg handlerSettings) (ewc *vmextension.E if ewc != nil { ctx.Log("event", "failed to execute command", "error", err, "output", dir) - ewc := vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrap(ewc.Err, "failed to execute command")) - return &ewc + return vmextension.NewErrorWithClarificationPtr(ewc.ErrorCode, errors.Wrap(ewc.Err, "failed to execute command")) } ctx.Log("event", "executed command", "output", dir) return nil diff --git a/main/exec.go b/main/exec.go index 07017ac..d9328df 100644 --- a/main/exec.go +++ b/main/exec.go @@ -32,16 +32,14 @@ func Exec(cmd, workdir string, stdout, stderr io.WriteCloser) (int, *vmextension if ok { if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { code := status.ExitStatus() - ewc := vmextension.NewErrorWithClarification(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command terminated with exit status=%d", code)) - return code, &ewc + return code, vmextension.NewErrorWithClarificationPtr(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command terminated with exit status=%d", code)) } } if err == nil { return 0, nil } - ewc := vmextension.NewErrorWithClarification(errorutil.CommandExecution_failedUnknownError, errors.Wrapf(err, "failed to execute command")) - return 0, &ewc + return 0, vmextension.NewErrorWithClarificationPtr(errorutil.CommandExecution_failedUnknownError, errors.Wrapf(err, "failed to execute command")) } // ExecCmdInDir executes the given command in given directory and saves output @@ -55,13 +53,11 @@ func ExecCmdInDir(cmd, workdir string) *vmextension.ErrorWithClarification { outF, err := os.OpenFile(outFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.Os_FailedToOpenStdOut, errors.Wrapf(err, "failed to open stdout file")) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.Os_FailedToOpenStdOut, errors.Wrapf(err, "failed to open stdout file")) } errF, err := os.OpenFile(errFn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.Os_FailedToOpenStdErr, errors.Wrapf(err, "failed to open stderr file")) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.Os_FailedToOpenStdErr, errors.Wrapf(err, "failed to open stderr file")) } _, ewc := Exec(cmd, workdir, outF, errF) diff --git a/main/files.go b/main/files.go index c561f66..dc7ce6e 100644 --- a/main/files.go +++ b/main/files.go @@ -26,13 +26,11 @@ import ( func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handlerSettings) *vmextension.ErrorWithClarification { fn, err := urlToFileName(url) if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, err) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_invalidFileUris, err) } if !urlutil.IsValidUrl(url) { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, fmt.Errorf("[REDACTED] is not a valid url")) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_invalidFileUris, fmt.Errorf("[REDACTED] is not a valid url")) } dl, ewc := getDownloaders(url, cfg.StorageAccountName, cfg.StorageAccountKey, cfg.ManagedIdentity) @@ -51,8 +49,7 @@ func downloadAndProcessURL(ctx *log.Context, url, downloadDir string, cfg *handl } if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.SystemError, errors.Wrapf(err, "failed to post-process '%s'", fn)) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.SystemError, errors.Wrapf(err, "failed to post-process '%s'", fn)) } return nil @@ -78,8 +75,7 @@ func getDownloaders(fileURL string, storageAccountName, storageAccountKey string case managedIdentity.ClientId == "" && managedIdentity.ObjectId != "": msiProvider = download.GetMsiProviderForStorageAccountsWithObjectId(fileURL, managedIdentity.ObjectId) default: - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_clientIdObjectIdBothSpecified, fmt.Errorf("unexpected combination of ClientId and ObjectId found")) - return nil, &ewc + return nil, vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_clientIdObjectIdBothSpecified, fmt.Errorf("unexpected combination of ClientId and ObjectId found")) } return []download.Downloader{ // try downloading without MSI token first, but attempt with MSI if the download fails @@ -95,8 +91,7 @@ func getDownloaders(fileURL string, storageAccountName, storageAccountKey string // this preserves old behavior blob, err := blobutil.ParseBlobURL(fileURL) if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_invalidFileUris, err) - return nil, &ewc + return nil, vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_invalidFileUris, err) } return []download.Downloader{download.NewBlobDownload( storageAccountName, storageAccountKey, blob)}, diff --git a/main/handlersettings.go b/main/handlersettings.go index b5c3369..430ee9d 100644 --- a/main/handlersettings.go +++ b/main/handlersettings.go @@ -53,44 +53,36 @@ func (s *handlerSettings) fileUrls() []string { // the schema validation. func (h handlerSettings) validate() *vmextension.ErrorWithClarification { if h.commandToExecute() == "" && h.script() == "" { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteAndScriptNotSpecified, errCmdMissing) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_commandToExecuteAndScriptNotSpecified, errCmdMissing) } if h.publicSettings.CommandToExecute != "" && h.protectedSettings.CommandToExecute != "" { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteSpecifiedInTwoPlaces, errCmdTooMany) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_commandToExecuteSpecifiedInTwoPlaces, errCmdTooMany) } if h.publicSettings.Script != "" && h.protectedSettings.Script != "" { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_scriptSpecifiedInTwoPlaces, errScriptTooMany) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_scriptSpecifiedInTwoPlaces, errScriptTooMany) } if (h.publicSettings.FileURLs != nil && len(h.publicSettings.FileURLs) > 0) && (h.protectedSettings.FileURLs != nil && len(h.protectedSettings.FileURLs) > 0) { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_fileUrisSpecifiedInTwoPlaces, errFileUrisTooMany) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_fileUrisSpecifiedInTwoPlaces, errFileUrisTooMany) } if h.commandToExecute() != "" && h.script() != "" { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_commandToExecuteAndScriptBothSpecified, errCmdAndScript) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_commandToExecuteAndScriptBothSpecified, errCmdAndScript) } if (h.protectedSettings.StorageAccountName != "") != (h.protectedSettings.StorageAccountKey != "") { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_incompleteStorageCreds, errStoragePartialCredentials) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_incompleteStorageCreds, errStoragePartialCredentials) } if (h.protectedSettings.StorageAccountKey != "" || h.protectedSettings.StorageAccountName != "") && h.protectedSettings.ManagedIdentity != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_storageCredsAndMIBothSpecified, errUsingBothKeyAndMsi) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_storageCredsAndMIBothSpecified, errUsingBothKeyAndMsi) } if h.protectedSettings.ManagedIdentity != nil { if h.protectedSettings.ManagedIdentity.ClientId != "" && h.protectedSettings.ManagedIdentity.ObjectId != "" { - ewc := vmextension.NewErrorWithClarification(errorutil.CustomerInput_clientIdObjectIdBothSpecified, errUsingBothClientIdAndObjectId) - return &ewc + return vmextension.NewErrorWithClarificationPtr(errorutil.CustomerInput_clientIdObjectIdBothSpecified, errUsingBothClientIdAndObjectId) } } @@ -132,22 +124,19 @@ func parseAndValidateSettings(ctx *log.Context, configFolder string, seqNum int) ctx.Log("event", "reading configuration") pubJSON, protJSON, err := readSettings(configFolder, seqNum) if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, err) - return h, &ewc + return h, vmextension.NewErrorWithClarificationPtr(errorutil.Internal_badConfig, err) } ctx.Log("event", "read configuration") ctx.Log("event", "validating json schema") if err := validateSettingsSchema(pubJSON, protJSON); err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, errors.Wrap(err, "json validation error")) - return h, &ewc + return h, vmextension.NewErrorWithClarificationPtr(errorutil.Internal_badConfig, errors.Wrap(err, "json validation error")) } ctx.Log("event", "json schema valid") ctx.Log("event", "parsing configuration json") if err := UnmarshalHandlerSettings(pubJSON, protJSON, &h.publicSettings, &h.protectedSettings); err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.Internal_badConfig, errors.Wrap(err, "json parsing error")) - return h, &ewc + return h, vmextension.NewErrorWithClarificationPtr(errorutil.Internal_badConfig, errors.Wrap(err, "json parsing error")) } ctx.Log("event", "parsed configuration json") diff --git a/main/status_test.go b/main/status_test.go index 8e7e47b..333539e 100644 --- a/main/status_test.go +++ b/main/status_test.go @@ -56,9 +56,9 @@ func Test_reportErrorStatus_fileExists(t *testing.T) { fakeEnv := HandlerEnvironment{} fakeEnv.HandlerEnvironment.StatusFolder = tmpDir - ewc := vmextension.NewErrorWithClarification(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command failed with exit code = 1")) + ewc := vmextension.NewErrorWithClarificationPtr(errorutil.CommandExecution_failureExitCode, fmt.Errorf("command failed with exit code = 1")) - require.Nil(t, reportErrorStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusError, cmdEnable, &ewc)) + require.Nil(t, reportErrorStatus(log.NewContext(log.NewNopLogger()), fakeEnv, 1, StatusError, cmdEnable, ewc)) path := filepath.Join(tmpDir, "1.status") b, err := ioutil.ReadFile(path) diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index 96ac406..bb7589c 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -52,8 +52,7 @@ var ( func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, *vmextension.ErrorWithClarification) { req, err := d.GetRequest() if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_genericError, errors.Wrapf(err, "failed to create http request")) - return -1, nil, &ewc + return -1, nil, vmextension.NewErrorWithClarificationPtr(errorutil.FileDownload_genericError, errors.Wrapf(err, "failed to create http request")) } requestID := req.Header.Get(xMsClientRequestIdHeaderName) if len(requestID) > 0 { @@ -63,12 +62,10 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, *vmextension. if err != nil { if (err.(*url.Error)).Timeout() { err = urlutil.RemoveUrlFromErr(err) - ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_exceededTimeout, errors.Wrapf(err, "http request timed out")) - return -1, nil, &ewc + return -1, nil, vmextension.NewErrorWithClarificationPtr(errorutil.FileDownload_exceededTimeout, errors.Wrapf(err, "http request timed out")) } err = urlutil.RemoveUrlFromErr(err) - ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_unknownError, errors.Wrapf(err, "http request failed")) - return -1, nil, &ewc + return -1, nil, vmextension.NewErrorWithClarificationPtr(errorutil.FileDownload_unknownError, errors.Wrapf(err, "http request failed")) } if resp.StatusCode == http.StatusOK { @@ -135,6 +132,5 @@ func Download(ctx *log.Context, d Downloader) (int, io.ReadCloser, *vmextension. return resp.StatusCode, nil, nil } - ewc := vmextension.NewErrorWithClarification(errClarificationCode, errors.New(errString)) - return resp.StatusCode, nil, &ewc + return resp.StatusCode, nil, vmextension.NewErrorWithClarificationPtr(errClarificationCode, errors.New(errString)) } diff --git a/pkg/download/retry.go b/pkg/download/retry.go index 5c11410..515b36e 100644 --- a/pkg/download/retry.go +++ b/pkg/download/retry.go @@ -106,8 +106,7 @@ func WithRetries(ctx *log.Context, f *os.File, downloaders []Downloader, sf Slee return 0, nil } - ewc := vmextension.NewErrorWithClarification(lastErrCode, lastErr) - return 0, &ewc + return 0, vmextension.NewErrorWithClarificationPtr(lastErrCode, lastErr) } func isTransientHttpStatusCode(statusCode int) bool { diff --git a/pkg/download/save.go b/pkg/download/save.go index b0de719..c74a57f 100644 --- a/pkg/download/save.go +++ b/pkg/download/save.go @@ -16,16 +16,14 @@ import ( func SaveTo(ctx *log.Context, d []Downloader, dst string, mode os.FileMode) (int64, *vmextension.ErrorWithClarification) { f, err := os.OpenFile(dst, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, mode) if err != nil { - ewc := vmextension.NewErrorWithClarification(errorutil.FileDownload_unknownError, errors.Wrap(err, "failed to open file for writing")) - return 0, &ewc + return 0, vmextension.NewErrorWithClarificationPtr(errorutil.FileDownload_unknownError, errors.Wrap(err, "failed to open file for writing")) } defer f.Close() n, ewc := WithRetries(ctx, f, d, ActualSleep) if ewc != nil { - ewc := vmextension.NewErrorWithClarification(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download response and write to file: %s", dst)) - return n, &ewc + return n, vmextension.NewErrorWithClarificationPtr(ewc.ErrorCode, errors.Wrapf(ewc.Err, "failed to download response and write to file: %s", dst)) }