diff --git a/go.mod b/go.mod index 7300f12..9566c92 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,36 @@ module github.com/sacloud/sacloud-go go 1.17 -require github.com/stretchr/testify v1.7.0 +require ( + github.com/go-playground/validator/v10 v10.10.1 + github.com/sacloud/ftps v1.1.0 + github.com/sacloud/iaas-api-go v0.0.0-20220314063652-5eaa6e6cade6 + github.com/sacloud/sacloud-go/pkg v0.0.0-20220314055142-1db1c3d10889 + github.com/stretchr/testify v1.7.0 +) + +replace github.com/sacloud/sacloud-go/pkg => ./pkg require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.1 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + github.com/sacloud/api-client-go v0.0.0-20220311054319-f37467272e84 // indirect + github.com/sacloud/go-http v0.0.4 // indirect + go.uber.org/ratelimit v0.2.0 // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect + golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index acb88a4..e08e5b1 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,91 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= +github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= +github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/sacloud/api-client-go v0.0.0-20220311054319-f37467272e84 h1:yhGNZLgxTDHdVZwzvEN25VlL9BxAcfzuy//vG+9rABk= +github.com/sacloud/api-client-go v0.0.0-20220311054319-f37467272e84/go.mod h1:RvP4b31YdaVjXM/XQAvKAeTTs0vGdvfypvP/7ysWdAA= +github.com/sacloud/ftps v1.1.0 h1:cYv+b6qhrIT8msfx64XXRJzbv5S+Dqwb/rXa5Y641XA= +github.com/sacloud/ftps v1.1.0/go.mod h1:h4awhOi3PEyhKLj1FpXjoVV5yVkmRUU+d5L95EwX2JU= +github.com/sacloud/go-http v0.0.4 h1:+vgx/uCctcGiHMe8jE+qirMKd3+d73MNZXK7nrUo0po= +github.com/sacloud/go-http v0.0.4/go.mod h1:aYTXNuAnPmD6Ar3ktDaR1gPxJCPv2CqpppcYciy1hmo= +github.com/sacloud/iaas-api-go v0.0.0-20220314063652-5eaa6e6cade6 h1:OV8Sb6IM9JcDykdJ4x6kUs01ibWoZvEU3m+vvam1aB4= +github.com/sacloud/iaas-api-go v0.0.0-20220314063652-5eaa6e6cade6/go.mod h1:w9l3Po+rVyaIrf8/yGzA8q0DGnVWKYZ24FrC9bDtdJI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= +go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/service/iaas/archive/builder/api_client.go b/service/iaas/archive/builder/api_client.go new file mode 100644 index 0000000..751f25a --- /dev/null +++ b/service/iaas/archive/builder/api_client.go @@ -0,0 +1,31 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import "github.com/sacloud/iaas-api-go" + +// APIClient builderが利用するAPIクライアント +type APIClient struct { + Archive iaas.ArchiveAPI + Zone iaas.ZoneAPI +} + +// NewAPIClient builderが利用するAPIクライアントを返す +func NewAPIClient(caller iaas.APICaller) *APIClient { + return &APIClient{ + Archive: iaas.NewArchiveOp(caller), + Zone: iaas.NewZoneOp(caller), + } +} diff --git a/service/iaas/archive/builder/blank_archive_builder.go b/service/iaas/archive/builder/blank_archive_builder.go new file mode 100644 index 0000000..29ec94b --- /dev/null +++ b/service/iaas/archive/builder/blank_archive_builder.go @@ -0,0 +1,93 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "errors" + "fmt" + "io" + + "github.com/sacloud/ftps" + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/pkg/size" +) + +// BlankArchiveBuilder ブランクアーカイブの作成〜FTPSでのファイルアップロードを行う +type BlankArchiveBuilder struct { + Name string + Description string + Tags types.Tags + IconID types.ID + SizeGB int + SourceReader io.Reader + + NoWait bool + + Client *APIClient +} + +// Validate 設定値の検証 +func (b *BlankArchiveBuilder) Validate(ctx context.Context, zone string) error { + if b.NoWait { + return errors.New("NoWait=true is not supported when uploading files and creating archives") + } + requiredValues := map[string]bool{ + "Name": b.Name == "", + "SizeGB": b.SizeGB == 0, + "SourceReader": b.SourceReader == nil, + } + for key, empty := range requiredValues { + if empty { + return fmt.Errorf("%s is required", key) + } + } + return nil +} + +// Build ブランクアーカイブの作成〜FTPSでのファイルアップロードを行う +func (b *BlankArchiveBuilder) Build(ctx context.Context, zone string) (*iaas.Archive, error) { + if err := b.Validate(ctx, zone); err != nil { + return nil, err + } + + archive, ftpServer, err := b.Client.Archive.CreateBlank(ctx, zone, + &iaas.ArchiveCreateBlankRequest{ + Name: b.Name, + Description: b.Description, + Tags: b.Tags, + IconID: b.IconID, + SizeMB: b.SizeGB * size.GiB, + }) + if err != nil { + return nil, err + } + + // upload sources via FTPS + ftpsClient := ftps.NewClient(ftpServer.User, ftpServer.Password, ftpServer.HostName) + + if err := ftpsClient.UploadReader("data.raw", b.SourceReader); err != nil { + return archive, fmt.Errorf("uploading file via FTPS is failed: %s", err) + } + + // close FTP + if err := b.Client.Archive.CloseFTP(ctx, zone, archive.ID); err != nil { + return archive, err + } + + // reload + return b.Client.Archive.Read(ctx, zone, archive.ID) +} diff --git a/service/iaas/archive/builder/blank_archive_builder_test.go b/service/iaas/archive/builder/blank_archive_builder_test.go new file mode 100644 index 0000000..292949b --- /dev/null +++ b/service/iaas/archive/builder/blank_archive_builder_test.go @@ -0,0 +1,86 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "bytes" + "context" + "testing" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/testutil" + "github.com/sacloud/iaas-api-go/types" + "github.com/stretchr/testify/require" +) + +func TestBlankArchiveBuilder_Validate(t *testing.T) { + builder := &BlankArchiveBuilder{ + Name: "test", + SizeGB: 20, + SourceReader: bytes.NewBufferString(""), + NoWait: true, + Client: NewAPIClient(testutil.SingletonAPICaller()), + } + + err := builder.Validate(context.Background(), testutil.TestZone()) + require.EqualError(t, err, "NoWait=true is not supported when uploading files and creating archives") +} + +func TestBlankArchiveBuilder_Build(t *testing.T) { + if !testutil.IsAccTest() { + t.Skip("TestBlankArchiveBuilder_Build only exec when running an Acceptance Test") + } + + testZone := testutil.TestZone() + testutil.RunCRUD(t, &testutil.CRUDTestCase{ + SetupAPICallerFunc: func() iaas.APICaller { + return testutil.SingletonAPICaller() + }, + Parallel: true, + IgnoreStartupWait: true, + Create: &testutil.CRUDTestFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) (interface{}, error) { + source := bytes.NewBufferString("dummy") + + builder := &BlankArchiveBuilder{ + Name: testutil.ResourceName("archive-from-shared-builder"), + Description: "description", + Tags: types.Tags{"tag1", "tag2"}, + SizeGB: 20, + SourceReader: source, + Client: NewAPIClient(caller), + } + return builder.Build(ctx, testZone) + }, + }, + Read: &testutil.CRUDTestFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) (interface{}, error) { + return iaas.NewArchiveOp(caller).Read(ctx, testZone, ctx.ID) + }, + CheckFunc: func(t testutil.TestT, ctx *testutil.CRUDTestContext, value interface{}) error { + archive := value.(*iaas.Archive) + return testutil.DoAsserts( + testutil.AssertNotNilFunc(t, archive, "Archive"), + testutil.AssertTrueFunc(t, archive.Availability.IsAvailable(), "Archive.Availability.IsAvailable"), + ) + }, + }, + Delete: &testutil.CRUDTestDeleteFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) error { + return iaas.NewArchiveOp(caller).Delete(ctx, testZone, ctx.ID) + }, + }, + }) +} diff --git a/service/iaas/archive/builder/director.go b/service/iaas/archive/builder/director.go new file mode 100644 index 0000000..7aacafd --- /dev/null +++ b/service/iaas/archive/builder/director.go @@ -0,0 +1,113 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "io" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/types" +) + +// Builder アーカイブビルダーが持つ共通インターフェース +type Builder interface { + Build(ctx context.Context, zone string) (*iaas.Archive, error) + Validate(ctx context.Context, zone string) error +} + +// Director パラメータに応じて適切なアーカイブビルダーを返す +type Director struct { + Name string + Description string + Tags types.Tags + IconID types.ID + SizeGB int + + // for blank builder + SourceReader io.Reader + + // for standard builder + SourceDiskID types.ID + SourceArchiveID types.ID + + // transfer archive builder + SourceArchiveZone string + + // for shared archive builder + SourceSharedKey types.ArchiveShareKey + + // trueの場合アーカイブ作成完了まで待たずにreturnする。SourceReaderを指定する場合(BlankArchiveBuilder)にNoWaitをtrueにするとエラーとする + NoWait bool + + Client *APIClient +} + +// パラメータに応じて適切なアーカイブビルダーを返す +// +// Note: 他ゾーンからの転送の場合、転送元/先でゾーンが同一でもエラーとならない。 +// このためDirectorでは転送元/先ゾーンを意識せずにSourceArchiveZoneが指定されていた場合は +// 一律でTransferArchiveBuilderを返す。 +// +// もしこの挙動で問題が発生する場合は呼び出し側で適切にビルダーを切り替える実装を行う必要がある。 +func (d *Director) Builder() Builder { + if d.SourceReader != nil { + return &BlankArchiveBuilder{ + Name: d.Name, + Description: d.Description, + Tags: d.Tags, + IconID: d.IconID, + SizeGB: d.SizeGB, + SourceReader: d.SourceReader, + NoWait: d.NoWait, + Client: d.Client, + } + } + if d.SourceSharedKey.String() != "" { + return &FromSharedArchiveBuilder{ + Name: d.Name, + Description: d.Description, + Tags: d.Tags, + IconID: d.IconID, + SourceSharedKey: d.SourceSharedKey, + NoWait: d.NoWait, + Client: d.Client, + } + } + + if d.SourceArchiveZone != "" { + return &TransferArchiveBuilder{ + Name: d.Name, + Description: d.Description, + Tags: d.Tags, + IconID: d.IconID, + SourceArchiveID: d.SourceArchiveID, + SourceArchiveZone: d.SourceArchiveZone, + NoWait: d.NoWait, + Client: d.Client, + } + } + + return &StandardArchiveBuilder{ + Name: d.Name, + Description: d.Description, + Tags: d.Tags, + IconID: d.IconID, + SourceDiskID: d.SourceDiskID, + SourceArchiveID: d.SourceArchiveID, + NoWait: d.NoWait, + Client: d.Client, + } +} diff --git a/service/iaas/archive/builder/director_test.go b/service/iaas/archive/builder/director_test.go new file mode 100644 index 0000000..9df3768 --- /dev/null +++ b/service/iaas/archive/builder/director_test.go @@ -0,0 +1,96 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDirector_Builder(t *testing.T) { + dummySource := bytes.NewBufferString("dummy") + + cases := []struct { + msg string + in *Director + out Builder + }{ + { + msg: "BlankBuilder", + in: &Director{ + Name: "blank", + SizeGB: 20, + SourceReader: dummySource, + }, + out: &BlankArchiveBuilder{ + Name: "blank", + SizeGB: 20, + SourceReader: dummySource, + }, + }, + { + msg: "FromSharedArchiveBuilder", + in: &Director{ + Name: "shared", + SourceSharedKey: "is1a:0000:xxxx", + }, + out: &FromSharedArchiveBuilder{ + Name: "shared", + SourceSharedKey: "is1a:0000:xxxx", + }, + }, + { + msg: "TransferArchiveBuilder", + in: &Director{ + Name: "transfer", + SourceArchiveID: 1, + SourceArchiveZone: "is1a", + }, + out: &TransferArchiveBuilder{ + Name: "transfer", + SourceArchiveID: 1, + SourceArchiveZone: "is1a", + }, + }, + { + msg: "StandardArchiveBuilder_with_ArchiveID", + in: &Director{ + Name: "standard-with-archive-id", + SourceArchiveID: 1, + }, + out: &StandardArchiveBuilder{ + Name: "standard-with-archive-id", + SourceArchiveID: 1, + }, + }, + { + msg: "StandardArchiveBuilder_with_DiskID", + in: &Director{ + Name: "standard-with-disk-id", + SourceDiskID: 1, + }, + out: &StandardArchiveBuilder{ + Name: "standard-with-disk-id", + SourceDiskID: 1, + }, + }, + } + + for _, tc := range cases { + require.EqualValues(t, tc.out, tc.in.Builder(), tc.msg) + } +} diff --git a/service/iaas/archive/builder/from_shared_archive_builder.go b/service/iaas/archive/builder/from_shared_archive_builder.go new file mode 100644 index 0000000..0f26206 --- /dev/null +++ b/service/iaas/archive/builder/from_shared_archive_builder.go @@ -0,0 +1,91 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "fmt" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/helper/query" + "github.com/sacloud/iaas-api-go/types" +) + +// FromSharedArchiveBuilder 共有アーカイブからアーカイブの作成を行う +type FromSharedArchiveBuilder struct { + Name string + Description string + Tags types.Tags + IconID types.ID + SourceSharedKey types.ArchiveShareKey + + NoWait bool + Client *APIClient +} + +// Validate 設定値の検証 +func (b *FromSharedArchiveBuilder) Validate(ctx context.Context, zone string) error { + requiredValues := map[string]bool{ + "Name": b.Name == "", + "SourceSharedKey": b.SourceSharedKey == "", + } + for key, empty := range requiredValues { + if empty { + return fmt.Errorf("%s is required", key) + } + } + if !b.SourceSharedKey.ValidFormat() { + return fmt.Errorf("archive shared key is invalid format: key:%q", b.SourceSharedKey) + } + return nil +} + +// Build 共有アーカイブからアーカイブの作成を行う +func (b *FromSharedArchiveBuilder) Build(ctx context.Context, zone string) (*iaas.Archive, error) { + if err := b.Validate(ctx, zone); err != nil { + return nil, err + } + + zoneID, err := query.ZoneIDFromName(ctx, b.Client.Zone, zone) + if err != nil { + return nil, err + } + + archive, err := b.Client.Archive.CreateFromShared(ctx, b.SourceSharedKey.Zone(), b.SourceSharedKey.SourceArchiveID(), zoneID, + &iaas.ArchiveCreateRequestFromShared{ + Name: b.Name, + Description: b.Description, + Tags: b.Tags, + IconID: b.IconID, + SourceSharedKey: b.SourceSharedKey, + }) + if err != nil { + return nil, err + } + + if b.NoWait { + return archive, nil + } + + lastState, err := iaas.WaiterForReady(func() (interface{}, error) { + return b.Client.Archive.Read(ctx, zone, archive.ID) + }).WaitForState(ctx) + + var ret *iaas.Archive + if lastState != nil { + ret = lastState.(*iaas.Archive) + } + return ret, err +} diff --git a/service/iaas/archive/builder/from_shared_archive_builder_test.go b/service/iaas/archive/builder/from_shared_archive_builder_test.go new file mode 100644 index 0000000..7710cc8 --- /dev/null +++ b/service/iaas/archive/builder/from_shared_archive_builder_test.go @@ -0,0 +1,119 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "testing" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/helper/query" + "github.com/sacloud/iaas-api-go/ostype" + "github.com/sacloud/iaas-api-go/testutil" + "github.com/sacloud/iaas-api-go/types" +) + +func TestFromSharedArchiveBuilder_Build(t *testing.T) { + zoneFrom := "is1a" + zoneTo := "is1b" + var sourceArchive *iaas.Archive + var shareInfo *iaas.ArchiveShareInfo + + testutil.RunCRUD(t, &testutil.CRUDTestCase{ + SetupAPICallerFunc: func() iaas.APICaller { + return testutil.SingletonAPICaller() + }, + Parallel: true, + IgnoreStartupWait: true, + Setup: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) error { + archiveOp := iaas.NewArchiveOp(caller) + source, err := query.FindArchiveByOSType(ctx, archiveOp, zoneFrom, ostype.CentOS) + if err != nil { + return err + } + + created, err := archiveOp.Create(ctx, zoneFrom, &iaas.ArchiveCreateRequest{ + SourceArchiveID: source.ID, + Name: testutil.ResourceName("source-archive"), + }) + if err != nil { + return err + } + sourceArchive = created + _, err = iaas.WaiterForReady(func() (interface{}, error) { + return archiveOp.Read(ctx, zoneFrom, sourceArchive.ID) + }).WaitForState(ctx) + if err != nil { + return err + } + + si, err := archiveOp.Share(ctx, zoneFrom, sourceArchive.ID) + if err != nil { + return err + } + shareInfo = si + return nil + }, + Create: &testutil.CRUDTestFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) (interface{}, error) { + builder := &FromSharedArchiveBuilder{ + Name: testutil.ResourceName("archive-from-shared-builder"), + Description: "description", + Tags: types.Tags{"tag1", "tag2"}, + SourceSharedKey: shareInfo.SharedKey, + Client: NewAPIClient(caller), + } + return builder.Build(ctx, zoneTo) + }, + }, + Read: &testutil.CRUDTestFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) (interface{}, error) { + return iaas.NewArchiveOp(caller).Read(ctx, zoneTo, ctx.ID) + }, + CheckFunc: func(t testutil.TestT, ctx *testutil.CRUDTestContext, value interface{}) error { + archive := value.(*iaas.Archive) + return testutil.DoAsserts( + testutil.AssertNotNilFunc(t, archive, "Archive"), + ) + }, + }, + Delete: &testutil.CRUDTestDeleteFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) error { + archiveOp := iaas.NewArchiveOp(caller) + + _, err := iaas.WaiterForReady(func() (interface{}, error) { + return archiveOp.Read(ctx, zoneTo, ctx.ID) + }).WaitForState(ctx) + if err != nil { + return err + } + if err := archiveOp.Delete(ctx, zoneTo, ctx.ID); err != nil { + return err + } + + if sourceArchive != nil { + if sourceArchive.Availability.IsUploading() { + if err := archiveOp.CloseFTP(ctx, zoneFrom, sourceArchive.ID); err != nil { + return err + } + } + if err := archiveOp.Delete(ctx, zoneFrom, sourceArchive.ID); err != nil { + return err + } + } + return nil + }, + }, + }) +} diff --git a/service/iaas/archive/builder/standard_archive_builder.go b/service/iaas/archive/builder/standard_archive_builder.go new file mode 100644 index 0000000..e72034f --- /dev/null +++ b/service/iaas/archive/builder/standard_archive_builder.go @@ -0,0 +1,84 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "fmt" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/types" +) + +// StandardArchiveBuilder 同一アカウント/同一ゾーンのディスク/アーカイブからアーカイブの作成を行う +type StandardArchiveBuilder struct { + Name string + Description string + Tags types.Tags + IconID types.ID + SourceDiskID types.ID + SourceArchiveID types.ID + + NoWait bool + Client *APIClient +} + +// Validate 設定値の検証 +func (b *StandardArchiveBuilder) Validate(ctx context.Context, zone string) error { + requiredValues := map[string]bool{ + "Name": b.Name == "", + "SourceDiskID or SourceArchiveID": b.SourceArchiveID.IsEmpty() && b.SourceDiskID.IsEmpty(), + } + for key, empty := range requiredValues { + if empty { + return fmt.Errorf("%s is required", key) + } + } + return nil +} + +// Build 同一アカウント/同一ゾーンのディスク/アーカイブからアーカイブの作成を行う +func (b *StandardArchiveBuilder) Build(ctx context.Context, zone string) (*iaas.Archive, error) { + if err := b.Validate(ctx, zone); err != nil { + return nil, err + } + + archive, err := b.Client.Archive.Create(ctx, zone, + &iaas.ArchiveCreateRequest{ + Name: b.Name, + Description: b.Description, + Tags: b.Tags, + IconID: b.IconID, + SourceDiskID: b.SourceDiskID, + SourceArchiveID: b.SourceArchiveID, + }) + if err != nil { + return nil, err + } + + if b.NoWait { + return archive, nil + } + + lastState, err := iaas.WaiterForReady(func() (interface{}, error) { + return b.Client.Archive.Read(ctx, zone, archive.ID) + }).WaitForState(ctx) + + var ret *iaas.Archive + if lastState != nil { + ret = lastState.(*iaas.Archive) + } + return ret, err +} diff --git a/service/iaas/archive/builder/standard_archive_builder_test.go b/service/iaas/archive/builder/standard_archive_builder_test.go new file mode 100644 index 0000000..40d5523 --- /dev/null +++ b/service/iaas/archive/builder/standard_archive_builder_test.go @@ -0,0 +1,83 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "testing" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/helper/query" + "github.com/sacloud/iaas-api-go/ostype" + "github.com/sacloud/iaas-api-go/testutil" + "github.com/sacloud/iaas-api-go/types" +) + +func TestStandardArchiveBuilder_Build(t *testing.T) { + testZone := testutil.TestZone() + var sourceArchive *iaas.Archive + + testutil.RunCRUD(t, &testutil.CRUDTestCase{ + SetupAPICallerFunc: func() iaas.APICaller { + return testutil.SingletonAPICaller() + }, + Parallel: true, + IgnoreStartupWait: true, + Setup: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) error { + archiveOp := iaas.NewArchiveOp(caller) + source, err := query.FindArchiveByOSType(ctx, archiveOp, testZone, ostype.CentOS) + if err != nil { + return err + } + sourceArchive = source + return nil + }, + Create: &testutil.CRUDTestFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) (interface{}, error) { + builder := &StandardArchiveBuilder{ + Name: testutil.ResourceName("standard-archive-builder"), + Description: "description", + Tags: types.Tags{"tag1", "tag2"}, + SourceArchiveID: sourceArchive.ID, + Client: NewAPIClient(caller), + } + return builder.Build(ctx, testZone) + }, + }, + Read: &testutil.CRUDTestFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) (interface{}, error) { + return iaas.NewArchiveOp(caller).Read(ctx, testZone, ctx.ID) + }, + CheckFunc: func(t testutil.TestT, ctx *testutil.CRUDTestContext, value interface{}) error { + archive := value.(*iaas.Archive) + return testutil.DoAsserts( + testutil.AssertNotNilFunc(t, archive, "Archive"), + ) + }, + }, + Delete: &testutil.CRUDTestDeleteFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) error { + archiveOp := iaas.NewArchiveOp(caller) + + _, err := iaas.WaiterForReady(func() (interface{}, error) { + return archiveOp.Read(ctx, testZone, ctx.ID) + }).WaitForState(ctx) + if err != nil { + return err + } + return archiveOp.Delete(ctx, testZone, ctx.ID) + }, + }, + }) +} diff --git a/service/iaas/archive/builder/transfer_archive_builder.go b/service/iaas/archive/builder/transfer_archive_builder.go new file mode 100644 index 0000000..73dfc7f --- /dev/null +++ b/service/iaas/archive/builder/transfer_archive_builder.go @@ -0,0 +1,95 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "context" + "fmt" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/helper/query" + "github.com/sacloud/iaas-api-go/types" +) + +// TransferArchiveBuilder 共有アーカイブからアーカイブの作成を行う +type TransferArchiveBuilder struct { + Name string + Description string + Tags types.Tags + IconID types.ID + + SourceArchiveID types.ID + SourceArchiveZone string + + NoWait bool + Client *APIClient +} + +// Validate 設定値の検証 +func (b *TransferArchiveBuilder) Validate(ctx context.Context, zone string) error { + requiredValues := map[string]bool{ + "Name": b.Name == "", + "SourceArchiveID": b.SourceArchiveID.IsEmpty(), + "SourceArchiveZone": b.SourceArchiveZone == "", + } + for key, empty := range requiredValues { + if empty { + return fmt.Errorf("%s is required", key) + } + } + return nil +} + +// Build 他ゾーンのアーカイブからアーカイブの作成を行う +func (b *TransferArchiveBuilder) Build(ctx context.Context, zone string) (*iaas.Archive, error) { + if err := b.Validate(ctx, zone); err != nil { + return nil, err + } + + zoneID, err := query.ZoneIDFromName(ctx, b.Client.Zone, zone) + if err != nil { + return nil, err + } + + sourceInfo, err := b.Client.Archive.Read(ctx, b.SourceArchiveZone, b.SourceArchiveID) + if err != nil { + return nil, err + } + + archive, err := b.Client.Archive.Transfer(ctx, b.SourceArchiveZone, b.SourceArchiveID, zoneID, + &iaas.ArchiveTransferRequest{ + Name: b.Name, + Description: b.Description, + Tags: b.Tags, + IconID: b.IconID, + SizeMB: sourceInfo.SizeMB, + }) + if err != nil { + return nil, err + } + if b.NoWait { + return archive, nil + } + + lastState, err := iaas.WaiterForReady(func() (interface{}, error) { + return b.Client.Archive.Read(ctx, zone, archive.ID) + }).WaitForState(ctx) + + var ret *iaas.Archive + if lastState != nil { + ret = lastState.(*iaas.Archive) + } + return ret, err +} diff --git a/service/iaas/archive/builder/transfer_archive_builder_test.go b/service/iaas/archive/builder/transfer_archive_builder_test.go new file mode 100644 index 0000000..fa5a1a1 --- /dev/null +++ b/service/iaas/archive/builder/transfer_archive_builder_test.go @@ -0,0 +1,106 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builder + +import ( + "testing" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/helper/query" + "github.com/sacloud/iaas-api-go/ostype" + "github.com/sacloud/iaas-api-go/testutil" + "github.com/sacloud/iaas-api-go/types" +) + +func TestTransferArchiveBuilder_Build(t *testing.T) { + zoneFrom := "is1a" + zoneTo := "is1b" + var sourceArchive *iaas.Archive + + testutil.RunCRUD(t, &testutil.CRUDTestCase{ + SetupAPICallerFunc: func() iaas.APICaller { + return testutil.SingletonAPICaller() + }, + Parallel: true, + IgnoreStartupWait: true, + Setup: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) error { + archiveOp := iaas.NewArchiveOp(caller) + source, err := query.FindArchiveByOSType(ctx, archiveOp, zoneFrom, ostype.CentOS) + if err != nil { + return err + } + + created, err := archiveOp.Create(ctx, zoneFrom, &iaas.ArchiveCreateRequest{ + SourceArchiveID: source.ID, + Name: testutil.ResourceName("source-archive-for-transfer"), + }) + if err != nil { + return err + } + sourceArchive = created + _, err = iaas.WaiterForReady(func() (interface{}, error) { + return archiveOp.Read(ctx, zoneFrom, sourceArchive.ID) + }).WaitForState(ctx) + + return err + }, + Create: &testutil.CRUDTestFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) (interface{}, error) { + builder := &TransferArchiveBuilder{ + Name: testutil.ResourceName("archive-from-other-zone"), + Description: "description", + Tags: types.Tags{"tag1", "tag2"}, + SourceArchiveID: sourceArchive.ID, + SourceArchiveZone: zoneFrom, + Client: NewAPIClient(caller), + } + return builder.Build(ctx, zoneTo) + }, + }, + Read: &testutil.CRUDTestFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) (interface{}, error) { + return iaas.NewArchiveOp(caller).Read(ctx, zoneTo, ctx.ID) + }, + CheckFunc: func(t testutil.TestT, ctx *testutil.CRUDTestContext, value interface{}) error { + archive := value.(*iaas.Archive) + return testutil.DoAsserts( + testutil.AssertNotNilFunc(t, archive, "Archive"), + ) + }, + }, + Delete: &testutil.CRUDTestDeleteFunc{ + Func: func(ctx *testutil.CRUDTestContext, caller iaas.APICaller) error { + archiveOp := iaas.NewArchiveOp(caller) + + _, err := iaas.WaiterForReady(func() (interface{}, error) { + return archiveOp.Read(ctx, zoneTo, ctx.ID) + }).WaitForState(ctx) + if err != nil { + return err + } + if err := archiveOp.Delete(ctx, zoneTo, ctx.ID); err != nil { + return err + } + + if sourceArchive != nil { + if err := archiveOp.Delete(ctx, zoneFrom, sourceArchive.ID); err != nil { + return err + } + } + return nil + }, + }, + }) +} diff --git a/service/iaas/archive/close_ftp_request.go b/service/iaas/archive/close_ftp_request.go new file mode 100644 index 0000000..f8dfd90 --- /dev/null +++ b/service/iaas/archive/close_ftp_request.go @@ -0,0 +1,29 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/validate" +) + +type CloseFTPRequest struct { + Zone string `request:"-" validate:"required"` + ID types.ID `request:"-" validate:"required"` +} + +func (req *CloseFTPRequest) Validate() error { + return validate.Struct(req) +} diff --git a/service/iaas/archive/close_ftp_service.go b/service/iaas/archive/close_ftp_service.go new file mode 100644 index 0000000..b1f06d3 --- /dev/null +++ b/service/iaas/archive/close_ftp_service.go @@ -0,0 +1,34 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + + "github.com/sacloud/iaas-api-go" +) + +func (s *Service) CloseFTP(req *CloseFTPRequest) error { + return s.CloseFTPWithContext(context.Background(), req) +} + +func (s *Service) CloseFTPWithContext(ctx context.Context, req *CloseFTPRequest) error { + if err := req.Validate(); err != nil { + return err + } + + client := iaas.NewArchiveOp(s.caller) + return client.CloseFTP(ctx, req.Zone, req.ID) +} diff --git a/service/iaas/archive/create_request.go b/service/iaas/archive/create_request.go new file mode 100644 index 0000000..6df7ba0 --- /dev/null +++ b/service/iaas/archive/create_request.go @@ -0,0 +1,44 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "io" + + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/validate" +) + +type CreateRequest struct { + Zone string `request:"-" validate:"required"` + + Name string `validate:"required"` + Description string `validate:"min=0,max=512"` + Tags types.Tags + IconID types.ID + SizeGB int + SourcePath string `validate:"omitempty,file"` + SourceReader io.Reader + SourceDiskID types.ID + SourceArchiveID types.ID + SourceArchiveZone string + SourceSharedKey types.ArchiveShareKey + + NoWait bool +} + +func (req *CreateRequest) Validate() error { + return validate.Struct(req) +} diff --git a/service/iaas/archive/create_service.go b/service/iaas/archive/create_service.go new file mode 100644 index 0000000..9b19b23 --- /dev/null +++ b/service/iaas/archive/create_service.go @@ -0,0 +1,67 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/sacloud/iaas-api-go" + archiveBuilder "github.com/sacloud/sacloud-go/service/iaas/archive/builder" +) + +func (s *Service) Create(req *CreateRequest) (*iaas.Archive, error) { + return s.CreateWithContext(context.Background(), req) +} + +func (s *Service) CreateWithContext(ctx context.Context, req *CreateRequest) (*iaas.Archive, error) { + if err := req.Validate(); err != nil { + return nil, err + } + + var reader io.Reader + switch req.SourcePath { + case "": + reader = req.SourceReader + default: + file, err := os.Open(req.SourcePath) + if err != nil { + return nil, fmt.Errorf("reading source file[%s] failed: %s", req.SourcePath, err) + } + defer file.Close() // nolint + reader = file + } + + builder := (&archiveBuilder.Director{ + Name: req.Name, + Description: req.Description, + Tags: req.Tags, + IconID: req.IconID, + SizeGB: req.SizeGB, + SourceReader: reader, + SourceDiskID: req.SourceDiskID, + SourceArchiveID: req.SourceArchiveID, + SourceArchiveZone: req.SourceArchiveZone, + SourceSharedKey: "", + NoWait: req.NoWait, + Client: archiveBuilder.NewAPIClient(s.caller), + }).Builder() + if err := builder.Validate(ctx, req.Zone); err != nil { + return nil, err + } + return builder.Build(ctx, req.Zone) +} diff --git a/service/iaas/archive/delete_request.go b/service/iaas/archive/delete_request.go new file mode 100644 index 0000000..29e15c2 --- /dev/null +++ b/service/iaas/archive/delete_request.go @@ -0,0 +1,31 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/validate" +) + +type DeleteRequest struct { + Zone string `request:"-" validate:"required"` + ID types.ID `request:"-" validate:"required"` + + FailIfNotFound bool `request:"-"` +} + +func (req *DeleteRequest) Validate() error { + return validate.Struct(req) +} diff --git a/service/iaas/archive/delete_service.go b/service/iaas/archive/delete_service.go new file mode 100644 index 0000000..d6705e0 --- /dev/null +++ b/service/iaas/archive/delete_service.go @@ -0,0 +1,38 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/sacloud-go/service/iaas/serviceutil" +) + +func (s *Service) Delete(req *DeleteRequest) error { + return s.DeleteWithContext(context.Background(), req) +} + +func (s *Service) DeleteWithContext(ctx context.Context, req *DeleteRequest) error { + if err := req.Validate(); err != nil { + return err + } + + client := iaas.NewArchiveOp(s.caller) + if err := client.Delete(ctx, req.Zone, req.ID); err != nil { + return serviceutil.HandleNotFoundError(err, !req.FailIfNotFound) + } + return nil +} diff --git a/service/iaas/archive/download_request.go b/service/iaas/archive/download_request.go new file mode 100644 index 0000000..27ecf19 --- /dev/null +++ b/service/iaas/archive/download_request.go @@ -0,0 +1,34 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "io" + + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/validate" +) + +type DownloadRequest struct { + Zone string `request:"-" validate:"required"` + ID types.ID `request:"-" validate:"required"` + + Path string `request:"-"` + Writer io.Writer `request:"-"` +} + +func (req *DownloadRequest) Validate() error { + return validate.Struct(req) +} diff --git a/service/iaas/archive/download_service.go b/service/iaas/archive/download_service.go new file mode 100644 index 0000000..873118b --- /dev/null +++ b/service/iaas/archive/download_service.go @@ -0,0 +1,73 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/sacloud/ftps" + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/types" +) + +func (s *Service) Download(req *DownloadRequest) error { + return s.DownloadWithContext(context.Background(), req) +} + +func (s *Service) DownloadWithContext(ctx context.Context, req *DownloadRequest) error { + if err := req.Validate(); err != nil { + return err + } + + client := iaas.NewArchiveOp(s.caller) + resource, err := client.Read(ctx, req.Zone, req.ID) + if err != nil { + return fmt.Errorf("reading Archive[%s] failed: %s", req.ID, err) + } + + if resource.Scope != types.Scopes.User { + return fmt.Errorf("Archive[%s] is not allowed to download", req.ID) + } + + ftpServer, err := client.OpenFTP(ctx, req.Zone, req.ID, &iaas.OpenFTPRequest{ChangePassword: true}) + if err != nil { + return fmt.Errorf("requesting FTP server information failed: %s", err) + } + + ftpsClient := ftps.NewClient(ftpServer.User, ftpServer.Password, ftpServer.HostName) + switch req.Path { + case "": + var out io.Writer = os.Stdout + if req.Writer != nil { + out = req.Writer + } + if err := ftpsClient.DownloadWriter(out); err != nil { + return fmt.Errorf("downloading via FTP failed: %s", err) + } + default: + if err := ftpsClient.Download(req.Path); err != nil { + return fmt.Errorf("downloading via FTP failed: %s", err) + } + } + + // close + if err := client.CloseFTP(ctx, req.Zone, req.ID); err != nil { + return fmt.Errorf("closing FTP server failed: %s", err) + } + return nil +} diff --git a/service/iaas/archive/download_test.go b/service/iaas/archive/download_test.go new file mode 100644 index 0000000..448a976 --- /dev/null +++ b/service/iaas/archive/download_test.go @@ -0,0 +1,93 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "bytes" + "os" + "testing" + + "github.com/sacloud/iaas-api-go/testutil" + "github.com/sacloud/iaas-api-go/types" + "github.com/stretchr/testify/require" +) + +func TestArchiveService_downloadAfterBuild(t *testing.T) { + if !testutil.IsAccTest() { + t.SkipNow() + } + + caller := testutil.SingletonAPICaller() + zone := testutil.TestZone() + svc := New(caller) + + // file + filename := "test-archive-source.tmp" + if err := os.WriteFile(filename, []byte("test"), 0755); err != nil { + t.Fatal(err) + } + defer os.Remove(filename) // nolint + + // create + archive, err := svc.Create(&CreateRequest{ + Zone: zone, + Name: testutil.ResourceName("test-archive-service"), + Description: "desc", + Tags: types.Tags{"tag1", "tag2"}, + SizeGB: 20, + SourcePath: filename, + }) + if err != nil { + t.Fatal(err) + } + + // update + updName := archive.Name + "-upd" + updArchive, err := svc.Update(&UpdateRequest{ + Zone: zone, + ID: archive.ID, + Name: &updName, + }) + if err != nil { + t.Fatal(err) + } + require.Equal(t, updName, updArchive.Name) + require.Equal(t, archive.Description, updArchive.Description) + require.Equal(t, archive.Tags, updArchive.Tags) + require.Equal(t, archive.IconID, updArchive.IconID) + + // download + buf := bytes.NewBuffer([]byte{}) + err = svc.Download(&DownloadRequest{ + Zone: zone, + ID: archive.ID, + Writer: buf, + }) + if err != nil { + t.Fatal(err) + } + + if buf.String() != "test" { + t.Fatalf("unexpected value: got:%s want:%s", buf.String(), "test") + } + + // delete + if err := svc.Delete(&DeleteRequest{ + Zone: zone, + ID: archive.ID, + }); err != nil { + t.Fatal(err) + } +} diff --git a/service/iaas/archive/find_request.go b/service/iaas/archive/find_request.go new file mode 100644 index 0000000..3a07657 --- /dev/null +++ b/service/iaas/archive/find_request.go @@ -0,0 +1,70 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/ostype" + "github.com/sacloud/iaas-api-go/search" + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/pkg/objutil" + "github.com/sacloud/sacloud-go/service/iaas/serviceutil" + "github.com/sacloud/sacloud-go/service/validate" +) + +type FindRequest struct { + Zone string `request:"-" validate:"required"` + + // OSType OS種別、NamesやTagsを指定した場合はそちらが優先される + OSType ostype.ArchiveOSType `request:"-"` + + Names []string `request:"-"` + Tags []string `request:"-"` + Scope types.EScope `request:"-"` + + Sort search.SortKeys + Count int + From int +} + +func (req *FindRequest) Validate() error { + return validate.Struct(req) +} + +func (req *FindRequest) ToRequestParameter() (*iaas.FindCondition, error) { + condition := &iaas.FindCondition{ + Filter: map[search.FilterKey]interface{}{}, + } + if err := serviceutil.RequestConvertTo(req, condition); err != nil { + return nil, err + } + + filter, ok := ostype.ArchiveCriteria[req.OSType] + if ok { + for k, v := range filter { + condition.Filter[k] = v + } + } + if !objutil.IsEmpty(req.Names) { + condition.Filter[search.Key("Name")] = search.AndEqual(req.Names...) + } + if !objutil.IsEmpty(req.Tags) { + condition.Filter[search.Key("Tags.Name")] = search.TagsAndEqual(req.Tags...) + } + if !objutil.IsEmpty(req.Scope) { + condition.Filter[search.Key("Scope")] = search.OrEqual(req.Scope) + } + return condition, nil +} diff --git a/service/iaas/archive/find_service.go b/service/iaas/archive/find_service.go new file mode 100644 index 0000000..a0bc603 --- /dev/null +++ b/service/iaas/archive/find_service.go @@ -0,0 +1,43 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + + "github.com/sacloud/iaas-api-go" +) + +func (s *Service) Find(req *FindRequest) ([]*iaas.Archive, error) { + return s.FindWithContext(context.Background(), req) +} + +func (s *Service) FindWithContext(ctx context.Context, req *FindRequest) ([]*iaas.Archive, error) { + if err := req.Validate(); err != nil { + return nil, err + } + + params, err := req.ToRequestParameter() + if err != nil { + return nil, err + } + + client := iaas.NewArchiveOp(s.caller) + found, err := client.Find(ctx, req.Zone, params) + if err != nil { + return nil, err + } + return found.Archives, nil +} diff --git a/service/iaas/archive/open_ftp_request.go b/service/iaas/archive/open_ftp_request.go new file mode 100644 index 0000000..d304cb5 --- /dev/null +++ b/service/iaas/archive/open_ftp_request.go @@ -0,0 +1,31 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/validate" +) + +type OpenFTPRequest struct { + Zone string `request:"-" validate:"required"` + ID types.ID `request:"-" validate:"required"` + + ChangePassword bool +} + +func (req *OpenFTPRequest) Validate() error { + return validate.Struct(req) +} diff --git a/service/iaas/archive/open_ftp_service.go b/service/iaas/archive/open_ftp_service.go new file mode 100644 index 0000000..fb8be56 --- /dev/null +++ b/service/iaas/archive/open_ftp_service.go @@ -0,0 +1,33 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + + "github.com/sacloud/iaas-api-go" +) + +func (s *Service) OpenFTP(req *OpenFTPRequest) (*iaas.FTPServer, error) { + return s.OpenFTPWithContext(context.Background(), req) +} + +func (s *Service) OpenFTPWithContext(ctx context.Context, req *OpenFTPRequest) (*iaas.FTPServer, error) { + if err := req.Validate(); err != nil { + return nil, err + } + client := iaas.NewArchiveOp(s.caller) + return client.OpenFTP(ctx, req.Zone, req.ID, &iaas.OpenFTPRequest{ChangePassword: req.ChangePassword}) +} diff --git a/service/iaas/archive/read_request.go b/service/iaas/archive/read_request.go new file mode 100644 index 0000000..20befc0 --- /dev/null +++ b/service/iaas/archive/read_request.go @@ -0,0 +1,29 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/validate" +) + +type ReadRequest struct { + Zone string `request:"-" validate:"required"` + ID types.ID `request:"-" validate:"required"` +} + +func (req *ReadRequest) Validate() error { + return validate.Struct(req) +} diff --git a/service/iaas/archive/read_service.go b/service/iaas/archive/read_service.go new file mode 100644 index 0000000..a83e580 --- /dev/null +++ b/service/iaas/archive/read_service.go @@ -0,0 +1,33 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + + "github.com/sacloud/iaas-api-go" +) + +func (s *Service) Read(req *ReadRequest) (*iaas.Archive, error) { + return s.ReadWithContext(context.Background(), req) +} + +func (s *Service) ReadWithContext(ctx context.Context, req *ReadRequest) (*iaas.Archive, error) { + if err := req.Validate(); err != nil { + return nil, err + } + client := iaas.NewArchiveOp(s.caller) + return client.Read(ctx, req.Zone, req.ID) +} diff --git a/service/iaas/archive/service.go b/service/iaas/archive/service.go new file mode 100644 index 0000000..102b679 --- /dev/null +++ b/service/iaas/archive/service.go @@ -0,0 +1,27 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import "github.com/sacloud/iaas-api-go" + +// Service provides a high-level API of for Archive +type Service struct { + caller iaas.APICaller +} + +// New returns new service instance of Archive +func New(caller iaas.APICaller) *Service { + return &Service{caller: caller} +} diff --git a/service/iaas/archive/update_request.go b/service/iaas/archive/update_request.go new file mode 100644 index 0000000..54d61bb --- /dev/null +++ b/service/iaas/archive/update_request.go @@ -0,0 +1,47 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/iaas/serviceutil" + "github.com/sacloud/sacloud-go/service/validate" +) + +type UpdateRequest struct { + Zone string `request:"-" validate:"required"` + ID types.ID `request:"-" validate:"required"` + + Name *string `request:",omitempty" validate:"omitempty,min=1"` + Description *string `request:",omitempty" validate:"omitempty,min=1,max=512"` + Tags *types.Tags `request:",omitempty"` + IconID *types.ID `request:",omitempty"` +} + +func (req *UpdateRequest) Validate() error { + return validate.Struct(req) +} + +func (req *UpdateRequest) ToRequestParameter(current *iaas.Archive) (*iaas.ArchiveUpdateRequest, error) { + r := &iaas.ArchiveUpdateRequest{} + if err := serviceutil.RequestConvertTo(current, r); err != nil { + return nil, err + } + if err := serviceutil.RequestConvertTo(req, r); err != nil { + return nil, err + } + return r, nil +} diff --git a/service/iaas/archive/update_request_test.go b/service/iaas/archive/update_request_test.go new file mode 100644 index 0000000..73db991 --- /dev/null +++ b/service/iaas/archive/update_request_test.go @@ -0,0 +1,49 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "testing" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/testutil" + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/pkg/pointer" +) + +func TestUpdateRequest(t *testing.T) { + archive := &iaas.Archive{ + ID: 1, + Name: "hoge", + Description: "fuga", + Tags: types.Tags{"tag1", "tag2"}, + } + + updateRequest := &UpdateRequest{ + Zone: "is1a", + ID: 1, + Name: pointer.NewString(""), + //Description: pointer.NewString(""), // 未指定パラメータは元の値を保持(request:,omitemptyが必要) + Tags: &types.Tags{}, + } + + result, err := updateRequest.ToRequestParameter(archive) + if err != nil { + t.Fatal(err) + } + testutil.AssertEmpty(t, result.Name, "Name") // nolint + testutil.AssertEqual(t, "fuga", result.Description, "Description") // nolint + testutil.AssertEmpty(t, result.Tags, "Tags") // nolint +} diff --git a/service/iaas/archive/update_service.go b/service/iaas/archive/update_service.go new file mode 100644 index 0000000..6db8e99 --- /dev/null +++ b/service/iaas/archive/update_service.go @@ -0,0 +1,45 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + "fmt" + + "github.com/sacloud/iaas-api-go" +) + +func (s *Service) Update(req *UpdateRequest) (*iaas.Archive, error) { + return s.UpdateWithContext(context.Background(), req) +} + +func (s *Service) UpdateWithContext(ctx context.Context, req *UpdateRequest) (*iaas.Archive, error) { + if err := req.Validate(); err != nil { + return nil, err + } + + client := iaas.NewArchiveOp(s.caller) + current, err := client.Read(ctx, req.Zone, req.ID) + if err != nil { + return nil, fmt.Errorf("reading Archive[%s] failed: %s", req.ID, err) + } + + params, err := req.ToRequestParameter(current) + if err != nil { + return nil, fmt.Errorf("processing request parameter failed: %s", err) + } + + return client.Update(ctx, req.Zone, req.ID, params) +} diff --git a/service/iaas/archive/upload_request.go b/service/iaas/archive/upload_request.go new file mode 100644 index 0000000..bc74c8d --- /dev/null +++ b/service/iaas/archive/upload_request.go @@ -0,0 +1,34 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "io" + + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/validate" +) + +type UploadRequest struct { + Zone string `request:"-" validate:"required"` + ID types.ID `request:"-" validate:"required"` + + Path string `validate:"omitempty,file"` + Reader io.Reader +} + +func (req *UploadRequest) Validate() error { + return validate.Struct(req) +} diff --git a/service/iaas/archive/upload_service.go b/service/iaas/archive/upload_service.go new file mode 100644 index 0000000..07a1b84 --- /dev/null +++ b/service/iaas/archive/upload_service.go @@ -0,0 +1,78 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/sacloud/ftps" + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/types" +) + +func (s *Service) Upload(req *UploadRequest) error { + return s.UploadWithContext(context.Background(), req) +} + +func (s *Service) UploadWithContext(ctx context.Context, req *UploadRequest) error { + if err := req.Validate(); err != nil { + return err + } + + client := iaas.NewArchiveOp(s.caller) + resource, err := client.Read(ctx, req.Zone, req.ID) + if err != nil { + return fmt.Errorf("reading Archive[%s] failed: %s", req.ID, err) + } + + if resource.Scope != types.Scopes.User { + return fmt.Errorf("Archive[%s] is not allowed to download", req.ID) + } + + ftpServer, err := client.OpenFTP(ctx, req.Zone, req.ID, &iaas.OpenFTPRequest{ChangePassword: true}) + if err != nil { + return fmt.Errorf("requesting FTP server information failed: %s", err) + } + + ftpsClient := ftps.NewClient(ftpServer.User, ftpServer.Password, ftpServer.HostName) + var reader io.Reader + switch req.Path { + case "": + reader = os.Stdin + if req.Reader != nil { + reader = req.Reader + } + default: + f, err := os.Open(req.Path) + if err != nil { + return fmt.Errorf("opening upload file failed: %s", err) + } + defer f.Close() + reader = f + } + + if err := ftpsClient.UploadReader("upload.raw", reader); err != nil { + return fmt.Errorf("uploading file failed: %s", err) + } + + // close FTP + if err := client.CloseFTP(ctx, req.Zone, resource.ID); err != nil { + return fmt.Errorf("closing FTP server failed: %s", err) + } + return nil +} diff --git a/service/iaas/archive/wait_ready_request.go b/service/iaas/archive/wait_ready_request.go new file mode 100644 index 0000000..00300a5 --- /dev/null +++ b/service/iaas/archive/wait_ready_request.go @@ -0,0 +1,29 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "github.com/sacloud/iaas-api-go/types" + "github.com/sacloud/sacloud-go/service/validate" +) + +type WaitReadyRequest struct { + Zone string `request:"-" validate:"required"` + ID types.ID `request:"-" validate:"required"` +} + +func (req *WaitReadyRequest) Validate() error { + return validate.Struct(req) +} diff --git a/service/iaas/archive/wait_ready_service.go b/service/iaas/archive/wait_ready_service.go new file mode 100644 index 0000000..0e6e00b --- /dev/null +++ b/service/iaas/archive/wait_ready_service.go @@ -0,0 +1,36 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package archive + +import ( + "context" + + "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/helper/wait" +) + +func (s *Service) WaitReady(req *WaitReadyRequest) error { + return s.WaitReadyWithContext(context.Background(), req) +} + +func (s *Service) WaitReadyWithContext(ctx context.Context, req *WaitReadyRequest) error { + if err := req.Validate(); err != nil { + return err + } + + client := iaas.NewArchiveOp(s.caller) + _, err := wait.UntilArchiveIsReady(ctx, client, req.Zone, req.ID) + return err +} diff --git a/service/iaas/serviceutil/util.go b/service/iaas/serviceutil/util.go new file mode 100644 index 0000000..e4307a2 --- /dev/null +++ b/service/iaas/serviceutil/util.go @@ -0,0 +1,67 @@ +// Copyright 2022 The sacloud/sacloud-go Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package serviceutil + +import ( + "fmt" + "time" + + iaas "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/mapconv" + "github.com/sacloud/sacloud-go/pkg/size" +) + +func HandleNotFoundError(err error, ignoreNotFoundError bool) error { + if ignoreNotFoundError && iaas.IsNotFoundError(err) { + return nil // ignore: 404 not found + } + return err +} + +func MonitorCondition(start, end time.Time) (*iaas.MonitorCondition, error) { + e := end + if e.IsZero() { + e = time.Now() + } + + s := start + if s.IsZero() { + s = e.Add(-1 * time.Hour) + } + if !(s.Unix() <= e.Unix()) { + return nil, fmt.Errorf("start(%s) or end(%s) is invalid", start.String(), end.String()) + } + return &iaas.MonitorCondition{Start: s, End: e}, nil +} + +func RequestConvertTo(source interface{}, dest interface{}) error { + decoder := &mapconv.Decoder{ + Config: &mapconv.DecoderConfig{ + TagName: "request", // TODO 後で"service"にする + FilterFuncs: map[string]mapconv.FilterFunc{ + "gb_to_mb": gbToMb, + }, + }, + } + return decoder.ConvertTo(source, dest) +} + +func gbToMb(v interface{}) (interface{}, error) { + s, ok := v.(int) + if !ok { + return nil, fmt.Errorf("invalid size value: %v", v) + } + return size.GiBToMiB(s), nil +} diff --git a/service/validate/validate.go b/service/validate/validate.go new file mode 100644 index 0000000..d66ac20 --- /dev/null +++ b/service/validate/validate.go @@ -0,0 +1,24 @@ +// Copyright 2016-2022 The Libsacloud Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validate + +import "github.com/go-playground/validator/v10" + +var v = validator.New() + +// Struct go-playground/validatorを利用してバリデーションを行う +func Struct(s interface{}) error { + return v.Struct(s) +} diff --git a/service/validate/validate_test.go b/service/validate/validate_test.go new file mode 100644 index 0000000..3ccb280 --- /dev/null +++ b/service/validate/validate_test.go @@ -0,0 +1,31 @@ +// Copyright 2016-2022 The Libsacloud Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validate + +import ( + "fmt" + "testing" +) + +type Foo struct { + Required string `validate:"required"` +} + +func TestValidator_Struct(t *testing.T) { + err := Struct(&Foo{}) + + fmt.Println(err) + // Output: Key: 'Foo.Required' Error:Field validation for 'Required' failed on the 'required' tag +}