diff --git a/cmd/climc/shell/cloudaccounts.go b/cmd/climc/shell/cloudaccounts.go index 568bd14544..46c6ca65d7 100644 --- a/cmd/climc/shell/cloudaccounts.go +++ b/cmd/climc/shell/cloudaccounts.go @@ -16,6 +16,7 @@ package shell import ( "fmt" + "io/ioutil" "yunion.io/x/jsonutils" @@ -117,6 +118,29 @@ func init() { return nil }) + R(&options.SGoogleCloudAccountCreateOptions{}, "cloud-account-create-google", "Create a Google cloud account", func(s *mcclient.ClientSession, args *options.SGoogleCloudAccountCreateOptions) error { + params := jsonutils.Marshal(args) + params.(*jsonutils.JSONDict).Add(jsonutils.NewString("Google"), "provider") + data, err := ioutil.ReadFile(args.GoogleJsonFile) + if err != nil { + return err + } + authParams, err := jsonutils.Parse(data) + if err != nil { + return err + } + err = jsonutils.Update(params, authParams) + if err != nil { + return err + } + result, err := modules.Cloudaccounts.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + R(&options.SAWSCloudAccountCreateOptions{}, "cloud-account-create-aws", "Create an AWS cloud account", func(s *mcclient.ClientSession, args *options.SAWSCloudAccountCreateOptions) error { params := jsonutils.Marshal(args).(*jsonutils.JSONDict) options := jsonutils.NewDict() diff --git a/cmd/climc/shell/globalnetworks.go b/cmd/climc/shell/globalnetworks.go new file mode 100644 index 0000000000..99f5b3a8d2 --- /dev/null +++ b/cmd/climc/shell/globalnetworks.go @@ -0,0 +1,51 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type GlobalNetworkListOptions struct { + options.BaseListOptions + } + R(&GlobalNetworkListOptions{}, "global-network-list", "List global networks", func(s *mcclient.ClientSession, args *GlobalNetworkListOptions) error { + params, err := options.ListStructToParams(args) + if err != nil { + return err + } + result, err := modules.GlobalNetworks.List(s, params) + if err != nil { + return err + } + printList(result, modules.GlobalNetworks.GetColumns(s)) + return nil + }) + + type GlobalNetworkShowOptions struct { + ID string `help:"ID or Name of globalnetwork"` + } + R(&GlobalNetworkShowOptions{}, "global-network-show", "Show details of a global network", func(s *mcclient.ClientSession, args *GlobalNetworkShowOptions) error { + result, err := modules.GlobalNetworks.GetById(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) +} diff --git a/cmd/googlecli/main.go b/cmd/googlecli/main.go new file mode 100644 index 0000000000..e4821c5272 --- /dev/null +++ b/cmd/googlecli/main.go @@ -0,0 +1,147 @@ +// Copyright 2019 Yunion +// +// 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 main + +import ( + "fmt" + "os" + + "yunion.io/x/log" + "yunion.io/x/structarg" + + "yunion.io/x/onecloud/pkg/multicloud/google" + _ "yunion.io/x/onecloud/pkg/multicloud/google/shell" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +type BaseOptions struct { + Debug bool `help:"debug mode"` + Help bool `help:"Show help"` + ClientEmail string `help:"Client email" default:"$GOOGLE_CLIENT_EMAIL"` + ProjectID string `help:"Project ID" default:"$GOOGLE_PROJECT_ID"` + PrivateKeyID string `help:"Private Key ID" default:"$GOOGLE_PRIVATE_KEY_ID"` + PrivateKey string `help:"Private Key" default:"$GOOGLE_PRIVATE_KEY"` + RegionID string `help:"RegionID" default:"$GOOGLE_REGION"` + SUBCOMMAND string `help:"googlecli subcommand" subcommand:"true"` +} + +func getSubcommandParser() (*structarg.ArgumentParser, error) { + parse, e := structarg.NewArgumentParser(&BaseOptions{}, + "googlecli", + "Command-line interface to google API.", + `See "googlecli help COMMAND" for help on a specific command.`) + + if e != nil { + return nil, e + } + + subcmd := parse.GetSubcommand() + if subcmd == nil { + return nil, fmt.Errorf("No subcommand argument.") + } + type HelpOptions struct { + SUBCOMMAND string `help:"sub-command name"` + } + shellutils.R(&HelpOptions{}, "help", "Show help of a subcommand", func(args *HelpOptions) error { + helpstr, e := subcmd.SubHelpString(args.SUBCOMMAND) + if e != nil { + return e + } else { + fmt.Print(helpstr) + return nil + } + }) + for _, v := range shellutils.CommandTable { + _, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback) + if e != nil { + return nil, e + } + } + return parse, nil +} + +func showErrorAndExit(e error) { + log.Errorf("%s", e) + os.Exit(1) +} + +func newClient(options *BaseOptions) (*google.SRegion, error) { + if len(options.ClientEmail) == 0 { + return nil, fmt.Errorf("Missing ClientEmail") + } + + if len(options.PrivateKeyID) == 0 { + return nil, fmt.Errorf("Missing PrivateKeyID") + } + + if len(options.PrivateKey) == 0 { + return nil, fmt.Errorf("Missing PrivateKey") + } + + if len(options.ProjectID) == 0 { + return nil, fmt.Errorf("Missing ProjectID") + } + + cli, err := google.NewGoogleClient("", "", options.ProjectID, options.ClientEmail, options.PrivateKeyID, options.PrivateKey, options.Debug) + if err != nil { + return nil, err + } + + region := cli.GetRegion(options.RegionID) + if region == nil { + return nil, fmt.Errorf("No such region %s", options.RegionID) + } + + return region, nil +} + +func main() { + parser, e := getSubcommandParser() + if e != nil { + showErrorAndExit(e) + } + e = parser.ParseArgs(os.Args[1:], false) + options := parser.Options().(*BaseOptions) + + if options.Help { + fmt.Print(parser.HelpString()) + return + } + subcmd := parser.GetSubcommand() + subparser := subcmd.GetSubParser() + if e != nil { + if subparser != nil { + fmt.Print(subparser.Usage()) + } else { + fmt.Print(parser.Usage()) + } + showErrorAndExit(e) + return + } + suboptions := subparser.Options() + if options.SUBCOMMAND == "help" { + e = subcmd.Invoke(suboptions) + } else { + var region *google.SRegion + region, e = newClient(options) + if e != nil { + showErrorAndExit(e) + } + e = subcmd.Invoke(region, suboptions) + } + if e != nil { + showErrorAndExit(e) + } +} diff --git a/go.mod b/go.mod index 5e166b10db..09922426bf 100644 --- a/go.mod +++ b/go.mod @@ -115,6 +115,7 @@ require ( go.uber.org/atomic v1.4.0 // indirect golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc golang.org/x/net v0.0.0-20191007182048-72f939374954 + golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sys v0.0.0-20191008105621-543471e840be golang.zx2c4.com/wireguard/wgctrl v0.0.0-20191008142428-8d021180e987 diff --git a/pkg/apis/compute/cloudaccount.go b/pkg/apis/compute/cloudaccount.go index 3c04f83839..d59cce942e 100644 --- a/pkg/apis/compute/cloudaccount.go +++ b/pkg/apis/compute/cloudaccount.go @@ -45,6 +45,11 @@ type CloudaccountCredentialInput struct { AppId string //Qcloud SecretId string //Qcloud SecretKey string //Qcloud + + ClientEmail string //Google + ProjectId string //Google + PrivateKeyId string //Google + PrivateKey string //Google } type CloudaccountCreateInput struct { diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index 5e9a1a7285..a882a42a58 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -39,6 +39,7 @@ const ( CLOUD_PROVIDER_OPENSTACK = "OpenStack" CLOUD_PROVIDER_UCLOUD = "Ucloud" CLOUD_PROVIDER_ZSTACK = "ZStack" + CLOUD_PROVIDER_GOOGLE = "Google" CLOUD_PROVIDER_GENERICS3 = "S3" CLOUD_PROVIDER_CEPH = "Ceph" @@ -82,6 +83,7 @@ var ( CLOUD_PROVIDER_OPENSTACK, CLOUD_PROVIDER_UCLOUD, CLOUD_PROVIDER_ZSTACK, + CLOUD_PROVIDER_GOOGLE, } ) diff --git a/pkg/apis/compute/cloudregions_const.go b/pkg/apis/compute/cloudregions_const.go index 97e910c59c..9548623fce 100644 --- a/pkg/apis/compute/cloudregions_const.go +++ b/pkg/apis/compute/cloudregions_const.go @@ -31,6 +31,7 @@ const ( CITY_HONG_KONG = "Hongkong" //香港 CITY_NING_XIA = "Ningxia" //宁夏 CITY_GUANG_ZHOU = "Guangzhou" //广州 + CITY_TAI_WAN = "Taiwan" //台湾 CITY_GUI_YANG = "Guiyang" //贵阳 CITY_TAIPEI = "Taipei" //台北市 CITY_KAOHSIUNG = "Kaohsiung" //高雄市 @@ -49,6 +50,15 @@ const ( CITY_YARRALUMLA = "Yarralumla" //亚拉伦拉 CITY_MELBOURNE = "Melbourne" //墨尔本 + //芬兰 + CITY_FINLAND = "Finland" + + //比利时 + CITY_BELGIUM = "Belgium" //比利时 + + //瑞士 + CITY_ZURICH = "Zurich" //苏黎世 + // 马来西亚 CITY_KUALA_LUMPUR = "Kuala Lumpur" //吉隆坡 @@ -61,20 +71,21 @@ const ( CITY_MAHARASHTRA = "Maharashtra" //马哈拉施特拉邦 // 美国 - CITY_VIRGINIA = "Virginia" //弗吉尼亚 - CITY_SILICONVALLEY = "Siliconvalley" //硅谷 - CITY_OHIO = "Ohio" //俄亥俄州 - CITY_N_VIRGINIA = "N. Virginia" //北弗吉尼亚 - CITY_N_CALIFORNIA = "N. California" //北加州 - CITY_OREGON = "Oregon" //俄勒冈州 - CITY_LOS_ANGELES = "Los Angeles" //洛杉矶 - CITY_SAN_FRANCISCO = "San Francisco" //旧金山 - CITY_UTAH = "Utah" //犹他州 - CITY_WASHINGTON = "Washington" //华盛顿 - CITY_TEXAS = "Texas" //德克萨斯 - CITY_CHICAGO = "Chicago" //芝加哥 - CITY_IOWA = "Iowa" //爱荷华 - CITY_US_GOV_WEST = "us-gov-west" //??? + CITY_VIRGINIA = "Virginia" //弗吉尼亚 + CITY_SILICONVALLEY = "Siliconvalley" //硅谷 + CITY_OHIO = "Ohio" //俄亥俄州 + CITY_N_VIRGINIA = "N. Virginia" //北弗吉尼亚 + CITY_N_CALIFORNIA = "N. California" //北加州 + CITY_OREGON = "Oregon" //俄勒冈州 + CITY_LOS_ANGELES = "Los Angeles" //洛杉矶 + CITY_SAN_FRANCISCO = "San Francisco" //旧金山 + CITY_UTAH = "Utah" //犹他州 + CITY_WASHINGTON = "Washington" //华盛顿 + CITY_TEXAS = "Texas" //德克萨斯 + CITY_CHICAGO = "Chicago" //芝加哥 + CITY_IOWA = "Iowa" //爱荷华 + CITY_US_GOV_WEST = "us-gov-west" //??? + CITY_SOUTH_CAROLINA = "South Carolina" //南卡罗来纳州 // 英国 CITY_LONDON = "London" //伦敦 @@ -95,6 +106,7 @@ const ( CITY_CANADA_CENTRAL = "Canada Central" //加拿大中部 CITY_QUEBEC = "Quebec" //魁北克市 CITY_TORONTO = "Toronto" //多伦多 + CITY_MONTREAL = "Montreal" //蒙特利尔 // 爱尔兰 CITY_IRELAND = "Ireland" //爱尔兰 @@ -154,4 +166,5 @@ const ( COUNTRY_CODE_RU = "RU" //俄罗斯 COUNTRY_CODE_NG = "NG" //尼日利亚 COUNTRY_CODE_VN = "VN" //越南 + COUNTRY_CODE_CH = "CH" //瑞士 ) diff --git a/pkg/apis/compute/globalnetwork_const.go b/pkg/apis/compute/globalnetwork_const.go new file mode 100644 index 0000000000..8a471f5d14 --- /dev/null +++ b/pkg/apis/compute/globalnetwork_const.go @@ -0,0 +1,20 @@ +// Copyright 2019 Yunion +// +// 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 compute + +const ( + GLOBAL_NETWORK_STATUS_AVAILABLE = "available" + GLOBAL_NETWORK_STATUS_UNKNOWN = "unknown" +) diff --git a/pkg/apis/compute/guest_const.go b/pkg/apis/compute/guest_const.go index 8574b3c3f4..56b4de140a 100644 --- a/pkg/apis/compute/guest_const.go +++ b/pkg/apis/compute/guest_const.go @@ -140,6 +140,7 @@ const ( HYPERVISOR_OPENSTACK = "openstack" HYPERVISOR_UCLOUD = "ucloud" HYPERVISOR_ZSTACK = "zstack" + HYPERVISOR_GOOGLE = "google" // HYPERVISOR_DEFAULT = HYPERVISOR_KVM HYPERVISOR_DEFAULT = HYPERVISOR_KVM @@ -161,6 +162,7 @@ var HYPERVISORS = []string{ HYPERVISOR_OPENSTACK, HYPERVISOR_UCLOUD, HYPERVISOR_ZSTACK, + HYPERVISOR_GOOGLE, } var ONECLOUD_HYPERVISORS = []string{ @@ -176,6 +178,7 @@ var PUBLIC_CLOUD_HYPERVISORS = []string{ HYPERVISOR_QCLOUD, HYPERVISOR_HUAWEI, HYPERVISOR_UCLOUD, + HYPERVISOR_GOOGLE, } var PRIVATE_CLOUD_HYPERVISORS = []string{ @@ -198,6 +201,7 @@ var HYPERVISOR_HOSTTYPE = map[string]string{ HYPERVISOR_OPENSTACK: HOST_TYPE_OPENSTACK, HYPERVISOR_UCLOUD: HOST_TYPE_UCLOUD, HYPERVISOR_ZSTACK: HOST_TYPE_ZSTACK, + HYPERVISOR_GOOGLE: HOST_TYPE_GOOGLE, } var HOSTTYPE_HYPERVISOR = map[string]string{ @@ -213,6 +217,7 @@ var HOSTTYPE_HYPERVISOR = map[string]string{ HOST_TYPE_OPENSTACK: HYPERVISOR_OPENSTACK, HOST_TYPE_UCLOUD: HYPERVISOR_UCLOUD, HOST_TYPE_ZSTACK: HYPERVISOR_ZSTACK, + HOST_TYPE_GOOGLE: HYPERVISOR_GOOGLE, } const ( diff --git a/pkg/apis/compute/host_const.go b/pkg/apis/compute/host_const.go index 164bd72fdc..b7d7638fe2 100644 --- a/pkg/apis/compute/host_const.go +++ b/pkg/apis/compute/host_const.go @@ -31,6 +31,7 @@ const ( HOST_TYPE_OPENSTACK = "openstack" HOST_TYPE_UCLOUD = "ucloud" HOST_TYPE_ZSTACK = "zstack" + HOST_TYPE_GOOGLE = "google" HOST_TYPE_DEFAULT = HOST_TYPE_HYPERVISOR diff --git a/pkg/apis/compute/network_const.go b/pkg/apis/compute/network_const.go index 6ee24fa77a..db596b6e17 100644 --- a/pkg/apis/compute/network_const.go +++ b/pkg/apis/compute/network_const.go @@ -56,6 +56,7 @@ var ( REGIONAL_NETWORK_PROVIDERS = []string{ CLOUD_PROVIDER_HUAWEI, CLOUD_PROVIDER_UCLOUD, + CLOUD_PROVIDER_GOOGLE, } ) diff --git a/pkg/apis/compute/storage_const.go b/pkg/apis/compute/storage_const.go index 26ac291f7f..c2f78b18b3 100644 --- a/pkg/apis/compute/storage_const.go +++ b/pkg/apis/compute/storage_const.go @@ -70,6 +70,11 @@ const ( // Zstack storage type STORAGE_ZSTACK_LOCAL_STORAGE = "localstorage" STORAGE_ZSTACK_CEPH = "ceph" + + // Google storage type + STORAGE_GOOGLE_LOCAL_STORAGE = "local-storage" //本地SSD暂存盘 (最多8个) + STORAGE_GOOGLE_PD_STANDARD = "pd-standard" //标准永久性磁盘 + STORAGE_GOOGLE_PD_SSD = "pd-ssd" //SSD永久性磁盘 ) const ( diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index 6a1514110a..22f37bc169 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -73,6 +73,9 @@ type ICloudProvider interface { GetIProjects() ([]ICloudProject, error) GetIRegionById(id string) (ICloudRegion, error) + GetIGlobalnetworks() ([]ICloudGlobalnetwork, error) + GetIGlobalnetworkById(id string) (ICloudGlobalnetwork, error) + GetOnPremiseIRegion() (ICloudRegion, error) GetBalance() (float64, string, error) diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index bb1b53ae2c..77b33363be 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -427,6 +427,7 @@ type ICloudVpc interface { GetIWireById(wireId string) (ICloudWire, error) GetINatGateways() ([]ICloudNatGateway, error) + GetIGlobalNetworkId() string } type ICloudWire interface { @@ -930,3 +931,7 @@ type ICloudEvent interface { GetCreatedAt() time.Time } + +type ICloudGlobalnetwork interface { + ICloudResource +} diff --git a/pkg/compute/guestdrivers/google.go b/pkg/compute/guestdrivers/google.go new file mode 100644 index 0000000000..a1d0aba747 --- /dev/null +++ b/pkg/compute/guestdrivers/google.go @@ -0,0 +1,52 @@ +// Copyright 2019 Yunion +// +// 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 guestdrivers + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SGoogleGuestDriver struct { + SManagedVirtualizedGuestDriver +} + +func init() { + driver := SGoogleGuestDriver{} + models.RegisterGuestDriver(&driver) +} + +func (self *SGoogleGuestDriver) GetQuotaPlatformID() []string { + return []string{ + api.CLOUD_ENV_PUBLIC_CLOUD, + api.CLOUD_PROVIDER_GOOGLE, + } +} + +func (self *SGoogleGuestDriver) GetHypervisor() string { + return api.HYPERVISOR_GOOGLE +} + +func (self *SGoogleGuestDriver) GetProvider() string { + return api.CLOUD_PROVIDER_GOOGLE +} + +func (self *SGoogleGuestDriver) GetDefaultSysDiskBackend() string { + return api.STORAGE_GOOGLE_PD_STANDARD +} + +func (self *SGoogleGuestDriver) GetMinimalSysDiskSizeGb() int { + return 10 +} diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index c6da273c90..f5f21916e7 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -301,7 +301,10 @@ func (self *SManagedVirtualizedGuestDriver) RequestDeployGuestOnHost(ctx context return errors.Wrap(err, "vpc.GetRegion") } - vpcId := region.GetDriver().GetSecurityGroupVpcId(ctx, task.GetUserCred(), region, host, vpc, false) + vpcId, err := region.GetDriver().GetSecurityGroupVpcId(ctx, task.GetUserCred(), region, host, vpc, false) + if err != nil { + return errors.Wrap(err, "GetSecurityGroupVpcId") + } secgroups := guest.GetSecgroups() for i, secgroup := range secgroups { @@ -912,7 +915,10 @@ func (self *SManagedVirtualizedGuestDriver) RequestSyncSecgroupsOnHost(ctx conte region := host.GetRegion() - vpcId := region.GetDriver().GetSecurityGroupVpcId(ctx, task.GetUserCred(), region, host, vpc, false) + vpcId, err := region.GetDriver().GetSecurityGroupVpcId(ctx, task.GetUserCred(), region, host, vpc, false) + if err != nil { + return errors.Wrap(err, "GetSecurityGroupVpcId") + } secgroups := guest.GetSecgroups() externalIds := []string{} diff --git a/pkg/compute/hostdrivers/google.go b/pkg/compute/hostdrivers/google.go new file mode 100644 index 0000000000..cb7273a393 --- /dev/null +++ b/pkg/compute/hostdrivers/google.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// 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 hostdrivers + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SGoogleHostDriver struct { + SManagedVirtualizationHostDriver +} + +func init() { + driver := SGoogleHostDriver{} + models.RegisterHostDriver(&driver) +} + +func (self *SGoogleHostDriver) GetHostType() string { + return api.HOST_TYPE_GOOGLE +} + +func (self *SGoogleHostDriver) GetHypervisor() string { + return api.HYPERVISOR_GOOGLE +} diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index dc17d977a1..d4721a2129 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -81,7 +81,7 @@ type SCloudaccount struct { AccessUrl string `width:"64" charset:"ascii" nullable:"true" list:"domain" update:"domain" create:"domain_optional"` Account string `width:"128" charset:"ascii" nullable:"false" list:"domain" create:"domain_required"` // Column(VARCHAR(64, charset='ascii'), nullable=False) - Secret string `width:"256" charset:"ascii" nullable:"false" list:"domain" create:"domain_required"` // Column(VARCHAR(256, charset='ascii'), nullable=False) + Secret string `length:"0" charset:"ascii" nullable:"false" list:"domain" create:"domain_required"` // Google需要秘钥认证,需要此字段比较长 // BalanceKey string `width:"256" charset:"ascii" nullable:"true" list:"domain" update:"domain" create:"domain_optional"` @@ -1440,6 +1440,10 @@ func (account *SCloudaccount) syncAccountStatus(ctx context.Context, userCred mc log.Errorf("syncCloudproviderRegion fail %s", err) return err } + err = providers[i].syncCloudproviderGlobalnetworks(ctx, userCred) + if err != nil { + log.Errorf("failed to sync cloudprovider globalnetwork for %s %s error: %v", providers[i].Provider, providers[i].Name, err) + } } } return nil diff --git a/pkg/compute/models/cloudproviderregions.go b/pkg/compute/models/cloudproviderregions.go index 088a825bb6..cab38e49f5 100644 --- a/pkg/compute/models/cloudproviderregions.go +++ b/pkg/compute/models/cloudproviderregions.go @@ -356,9 +356,10 @@ func (self *SCloudproviderregion) DoSync(ctx context.Context, userCred mcclient. if localRegion.isManaged() { remoteRegion, err := driver.GetIRegionById(localRegion.ExternalId) - if err == nil { - err = syncPublicCloudProviderInfo(ctx, userCred, syncResults, provider, driver, localRegion, remoteRegion, &syncRange) + if err != nil { + return errors.Wrap(err, "GetIRegionById") } + err = syncPublicCloudProviderInfo(ctx, userCred, syncResults, provider, driver, localRegion, remoteRegion, &syncRange) } else { err = syncOnPremiseCloudProviderInfo(ctx, userCred, syncResults, provider, driver, &syncRange) } @@ -384,7 +385,10 @@ func (self *SCloudproviderregion) getSyncTaskKey() string { func (self *SCloudproviderregion) submitSyncTask(userCred mcclient.TokenCredential, syncRange SSyncRange, waitChan chan bool) { self.markStartSync(userCred) RunSyncCloudproviderRegionTask(self.getSyncTaskKey(), func() { - self.DoSync(context.Background(), userCred, syncRange) + err := self.DoSync(context.Background(), userCred, syncRange) + if err != nil { + log.Errorf("DoSync faild %v", err) + } if waitChan != nil { waitChan <- true } diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index 7be962b12b..0cfbf8710e 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -77,7 +77,7 @@ type SCloudprovider struct { AccessUrl string `width:"64" charset:"ascii" nullable:"true" list:"domain" update:"domain" create:"domain_optional"` Account string `width:"128" charset:"ascii" nullable:"false" list:"domain" create:"domain_required"` // Column(VARCHAR(64, charset='ascii'), nullable=False) - Secret string `width:"256" charset:"ascii" nullable:"false" list:"domain" create:"domain_required"` // Column(VARCHAR(256, charset='ascii'), nullable=False) + Secret string `length:"0" charset:"ascii" nullable:"false" list:"domain" create:"domain_required"` // Google需要秘钥认证,需要此字段比较长 CloudaccountId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` @@ -1085,6 +1085,25 @@ func (provider *SCloudprovider) markProviderConnected(ctx context.Context, userC return provider.ClearSchedDescCache() } +func (provider *SCloudprovider) syncCloudproviderGlobalnetworks(ctx context.Context, userCred mcclient.TokenCredential) error { + driver, err := provider.GetProvider() + if err != nil { + return err + } + if !driver.GetFactory().IsOnPremise() { + globalnetworks, err := driver.GetIGlobalnetworks() + if err != nil { + return errors.Wrap(err, "GetIGlobalnetworks") + } + result := GlobalNetworkManager.SyncGlobalnetworks(ctx, userCred, provider, globalnetworks) + if result.IsError() { + log.Errorf("syncGlobalnetworks fail %s", result.Result()) + } + return nil + } + return nil +} + func (provider *SCloudprovider) prepareCloudproviderRegions(ctx context.Context, userCred mcclient.TokenCredential) ([]SCloudproviderregion, error) { driver, err := provider.GetProvider() if err != nil { @@ -1181,6 +1200,7 @@ func (self *SCloudprovider) RealDelete(ctx context.Context, userCred mcclient.To DBInstanceBackupManager, ElasticcacheManager, VpcManager, + GlobalNetworkManager, ElasticipManager, NetworkInterfaceManager, CloudproviderRegionManager, diff --git a/pkg/compute/models/globalnetworks.go b/pkg/compute/models/globalnetworks.go new file mode 100644 index 0000000000..1754ec0bad --- /dev/null +++ b/pkg/compute/models/globalnetworks.go @@ -0,0 +1,233 @@ +// Copyright 2019 Yunion +// +// 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 models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SGlobalNetworkManager struct { + db.SEnabledStatusStandaloneResourceBaseManager +} + +var GlobalNetworkManager *SGlobalNetworkManager + +func init() { + GlobalNetworkManager = &SGlobalNetworkManager{ + SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager( + SGlobalNetwork{}, + "globalnetworks_tbl", + "globalnetwork", + "globalnetworks", + ), + } + GlobalNetworkManager.SetVirtualObject(GlobalNetworkManager) +} + +type SGlobalNetwork struct { + db.SEnabledStatusStandaloneResourceBase + db.SExternalizedResourceBase + + SManagedResourceBase + + Provider string `width:"64" charset:"ascii" list:"user"` +} + +func (manager *SGlobalNetworkManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + //current not support create + return false +} + +func (self *SGlobalNetwork) ValidateDeleteCondition(ctx context.Context) error { + return self.SEnabledStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SGlobalNetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + return self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query) +} + +func (self *SGlobalNetwork) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + return self.SEnabledStatusStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) +} + +func (manager *SGlobalNetworkManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data) +} + +func (self *SGlobalNetwork) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + return self.SEnabledStatusStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data) +} + +func (self *SGlobalNetwork) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + log.Infof("SGlobalNetwork delete do nothing") + self.SetStatus(userCred, api.NETWORK_STATUS_START_DELETE, "") + return nil +} + +func (self *SGlobalNetwork) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + if len(self.ExternalId) > 0 { + return self.StartDeleteGlobalNetworkTask(ctx, userCred) + } + return self.RealDelete(ctx, userCred) +} + +func (self *SGlobalNetwork) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + db.OpsLog.LogEvent(self, db.ACT_DELOCATE, self.GetShortDesc(ctx), userCred) + self.SetStatus(userCred, api.NETWORK_STATUS_DELETED, "real delete") + return self.SEnabledStatusStandaloneResourceBase.Delete(ctx, userCred) +} + +func (self *SGlobalNetwork) StartDeleteGlobalNetworkTask(ctx context.Context, userCred mcclient.TokenCredential) error { + task, err := taskman.TaskManager.NewTask(ctx, "GlobalNetworkDeleteTask", self, userCred, nil, "", "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (manager *SGlobalNetworkManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + return manager.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) +} + +func (self *SGlobalNetwork) ValidateUpdateCondition(ctx context.Context) error { + return self.SEnabledStatusStandaloneResourceBase.ValidateUpdateCondition(ctx) +} + +func (manager *SGlobalNetworkManager) GetGlobalNetworksByManagerId(id string) ([]SGlobalNetwork, error) { + q := manager.Query().Equals("manager_id", id) + q = q.Filter(sqlchemy.NOT(sqlchemy.IsNullOrEmpty(q.Field("external_id")))) + globalnetworks := []SGlobalNetwork{} + err := db.FetchModelObjects(manager, q, &globalnetworks) + if err != nil { + return nil, err + } + return globalnetworks, nil +} + +func (self *SGlobalNetwork) GetGlobalNetworkVpcs() ([]SGlobalnetworkVpc, error) { + gnvs := []SGlobalnetworkVpc{} + q := GlobalnetworkVpcManager.Query().Equals("globalnetwork_id", self.Id) + err := db.FetchModelObjects(GlobalnetworkVpcManager, q, &gnvs) + if err != nil { + return nil, err + } + return gnvs, nil +} + +func (manager *SGlobalNetworkManager) SyncGlobalnetworks(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, exts []cloudprovider.ICloudGlobalnetwork) compare.SyncResult { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + result := compare.SyncResult{} + + dbNetworks, err := manager.GetGlobalNetworksByManagerId(provider.Id) + if err != nil { + result.Error(err) + return result + } + + removed := make([]SGlobalNetwork, 0) + commondb := make([]SGlobalNetwork, 0) + commonext := make([]cloudprovider.ICloudGlobalnetwork, 0) + added := make([]cloudprovider.ICloudGlobalnetwork, 0) + err = compare.CompareSets(dbNetworks, exts, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "CompareSets")) + return result + } + for i := 0; i < len(removed); i += 1 { + err = removed[i].syncRemoveGlobalnetwork(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + for i := 0; i < len(commondb); i += 1 { + // update + err = commondb[i].syncWithCloudGlobalnetwork(ctx, userCred, provider, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + for i := 0; i < len(added); i += 1 { + err := manager.newFromCloudGlobalnetwork(ctx, userCred, provider, added[i]) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + return result +} + +func (self *SGlobalNetwork) syncWithCloudGlobalnetwork(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, ext cloudprovider.ICloudGlobalnetwork) error { + diff, err := db.UpdateWithLock(ctx, self, func() error { + self.Status = ext.GetStatus() + return nil + }) + if err != nil { + return errors.Wrap(err, "UpdateWithLock") + } + db.OpsLog.LogSyncUpdate(self, diff, userCred) + return nil +} + +func (self *SGlobalNetwork) syncRemoveGlobalnetwork(ctx context.Context, userCred mcclient.TokenCredential) error { + err := self.ValidateDeleteCondition(ctx) + if err != nil { + return err + } + return self.RealDelete(ctx, userCred) +} + +func (manager *SGlobalNetworkManager) newFromCloudGlobalnetwork(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, ext cloudprovider.ICloudGlobalnetwork) error { + network := SGlobalNetwork{} + network.SetModelManager(manager, &network) + + newName, err := db.GenerateName(manager, nil, ext.GetName()) + if err != nil { + return errors.Wrap(err, "GenerateName") + } + network.ExternalId = ext.GetGlobalId() + network.Name = newName + network.Status = ext.GetStatus() + network.Enabled = true + network.ManagerId = provider.Id + network.Provider = provider.Provider + + err = manager.TableSpec().Insert(&network) + if err != nil { + return errors.Wrap(err, "Insert") + } + db.OpsLog.LogEvent(&network, db.ACT_CREATE, network.GetShortDesc(ctx), userCred) + return nil +} diff --git a/pkg/compute/models/globalnetworkvpcs.go b/pkg/compute/models/globalnetworkvpcs.go new file mode 100644 index 0000000000..e80c88218b --- /dev/null +++ b/pkg/compute/models/globalnetworkvpcs.go @@ -0,0 +1,130 @@ +// Copyright 2019 Yunion +// +// 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 models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SGlobalnetworkVpcManager struct { + db.SJointResourceBaseManager +} + +var GlobalnetworkVpcManager *SGlobalnetworkVpcManager + +func init() { + db.InitManager(func() { + GlobalnetworkVpcManager = &SGlobalnetworkVpcManager{ + SJointResourceBaseManager: db.NewJointResourceBaseManager( + SGlobalnetworkVpc{}, + "globalnetworkvpcs_tbl", + "globalnetworkvpc", + "globalnetworkvpcs", + GlobalNetworkManager, + VpcManager, + ), + } + GlobalnetworkVpcManager.SetVirtualObject(GlobalnetworkVpcManager) + }) +} + +type SGlobalnetworkVpc struct { + db.SJointResourceBase + + GlobalnetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"` + VpcId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true"` +} + +func (manager *SGlobalnetworkVpcManager) GetMasterFieldName() string { + return "globalnetwork_id" +} + +func (manager *SGlobalnetworkVpcManager) GetSlaveFieldName() string { + return "vpc_id" +} + +func (joint *SGlobalnetworkVpc) Master() db.IStandaloneModel { + return db.JointMaster(joint) +} + +func (joint *SGlobalnetworkVpc) Slave() db.IStandaloneModel { + return db.JointSlave(joint) +} + +func (manager *SGlobalnetworkVpcManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsAdminAllowList(userCred, manager) +} + +func (manager *SGlobalnetworkVpcManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowCreate(userCred, manager) +} + +func (manager *SGlobalnetworkVpcManager) AllowListDescendent(ctx context.Context, userCred mcclient.TokenCredential, model db.IStandaloneModel, query jsonutils.JSONObject) bool { + return db.IsAdminAllowList(userCred, manager) +} + +func (manager *SGlobalnetworkVpcManager) AllowAttach(ctx context.Context, userCred mcclient.TokenCredential, master db.IStandaloneModel, slave db.IStandaloneModel) bool { + return db.IsAdminAllowCreate(userCred, manager) +} + +func (self *SGlobalnetworkVpc) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsAdminAllowGet(userCred, self) +} + +func (self *SGlobalnetworkVpc) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + return db.IsAdminAllowUpdate(userCred, self) +} + +func (self *SGlobalnetworkVpc) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowDelete(userCred, self) +} + +func (self *SGlobalnetworkVpc) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DetachJoint(ctx, userCred, self) +} + +func (self *SGlobalnetworkVpc) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + return self.SJointResourceBase.GetCustomizeColumns(ctx, userCred, query) +} + +func (self *SGlobalnetworkVpc) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + return self.SJointResourceBase.GetExtraDetails(ctx, userCred, query) +} + +func (manager *SGlobalnetworkVpcManager) NewGlobalnetworkVpc(vpc *SVpc, globalnetwork *SGlobalNetwork) error { + q := manager.Query().Equals("vpc_id", vpc.Id).Equals("globalnetwork_id", globalnetwork.Id) + count, err := q.CountWithError() + if err != nil { + return errors.Wrap(err, "CountWithError") + } + if count > 1 { + return sqlchemy.ErrDuplicateEntry + } + if count == 1 { + return nil + } + gv := &SGlobalnetworkVpc{} + gv.SetModelManager(manager, gv) + gv.VpcId = vpc.Id + gv.GlobalnetworkId = globalnetwork.Id + return manager.TableSpec().Insert(gv) +} diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index 418cc28973..c216c8bdb2 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -1620,3 +1620,56 @@ func (manager *SSecurityGroupCacheManager) purgeAll(ctx context.Context, userCre } return nil } + +func (gnv *SGlobalnetworkVpc) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, gnv) + defer lockman.ReleaseObject(ctx, gnv) + + return gnv.Delete(ctx, userCred) +} + +func (gn *SGlobalNetwork) purgeGlobalNetworkVpcs(ctx context.Context, userCred mcclient.TokenCredential) error { + globalnetworkVpcs, err := gn.GetGlobalNetworkVpcs() + if err != nil { + return errors.Wrap(err, "gn.GetGlobalNetworkVpcs") + } + for i := range globalnetworkVpcs { + err = globalnetworkVpcs[i].purge(ctx, userCred) + if err != nil { + return errors.Wrap(err, "globalnetworkVpcs[i].purge") + } + } + return nil +} + +func (gn *SGlobalNetwork) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, gn) + defer lockman.ReleaseObject(ctx, gn) + + err := gn.purgeGlobalNetworkVpcs(ctx, userCred) + if err != nil { + return errors.Wrap(err, "gn.purgeGlobalNetworkVpcs") + } + + err = gn.ValidateDeleteCondition(ctx) + if err != nil { + return errors.Wrapf(err, "globalnetwork %s(%s)", gn.Name, gn.Id) + } + + return gn.RealDelete(ctx, userCred) +} + +func (manager *SGlobalNetworkManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { + globalnetworks := []SGlobalNetwork{} + err := fetchByManagerId(manager, providerId, &globalnetworks) + if err != nil { + return err + } + for i := range globalnetworks { + err := globalnetworks[i].purge(ctx, userCred) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index e89797bf3b..7388cfde20 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -117,8 +117,9 @@ type IRegionDriver interface { RequestSyncSecurityGroup(ctx context.Context, userCred mcclient.TokenCredential, vpcId string, vpc *SVpc, secgroup *SSecurityGroup) (string, error) IsSupportClassicSecurityGroup() bool IsSecurityGroupBelongVpc() bool + IsSecurityGroupBelongGlobalNetwork() bool //安全组子账号范围内可用 GetDefaultSecurityGroupVpcId() string - GetSecurityGroupVpcId(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, host *SHost, vpc *SVpc, classic bool) string + GetSecurityGroupVpcId(ctx context.Context, userCred mcclient.TokenCredential, region *SCloudregion, host *SHost, vpc *SVpc, classic bool) (string, error) ValidateCreateDBInstanceData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, input *api.SDBInstanceCreateInput, skus []SDBInstanceSku, network *SNetwork) (*api.SDBInstanceCreateInput, error) ValidateCreateDBInstanceAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, instance *SDBInstance, input *api.SDBInstanceAccountCreateInput) (*api.SDBInstanceAccountCreateInput, error) diff --git a/pkg/compute/models/secgroupcache.go b/pkg/compute/models/secgroupcache.go index 6d976ef661..b9e115279a 100644 --- a/pkg/compute/models/secgroupcache.go +++ b/pkg/compute/models/secgroupcache.go @@ -223,10 +223,21 @@ func (manager *SSecurityGroupCacheManager) SyncSecurityGroupCaches(ctx context.C } vpcId := "" - if region.GetDriver().IsSecurityGroupBelongVpc() { + if region.GetDriver().IsSecurityGroupBelongGlobalNetwork() { //globalnetwork没有region属性 + vpcId, err = region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, false) + if err != nil { + syncResult.Error(errors.Wrap(err, "GetSecurityGroupVpcId")) + return localSecgroups, remoteSecgroups, syncResult + } + region = nil + } else if region.GetDriver().IsSecurityGroupBelongVpc() { vpcId = vpc.ExternalId } else if region.GetDriver().IsSupportClassicSecurityGroup() && len(secgroups) > 0 { - vpcId = region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, secgroups[0].GetVpcId() == "classic") + vpcId, err = region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, secgroups[0].GetVpcId() == "classic") + if err != nil { + syncResult.Error(errors.Wrap(err, "GetSecurityGroupVpcId")) + return localSecgroups, remoteSecgroups, syncResult + } } else { vpcId = region.GetDriver().GetDefaultSecurityGroupVpcId() } diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index 14afe8fdd8..52a61eba21 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -347,6 +347,13 @@ func (manager *SVpcManager) SyncVPCs(ctx context.Context, userCred mcclient.Toke remoteVPCs = append(remoteVPCs, commonext[i]) syncResult.Update() } + globalnetworkId := commonext[i].GetIGlobalNetworkId() + if len(globalnetworkId) > 0 { + err := commondb[i].checkAndSetGlobalNetwork(globalnetworkId) + if err != nil { + log.Errorf("failed to set globalnetwork for %s error: %v", globalnetworkId, err) + } + } } for i := 0; i < len(added); i += 1 { new, err := manager.newFromCloudVpc(ctx, userCred, added[i], provider, region) @@ -358,6 +365,13 @@ func (manager *SVpcManager) SyncVPCs(ctx context.Context, userCred mcclient.Toke remoteVPCs = append(remoteVPCs, added[i]) syncResult.Add() } + globalnetworkId := added[i].GetIGlobalNetworkId() + if len(globalnetworkId) > 0 { + err := new.checkAndSetGlobalNetwork(globalnetworkId) + if err != nil { + log.Errorf("failed to set globalnetwork for %s error: %v", globalnetworkId, err) + } + } } return localVPCs, remoteVPCs, syncResult @@ -400,6 +414,14 @@ func (self *SVpc) SyncWithCloudVpc(ctx context.Context, userCred mcclient.TokenC return nil } +func (vpc *SVpc) checkAndSetGlobalNetwork(globalnetworkId string) error { + globalnetwork, err := db.FetchByExternalId(GlobalNetworkManager, globalnetworkId) + if err != nil { + return errors.Wrap(err, "FetchByExternalId") + } + return GlobalnetworkVpcManager.NewGlobalnetworkVpc(vpc, globalnetwork.(*SGlobalNetwork)) +} + func (manager *SVpcManager) newFromCloudVpc(ctx context.Context, userCred mcclient.TokenCredential, extVPC cloudprovider.ICloudVpc, provider *SCloudprovider, region *SCloudregion) (*SVpc, error) { vpc := SVpc{} vpc.SetModelManager(manager, &vpc) @@ -787,3 +809,27 @@ func (vpc *SVpc) StartVpcSyncstatusTask(ctx context.Context, userCred mcclient.T task.ScheduleRun(nil) return nil } + +func (vpc *SVpc) GetGlobalNetwork() (*SGlobalNetwork, error) { + gv := GlobalnetworkVpcManager.Query().SubQuery() + q := GlobalNetworkManager.Query() + q.Join(gv, sqlchemy.Equals(q.Field("id"), gv.Field("globalnetwork_id"))) + q = q.Filter(sqlchemy.Equals(gv.Field("vpc_id"), vpc.Id)) + count, err := q.CountWithError() + if err != nil { + return nil, errors.Wrap(err, "CountWithError") + } + if count > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } + if count == 0 { + return nil, sql.ErrNoRows + } + globalnetwork := &SGlobalNetwork{} + globalnetwork.SetModelManager(GlobalNetworkManager, globalnetwork) + err = q.First(globalnetwork) + if err != nil { + return nil, errors.Wrap(err, "First") + } + return globalnetwork, nil +} diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index c86f7173d2..48022676fc 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -215,12 +215,16 @@ func (self *SBaseRegionDriver) IsSecurityGroupBelongVpc() bool { return false } +func (self *SBaseRegionDriver) IsSecurityGroupBelongGlobalNetwork() bool { + return false +} + func (self *SBaseRegionDriver) GetDefaultSecurityGroupVpcId() string { return api.NORMAL_VPC_ID } -func (self *SBaseRegionDriver) GetSecurityGroupVpcId(ctx context.Context, userCred mcclient.TokenCredential, region *models.SCloudregion, host *models.SHost, vpc *models.SVpc, classic bool) string { - return "" +func (self *SBaseRegionDriver) GetSecurityGroupVpcId(ctx context.Context, userCred mcclient.TokenCredential, region *models.SCloudregion, host *models.SHost, vpc *models.SVpc, classic bool) (string, error) { + return "", cloudprovider.ErrNotImplemented } func (self *SBaseRegionDriver) RequestSyncSecurityGroup(ctx context.Context, userCred mcclient.TokenCredential, vpcId string, vpc *models.SVpc, secgroup *models.SSecurityGroup) (string, error) { diff --git a/pkg/compute/regiondrivers/google.go b/pkg/compute/regiondrivers/google.go new file mode 100644 index 0000000000..54a34aeb7d --- /dev/null +++ b/pkg/compute/regiondrivers/google.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// 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 regiondrivers + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SGoogleRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SGoogleRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SGoogleRegionDriver) GetProvider() string { + return api.CLOUD_PROVIDER_GOOGLE +} + +func (self *SGoogleRegionDriver) IsSecurityGroupBelongGlobalNetwork() bool { + return true +} diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index eca4cfff92..bae8b99f6e 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -1384,13 +1384,19 @@ func (self *SManagedVirtualizationRegionDriver) BindIPToNatgatewayRollback(ctx c return nil } -func (self *SManagedVirtualizationRegionDriver) GetSecurityGroupVpcId(ctx context.Context, userCred mcclient.TokenCredential, region *models.SCloudregion, host *models.SHost, vpc *models.SVpc, classic bool) string { - if region.GetDriver().IsSupportClassicSecurityGroup() && (classic || (host != nil && strings.HasSuffix(host.Name, "-classic"))) { - return "classic" +func (self *SManagedVirtualizationRegionDriver) GetSecurityGroupVpcId(ctx context.Context, userCred mcclient.TokenCredential, region *models.SCloudregion, host *models.SHost, vpc *models.SVpc, classic bool) (string, error) { + if region.GetDriver().IsSecurityGroupBelongGlobalNetwork() { + globalnetwork, err := vpc.GetGlobalNetwork() + if err != nil { + return "", errors.Wrap(err, "vpc.GetGlobalNetwork") + } + return globalnetwork.ExternalId, nil + } else if region.GetDriver().IsSupportClassicSecurityGroup() && (classic || (host != nil && strings.HasSuffix(host.Name, "-classic"))) { + return "classic", nil } else if region.GetDriver().IsSecurityGroupBelongVpc() { - return vpc.ExternalId + return vpc.ExternalId, nil } - return region.GetDriver().GetDefaultSecurityGroupVpcId() + return region.GetDriver().GetDefaultSecurityGroupVpcId(), nil } func (self *SManagedVirtualizationRegionDriver) RequestSyncSecurityGroup(ctx context.Context, userCred mcclient.TokenCredential, vpcId string, vpc *models.SVpc, secgroup *models.SSecurityGroup) (string, error) { @@ -1473,7 +1479,10 @@ func (self *SManagedVirtualizationRegionDriver) RequestSyncSecurityGroup(ctx con func (self *SManagedVirtualizationRegionDriver) RequestCacheSecurityGroup(ctx context.Context, userCred mcclient.TokenCredential, region *models.SCloudregion, vpc *models.SVpc, secgroup *models.SSecurityGroup, classic bool, task taskman.ITask) error { - vpcId := region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, classic) + vpcId, err := region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, classic) + if err != nil { + return errors.Wrap(err, "GetSecurityGroupVpcId") + } taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { _, err := self.RequestSyncSecurityGroup(ctx, userCred, vpcId, vpc, secgroup) return nil, err @@ -1533,7 +1542,10 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx cont secgroup, _ := dbinstance.GetSecgroup() if secgroup != nil { - vpcId := region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, false) + vpcId, err := region.GetDriver().GetSecurityGroupVpcId(ctx, userCred, region, nil, vpc, false) + if err != nil { + return nil, errors.Wrap(err, "GetSecurityGroupVpcId") + } desc.SecgroupId, err = region.GetDriver().RequestSyncSecurityGroup(ctx, userCred, vpcId, vpc, secgroup) if err != nil { return nil, errors.Wrap(err, "SyncSecurityGroup") diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index c7ab46a312..4ac9629103 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -136,6 +136,7 @@ func InitHandlers(app *appsrv.Application) { models.ElasticcacheParameterManager, models.ElasticcacheBackupManager, models.ElasticcacheSkuManager, + models.GlobalNetworkManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) @@ -160,6 +161,7 @@ func InitHandlers(app *appsrv.Application) { models.NetworkinterfacenetworkManager, models.SnapshotPolicyDiskManager, models.InstanceSnapshotJointManager, + models.GlobalnetworkVpcManager, } { db.RegisterModelManager(manager) handler := db.NewJointModelHandler(manager) diff --git a/pkg/mcclient/modules/mod_globalnetworks.go b/pkg/mcclient/modules/mod_globalnetworks.go new file mode 100644 index 0000000000..ce70a8486b --- /dev/null +++ b/pkg/mcclient/modules/mod_globalnetworks.go @@ -0,0 +1,35 @@ +// Copyright 2019 Yunion +// +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type GlobalNetworkManager struct { + modulebase.ResourceManager +} + +var ( + GlobalNetworks GlobalNetworkManager +) + +func init() { + GlobalNetworks = GlobalNetworkManager{NewComputeManager("globalnetwork", "globalnetworks", + []string{}, + []string{"ID", "Name", "Description"})} + + registerCompute(&GlobalNetworks) +} diff --git a/pkg/mcclient/modules/mod_globalnetworkvpcs.go b/pkg/mcclient/modules/mod_globalnetworkvpcs.go new file mode 100644 index 0000000000..43f3df31b1 --- /dev/null +++ b/pkg/mcclient/modules/mod_globalnetworkvpcs.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + GlobalnetworkVpcs modulebase.JointResourceManager +) + +func init() { + GlobalnetworkVpcs = NewJointComputeManager( + "globalnetworkvpc", + "globalnetworkvpcs", + []string{"Globalnetwork_Id", "Globalnetwork", + "Vpc_Id", "Vpc"}, + []string{}, + &GlobalNetworks, + &Vpcs) + registerCompute(&GlobalnetworkVpcs) +} diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index 38850bd77e..6f94180099 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -105,6 +105,11 @@ type SQcloudCloudAccountCreateOptions struct { SQcloudCredential } +type SGoogleCloudAccountCreateOptions struct { + SCloudAccountCreateBaseOptions + GoogleJsonFile string `help:"Google auth json file" positional:"true"` +} + type SAWSCloudAccountCreateOptions struct { SCloudAccountCreateBaseOptions SAccessKeyCredentialWithEnvironment diff --git a/pkg/multicloud/aliyun/provider/provider.go b/pkg/multicloud/aliyun/provider/provider.go index e64f90e20a..62889d05e7 100644 --- a/pkg/multicloud/aliyun/provider/provider.go +++ b/pkg/multicloud/aliyun/provider/provider.go @@ -121,6 +121,14 @@ func (self *SAliyunProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (cli *SAliyunProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (cli *SAliyunProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SAliyunProvider) GetIRegionById(extId string) (cloudprovider.ICloudRegion, error) { return self.client.GetIRegionById(extId) } diff --git a/pkg/multicloud/aws/provider/provider.go b/pkg/multicloud/aws/provider/provider.go index 5012ee2669..93e2a07204 100644 --- a/pkg/multicloud/aws/provider/provider.go +++ b/pkg/multicloud/aws/provider/provider.go @@ -114,6 +114,14 @@ func (self *SAwsProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (self *SAwsProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (self *SAwsProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SAwsProvider) GetSysInfo() (jsonutils.JSONObject, error) { regions := self.client.GetIRegions() info := jsonutils.NewDict() diff --git a/pkg/multicloud/azure/provider/provider.go b/pkg/multicloud/azure/provider/provider.go index 6e92a17e30..1bd4aee897 100644 --- a/pkg/multicloud/azure/provider/provider.go +++ b/pkg/multicloud/azure/provider/provider.go @@ -166,6 +166,14 @@ func (self *SAzureProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (cli *SAzureProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (cli *SAzureProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SAzureProvider) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { return self.client.GetIRegionById(id) } diff --git a/pkg/multicloud/esxi/provider/provider.go b/pkg/multicloud/esxi/provider/provider.go index 3e681ad2c9..66d94fa526 100644 --- a/pkg/multicloud/esxi/provider/provider.go +++ b/pkg/multicloud/esxi/provider/provider.go @@ -166,6 +166,14 @@ func (self *SESXiProvider) GetIRegions() []cloudprovider.ICloudRegion { return nil } +func (cli *SESXiProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (cli *SESXiProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SESXiProvider) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { return nil, cloudprovider.ErrNotSupported } diff --git a/pkg/multicloud/google/disk.go b/pkg/multicloud/google/disk.go new file mode 100644 index 0000000000..4c0ce32a5b --- /dev/null +++ b/pkg/multicloud/google/disk.go @@ -0,0 +1,228 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "context" + "fmt" + "time" + + "yunion.io/x/jsonutils" + + billing "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SDisk struct { + storage *SStorage + + Id string + CreationTimestamp time.Time + Name string + SizeGB int + Zone string + Status string + SelfLink string + Type string + SourceImage string + LastAttachTimestamp time.Time + LastDetachTimestamp time.Time + LabelFingerprint string + PhysicalBlockSizeBytes string + ResourcePolicies []string + Kind string + autoDelete bool + boot bool + index int +} + +func (region *SRegion) GetDisks(zone string, storageType string, maxResults int, pageToken string) ([]SDisk, error) { + disks := []SDisk{} + if len(zone) == 0 { + return nil, fmt.Errorf("zone params can not be empty") + } + params := map[string]string{} + if len(storageType) > 0 { + params["filter"] = fmt.Sprintf(`type="%s/zones/%s/diskTypes/%s"`, region.GetUrlPrefixWithProjectId(), zone, storageType) + } + return disks, region.List(fmt.Sprintf("zones/%s/disks", zone), params, maxResults, pageToken, &disks) +} + +func (region *SRegion) GetDisk(id string) (*SDisk, error) { + disk := &SDisk{} + return disk, region.Get(id, disk) +} + +func (disk *SDisk) GetId() string { + return disk.SelfLink +} + +func (disk *SDisk) GetGlobalId() string { + return getGlobalId(disk.SelfLink) +} + +func (disk *SDisk) GetName() string { + return disk.Name +} + +func (disk *SDisk) GetStatus() string { + switch disk.Status { + case "READY": + return api.DISK_READY + case "CREATING": + return api.DISK_ALLOCATING + case "RESTORING": + return api.DISK_RESET + case "FAILED": + return api.DISK_ALLOC_FAILED + case "DELETING": + return api.DISK_DEALLOC + default: + return api.DISK_UNKNOWN + } +} + +func (disk *SDisk) IsEmulated() bool { + return false +} + +func (disk *SDisk) Refresh() error { + _disk, err := disk.storage.zone.region.GetDisk(disk.SelfLink) + if err != nil { + return err + } + return jsonutils.Update(disk, _disk) +} + +func (disk *SDisk) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (disk *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { + return disk.storage, nil +} + +func (disk *SDisk) GetIStorageId() string { + return disk.storage.GetGlobalId() +} + +func (disk *SDisk) GetDiskFormat() string { + return "" +} + +func (disk *SDisk) GetDiskSizeMB() int { + return disk.SizeGB * 1024 +} + +func (disk *SDisk) GetIsAutoDelete() bool { + return disk.autoDelete +} + +func (disk *SDisk) GetTemplateId() string { + return disk.SourceImage +} + +func (disk *SDisk) GetDiskType() string { + if disk.index == 0 || disk.boot { + return api.DISK_TYPE_SYS + } + return api.DISK_TYPE_DATA +} + +func (disk *SDisk) GetFsFormat() string { + return "" +} + +func (disk *SDisk) GetIsNonPersistent() bool { + return false +} + +func (disk *SDisk) GetDriver() string { + return "scsi" +} + +func (disk *SDisk) GetCacheMode() string { + return "none" +} + +func (disk *SDisk) GetMountpoint() string { + return "" +} + +func (disk *SDisk) GetAccessPath() string { + return "" +} + +func (disk *SDisk) Delete(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (disk *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + snapshots, err := disk.storage.zone.region.GetSnapshots(disk.SelfLink, 0, "") + if err != nil { + return nil, err + } + isnapshots := []cloudprovider.ICloudSnapshot{} + for i := range snapshots { + snapshots[i].region = disk.storage.zone.region + isnapshots = append(isnapshots, &snapshots[i]) + } + return isnapshots, nil +} + +func (disk *SDisk) GetISnapshot(id string) (cloudprovider.ICloudSnapshot, error) { + return disk.storage.zone.region.GetSnapshot(id) +} + +func (disk *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { + result := []string{} + for _, policy := range disk.ResourcePolicies { + result = append(result, getGlobalId(policy)) + } + return result, nil +} + +func (disk *SDisk) Resize(ctx context.Context, newSizeMB int64) error { + return cloudprovider.ErrNotImplemented +} + +func (disk *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { + return "", cloudprovider.ErrNotImplemented +} + +func (disk *SDisk) Rebuild(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (disk *SDisk) GetBillingType() string { + return billing.BILLING_TYPE_POSTPAID +} + +func (disk *SDisk) GetCreatedAt() time.Time { + return disk.CreationTimestamp +} + +func (disk *SDisk) GetExpiredAt() time.Time { + return time.Time{} +} + +func (disk *SDisk) GetProjectId() string { + return disk.storage.zone.region.GetProjectId() +} diff --git a/pkg/multicloud/google/doc.go b/pkg/multicloud/google/doc.go new file mode 100644 index 0000000000..9aeaa8dfc4 --- /dev/null +++ b/pkg/multicloud/google/doc.go @@ -0,0 +1 @@ +package google // import "yunion.io/x/onecloud/pkg/multicloud/google" diff --git a/pkg/multicloud/google/eip.go b/pkg/multicloud/google/eip.go new file mode 100644 index 0000000000..ad3e782841 --- /dev/null +++ b/pkg/multicloud/google/eip.go @@ -0,0 +1,173 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + billing "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SAddress struct { + region *SRegion + + Id string + CreationTimestamp time.Time + Name string + Description string + Address string + Status string + Region string + Users []string + SelfLink string + NetworkTier string + AddressType string + Kind string +} + +func (region *SRegion) GetEips(address string, maxResults int, pageToken string) ([]SAddress, error) { + eips := []SAddress{} + params := map[string]string{} + if len(address) > 0 { + params["filter"] = fmt.Sprintf(`address="%s"`, address) + } + resource := fmt.Sprintf("regions/%s/addresses", region.Name) + return eips, region.List(resource, params, maxResults, pageToken, &eips) +} + +func (region *SRegion) GetEip(id string) (*SAddress, error) { + eip := &SAddress{region: region} + return eip, region.Get(id, eip) +} + +func (addr *SAddress) GetId() string { + return addr.SelfLink +} + +func (addr *SAddress) GetName() string { + return addr.Name +} + +func (addr *SAddress) GetGlobalId() string { + return getGlobalId(addr.SelfLink) +} + +func (addr *SAddress) GetStatus() string { + switch addr.Status { + case "RESERVING": + return api.EIP_STATUS_ASSOCIATE + case "RESERVED": + return api.EIP_STATUS_READY + case "IN_USE": + return api.EIP_STATUS_READY + default: + log.Errorf("Unknown eip status: %s", addr.Status) + return api.EIP_STATUS_UNKNOWN + } +} + +func (addr *SAddress) GetProjectId() string { + return addr.region.GetProjectId() +} + +func (addr *SAddress) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (addr *SAddress) IsEmulated() bool { + if addr.Id == addr.SelfLink { + return true + } + return false +} + +func (addr *SAddress) GetCreatedAt() time.Time { + return addr.CreationTimestamp +} + +func (addr *SAddress) GetExpiredAt() time.Time { + return time.Time{} +} + +func (addr *SAddress) GetBillingType() string { + return billing.BILLING_TYPE_POSTPAID +} + +func (addr *SAddress) Refresh() error { + if addr.IsEmulated() { + return nil + } + _addr, err := addr.region.GetEip(addr.SelfLink) + if err != nil { + return err + } + return jsonutils.Update(addr, _addr) +} + +func (addr *SAddress) GetIpAddr() string { + return addr.Address +} + +func (addr *SAddress) GetMode() string { + if addr.IsEmulated() { + return api.EIP_MODE_INSTANCE_PUBLICIP + } + return api.EIP_MODE_STANDALONE_EIP +} + +func (addr *SAddress) GetINetworkId() string { + return "" +} + +func (addr *SAddress) GetAssociationType() string { + return api.EIP_ASSOCIATE_TYPE_SERVER +} + +func (addr *SAddress) GetAssociationExternalId() string { + if len(addr.Users) > 0 { + return getGlobalId(addr.Users[0]) + } + return "" +} + +func (addr *SAddress) GetBandwidth() int { + return 0 +} + +func (addr *SAddress) GetInternetChargeType() string { + return api.EIP_CHARGE_TYPE_BY_TRAFFIC +} + +func (addr *SAddress) Delete() error { + return cloudprovider.ErrNotImplemented +} + +func (addr *SAddress) Associate(instanceId string) error { + return cloudprovider.ErrNotImplemented +} + +func (addr *SAddress) Dissociate() error { + return cloudprovider.ErrNotImplemented +} + +func (addr *SAddress) ChangeBandwidth(bw int) error { + return cloudprovider.ErrNotImplemented +} diff --git a/pkg/multicloud/google/globalnetwork.go b/pkg/multicloud/google/globalnetwork.go new file mode 100644 index 0000000000..bec584561f --- /dev/null +++ b/pkg/multicloud/google/globalnetwork.go @@ -0,0 +1,73 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SGlobalNetwork struct { + Id string + //CreationTimestamp time.Time + Name string + Description string + SelfLink string + AutoCreateSubnetworks bool + Subnetworks []string + RoutingConfig map[string]string + Kind string +} + +func (cli *SGoogleClient) GetGlobalNetwork(id string) (*SGlobalNetwork, error) { + net := &SGlobalNetwork{} + return net, cli.get(id, net) +} + +func (cli *SGoogleClient) GetGlobalNetworks(maxResults int, pageToken string) ([]SGlobalNetwork, error) { + networks := []SGlobalNetwork{} + params := map[string]string{} + resource := "global/networks" + return networks, cli.list(resource, params, maxResults, pageToken, &networks) +} + +func (net *SGlobalNetwork) GetId() string { + return net.SelfLink +} + +func (net *SGlobalNetwork) GetGlobalId() string { + return getGlobalId(net.SelfLink) +} + +func (net *SGlobalNetwork) GetName() string { + return net.Name +} + +func (net *SGlobalNetwork) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (net *SGlobalNetwork) GetStatus() string { + return api.GLOBAL_NETWORK_STATUS_AVAILABLE +} + +func (net *SGlobalNetwork) IsEmulated() bool { + return false +} + +func (net *SGlobalNetwork) Refresh() error { + return nil +} diff --git a/pkg/multicloud/google/google.go b/pkg/multicloud/google/google.go new file mode 100644 index 0000000000..85983e48f2 --- /dev/null +++ b/pkg/multicloud/google/google.go @@ -0,0 +1,318 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "golang.org/x/oauth2/jwt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +const ( + CLOUD_PROVIDER_GOOGLE = api.CLOUD_PROVIDER_GOOGLE + CLOUD_PROVIDER_GOOGLE_CN = "谷歌云" + + GOOGLE_DEFAULT_REGION = "asia-east1" + + GOOGLE_API_VERSION = "v1" +) + +type SGoogleClient struct { + providerId string + providerName string + projectId string + privateKey string + privateKeyId string + clientEmail string + iregions []cloudprovider.ICloudRegion + images []SImage + snapshots map[string][]SSnapshot + globalnetworks []SGlobalNetwork + resourcepolices []SResourcePolicy + + client *http.Client + + Debug bool +} + +func NewGoogleClient(providerId string, providerName string, projectId, clientEmail, privateKeyId, privateKey string, isDebug bool) (*SGoogleClient, error) { + client := SGoogleClient{ + providerId: providerId, + providerName: providerName, + projectId: projectId, + privateKey: strings.Replace(privateKey, "\\n", "\n", -1), + privateKeyId: privateKeyId, + clientEmail: clientEmail, + Debug: isDebug, + } + conf := &jwt.Config{ + Email: clientEmail, + PrivateKeyID: privateKeyId, + PrivateKey: []byte(client.privateKey), + Scopes: []string{ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly", + "https://www.googleapis.com/auth/cloud-platform.read-only", + "https://www.googleapis.com/auth/cloudplatformprojects", + "https://www.googleapis.com/auth/cloudplatformprojects.readonly", + }, + TokenURL: google.JWTTokenURL, + } + client.client = conf.Client(oauth2.NoContext) + return &client, client.fetchRegions() +} + +func (self *SGoogleClient) GetAccountId() string { + return self.clientEmail +} + +func (self *SGoogleClient) fetchRegions() error { + regions := []SRegion{} + err := self.listAll("regions", nil, ®ions) + if err != nil { + return err + } + + self.iregions = []cloudprovider.ICloudRegion{} + for i := 0; i < len(regions); i++ { + regions[i].client = self + self.iregions = append(self.iregions, ®ions[i]) + } + return nil +} + +func (self *SGoogleClient) get(id string, retval interface{}) error { + if !strings.HasPrefix(id, getUrlPrefix()) { + id = getUrlPrefix() + id + } + data, err := jsonRequest(self.client, "GET", id, nil, self.Debug) + if err != nil { + if strings.Index(err.Error(), "not found") > 0 { + return cloudprovider.ErrNotFound + } + return errors.Wrap(err, "JSONRequest") + } + err = data.Unmarshal(retval) + if err != nil { + return errors.Wrap(err, "Unmarshal") + } + return nil +} + +func (self *SGoogleClient) listAll(resource string, params map[string]string, retval interface{}) error { + var ( + items *jsonutils.JSONArray = jsonutils.NewArray() + _items []jsonutils.JSONObject = []jsonutils.JSONObject{} + + maxResults int = 500 + nextPageToken string = "" + err error = nil + ) + for { + _items, nextPageToken, err = self._listAll(resource, params, maxResults, nextPageToken) + if err != nil { + return errors.Wrapf(err, `_listAll("%s")`, resource) + } + items.Add(_items...) + if len(nextPageToken) == 0 || len(_items) == 0 { + break + } + } + return items.Unmarshal(retval) +} + +func (self *SGoogleClient) _listAll(resource string, params map[string]string, maxResults int, pageToken string) ([]jsonutils.JSONObject, string, error) { + if params == nil { + params = map[string]string{} + } + params["maxResults"] = fmt.Sprintf("%d", maxResults) + params["pageToken"] = pageToken + data, err := self._list(resource, params) + if err != nil { + return nil, "", err + } + items := []jsonutils.JSONObject{} + if data.Contains("items") { + items, err = data.GetArray("items") + if err != nil { + return nil, "", errors.Wrap(err, "data.GetArray") + } + } + nextPageToken, _ := data.GetString("nextPageToken") + return items, nextPageToken, nil +} + +func (self *SGoogleClient) list(resource string, params map[string]string, maxResults int, pageToken string, retval interface{}) error { + if maxResults == 0 && len(pageToken) == 0 { + return self.listAll(resource, params, retval) + } + params["maxResults"] = fmt.Sprintf("%d", maxResults) + params["pageToken"] = pageToken + data, err := self._list(resource, params) + if err != nil { + return errors.Wrapf(err, "_list(%s)", resource) + } + if data.Contains("items") { + err := data.Unmarshal(retval, "items") + if err != nil { + return errors.Wrap(err, "data.Unmarshal") + } + } + return nil +} + +func jsonRequest(client *http.Client, method httputils.THttpMethod, url string, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) { + _, data, err := httputils.JSONRequest(client, context.Background(), method, url, nil, body, debug) + if err != nil { + if strings.Index(err.Error(), "not found") > 0 { + return nil, cloudprovider.ErrNotFound + } + return nil, errors.Wrap(err, "JSONRequest") + } + return data, nil +} + +func (self *SGoogleClient) _list(resource string, params map[string]string) (jsonutils.JSONObject, error) { + baseUrl := fmt.Sprintf("%s%s/%s", getUrlPrefix(), self.projectId, resource) + values := url.Values{} + for k, v := range params { + values.Set(k, v) + } + if len(values) > 0 { + baseUrl = fmt.Sprintf("%s?%s", baseUrl, values.Encode()) + } + return jsonRequest(self.client, "GET", baseUrl, nil, self.Debug) +} + +func (self *SGoogleClient) GetRegion(regionId string) *SRegion { + if len(regionId) == 0 { + regionId = GOOGLE_DEFAULT_REGION + } + for i := 0; i < len(self.iregions); i++ { + if self.iregions[i].GetId() == regionId { + return self.iregions[i].(*SRegion) + } + } + return nil +} + +func (client *SGoogleClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + projects, err := client.GetProjects() + if err != nil { + return nil, errors.Wrap(err, "GetProjects") + } + accounts := []cloudprovider.SSubAccount{} + for _, project := range projects { + subAccount := cloudprovider.SSubAccount{} + subAccount.Name = client.providerName + subAccount.Account = fmt.Sprintf("%s/%s", project.ProjectId, client.clientEmail) + if project.LifecycleState == "ACTIVE" { + subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL + } else { + subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_ARREARS + } + accounts = append(accounts, subAccount) + } + return accounts, nil +} + +func (self *SGoogleClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { + for i := 0; i < len(self.iregions); i++ { + if self.iregions[i].GetGlobalId() == id { + return self.iregions[i].(*SRegion), nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SGoogleClient) GetIRegions() []cloudprovider.ICloudRegion { + return self.iregions +} + +func (self *SGoogleClient) fetchGlobalNetwork() ([]SGlobalNetwork, error) { + if len(self.globalnetworks) > 0 { + return self.globalnetworks, nil + } + globalnetworks, err := self.GetGlobalNetworks(0, "") + if err != nil { + return nil, err + } + self.globalnetworks = globalnetworks + return globalnetworks, nil +} + +func (cli *SGoogleClient) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + networks, err := cli.fetchGlobalNetwork() + if err != nil { + return nil, errors.Wrap(err, "fetchGlobalNetwork") + } + inetworks := []cloudprovider.ICloudGlobalnetwork{} + for i := range networks { + inetworks = append(inetworks, &networks[i]) + } + return inetworks, nil +} + +func (cli *SGoogleClient) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + network, err := cli.GetGlobalNetwork(id) + if err != nil { + return nil, err + } + return network, nil +} + +func (self *SGoogleClient) GetRegions() []SRegion { + regions := make([]SRegion, len(self.iregions)) + for i := 0; i < len(regions); i++ { + region := self.iregions[i].(*SRegion) + regions[i] = *region + } + return regions +} + +func (self *SGoogleClient) GetIProjects() ([]cloudprovider.ICloudProject, error) { + projects, err := self.GetProjects() + if err != nil { + return nil, err + } + + iprojects := []cloudprovider.ICloudProject{} + for i := range projects { + iprojects = append(iprojects, &projects[i]) + } + return iprojects, nil +} + +func getUrlPrefix() string { + return fmt.Sprintf("https://www.googleapis.com/compute/%s/projects/", GOOGLE_API_VERSION) +} + +func getGlobalId(id string) string { + return strings.TrimPrefix(id, getUrlPrefix()) +} diff --git a/pkg/multicloud/google/host.go b/pkg/multicloud/google/host.go new file mode 100644 index 0000000000..4b671df714 --- /dev/null +++ b/pkg/multicloud/google/host.go @@ -0,0 +1,187 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SHost struct { + multicloud.SHostBase + + zone *SZone +} + +func (self *SHost) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (host *SHost) GetId() string { + return getGlobalId(host.zone.GetId()) +} + +func (host *SHost) GetGlobalId() string { + return host.GetId() +} + +func (host *SHost) GetName() string { + return fmt.Sprintf("%s-%s", host.zone.region.client.providerName, host.zone.GetName()) +} + +func (host *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + return host.zone.GetIStorages() +} + +func (host *SHost) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + return host.zone.GetIStorageById(id) +} + +func (host *SHost) IsEmulated() bool { + return false +} + +func (host *SHost) GetStatus() string { + return api.HOST_STATUS_RUNNING +} + +func (host *SHost) Refresh() error { + return nil +} + +func (host *SHost) GetHostStatus() string { + return api.HOST_ONLINE +} + +func (host *SHost) GetEnabled() bool { + return true +} + +func (host *SHost) GetAccessIp() string { + return "" +} + +func (host *SHost) GetAccessMac() string { + return "" +} + +func (host *SHost) GetSysInfo() jsonutils.JSONObject { + info := jsonutils.NewDict() + info.Add(jsonutils.NewString(CLOUD_PROVIDER_GOOGLE), "manufacture") + return info +} + +func (host *SHost) GetSN() string { + return "" +} + +func (host *SHost) GetCpuCount() int { + return 0 +} + +func (host *SHost) GetNodeCount() int8 { + return 0 +} + +func (host *SHost) GetCpuDesc() string { + return "" +} + +func (host *SHost) GetCpuMhz() int { + return 0 +} + +func (host *SHost) GetMemSizeMB() int { + return 0 +} + +func (host *SHost) GetStorageSizeMB() int { + return 0 +} + +func (host *SHost) GetStorageType() string { + return api.DISK_TYPE_HYBRID +} + +func (host *SHost) GetHostType() string { + return api.HOST_TYPE_GOOGLE +} + +func (host *SHost) GetWire() *SWire { + vpc := &SVpc{region: host.zone.region} + return &SWire{vpc: vpc} +} + +func (host *SHost) GetIWires() ([]cloudprovider.ICloudWire, error) { + ivpcs, err := host.zone.region.GetIVpcs() + if err != nil { + return nil, errors.Wrap(err, "region.GetIVpcs") + } + iwires := []cloudprovider.ICloudWire{} + for i := range ivpcs { + _iwires, err := ivpcs[i].GetIWires() + if err != nil { + return nil, errors.Wrap(err, "ivpcs[i].GetIWires") + } + iwires = append(iwires, _iwires...) + } + return iwires, nil +} + +func (host *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) { + instances, err := host.zone.region.GetInstances(host.zone.Name, 0, "") + if err != nil { + return nil, err + } + iVMs := []cloudprovider.ICloudVM{} + for i := range instances { + instances[i].host = host + iVMs = append(iVMs, &instances[i]) + } + return iVMs, nil +} + +func (host *SHost) GetIVMById(id string) (cloudprovider.ICloudVM, error) { + instance, err := host.zone.region.GetInstance(id) + if err != nil { + return nil, err + } + if instance.Zone != host.zone.SelfLink { + return nil, cloudprovider.ErrNotFound + } + return instance, nil +} + +func (host *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (host *SHost) GetIHostNics() ([]cloudprovider.ICloudHostNetInterface, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (host *SHost) GetIsMaintenance() bool { + return false +} + +func (host *SHost) GetVersion() string { + return GOOGLE_API_VERSION +} diff --git a/pkg/multicloud/google/image.go b/pkg/multicloud/google/image.go new file mode 100644 index 0000000000..2253dcfe84 --- /dev/null +++ b/pkg/multicloud/google/image.go @@ -0,0 +1,228 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "context" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/imagetools" +) + +type GuestOsFeature struct { + Type string +} + +type SDeprecated struct { + State string + Replacement string + Deprecated bool +} + +type SImage struct { + storagecache *SStoragecache + + Id string + CreationTimestamp time.Time + Name string + Description string + SourceType string + RawDisk map[string]string + Deprecated SDeprecated + Status string + ArchiveSizeBytes int64 + DiskSizeGb int + Licenses []string + Family string + SelfLink string + LabelFingerprint string + GuestOsFeatures []GuestOsFeature + LicenseCodes []string + StorageLocations []string + Kind string +} + +func (region *SRegion) SetProjectId(id string) { + region.client.projectId = id +} + +func (region *SRegion) GetAllAvailableImages() ([]SImage, error) { + images := []SImage{} + projectId := region.client.projectId + for _, project := range []string{ + "centos-cloud", + "ubuntu-os-cloud", + "windows-cloud", + "windows-sql-cloud", + "suse-cloud", + "suse-sap-cloud", + "rhel-cloud", + "rhel-sap-cloud", + "cos-cloud", + "coreos-cloud", + "debian-cloud", + projectId, + } { + _images, err := region.GetImages(project, 0, "") + if err != nil { + return nil, err + } + for _, image := range _images { + if image.Deprecated.State == "" { + images = append(images, image) + } + } + } + return images, nil +} + +func (region *SRegion) GetImages(project string, maxResults int, pageToken string) ([]SImage, error) { + images := []SImage{} + resource := "global/images" + params := map[string]string{} + if len(project) > 0 { + region.SetProjectId(project) + } + return images, region.List(resource, params, maxResults, pageToken, &images) +} + +func (region *SRegion) GetImage(id string) (*SImage, error) { + image := &SImage{} + return image, region.Get(id, image) +} + +func (image *SImage) GetId() string { + return image.SelfLink +} + +func (image *SImage) GetGlobalId() string { + return getGlobalId(image.SelfLink) +} + +func (image *SImage) GetName() string { + return image.Name +} + +func (image *SImage) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (image *SImage) GetMinRamSizeMb() int { + return 0 +} + +func (image *SImage) GetStatus() string { + switch image.Status { + case "READY": + return api.CACHED_IMAGE_STATUS_READY + case "FAILED": + return api.CACHED_IMAGE_STATUS_CACHE_FAILED + case "PENDING": + return api.CACHED_IMAGE_STATUS_SAVING + default: + log.Errorf("Unknown image status: %s", image.Status) + return api.CACHED_IMAGE_STATUS_CACHE_FAILED + } +} + +func (image *SImage) GetImageStatus() string { + switch image.Status { + case "READY": + return cloudprovider.IMAGE_STATUS_ACTIVE + case "FAILED": + return cloudprovider.IMAGE_STATUS_KILLED + case "PENDING": + return cloudprovider.IMAGE_STATUS_QUEUED + default: + return cloudprovider.IMAGE_STATUS_KILLED + } +} + +func (image *SImage) Refresh() error { + _image, err := image.storagecache.region.GetImage(image.SelfLink) + if err != nil { + return err + } + return jsonutils.Update(image, _image) +} + +func (image *SImage) GetImageType() string { + if strings.Index(image.SelfLink, image.storagecache.region.GetProjectId()) >= 0 { + return cloudprovider.CachedImageTypeCustomized + } + return cloudprovider.CachedImageTypeSystem +} + +func (image *SImage) GetSizeByte() int64 { + return image.ArchiveSizeBytes +} + +func (image *SImage) GetOsType() string { + return imagetools.NormalizeImageInfo(image.Name, "", "", "", "").OsType +} + +func (image *SImage) GetOsDist() string { + return imagetools.NormalizeImageInfo(image.Name, "", "", "", "").OsDistro +} + +func (image *SImage) GetOsVersion() string { + return imagetools.NormalizeImageInfo(image.Name, "", "", "", "").OsVersion +} + +func (image *SImage) GetOsArch() string { + return imagetools.NormalizeImageInfo(image.Name, "", "", "", "").OsArch +} + +func (image *SImage) GetMinOsDiskSizeGb() int { + return image.DiskSizeGb +} + +func (image *SImage) GetCreatedAt() time.Time { + return image.CreationTimestamp +} + +func (image *SImage) GetImageFormat() string { + return "vhd" +} + +func (image *SImage) IsEmulated() bool { + return false +} + +func (image *SImage) Delete(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (image *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return image.storagecache +} + +func (region *SRegion) fetchImages() ([]SImage, error) { + if len(region.client.images) > 0 { + return region.client.images, nil + } + images, err := region.GetAllAvailableImages() + if err != nil { + return nil, err + } + region.client.images = images + return images, nil +} diff --git a/pkg/multicloud/google/instance.go b/pkg/multicloud/google/instance.go new file mode 100644 index 0000000000..693b9701e8 --- /dev/null +++ b/pkg/multicloud/google/instance.go @@ -0,0 +1,409 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "context" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/osprofile" + "yunion.io/x/pkg/utils" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/imagetools" +) + +type AccessConfig struct { + Type string + Name string + NatIP string + NetworkTier string + Kind string +} + +type InstanceDisk struct { + Type string + Mode string + Source string + DeviceName string + Index int + Boot bool + AutoDelete bool + Licenses []string + Interface string + GuestOsFeatures []GuestOsFeature + Kind string +} + +type ServiceAccount struct { + Email string + scopes []string +} + +type SInstanceTag struct { + Items []string + Fingerprint string +} + +type SInstance struct { + multicloud.SInstanceBase + host *SHost + + Id string + CreationTimestamp time.Time + Name string + Description string + Tags SInstanceTag + MachineType string + Status string + Zone string + CanIpForward bool + NetworkInterfaces []SNetworkInterface + Disks []InstanceDisk + Metadata map[string]string + ServiceAccounts []ServiceAccount + SelfLink string + Scheduling map[string]interface{} + CpuPlatform string + LabelFingerprint string + StartRestricted bool + DeletionProtection bool + Kind string + + guestCpus int + memoryMb int + machineType string +} + +func (region *SRegion) GetInstances(zone string, maxResults int, pageToken string) ([]SInstance, error) { + instances := []SInstance{} + params := map[string]string{} + if len(zone) == 0 { + return nil, fmt.Errorf("zone params can not be empty") + } + resource := fmt.Sprintf("zones/%s/instances", zone) + return instances, region.List(resource, params, maxResults, pageToken, &instances) +} + +func (region *SRegion) GetInstance(id string) (*SInstance, error) { + instance := &SInstance{} + return instance, region.Get(id, instance) +} + +func (instance *SInstance) GetId() string { + return instance.SelfLink +} + +func (instnace *SInstance) GetGlobalId() string { + return getGlobalId(instnace.SelfLink) +} + +func (instance *SInstance) GetName() string { + return instance.Name +} + +func (instnace *SInstance) IsEmulated() bool { + return false +} + +func (instance *SInstance) fetchMachineType() error { + if instance.guestCpus > 0 || instance.memoryMb > 0 || len(instance.machineType) > 0 { + return nil + } + machinetype, err := instance.host.zone.region.GetMachineType(instance.MachineType) + if err != nil { + return err + } + instance.guestCpus = machinetype.GuestCpus + instance.memoryMb = machinetype.MemoryMb + instance.machineType = machinetype.Name + return nil +} + +func (instance *SInstance) Refresh() error { + _instance, err := instance.host.zone.region.GetInstance(instance.SelfLink) + if err != nil { + return err + } + return jsonutils.Update(instance, _instance) +} + +//PROVISIONING, STAGING, RUNNING, STOPPING, STOPPED, SUSPENDING, SUSPENDED, and TERMINATED. +func (instance *SInstance) GetStatus() string { + switch instance.Status { + case "PROVISIONING": + return api.VM_DEPLOYING + case "STAGING": + return api.VM_STARTING + case "RUNNING": + return api.VM_RUNNING + case "STOPPING": + return api.VM_STOPPING + case "STOPPED": + return api.VM_READY + case "SUSPENDING": + return api.VM_SUSPENDING + case "SUSPENDED": + return api.VM_SUSPEND + case "TERMINATED": + return api.VM_DELETING + default: + return api.VM_UNKNOWN + } +} + +func (instance *SInstance) GetBillingType() string { + return billing_api.BILLING_TYPE_POSTPAID +} + +func (instance *SInstance) GetCreatedAt() time.Time { + return instance.CreationTimestamp +} + +func (instance *SInstance) GetExpiredAt() time.Time { + return time.Time{} +} + +func (instance *SInstance) GetProjectId() string { + return instance.host.zone.region.GetProjectId() +} + +func (instance *SInstance) GetIHost() cloudprovider.ICloudHost { + return instance.host +} + +func (instance *SInstance) GetIHostId() string { + return instance.host.GetGlobalId() +} + +func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + idisks := []cloudprovider.ICloudDisk{} + for _, disk := range instance.Disks { + disk, err := instance.host.zone.region.GetDisk(disk.Source) + if err != nil { + return nil, errors.Wrap(err, "GetDisk") + } + storage, err := instance.host.zone.region.GetStorage(disk.Type) + if err != nil { + return nil, errors.Wrap(err, "GetStorage") + } + storage.zone = instance.host.zone + disk.storage = storage + idisks = append(idisks, disk) + } + return idisks, nil +} + +func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { + nics := []cloudprovider.ICloudNic{} + for i := range instance.NetworkInterfaces { + instance.NetworkInterfaces[i].instance = instance + nics = append(nics, &instance.NetworkInterfaces[i]) + } + return nics, nil +} + +func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { + for _, networkinterface := range instance.NetworkInterfaces { + for _, conf := range networkinterface.AccessConfigs { + if len(conf.NatIP) > 0 { + eips, err := instance.host.zone.region.GetEips(conf.NatIP, 0, "") + if err != nil { + return nil, errors.Wrapf(err, "region.GetEip(%s)", conf.NatIP) + } + if len(eips) == 1 { + return &eips[0], nil + } + eip := &SAddress{ + region: instance.host.zone.region, + SelfLink: instance.SelfLink, + Id: instance.SelfLink, + Address: conf.NatIP, + } + return eip, nil + } + } + } + return nil, nil +} + +func (instance *SInstance) GetVcpuCount() int { + instance.fetchMachineType() + return instance.guestCpus +} + +func (instance *SInstance) GetVmemSizeMB() int { + instance.fetchMachineType() + return instance.memoryMb +} + +func (instance *SInstance) GetBootOrder() string { + return "cdn" +} + +func (instance *SInstance) GetVga() string { + return "std" +} + +func (instance *SInstance) GetVdi() string { + return "vnc" +} + +func (instance *SInstance) GetOSType() string { + for _, disk := range instance.Disks { + if disk.Index == 0 { + for _, license := range disk.Licenses { + if strings.Index(strings.ToLower(license), "windows") < 0 { + return osprofile.OS_TYPE_LINUX + } else { + return osprofile.OS_TYPE_WINDOWS + } + } + } + } + return osprofile.OS_TYPE_LINUX +} + +func (instance *SInstance) GetOSName() string { + for _, disk := range instance.Disks { + if disk.Index == 0 { + for _, license := range disk.Licenses { + return imagetools.NormalizeImageInfo(license, "", "", "", "").OsDistro + } + } + } + return "" +} + +func (instance *SInstance) GetBios() string { + return "BIOS" +} + +func (instance *SInstance) GetMachine() string { + return "pc" +} + +func (instance *SInstance) GetInstanceType() string { + instance.fetchMachineType() + return instance.machineType +} + +func (instance *SInstance) AssignSecurityGroup(id string) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) GetSecurityGroupIds() ([]string, error) { + secgroupIds := []string{} + isecgroups := []cloudprovider.ICloudSecurityGroup{} + for _, networkinterface := range instance.NetworkInterfaces { + globalnetwork, err := instance.host.zone.region.client.GetGlobalNetwork(networkinterface.Network) + if err != nil { + return nil, errors.Wrap(err, "GetGlobalNetwork") + } + vpc := &SVpc{globalnetwork: globalnetwork, region: instance.host.zone.region} + _isecgroups, err := vpc.GetISecurityGroups() + if err != nil { + return nil, errors.Wrap(err, "vpc.GetISecurityGroups") + } + isecgroups = append(isecgroups, _isecgroups...) + for _, isecgroup := range _isecgroups { + if len(instance.ServiceAccounts) > 0 && isecgroup.GetName() == instance.ServiceAccounts[0].Email { + secgroupIds = append(secgroupIds, isecgroup.GetGlobalId()) + } + if isecgroup.GetName() == globalnetwork.GetName() { + secgroupIds = append(secgroupIds, isecgroup.GetGlobalId()) + } + } + } + if len(instance.NetworkInterfaces) == 1 { + for _, secgroup := range isecgroups { + if utils.IsInStringArray(secgroup.GetName(), instance.Tags.Items) { + secgroupIds = append(secgroupIds, secgroup.GetGlobalId()) + } + } + } + return secgroupIds, nil +} + +func (instance *SInstance) SetSecurityGroups(ids []string) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) GetHypervisor() string { + return api.HYPERVISOR_GOOGLE +} + +func (instance *SInstance) StartVM(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) StopVM(ctx context.Context, isForce bool) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) DeleteVM(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) UpdateVM(ctx context.Context, name string) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) UpdateUserData(userData string) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) RebuildRoot(ctx context.Context, imageId string, passwd string, publicKey string, sysSizeGB int) (string, error) { + return "", cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { + return nil, cloudprovider.ErrNotImplemented +} +func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) CreateDisk(ctx context.Context, sizeMb int, uuid string, driver string) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) Renew(bc billing.SBillingCycle) error { + return cloudprovider.ErrNotImplemented +} + +func (instance *SInstance) GetError() error { + return nil +} diff --git a/pkg/multicloud/google/instancenic.go b/pkg/multicloud/google/instancenic.go new file mode 100644 index 0000000000..f4519686d4 --- /dev/null +++ b/pkg/multicloud/google/instancenic.go @@ -0,0 +1,58 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SNetworkInterface struct { + instance *SInstance + + Network string + Subnetwork string + NetworkIP string + Name string + AccessConfigs []AccessConfig + Fingerprint string + Kind string +} + +func (nic *SNetworkInterface) GetIP() string { + return nic.NetworkIP +} + +func (nic *SNetworkInterface) GetMAC() string { + ip, _ := netutils.NewIPV4Addr(nic.NetworkIP) + return ip.ToMac("00:16:") +} + +func (nic *SNetworkInterface) GetDriver() string { + return "virtio" +} + +func (nic *SNetworkInterface) GetINetwork() cloudprovider.ICloudNetwork { + network, err := nic.instance.host.zone.region.GetNetwork(nic.Subnetwork) + if err != nil { + log.Errorf("failed to found network(%s) for nic error: %v", nic.Subnetwork, err) + return nil + } + wire := nic.instance.host.GetWire() + network.wire = wire + return network +} diff --git a/pkg/multicloud/google/latitud_and_longitude.go b/pkg/multicloud/google/latitud_and_longitude.go new file mode 100644 index 0000000000..915335eae6 --- /dev/null +++ b/pkg/multicloud/google/latitud_and_longitude.go @@ -0,0 +1,70 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +var LatitudeAndLongitude = map[string]cloudprovider.SGeographicInfo{ + "asia-east1": {Latitude: 25.0443, Longitude: 121.509, City: api.CITY_TAI_WAN, CountryCode: api.COUNTRY_CODE_CN}, + "asia-east2": {Latitude: 22.396427, Longitude: 114.109497, City: api.CITY_HONG_KONG, CountryCode: api.COUNTRY_CODE_CN}, + "asia-northeast1": {Latitude: 35.709026, Longitude: 139.731995, City: api.CITY_TOKYO, CountryCode: api.COUNTRY_CODE_JP}, + "asia-northeast2": {Latitude: 34.6937378, Longitude: 135.5021651, City: api.CITY_OSAKA, CountryCode: api.COUNTRY_CODE_JP}, + "asia-south1": {Latitude: 19.075983, Longitude: 72.877655, City: api.CITY_MUMBAI, CountryCode: api.COUNTRY_CODE_IN}, + "asia-southeast1": {Latitude: 1.352083, Longitude: 103.819839, City: api.CITY_SINGAPORE, CountryCode: api.COUNTRY_CODE_SG}, + "australia-southeast1": {Latitude: -33.8688197, Longitude: 151.2092955, City: api.CITY_SYDNEY, CountryCode: api.COUNTRY_CODE_AU}, + + "europe-north1": {Latitude: 39.904202, Longitude: 116.407394, City: api.CITY_FINLAND, CountryCode: api.COUNTRY_CODE_CN}, + "europe-west1": {Latitude: 39.904202, Longitude: 116.407394, City: api.CITY_BELGIUM, CountryCode: api.COUNTRY_CODE_CN}, + "europe-west2": {Latitude: 51.507351, Longitude: -0.127758, City: api.CITY_LONDON, CountryCode: api.COUNTRY_CODE_GB}, + "europe-west3": {Latitude: 51.165691, Longitude: 10.451526, City: api.CITY_FRANKFURT, CountryCode: api.COUNTRY_CODE_DE}, + "europe-west4": {Latitude: 52.2076831, Longitude: 4.1585786, City: api.CITY_HOLLAND, CountryCode: api.COUNTRY_CODE_NL}, + "europe-west6": {Latitude: 47.3774497, Longitude: 8.5016958, City: api.CITY_ZURICH, CountryCode: api.COUNTRY_CODE_CH}, + + "northamerica-northeast1": {Latitude: 45.5580206, Longitude: -73.8003414, City: api.CITY_MONTREAL, CountryCode: api.COUNTRY_CODE_CA}, + "southamerica-east1": {Latitude: -23.5505199, Longitude: -46.6333094, City: api.CITY_SAO_PAULO, CountryCode: api.COUNTRY_CODE_BR}, + "us-central1": {Latitude: 41.9328655, Longitude: -94.5106809, City: api.CITY_IOWA, CountryCode: api.COUNTRY_CODE_US}, + "us-east1": {Latitude: 33.6194409, Longitude: -82.0475635, City: api.CITY_SOUTH_CAROLINA, CountryCode: api.COUNTRY_CODE_US}, + "us-east4": {Latitude: 37.4315734, Longitude: -78.6568942, City: api.CITY_N_VIRGINIA, CountryCode: api.COUNTRY_CODE_US}, + "us-west1": {Latitude: 43.8041334, Longitude: -120.5542012, City: api.CITY_OREGON, CountryCode: api.COUNTRY_CODE_US}, + "us-west2": {Latitude: 34.0522342, Longitude: -118.2436849, City: api.CITY_LOS_ANGELES, CountryCode: api.COUNTRY_CODE_US}, +} + +var RegionNames = map[string]string{ + "asia-east1": "台湾", + "asia-east2": "香港", + "asia-northeast1": "东京", + "asia-northeast2": "大阪", + "asia-south1": "孟买", + "asia-southeast1": "新加坡", + "australia-southeast1": "悉尼", + + "europe-north1": "芬兰", + "europe-west1": "比利时", + "europe-west2": "伦敦", + "europe-west3": "法兰克福", + "europe-west4": "荷兰", + "europe-west6": "苏黎世", + + "northamerica-northeast1": "蒙特利尔", + "southamerica-east1": "圣保罗", + "us-central1": "艾奥瓦", + "us-east1": "南卡罗来纳州", + "us-east4": "北弗吉尼亚", + "us-west1": "俄勒冈州", + "us-west2": "洛杉矶", +} diff --git a/pkg/multicloud/google/machinetype.go b/pkg/multicloud/google/machinetype.go new file mode 100644 index 0000000000..96eadcea99 --- /dev/null +++ b/pkg/multicloud/google/machinetype.go @@ -0,0 +1,57 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "time" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SMachineType struct { + Id string + CreationTimestamp time.Time + Name string + Description string + GuestCpus int + MemoryMb int + ImageSpaceGb int + MaximumPersistentDisks int + MaximumPersistentDisksSizeGb int + Zone string + SelfLink string + IsSharedCpu bool + Kind string +} + +func (region *SRegion) GetMachineTypes(zone string, maxResults int, pageToken string) ([]SMachineType, error) { + machines := []SMachineType{} + params := map[string]string{} + if len(zone) == 0 { + return nil, cloudprovider.ErrNotFound + } + resource := fmt.Sprintf("zones/%s/machineTypes", zone) + return machines, region.List(resource, params, maxResults, pageToken, &machines) +} + +func (region *SRegion) GetMachineType(id string) (*SMachineType, error) { + machine := &SMachineType{} + err := region.client.get(id, machine) + if err != nil { + return nil, err + } + return machine, nil +} diff --git a/pkg/multicloud/google/network.go b/pkg/multicloud/google/network.go new file mode 100644 index 0000000000..2e3feb0c5f --- /dev/null +++ b/pkg/multicloud/google/network.go @@ -0,0 +1,144 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/util/netutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +type SNetwork struct { + wire *SWire + + Id string + CreationTimestamp time.Time + Name string + Network string + IpCidrRange string + Region string + GatewayAddress string + SelfLink string + Status string + AvailableCpuPlatforms []string + PrivateIpGoogleAccess bool + Fingerprint string + Purpose string + Kind string +} + +func (region *SRegion) GetNetworks(network string, maxResults int, pageToken string) ([]SNetwork, error) { + networks := []SNetwork{} + params := map[string]string{} + if len(network) > 0 { + params["filter"] = fmt.Sprintf(`network="%s"`, network) + } + resource := fmt.Sprintf("regions/%s/subnetworks", region.Name) + return networks, region.List(resource, params, maxResults, pageToken, &networks) +} + +func (region *SRegion) GetNetwork(id string) (*SNetwork, error) { + network := &SNetwork{} + return network, region.Get(id, network) +} + +func (network *SNetwork) GetId() string { + return network.SelfLink +} + +func (network *SNetwork) GetName() string { + return network.Name +} + +func (network *SNetwork) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (network *SNetwork) GetProjectId() string { + return network.wire.vpc.region.GetProjectId() +} + +func (network *SNetwork) Refresh() error { + _network, err := network.wire.vpc.region.GetNetwork(network.SelfLink) + if err != nil { + return err + } + return jsonutils.Update(network, _network) +} + +func (network *SNetwork) IsEmulated() bool { + return false +} + +func (network *SNetwork) GetStatus() string { + return api.NETWORK_INTERFACE_STATUS_AVAILABLE +} + +func (network *SNetwork) GetGlobalId() string { + return getGlobalId(network.SelfLink) +} + +func (network *SNetwork) Delete() error { + return cloudprovider.ErrNotImplemented +} + +func (network *SNetwork) GetAllocTimeoutSeconds() int { + return 300 +} + +func (network *SNetwork) GetIWire() cloudprovider.ICloudWire { + return network.wire +} + +func (network *SNetwork) GetIpStart() string { + pref, _ := netutils.NewIPV4Prefix(network.IpCidrRange) + startIp := pref.Address.NetAddr(pref.MaskLen) // 0 + startIp = startIp.StepUp() // 1 + return startIp.String() +} + +func (network *SNetwork) GetIpEnd() string { + pref, _ := netutils.NewIPV4Prefix(network.IpCidrRange) + endIp := pref.Address.BroadcastAddr(pref.MaskLen) // 255 + endIp = endIp.StepDown() // 254 + return endIp.String() +} + +func (network *SNetwork) GetIpMask() int8 { + pref, _ := netutils.NewIPV4Prefix(network.IpCidrRange) + return pref.MaskLen +} + +func (network *SNetwork) GetGateway() string { + return network.GatewayAddress +} + +func (network *SNetwork) GetServerType() string { + return api.NETWORK_TYPE_GUEST +} + +func (network *SNetwork) GetIsPublic() bool { + return true +} + +func (network *SNetwork) GetPublicScope() rbacutils.TRbacScope { + return rbacutils.ScopeDomain +} diff --git a/pkg/multicloud/google/project.go b/pkg/multicloud/google/project.go new file mode 100644 index 0000000000..bbf1ed04b8 --- /dev/null +++ b/pkg/multicloud/google/project.go @@ -0,0 +1,94 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "time" + + "github.com/pkg/errors" + + "yunion.io/x/jsonutils" +) + +type SProject struct { + Name string + CreateTime time.Time + LifecycleState string + ProjectId string + ProjectNumber string +} + +func (cli *SGoogleClient) GetProject(id string) (*SProject, error) { + project := &SProject{} + return project, cli.get(id, project) +} + +func (cli *SGoogleClient) GetProjects() ([]SProject, error) { + baseUrl := "https://cloudresourcemanager.googleapis.com/v1/projects" + nextPageToken := "" + result := []SProject{} + for { + url := baseUrl + if len(nextPageToken) > 0 { + url = fmt.Sprintf("%s?pageToken=%s", baseUrl, nextPageToken) + } + data, err := jsonRequest(cli.client, "GET", url, nil, cli.Debug) + if err != nil { + return nil, errors.Wrap(err, "JSONRequest") + } + _result := []SProject{} + if data.Contains("projects") { + err = data.Unmarshal(&_result, "projects") + if err != nil { + return nil, errors.Wrap(err, "data.Unmarshal") + } + } + result = append(result, _result...) + nextPageToken, _ = data.GetString("nextPageToken") + if len(nextPageToken) == 0 || len(_result) == 0 { + break + } + } + return result, nil +} + +func (p *SProject) GetName() string { + return p.Name +} + +func (p *SProject) GetId() string { + return p.ProjectId +} + +func (p *SProject) GetGlobalId() string { + return p.ProjectId +} + +func (p *SProject) GetStatus() string { + return "" +} + +func (p *SProject) Refresh() error { + return nil +} + +func (p *SProject) IsEmulated() bool { + return false +} + +func (p *SProject) GetMetadata() *jsonutils.JSONDict { + return nil +} diff --git a/pkg/multicloud/google/provider/doc.go b/pkg/multicloud/google/provider/doc.go new file mode 100644 index 0000000000..7ff1ffef50 --- /dev/null +++ b/pkg/multicloud/google/provider/doc.go @@ -0,0 +1 @@ +package provider // import "yunion.io/x/onecloud/pkg/multicloud/google/provider" diff --git a/pkg/multicloud/google/provider/provider.go b/pkg/multicloud/google/provider/provider.go new file mode 100644 index 0000000000..b92e255730 --- /dev/null +++ b/pkg/multicloud/google/provider/provider.go @@ -0,0 +1,191 @@ +// Copyright 2019 Yunion +// +// 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 provider + +import ( + "context" + "fmt" + "strings" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/multicloud/google" +) + +type SGoogleProviderFactory struct { + cloudprovider.SPublicCloudBaseProviderFactor +} + +func (self *SGoogleProviderFactory) GetId() string { + return google.CLOUD_PROVIDER_GOOGLE +} + +func (self *SGoogleProviderFactory) GetName() string { + return google.CLOUD_PROVIDER_GOOGLE_CN +} + +func (self *SGoogleProviderFactory) ValidateChangeBandwidth(instanceId string, bandwidth int64) error { + return nil +} + +func (self *SGoogleProviderFactory) IsSupportPrepaidResources() bool { + return true +} + +func (self *SGoogleProviderFactory) NeedSyncSkuFromCloud() bool { + return false +} + +func (self *SGoogleProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, input *api.CloudaccountCreateInput) error { + for key, value := range map[string]string{ + "client_email": input.ClientEmail, + "project_id": input.ProjectId, + "private_key_id": input.PrivateKeyId, + "private_key": input.PrivateKey, + } { + if len(value) == 0 { + return httperrors.NewMissingParameterError(key) + } + } + input.Account = fmt.Sprintf("%s/%s", input.ProjectId, input.ClientEmail) + input.Secret = fmt.Sprintf("%s/%s", input.PrivateKeyId, input.PrivateKey) + return nil +} + +func (self *SGoogleProviderFactory) ValidateUpdateCloudaccountCredential(ctx context.Context, userCred mcclient.TokenCredential, input *api.CloudaccountCredentialInput, cloudaccount string) (*cloudprovider.SCloudaccount, error) { + projectID, clientEmail := "", "" + accountInfo := strings.Split(cloudaccount, "/") + if len(accountInfo) == 2 { + projectID, clientEmail = accountInfo[0], accountInfo[1] + } + + for key, value := range map[string]string{ + "private_key_id": input.PrivateKeyId, + "private_key": input.PrivateKey, + } { + if len(value) == 0 { + return nil, httperrors.NewMissingParameterError(key) + } + } + if len(input.ClientEmail) == 0 { + input.ClientEmail = clientEmail + } + + if len(input.ProjectId) == 0 { + input.ProjectId = projectID + } + + account := &cloudprovider.SCloudaccount{ + Account: fmt.Sprintf("%s/%s", input.ProjectId, input.ClientEmail), + Secret: fmt.Sprintf("%s/%s", input.PrivateKeyId, input.PrivateKey), + } + return account, nil +} + +func (self *SGoogleProviderFactory) GetProvider(providerId, providerName, url, account, secret string) (cloudprovider.ICloudProvider, error) { + privateKeyID, privateKey := "", "" + privateKeyInfo := strings.Split(secret, "/") + if len(privateKeyInfo) < 2 { + return nil, fmt.Errorf("Missing privateKeyID or privateKey for google cloud") + } + privateKeyID = privateKeyInfo[0] + privateKey = strings.Join(privateKeyInfo[1:], "/") + projectID, clientEmail := "", "" + accountInfo := strings.Split(account, "/") + if len(accountInfo) < 2 { + return nil, fmt.Errorf("Invalid projectID or client email for google cloud %s", account) + } + projectID, clientEmail = accountInfo[0], accountInfo[1] + client, err := google.NewGoogleClient(providerId, providerName, projectID, clientEmail, privateKeyID, privateKey, false) + if err != nil { + return nil, err + } + return &SGoogleProvider{ + SBaseProvider: cloudprovider.NewBaseProvider(self), + client: client, + }, nil +} + +func (self *SGoogleProviderFactory) GetClientRC(url, account, secret string) (map[string]string, error) { + return map[string]string{ + "ALIYUN_ACCESS_KEY": account, + "ALIYUN_SECRET": secret, + "ALIYUN_REGION": google.GOOGLE_DEFAULT_REGION, + }, nil +} + +func init() { + factory := SGoogleProviderFactory{} + cloudprovider.RegisterFactory(&factory) +} + +type SGoogleProvider struct { + cloudprovider.SBaseProvider + client *google.SGoogleClient +} + +func (self *SGoogleProvider) GetSysInfo() (jsonutils.JSONObject, error) { + regions := self.client.GetIRegions() + info := jsonutils.NewDict() + info.Add(jsonutils.NewInt(int64(len(regions))), "region_count") + info.Add(jsonutils.NewString(google.GOOGLE_API_VERSION), "api_version") + return info, nil +} + +func (self *SGoogleProvider) GetVersion() string { + return google.GOOGLE_API_VERSION +} + +func (self *SGoogleProvider) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + return self.client.GetSubAccounts() +} + +func (self *SGoogleProvider) GetAccountId() string { + return self.client.GetAccountId() +} + +func (self *SGoogleProvider) GetIRegions() []cloudprovider.ICloudRegion { + return self.client.GetIRegions() +} + +func (self *SGoogleProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return self.client.GetIGlobalnetworks() +} + +func (self *SGoogleProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return self.client.GetIGlobalnetworkById(id) +} + +func (self *SGoogleProvider) GetIRegionById(extId string) (cloudprovider.ICloudRegion, error) { + return self.client.GetIRegionById(extId) +} + +func (self *SGoogleProvider) GetBalance() (float64, string, error) { + return 0.0, api.CLOUD_PROVIDER_HEALTH_NORMAL, cloudprovider.ErrNotSupported +} + +func (self *SGoogleProvider) GetIProjects() ([]cloudprovider.ICloudProject, error) { + return self.client.GetIProjects() +} + +func (self *SGoogleProvider) GetStorageClasses(regionId string) []string { + return []string{ + "Standard", "IA", "Archive", + } +} diff --git a/pkg/multicloud/google/region.go b/pkg/multicloud/google/region.go new file mode 100644 index 0000000000..76ec487495 --- /dev/null +++ b/pkg/multicloud/google/region.go @@ -0,0 +1,300 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SRegion struct { + cloudprovider.SFakeOnPremiseRegion + multicloud.SRegion + multicloud.SNoObjectStorageRegion + client *SGoogleClient + + Description string + ID string + Kind string + Name string + Status string + SelfLink string + CreationTimestamp time.Time +} + +func (region *SRegion) GetClient() *SGoogleClient { + return region.client +} + +func (region *SRegion) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (region *SRegion) GetName() string { + if name, ok := RegionNames[region.Name]; ok { + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_GOOGLE_CN, name) + } + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_GOOGLE_CN, region.Name) +} + +func (region *SRegion) GetId() string { + return region.Name +} + +func (region *SRegion) GetGlobalId() string { + return fmt.Sprintf("%s/%s", CLOUD_PROVIDER_GOOGLE, region.Name) +} + +func (region *SRegion) GetGeographicInfo() cloudprovider.SGeographicInfo { + if geoInfo, ok := LatitudeAndLongitude[region.Name]; ok { + return geoInfo + } + return cloudprovider.SGeographicInfo{} +} + +func (region *SRegion) GetProvider() string { + return CLOUD_PROVIDER_GOOGLE +} + +func (region *SRegion) GetStatus() string { + if region.Status == "UP" { + return api.CLOUD_REGION_STATUS_INSERVER + } + return api.CLOUD_REGION_STATUS_OUTOFSERVICE +} + +func (region *SRegion) IsEmulated() bool { + return false +} + +func (region *SRegion) GetIZones() ([]cloudprovider.ICloudZone, error) { + zones, err := region.GetZones(region.Name, 0, "") + if err != nil { + return nil, err + } + izones := []cloudprovider.ICloudZone{} + for i := 0; i < len(zones); i++ { + zones[i].region = region + izones = append(izones, &zones[i]) + } + return izones, nil +} + +func (region *SRegion) GetIZoneById(id string) (cloudprovider.ICloudZone, error) { + zones, err := region.GetIZones() + if err != nil { + return nil, err + } + for i := range zones { + if zones[i].GetGlobalId() == id { + return zones[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) GetIVpcs() ([]cloudprovider.ICloudVpc, error) { + globalnetworks, err := region.client.fetchGlobalNetwork() + if err != nil { + return nil, errors.Wrap(err, "fetchGlobalNetwork") + } + substr := fmt.Sprintf("regions/%s/subnetworks", region.Name) + ivpcs := []cloudprovider.ICloudVpc{} + for i := range globalnetworks { + for _, subnet := range globalnetworks[i].Subnetworks { + if strings.Index(subnet, substr) >= 0 { + vpc := SVpc{region: region, globalnetwork: &globalnetworks[i]} + ivpcs = append(ivpcs, &vpc) + } + } + } + return ivpcs, nil +} + +func (region *SRegion) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) { + ivpcs, err := region.GetIVpcs() + if err != nil { + return nil, err + } + for i := range ivpcs { + if ivpcs[i].GetGlobalId() == id { + return ivpcs[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) GetUrlPrefixWithProjectId() string { + return getUrlPrefix() + region.GetProjectId() +} + +func (region *SRegion) GetProjectId() string { + return region.client.projectId +} + +func (region *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) { + eips, err := region.GetEips("", 0, "") + if err != nil { + return nil, err + } + ieips := []cloudprovider.ICloudEIP{} + for i := range eips { + eips[i].region = region + ieips = append(ieips, &eips[i]) + } + return ieips, nil +} + +func (region *SRegion) GetIVMById(id string) (cloudprovider.ICloudVM, error) { + ivm, err := region.GetInstance(id) + if err != nil { + return nil, err + } + return ivm, nil +} + +func (region *SRegion) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) { + disk, err := region.GetDisk(id) + if err != nil { + return nil, err + } + return disk, nil +} + +func (region *SRegion) GetIEipById(id string) (cloudprovider.ICloudEIP, error) { + eip, err := region.GetEip(id) + if err != nil { + return nil, err + } + return eip, nil +} + +func (region *SRegion) fetchSnapshots() error { + if len(region.client.snapshots) > 0 { + return nil + } + region.client.snapshots = map[string][]SSnapshot{} + snapshots, err := region.GetSnapshots("", 0, "") + if err != nil { + return err + } + regionNames := []string{} + for _, region := range region.client.iregions { + regionNames = append(regionNames, region.GetId()) + } + for _, snapshot := range snapshots { + for _, location := range snapshot.StorageLocations { + _regionName := "" + if utils.IsInStringArray(location, regionNames) { + _regionName = location + } else { + for _, regionName := range regionNames { + if strings.HasPrefix(regionName, location) { + _regionName = regionName + break + } + } + } + if len(_regionName) > 0 { + if _, ok := region.client.snapshots[_regionName]; !ok { + region.client.snapshots[_regionName] = []SSnapshot{} + } + region.client.snapshots[_regionName] = append(region.client.snapshots[_regionName], snapshot) + break + } + + } + } + return nil +} + +func (region *SRegion) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + region.fetchSnapshots() + isnapshots := []cloudprovider.ICloudSnapshot{} + if snapshots, ok := region.client.snapshots[region.Name]; ok { + for i := range snapshots { + snapshots[i].region = region + isnapshots = append(isnapshots, &snapshots[i]) + } + } + return isnapshots, nil +} + +func (region *SRegion) GetISnapshotById(id string) (cloudprovider.ICloudSnapshot, error) { + snapshot, err := region.GetSnapshot(id) + if err != nil { + return nil, err + } + return snapshot, nil +} + +func (region *SRegion) ListAll(resource string, params map[string]string, retval interface{}) error { + return region.client.listAll(resource, params, retval) +} + +func (region *SRegion) List(resource string, params map[string]string, maxResults int, pageToken string, retval interface{}) error { + return region.client.list(resource, params, maxResults, pageToken, retval) +} + +func (region *SRegion) Get(id string, retval interface{}) error { + return region.client.get(id, retval) +} + +func (region *SRegion) fetchResourcePolicies() ([]SResourcePolicy, error) { + if len(region.client.resourcepolices) > 0 { + return region.client.resourcepolices, nil + } + policies, err := region.GetResourcePolicies(0, "") + if err != nil { + return nil, err + } + region.client.resourcepolices = policies + return policies, nil +} + +func (region *SRegion) GetISnapshotPolicies() ([]cloudprovider.ICloudSnapshotPolicy, error) { + policies, err := region.fetchResourcePolicies() + if err != nil { + return nil, err + } + ipolicies := []cloudprovider.ICloudSnapshotPolicy{} + for i := range policies { + policies[i].region = region + if strings.Contains(region.Name, policies[i].SnapshotSchedulePolicy.SnapshotProperties.StorageLocations[0]) { + ipolicies = append(ipolicies, &policies[i]) + } + } + return ipolicies, nil +} + +func (region *SRegion) GetISnapshotPolicyById(id string) (cloudprovider.ICloudSnapshotPolicy, error) { + policy, err := region.GetResourcePolicy(id) + if err != nil { + return nil, err + } + if !strings.Contains(region.Name, policy.SnapshotSchedulePolicy.SnapshotProperties.StorageLocations[0]) { + return nil, cloudprovider.ErrNotFound + } + return policy, nil +} diff --git a/pkg/multicloud/google/regiondisk.go b/pkg/multicloud/google/regiondisk.go new file mode 100644 index 0000000000..eedbac086c --- /dev/null +++ b/pkg/multicloud/google/regiondisk.go @@ -0,0 +1,53 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "time" +) + +type SRegionDisk struct { + storage *SStorage + + Id string + CreationTimestamp time.Time + Name string + SizeGB int + Zone string + Status string + SelfLink string + Type string + LastAttachTimestamp time.Time + LastDetachTimestamp time.Time + LabelFingerprint string + PhysicalBlockSizeBytes string + Kind string +} + +func (region *SRegion) GetRegionDisks(storageType string, maxResults int, pageToken string) ([]SRegionDisk, error) { + disks := []SRegionDisk{} + params := map[string]string{} + if len(storageType) > 0 { + params["filter"] = fmt.Sprintf(`type="%s/regions/%s/diskTypes/%s"`, region.GetUrlPrefixWithProjectId(), region.Name, storageType) + } + resource := fmt.Sprintf("regions/%s/disks", region.Name) + return disks, region.List(resource, params, maxResults, pageToken, &disks) +} + +func (region *SRegion) GetRegionDisk(id string) (*SRegionDisk, error) { + disk := &SRegionDisk{} + return disk, region.Get(id, disk) +} diff --git a/pkg/multicloud/google/regionstorage.go b/pkg/multicloud/google/regionstorage.go new file mode 100644 index 0000000000..cfd5a4407a --- /dev/null +++ b/pkg/multicloud/google/regionstorage.go @@ -0,0 +1,45 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "time" +) + +type SRegionStorage struct { + region *SRegion + + CreationTimestamp time.Time + Name string + Description string + ValidDiskSize string + Zone string + SelfLink string + DefaultDiskSizeGb string + Kind string +} + +func (region *SRegion) GetRegionStorages(maxResults int, pageToken string) ([]SRegionStorage, error) { + storages := []SRegionStorage{} + resource := fmt.Sprintf("regions/%s/diskTypes", region.Name) + params := map[string]string{} + return storages, region.List(resource, params, maxResults, pageToken, &storages) +} + +func (region *SRegion) GetRegionStorage(id string) (*SRegionStorage, error) { + storage := &SRegionStorage{region: region} + return storage, region.Get(id, storage) +} diff --git a/pkg/multicloud/google/resourcepolicy.go b/pkg/multicloud/google/resourcepolicy.go new file mode 100644 index 0000000000..cd9b6b61d2 --- /dev/null +++ b/pkg/multicloud/google/resourcepolicy.go @@ -0,0 +1,177 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SDailySchedule struct { + DaysInCycle int + StartTime string + Duration string +} + +type SDayOfWeek struct { + Day string + StartTime string + Duration string +} + +type SHourlySchedule struct { + HoursInCycle int + StartTime string + Duration string +} + +type SWeeklySchedule struct { + DayOfWeeks []SDayOfWeek +} + +type SSchedule struct { + WeeklySchedule SWeeklySchedule + DailySchedule SDailySchedule + HourlySchedule SHourlySchedule +} + +type SRetentionPolicy struct { + MaxRetentionDays int + OnSourceDiskDelete string +} + +type SSnapshotProperties struct { + StorageLocations []string + GuestFlush bool +} + +type SSnapshotSchedulePolicy struct { + Schedule SSchedule + RetentionPolicy SRetentionPolicy + SnapshotProperties SSnapshotProperties +} + +type SResourcePolicy struct { + region *SRegion + + Id string + + CreationTimestamp time.Time + SelfLink string + Region string + Name string + Status string + Kind string + SnapshotSchedulePolicy SSnapshotSchedulePolicy `json:"snapshotSchedulePolicy"` +} + +func (region *SRegion) GetResourcePolicies(maxResults int, pageToken string) ([]SResourcePolicy, error) { + policies := []SResourcePolicy{} + resource := fmt.Sprintf("regions/%s/resourcePolicies", region.Name) + params := map[string]string{} + return policies, region.List(resource, params, maxResults, pageToken, &policies) +} + +func (region *SRegion) GetResourcePolicy(id string) (*SResourcePolicy, error) { + policy := &SResourcePolicy{region: region} + return policy, region.Get(id, policy) +} + +func (policy *SResourcePolicy) GetId() string { + return getGlobalId(policy.SelfLink) +} + +func (policy *SResourcePolicy) GetGlobalId() string { + return policy.GetId() +} + +func (policy *SResourcePolicy) GetName() string { + return policy.Name +} + +func (policy *SResourcePolicy) GetStatus() string { + switch policy.Status { + case "READY": + return api.SNAPSHOT_POLICY_READY + default: + log.Errorf("unknown policy status %s", policy.Status) + return api.SNAPSHOT_POLICY_UNKNOWN + } +} + +func (policy *SResourcePolicy) Refresh() error { + _policy, err := policy.region.GetResourcePolicy(policy.SelfLink) + if err != nil { + return err + } + return jsonutils.Update(policy, _policy) +} + +func (policy *SResourcePolicy) IsEmulated() bool { + return false +} + +func (policy *SResourcePolicy) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (policy *SResourcePolicy) GetProjectId() string { + return policy.region.GetProjectId() +} + +func (policy *SResourcePolicy) GetRetentionDays() int { + return policy.SnapshotSchedulePolicy.RetentionPolicy.MaxRetentionDays +} + +func (policy *SResourcePolicy) GetRepeatWeekdays() ([]int, error) { + result := []int{1, 2, 3, 4, 5, 6, 7} + if len(policy.SnapshotSchedulePolicy.Schedule.WeeklySchedule.DayOfWeeks) > 0 { + return nil, fmt.Errorf("current not support dayOfWeeks") + } + if policy.SnapshotSchedulePolicy.Schedule.HourlySchedule.HoursInCycle != 0 { + return nil, fmt.Errorf("current not support hourlySchedule") + } + return result, nil +} + +func (policy *SResourcePolicy) GetTimePoints() ([]int, error) { + result := []int{} + if len(policy.SnapshotSchedulePolicy.Schedule.DailySchedule.StartTime) == 0 { + return nil, fmt.Errorf("current only support dailySchedule") + } + if startInfo := strings.Split(policy.SnapshotSchedulePolicy.Schedule.DailySchedule.StartTime, ":"); len(startInfo) >= 2 { + point, err := strconv.Atoi(startInfo[0]) + if err != nil { + return nil, errors.Wrapf(err, "convert %s", policy.SnapshotSchedulePolicy.Schedule.DailySchedule.StartTime) + } + result = append(result, point) + if startInfo[1] != "00" { + result = append(result, point+1) + } + } + return result, nil +} + +func (policy *SResourcePolicy) IsActivated() bool { + return true +} diff --git a/pkg/multicloud/google/securitygroup.go b/pkg/multicloud/google/securitygroup.go new file mode 100644 index 0000000000..90831fd174 --- /dev/null +++ b/pkg/multicloud/google/securitygroup.go @@ -0,0 +1,302 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "net" + "sort" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/util/secrules" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SFirewallAction struct { + IPProtocol string + Ports []string +} + +type SFirewall struct { + Id string + CreationTimestamp time.Time + Name string + Description string + Network string + Priority int + SourceRanges []string + TargetServiceAccounts []string + TargetTags []string + Allowed []SFirewallAction + Denied []SFirewallAction + Direction string + Disabled bool + SelfLink string + Kind string +} + +type FirewallSet []SFirewall + +func (f FirewallSet) Len() int { + return len(f) +} + +func (f FirewallSet) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +func (f FirewallSet) Less(i, j int) bool { + if f[i].Priority != f[j].Priority { + return f[i].Priority < f[j].Priority + } + return len(f[i].Allowed) < len(f[j].Allowed) +} + +type SSecurityGroup struct { + vpc *SVpc + + ServiceAccount string + Tag string +} + +func (region *SRegion) GetFirewalls(network string, maxResults int, pageToken string) ([]SFirewall, error) { + firewalls := []SFirewall{} + params := map[string]string{"filter": "disabled = false"} + resource := "global/firewalls" + return firewalls, region.List(resource, params, maxResults, pageToken, &firewalls) +} + +func (region *SRegion) GetFirewall(id string) (*SFirewall, error) { + firewall := &SFirewall{} + return firewall, region.Get(id, firewall) +} + +func (firewall *SFirewall) _toRules(action secrules.TSecurityRuleAction) ([]secrules.SecurityRule, error) { + rules := []secrules.SecurityRule{} + list := firewall.Allowed + if action == secrules.SecurityRuleDeny { + list = firewall.Denied + } + for _, allow := range list { + rule := secrules.SecurityRule{ + Action: action, + Direction: secrules.DIR_IN, + Description: firewall.Description, + Priority: firewall.Priority, + } + if firewall.Direction == "EGRESS" { + rule.Direction = secrules.DIR_OUT + } + switch allow.IPProtocol { + case "tcp", "udp", "icmp": + rule.Protocol = allow.IPProtocol + case "all": + rule.Protocol = secrules.PROTO_ANY + default: + return nil, fmt.Errorf("unsupport protocol %s", allow.IPProtocol) + } + for _, sourceRange := range firewall.SourceRanges { + if regutils.MatchCIDR(sourceRange) { + _, rule.IPNet, _ = net.ParseCIDR(sourceRange) + } else { + rule.IPNet = &net.IPNet{ + IP: net.ParseIP(sourceRange), + Mask: net.CIDRMask(32, 32), + } + } + ports := []int{} + for _, port := range allow.Ports { + if strings.Index(port, "-") > 0 { + err := rule.ParsePorts(port) + if err != nil { + return nil, errors.Wrapf(err, "Parse port %s", port) + } + rules = append(rules, rule) + } else { + _port, err := strconv.Atoi(port) + if err != nil { + return nil, errors.Wrapf(err, "Atio port %s", port) + } + ports = append(ports, _port) + } + } + if len(ports) > 0 { + rule.Ports = ports + rule.PortStart = -1 + rule.PortEnd = -1 + rules = append(rules, rule) + } + } + } + return rules, nil +} + +func (firewall *SFirewall) toRules() ([]secrules.SecurityRule, error) { + rules := []secrules.SecurityRule{} + _rules, err := firewall._toRules(secrules.SecurityRuleAllow) + if err != nil { + return nil, err + } + rules = append(rules, _rules...) + _rules, err = firewall._toRules(secrules.SecurityRuleDeny) + if err != nil { + return nil, err + } + rules = append(rules, _rules...) + return rules, nil +} + +func (secgroup *SSecurityGroup) GetId() string { + return secgroup.vpc.globalnetwork.GetGlobalId() +} + +func (secgroup *SSecurityGroup) GetGlobalId() string { + if len(secgroup.Tag) > 0 { + return fmt.Sprintf("%s/%s", secgroup.GetId(), secgroup.Tag) + } + if len(secgroup.ServiceAccount) > 0 { + return fmt.Sprintf("%s/%s", secgroup.GetId(), secgroup.ServiceAccount) + } + return secgroup.GetId() +} + +func (secgroup *SSecurityGroup) GetDescription() string { + return "" +} + +func (secgroup *SSecurityGroup) GetName() string { + if len(secgroup.Tag) > 0 { + return secgroup.Tag + } + if len(secgroup.ServiceAccount) > 0 { + return secgroup.ServiceAccount + } + return secgroup.vpc.globalnetwork.GetName() +} + +func (secgroup *SSecurityGroup) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (secgroup *SSecurityGroup) GetStatus() string { + return "" +} + +func (secgroup *SSecurityGroup) IsEmulated() bool { + return false +} + +func (secgroup *SSecurityGroup) Refresh() error { + return nil +} + +func (secgroup *SSecurityGroup) Delete() error { + return nil +} + +func (secgroup *SSecurityGroup) GetProjectId() string { + return "" +} + +func (secgroup *SSecurityGroup) GetVpcId() string { + return secgroup.vpc.GetGlobalId() +} + +func (self *SSecurityGroup) GetRules() ([]secrules.SecurityRule, error) { + _firewalls, err := self.vpc.region.GetFirewalls(self.vpc.globalnetwork.SelfLink, 0, "") + if err != nil { + return nil, err + } + firewalls := []SFirewall{} + for _, firewall := range _firewalls { + if len(self.Tag) > 0 && utils.IsInStringArray(self.Tag, firewall.TargetTags) { + firewalls = append(firewalls, firewall) + } else if len(self.ServiceAccount) > 0 && utils.IsInStringArray(self.ServiceAccount, firewall.TargetServiceAccounts) { + firewalls = append(firewalls, firewall) + } else { + if len(self.Tag) == 0 && len(self.ServiceAccount) == 0 && len(firewall.TargetServiceAccounts) == 0 && len(firewall.TargetTags) == 0 { + firewalls = append(firewalls, firewall) + } + } + } + sort.Sort(FirewallSet(firewalls)) + rules := []secrules.SecurityRule{} + priority := 100 + for _, firewall := range firewalls { + firewall.Priority = priority + if priority > 2 { + priority-- + } + _rules, err := firewall.toRules() + if err != nil { + return nil, err + } + rules = append(rules, _rules...) + } + return rules, nil +} + +func (secgroup *SSecurityGroup) SyncRules(rules []secrules.SecurityRule) error { + return cloudprovider.ErrNotImplemented +} + +func (region *SRegion) GetISecurityGroupById(id string) (cloudprovider.ICloudSecurityGroup, error) { + vpcs, err := region.GetIVpcs() + if err != nil { + return nil, errors.Wrap(err, "GetIVpcs") + } + + for _, vpc := range vpcs { + secgroups, err := vpc.GetISecurityGroups() + if err != nil { + return nil, errors.Wrap(err, "GetISecurityGroups") + } + for _, secgroup := range secgroups { + if secgroup.GetGlobalId() == id { + return secgroup, nil + } + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) GetISecurityGroupByName(vpcId string, name string) (cloudprovider.ICloudSecurityGroup, error) { + ivpc, err := region.GetIVpcById(vpcId) + if err != nil { + return nil, err + } + secgroups, err := ivpc.GetISecurityGroups() + if err != nil { + return nil, errors.Wrap(err, "ivpc.GetISecurityGroups") + } + for _, secgroup := range secgroups { + if secgroup.GetName() == name { + return secgroup, nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (region *SRegion) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) { + return region.GetISecurityGroupByName(conf.VpcId, "") +} diff --git a/pkg/multicloud/google/shell/disk.go b/pkg/multicloud/google/shell/disk.go new file mode 100644 index 0000000000..3950c0ae0e --- /dev/null +++ b/pkg/multicloud/google/shell/disk.go @@ -0,0 +1,76 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DiskListOptions struct { + ZONE string + StorageType string + MaxResults int + PageToken string + } + shellutils.R(&DiskListOptions{}, "disk-list", "List disks", func(cli *google.SRegion, args *DiskListOptions) error { + disks, err := cli.GetDisks(args.ZONE, args.StorageType, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(disks, 0, 0, 0, nil) + return nil + }) + + type DiskShowOptions struct { + ID string + } + shellutils.R(&DiskShowOptions{}, "disk-show", "Show disk", func(cli *google.SRegion, args *DiskShowOptions) error { + disk, err := cli.GetDisk(args.ID) + if err != nil { + return err + } + printObject(disk) + return nil + }) + + type RegionDiskListOptions struct { + StorageType string + MaxResults int + PageToken string + } + shellutils.R(&RegionDiskListOptions{}, "region-disk-list", "List region disks", func(cli *google.SRegion, args *RegionDiskListOptions) error { + disks, err := cli.GetRegionDisks(args.StorageType, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(disks, 0, 0, 0, nil) + return nil + }) + + type RegionDiskShowOptions struct { + ID string + } + shellutils.R(&RegionDiskShowOptions{}, "region-disk-show", "Show region disk", func(cli *google.SRegion, args *RegionDiskShowOptions) error { + disk, err := cli.GetRegionDisk(args.ID) + if err != nil { + return err + } + printObject(disk) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/doc.go b/pkg/multicloud/google/shell/doc.go new file mode 100644 index 0000000000..e7936b2011 --- /dev/null +++ b/pkg/multicloud/google/shell/doc.go @@ -0,0 +1 @@ +package shell // import "yunion.io/x/onecloud/pkg/multicloud/google/shell" diff --git a/pkg/multicloud/google/shell/eip.go b/pkg/multicloud/google/shell/eip.go new file mode 100644 index 0000000000..e0b0ee2157 --- /dev/null +++ b/pkg/multicloud/google/shell/eip.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type EipListOptions struct { + Address string + MaxResults int + PageToken string + } + shellutils.R(&EipListOptions{}, "eip-list", "List eips", func(cli *google.SRegion, args *EipListOptions) error { + eips, err := cli.GetEips(args.Address, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(eips, 0, 0, 0, nil) + return nil + }) + + type EipShowOptions struct { + ID string + } + shellutils.R(&EipShowOptions{}, "eip-show", "Show eip", func(cli *google.SRegion, args *EipShowOptions) error { + eip, err := cli.GetEip(args.ID) + if err != nil { + return err + } + printObject(eip) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/firewall.go b/pkg/multicloud/google/shell/firewall.go new file mode 100644 index 0000000000..09719b0452 --- /dev/null +++ b/pkg/multicloud/google/shell/firewall.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type FirewallListOptions struct { + Network string + MaxResults int + PageToken string + } + shellutils.R(&FirewallListOptions{}, "firewall-list", "List firewalls", func(cli *google.SRegion, args *FirewallListOptions) error { + firewalls, err := cli.GetFirewalls(args.Network, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(firewalls, 0, 0, 0, nil) + return nil + }) + + type FirewallShowOptions struct { + ID string + } + shellutils.R(&FirewallShowOptions{}, "firewall-show", "Show firewall", func(cli *google.SRegion, args *FirewallShowOptions) error { + firewall, err := cli.GetFirewall(args.ID) + if err != nil { + return err + } + printObject(firewall) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/globalnetwork.go b/pkg/multicloud/google/shell/globalnetwork.go new file mode 100644 index 0000000000..6f09c9e288 --- /dev/null +++ b/pkg/multicloud/google/shell/globalnetwork.go @@ -0,0 +1,48 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type GlobalNetworkListOptions struct { + MaxResults int + PageToken string + } + shellutils.R(&GlobalNetworkListOptions{}, "global-network-list", "List globalnetworks", func(cli *google.SRegion, args *GlobalNetworkListOptions) error { + globalnetworks, err := cli.GetClient().GetGlobalNetworks(args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(globalnetworks, 0, 0, 0, nil) + return nil + }) + + type GlobalNetworkShowOptions struct { + ID string + } + shellutils.R(&GlobalNetworkShowOptions{}, "global-network-show", "Show globalnetwork", func(cli *google.SRegion, args *GlobalNetworkShowOptions) error { + globalnetwork, err := cli.GetClient().GetGlobalNetwork(args.ID) + if err != nil { + return err + } + printObject(globalnetwork) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/image.go b/pkg/multicloud/google/shell/image.go new file mode 100644 index 0000000000..fb39860bef --- /dev/null +++ b/pkg/multicloud/google/shell/image.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ImageListOptions struct { + Project string + MaxResults int + PageToken string + } + shellutils.R(&ImageListOptions{}, "image-list", "List images", func(cli *google.SRegion, args *ImageListOptions) error { + images, err := cli.GetImages(args.Project, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(images, 0, 0, 0, nil) + return nil + }) + + type ImageShowOptions struct { + ID string + } + shellutils.R(&ImageShowOptions{}, "image-show", "Show image", func(cli *google.SRegion, args *ImageShowOptions) error { + image, err := cli.GetImage(args.ID) + if err != nil { + return err + } + printObject(image) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/instance.go b/pkg/multicloud/google/shell/instance.go new file mode 100644 index 0000000000..ece07ded15 --- /dev/null +++ b/pkg/multicloud/google/shell/instance.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type InstanceListOptions struct { + ZONE string + MaxResults int + PageToken string + } + shellutils.R(&InstanceListOptions{}, "instance-list", "List instances", func(cli *google.SRegion, args *InstanceListOptions) error { + instances, err := cli.GetInstances(args.ZONE, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(instances, 0, 0, 0, nil) + return nil + }) + + type InstanceShowOptions struct { + ID string + } + shellutils.R(&InstanceShowOptions{}, "instance-show", "Show instance", func(cli *google.SRegion, args *InstanceShowOptions) error { + instance, err := cli.GetInstance(args.ID) + if err != nil { + return err + } + printObject(instance) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/machinetype.go b/pkg/multicloud/google/shell/machinetype.go new file mode 100644 index 0000000000..243797fd82 --- /dev/null +++ b/pkg/multicloud/google/shell/machinetype.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type MachineTypeListOptions struct { + ZONE string + MaxResults int + PageToken string + } + shellutils.R(&MachineTypeListOptions{}, "machine-type-list", "List machinetypes", func(cli *google.SRegion, args *MachineTypeListOptions) error { + machinetypes, err := cli.GetMachineTypes(args.ZONE, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(machinetypes, 0, 0, 0, nil) + return nil + }) + + type MachineTypeShowOptions struct { + ID string + } + shellutils.R(&MachineTypeShowOptions{}, "machine-type-show", "Show machinetype", func(cli *google.SRegion, args *MachineTypeShowOptions) error { + machinetype, err := cli.GetMachineType(args.ID) + if err != nil { + return err + } + printObject(machinetype) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/network.go b/pkg/multicloud/google/shell/network.go new file mode 100644 index 0000000000..db1931df21 --- /dev/null +++ b/pkg/multicloud/google/shell/network.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type NetworkListOptions struct { + Network string + MaxResults int + PageToken string + } + shellutils.R(&NetworkListOptions{}, "network-list", "List networks", func(cli *google.SRegion, args *NetworkListOptions) error { + networks, err := cli.GetNetworks(args.Network, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(networks, 0, 0, 0, nil) + return nil + }) + + type NetworkShowOptions struct { + ID string + } + shellutils.R(&NetworkShowOptions{}, "network-show", "Show network", func(cli *google.SRegion, args *NetworkShowOptions) error { + network, err := cli.GetNetwork(args.ID) + if err != nil { + return err + } + printObject(network) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/printutils.go b/pkg/multicloud/google/shell/printutils.go new file mode 100644 index 0000000000..1b46ec704a --- /dev/null +++ b/pkg/multicloud/google/shell/printutils.go @@ -0,0 +1,25 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import "yunion.io/x/onecloud/pkg/util/printutils" + +func printList(data interface{}, total, offset, limit int, columns []string) { + printutils.PrintInterfaceList(data, total, offset, limit, columns) +} + +func printObject(obj interface{}) { + printutils.PrintInterfaceObject(obj) +} diff --git a/pkg/multicloud/google/shell/project.go b/pkg/multicloud/google/shell/project.go new file mode 100644 index 0000000000..2ef5cc5712 --- /dev/null +++ b/pkg/multicloud/google/shell/project.go @@ -0,0 +1,35 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ProjectListOptions struct { + MaxResults int + PageToken string + } + shellutils.R(&ProjectListOptions{}, "project-list", "List projects", func(cli *google.SRegion, args *ProjectListOptions) error { + projects, err := cli.GetClient().GetProjects() + if err != nil { + return err + } + printList(projects, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/google/shell/region.go b/pkg/multicloud/google/shell/region.go new file mode 100644 index 0000000000..6442250ffb --- /dev/null +++ b/pkg/multicloud/google/shell/region.go @@ -0,0 +1,30 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type RegionListOptions struct { + } + shellutils.R(&RegionListOptions{}, "region-list", "List regions", func(cli *google.SRegion, args *RegionListOptions) error { + regions := cli.GetClient().GetRegions() + printList(regions, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/google/shell/resourcepolicy.go b/pkg/multicloud/google/shell/resourcepolicy.go new file mode 100644 index 0000000000..39b30714b2 --- /dev/null +++ b/pkg/multicloud/google/shell/resourcepolicy.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ResourcePolicyListOptions struct { + Disk string + MaxResults int + PageToken string + } + shellutils.R(&ResourcePolicyListOptions{}, "resource-policy-list", "List resourcepolicys", func(cli *google.SRegion, args *ResourcePolicyListOptions) error { + resourcepolicys, err := cli.GetResourcePolicies(args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(resourcepolicys, 0, 0, 0, nil) + return nil + }) + + type ResourcePolicyShowOptions struct { + ID string + } + shellutils.R(&ResourcePolicyShowOptions{}, "resource-policy-show", "Show resourcepolicy", func(cli *google.SRegion, args *ResourcePolicyShowOptions) error { + resourcepolicy, err := cli.GetResourcePolicy(args.ID) + if err != nil { + return err + } + printObject(resourcepolicy) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/snapshot.go b/pkg/multicloud/google/shell/snapshot.go new file mode 100644 index 0000000000..a4181ec4bb --- /dev/null +++ b/pkg/multicloud/google/shell/snapshot.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SnapshotListOptions struct { + Disk string + MaxResults int + PageToken string + } + shellutils.R(&SnapshotListOptions{}, "snapshot-list", "List snapshots", func(cli *google.SRegion, args *SnapshotListOptions) error { + snapshots, err := cli.GetSnapshots(args.Disk, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(snapshots, 0, 0, 0, nil) + return nil + }) + + type SnapshotShowOptions struct { + ID string + } + shellutils.R(&SnapshotShowOptions{}, "snapshot-show", "Show snapshot", func(cli *google.SRegion, args *SnapshotShowOptions) error { + snapshot, err := cli.GetSnapshot(args.ID) + if err != nil { + return err + } + printObject(snapshot) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/storage.go b/pkg/multicloud/google/shell/storage.go new file mode 100644 index 0000000000..88020878e9 --- /dev/null +++ b/pkg/multicloud/google/shell/storage.go @@ -0,0 +1,74 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type StorageListOptions struct { + ZONE string + MaxResults int + PageToken string + } + shellutils.R(&StorageListOptions{}, "storage-list", "List storages", func(cli *google.SRegion, args *StorageListOptions) error { + storages, err := cli.GetStorages(args.ZONE, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(storages, 0, 0, 0, nil) + return nil + }) + + type StorageShowOptions struct { + ID string + } + shellutils.R(&StorageShowOptions{}, "storage-show", "Show storage", func(cli *google.SRegion, args *StorageShowOptions) error { + storage, err := cli.GetStorage(args.ID) + if err != nil { + return err + } + printObject(storage) + return nil + }) + + type RegionStorageListOptions struct { + MaxResults int + PageToken string + } + shellutils.R(&RegionStorageListOptions{}, "region-storage-list", "List region storages", func(cli *google.SRegion, args *RegionStorageListOptions) error { + storages, err := cli.GetRegionStorages(args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(storages, 0, 0, 0, nil) + return nil + }) + + type RegionStorageShowOptions struct { + ID string + } + shellutils.R(&RegionStorageShowOptions{}, "region-storage-show", "Show region storage", func(cli *google.SRegion, args *RegionStorageShowOptions) error { + storage, err := cli.GetRegionStorage(args.ID) + if err != nil { + return err + } + printObject(storage) + return nil + }) + +} diff --git a/pkg/multicloud/google/shell/zone.go b/pkg/multicloud/google/shell/zone.go new file mode 100644 index 0000000000..1de9c52648 --- /dev/null +++ b/pkg/multicloud/google/shell/zone.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/google" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ZoneListOptions struct { + RegionId string + MaxResults int + PageToken string + } + shellutils.R(&ZoneListOptions{}, "zone-list", "List zones", func(cli *google.SRegion, args *ZoneListOptions) error { + zones, err := cli.GetZones(args.RegionId, args.MaxResults, args.PageToken) + if err != nil { + return err + } + printList(zones, 0, 0, 0, nil) + return nil + }) + + type ZoneShowOptions struct { + ID string + } + shellutils.R(&ZoneShowOptions{}, "zone-show", "Show zones", func(cli *google.SRegion, args *ZoneShowOptions) error { + zone, err := cli.GetZone(args.ID) + if err != nil { + return err + } + printObject(zone) + return nil + }) + +} diff --git a/pkg/multicloud/google/snapshot.go b/pkg/multicloud/google/snapshot.go new file mode 100644 index 0000000000..2ae96bdc53 --- /dev/null +++ b/pkg/multicloud/google/snapshot.go @@ -0,0 +1,127 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SSnapshot struct { + region *SRegion + + Id string + CreationTimestamp time.Time + Name string + Status string + SourceDisk string + SourceDiskId string + DiskSizeGb int32 + StorageBytes int + StorageBytesStatus string + Licenses []string + SelfLink string + LabelFingerprint string + LicenseCodes []string + StorageLocations []string + Kind string +} + +func (region *SRegion) GetSnapshots(disk string, maxResults int, pageToken string) ([]SSnapshot, error) { + snapshots := []SSnapshot{} + params := map[string]string{} + if len(disk) > 0 { + params["filter"] = fmt.Sprintf(`sourceDisk="%s"`, disk) + } + resource := "global/snapshots" + return snapshots, region.List(resource, params, maxResults, pageToken, &snapshots) +} + +func (region *SRegion) GetSnapshot(id string) (*SSnapshot, error) { + snapshot := &SSnapshot{region: region} + return snapshot, region.Get(id, snapshot) +} + +func (snapshot *SSnapshot) GetId() string { + return snapshot.SelfLink +} + +func (snapshot *SSnapshot) GetGlobalId() string { + return getGlobalId(snapshot.SelfLink) +} + +func (snapshot *SSnapshot) GetName() string { + return snapshot.Name +} + +//CREATING, DELETING, FAILED, READY, or UPLOADING +func (snapshot *SSnapshot) GetStatus() string { + switch snapshot.Status { + case "CREATING": + return api.SNAPSHOT_CREATING + case "DELETING": + return api.SNAPSHOT_DELETING + case "FAILED": + return api.SNAPSHOT_UNKNOWN + case "READY", "UPLOADING": + return api.SNAPSHOT_READY + default: + return api.SNAPSHOT_UNKNOWN + } +} + +func (snapshot *SSnapshot) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (snapshot *SSnapshot) IsEmulated() bool { + return false +} + +func (snapshot *SSnapshot) Refresh() error { + _snapshot, err := snapshot.region.GetSnapshot(snapshot.SelfLink) + if err != nil { + return err + } + return jsonutils.Update(snapshot, _snapshot) +} + +func (snapshot *SSnapshot) GetSizeMb() int32 { + return snapshot.DiskSizeGb * 1024 +} + +func (snapshot *SSnapshot) GetDiskId() string { + return snapshot.SourceDisk +} + +func (snapshot *SSnapshot) GetDiskType() string { + if len(snapshot.Licenses) > 0 { + return api.DISK_TYPE_SYS + } + return api.DISK_TYPE_DATA +} + +func (snapshot *SSnapshot) Delete() error { + return cloudprovider.ErrNotImplemented +} + +func (snapshot *SSnapshot) GetProjectId() string { + return snapshot.region.GetProjectId() +} diff --git a/pkg/multicloud/google/storage.go b/pkg/multicloud/google/storage.go new file mode 100644 index 0000000000..744e2ba809 --- /dev/null +++ b/pkg/multicloud/google/storage.go @@ -0,0 +1,145 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SStorage struct { + zone *SZone + + CreationTimestamp time.Time + Name string + Description string + ValidDiskSize string + Zone string + SelfLink string + DefaultDiskSizeGb string + Kind string +} + +func (region *SRegion) GetStorages(zone string, maxResults int, pageToken string) ([]SStorage, error) { + storages := []SStorage{} + if len(zone) == 0 { + return nil, fmt.Errorf("zone params can not be empty") + } + resource := fmt.Sprintf("zones/%s/diskTypes", zone) + params := map[string]string{} + return storages, region.List(resource, params, maxResults, pageToken, &storages) +} + +func (region *SRegion) GetStorage(id string) (*SStorage, error) { + storage := &SStorage{} + return storage, region.Get(id, storage) +} + +func (storage *SStorage) GetId() string { + return getGlobalId(storage.SelfLink) +} + +func (storage *SStorage) GetGlobalId() string { + return storage.GetId() +} + +func (storage *SStorage) GetName() string { + return storage.Description +} + +func (storage *SStorage) GetStatus() string { + return api.STORAGE_ONLINE +} + +func (storage *SStorage) IsEmulated() bool { + return true +} + +func (storage *SStorage) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (storage *SStorage) Refresh() error { + _storage, err := storage.zone.region.GetStorage(storage.SelfLink) + if err != nil { + return err + } + return jsonutils.Update(storage, _storage) +} + +func (storage *SStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return storage.zone.region.getStoragecache() +} + +func (storage *SStorage) GetIZone() cloudprovider.ICloudZone { + return storage.zone +} + +func (storage *SStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + disks, err := storage.zone.region.GetDisks(storage.zone.Name, storage.Name, 0, "") + if err != nil { + return nil, err + } + idisks := []cloudprovider.ICloudDisk{} + for i := range disks { + disks[i].storage = storage + idisks = append(idisks, &disks[i]) + } + return idisks, nil +} + +func (storage *SStorage) GetStorageType() string { + return storage.Name +} + +func (storage *SStorage) GetMediumType() string { + return api.DISK_TYPE_SSD +} + +func (storage *SStorage) GetCapacityMB() int64 { + return 0 +} + +func (storage *SStorage) GetStorageConf() jsonutils.JSONObject { + return jsonutils.Marshal(map[string]string{ + "ValidDiskSize": storage.ValidDiskSize, + "DefaultDiskSizeGb": storage.DefaultDiskSizeGb, + }) +} + +func (storage *SStorage) GetEnabled() bool { + return true +} + +func (storage *SStorage) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) { + return nil, cloudprovider.ErrNotFound +} + +func (storage *SStorage) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (storage *SStorage) GetMountPoint() string { + return "" +} + +func (storage *SStorage) IsSysDiskStore() bool { + return storage.Name != api.STORAGE_GOOGLE_LOCAL_STORAGE +} diff --git a/pkg/multicloud/google/storagecache.go b/pkg/multicloud/google/storagecache.go new file mode 100644 index 0000000000..3585170f43 --- /dev/null +++ b/pkg/multicloud/google/storagecache.go @@ -0,0 +1,123 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "context" + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SStoragecache struct { + region *SRegion + + iimages []cloudprovider.ICloudImage +} + +func (cache *SStoragecache) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (cache *SStoragecache) GetId() string { + return fmt.Sprintf("%s-%s", cache.region.client.providerId, cache.region.GetId()) +} + +func (cache *SStoragecache) GetName() string { + return fmt.Sprintf("%s-%s", cache.region.client.providerName, cache.region.GetId()) +} + +func (cache *SStoragecache) GetStatus() string { + return "available" +} + +func (cache *SStoragecache) Refresh() error { + return nil +} + +func (cache *SStoragecache) GetGlobalId() string { + return fmt.Sprintf("%s-%s", cache.region.client.providerId, cache.region.GetGlobalId()) +} + +func (cache *SStoragecache) IsEmulated() bool { + return true +} + +func (cache *SStoragecache) GetIImages() ([]cloudprovider.ICloudImage, error) { + images, err := cache.region.fetchImages() + if err != nil { + return nil, err + } + iimages := []cloudprovider.ICloudImage{} + for i := range images { + images[i].storagecache = cache + iimages = append(iimages, &images[i]) + } + return iimages, nil +} + +func (cache *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) { + image, err := cache.region.GetImage(extId) + if err != nil { + return nil, err + } + return image, nil +} + +func (cache *SStoragecache) GetPath() string { + return "" +} + +func (cache *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, isForce bool) (string, error) { + return "", cloudprovider.ErrNotImplemented +} + +func (cache *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (cache *SRegion) CheckBucket(bucketName string) (*oss.Bucket, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (cache *SRegion) CreateImage(snapshoutId, imageName, imageDesc string) (string, error) { + return "", cloudprovider.ErrNotImplemented +} + +func (cache *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + cache := &SStoragecache{region: region} + return []cloudprovider.ICloudStoragecache{cache}, nil +} + +func (region *SRegion) getStoragecache() cloudprovider.ICloudStoragecache { + return &SStoragecache{region: region} +} + +func (region *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) { + cache := region.getStoragecache() + if id == cache.GetGlobalId() { + return cache, nil + } + return nil, cloudprovider.ErrNotFound +} diff --git a/pkg/multicloud/google/vpc.go b/pkg/multicloud/google/vpc.go new file mode 100644 index 0000000000..4f26d6166e --- /dev/null +++ b/pkg/multicloud/google/vpc.go @@ -0,0 +1,122 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SVpc struct { + multicloud.SVpc + globalnetwork *SGlobalNetwork + + region *SRegion +} + +func (vpc *SVpc) GetName() string { + return fmt.Sprintf("%s(%s)", vpc.globalnetwork.Name, vpc.region.Name) +} + +func (vpc *SVpc) GetId() string { + return getGlobalId(vpc.globalnetwork.SelfLink) +} + +func (vpc *SVpc) GetGlobalId() string { + return vpc.GetId() +} + +func (vpc *SVpc) Refresh() error { + return nil +} + +func (vpc *SVpc) GetStatus() string { + return api.VPC_STATUS_AVAILABLE +} + +func (vpc *SVpc) Delete() error { + return cloudprovider.ErrNotSupported +} + +func (vpc *SVpc) GetCidrBlock() string { + return "" +} + +func (vpc *SVpc) GetIGlobalNetworkId() string { + return vpc.globalnetwork.GetGlobalId() +} + +func (vpc *SVpc) IsEmulated() bool { + return false +} + +func (vpc *SVpc) GetIsDefault() bool { + return false +} + +func (vpc *SVpc) GetRegion() cloudprovider.ICloudRegion { + return vpc.region +} + +func (vpc *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (vpc *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, error) { + firewalls, err := vpc.region.GetFirewalls(vpc.globalnetwork.SelfLink, 0, "") + if err != nil { + return nil, errors.Wrap(err, "GetFirewalls") + } + isecgroups := []cloudprovider.ICloudSecurityGroup{} + tags := []string{} + allInstance := false + for _, firewall := range firewalls { + if len(firewall.TargetServiceAccounts) > 0 { + secgroup := &SSecurityGroup{vpc: vpc, ServiceAccount: firewall.TargetServiceAccounts[0]} + isecgroups = append(isecgroups, secgroup) + } else if len(firewall.TargetTags) > 0 && !utils.IsInStringArray(firewall.TargetTags[0], tags) { + secgroup := &SSecurityGroup{vpc: vpc, Tag: firewall.TargetTags[0]} + tags = append(tags, firewall.TargetTags[0]) + isecgroups = append(isecgroups, secgroup) + } else if !allInstance { + secgroup := &SSecurityGroup{vpc: vpc} + isecgroups = append(isecgroups, secgroup) + allInstance = true + } + } + return isecgroups, nil +} + +func (vpc *SVpc) getWire() *SWire { + return &SWire{vpc: vpc} +} + +func (vpc *SVpc) GetIWires() ([]cloudprovider.ICloudWire, error) { + wire := vpc.getWire() + return []cloudprovider.ICloudWire{wire}, nil +} + +func (vpc *SVpc) GetIWireById(id string) (cloudprovider.ICloudWire, error) { + if id != vpc.getWire().GetGlobalId() { + return nil, cloudprovider.ErrNotFound + } + return &SWire{vpc: vpc}, nil +} diff --git a/pkg/multicloud/google/wire.go b/pkg/multicloud/google/wire.go new file mode 100644 index 0000000000..50c0d8944b --- /dev/null +++ b/pkg/multicloud/google/wire.go @@ -0,0 +1,96 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SWire struct { + vpc *SVpc +} + +func (wire *SWire) GetId() string { + return wire.vpc.GetGlobalId() +} + +func (wire *SWire) GetGlobalId() string { + return fmt.Sprintf("%s-%s", getGlobalId(wire.GetId()), wire.vpc.region.Name) +} + +func (wire *SWire) GetName() string { + return wire.vpc.GetName() +} + +func (wire *SWire) CreateINetwork(name string, cidr string, desc string) (cloudprovider.ICloudNetwork, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (wire *SWire) GetIVpc() cloudprovider.ICloudVpc { + return wire.vpc +} + +func (wire *SWire) GetIZone() cloudprovider.ICloudZone { + return nil +} + +func (wire *SWire) GetINetworks() ([]cloudprovider.ICloudNetwork, error) { + networks, err := wire.vpc.region.GetNetworks(wire.vpc.globalnetwork.SelfLink, 0, "") + if err != nil { + return nil, err + } + inetworks := []cloudprovider.ICloudNetwork{} + for i := range networks { + networks[i].wire = wire + inetworks = append(inetworks, &networks[i]) + } + return inetworks, nil +} + +func (wire *SWire) GetINetworkById(id string) (cloudprovider.ICloudNetwork, error) { + network, err := wire.vpc.region.GetNetwork(id) + if err != nil { + return nil, err + } + if network.Network != wire.vpc.globalnetwork.SelfLink { + return nil, cloudprovider.ErrNotFound + } + network.wire = wire + return network, nil +} + +func (wire *SWire) GetBandwidth() int { + return 0 +} + +func (wire *SWire) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (wire *SWire) GetStatus() string { + return "available" +} + +func (wire *SWire) IsEmulated() bool { + return false +} + +func (wire *SWire) Refresh() error { + return nil +} diff --git a/pkg/multicloud/google/zone.go b/pkg/multicloud/google/zone.go new file mode 100644 index 0000000000..9355328b24 --- /dev/null +++ b/pkg/multicloud/google/zone.go @@ -0,0 +1,125 @@ +// Copyright 2019 Yunion +// +// 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 google + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SZone struct { + region *SRegion + + Description string + ID string + Kind string + Name string + Region string + SelfLink string + AvailableCpuPlatforms []string + Status string +} + +func (region *SRegion) GetZone(id string) (*SZone, error) { + zone := &SZone{} + return zone, region.Get(id, zone) +} + +func (region *SRegion) GetZones(regionId string, maxResults int, pageToken string) ([]SZone, error) { + zones := []SZone{} + params := map[string]string{} + if len(regionId) > 0 { + params["filter"] = fmt.Sprintf(`region="%s/regions/%s"`, region.GetUrlPrefixWithProjectId(), regionId) + } + resource := "zones" + return zones, region.List(resource, params, maxResults, pageToken, &zones) +} + +func (zone *SZone) GetName() string { + return zone.Name +} + +func (zone *SZone) GetGlobalId() string { + return zone.GetId() +} + +func (zone *SZone) GetId() string { + return fmt.Sprintf("%s/%s", zone.region.GetGlobalId(), zone.Name) +} + +func (zone *SZone) GetIHostById(hostId string) (cloudprovider.ICloudHost, error) { + if hostId != zone.getHost().GetGlobalId() { + return nil, cloudprovider.ErrNotFound + } + return &SHost{zone: zone}, nil +} + +func (zone *SZone) getHost() *SHost { + return &SHost{zone: zone} +} + +func (zone *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) { + host := zone.getHost() + return []cloudprovider.ICloudHost{host}, nil +} + +func (zone *SZone) GetIRegion() cloudprovider.ICloudRegion { + return zone.region +} + +func (zone *SZone) GetIStorageById(storageId string) (cloudprovider.ICloudStorage, error) { + storage, err := zone.region.GetStorage(storageId) + if err != nil { + return nil, err + } + storage.zone = zone + return storage, nil +} + +func (zone *SZone) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + storages, err := zone.region.GetStorages(zone.Name, 0, "") + if err != nil { + return nil, err + } + istorages := []cloudprovider.ICloudStorage{} + for i := range storages { + storages[i].zone = zone + istorages = append(istorages, &storages[i]) + } + return istorages, nil +} + +func (zone *SZone) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (zone *SZone) IsEmulated() bool { + return true +} + +func (zone *SZone) Refresh() error { + return nil +} + +func (zone *SZone) GetStatus() string { + if zone.Status == "UP" { + return api.ZONE_ENABLE + } + return api.ZONE_SOLDOUT +} diff --git a/pkg/multicloud/huawei/provider/provider.go b/pkg/multicloud/huawei/provider/provider.go index 0d0c3f523d..275b3cabea 100644 --- a/pkg/multicloud/huawei/provider/provider.go +++ b/pkg/multicloud/huawei/provider/provider.go @@ -145,6 +145,14 @@ func (self *SHuaweiProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (cli *SHuaweiProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (cli *SHuaweiProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SHuaweiProvider) GetIRegionById(extId string) (cloudprovider.ICloudRegion, error) { return self.client.GetIRegionById(extId) } diff --git a/pkg/multicloud/loader/loader.go b/pkg/multicloud/loader/loader.go index 14be3d9de2..a1a43974e8 100644 --- a/pkg/multicloud/loader/loader.go +++ b/pkg/multicloud/loader/loader.go @@ -20,7 +20,8 @@ import ( _ "yunion.io/x/onecloud/pkg/multicloud/aliyun/provider" _ "yunion.io/x/onecloud/pkg/multicloud/aws/provider" _ "yunion.io/x/onecloud/pkg/multicloud/azure/provider" - _ "yunion.io/x/onecloud/pkg/multicloud/esxi/provider" // private clouds + _ "yunion.io/x/onecloud/pkg/multicloud/esxi/provider" // private clouds + _ "yunion.io/x/onecloud/pkg/multicloud/google/provider" // public clouds _ "yunion.io/x/onecloud/pkg/multicloud/huawei/provider" _ "yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph/provider" _ "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider" diff --git a/pkg/multicloud/objectstore/provider/provider.go b/pkg/multicloud/objectstore/provider/provider.go index faaa0a901e..5e48ea6a9d 100644 --- a/pkg/multicloud/objectstore/provider/provider.go +++ b/pkg/multicloud/objectstore/provider/provider.go @@ -114,6 +114,14 @@ func (self *SObjectStoreProvider) GetIRegions() []cloudprovider.ICloudRegion { return nil } +func (self *SObjectStoreProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (self *SObjectStoreProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SObjectStoreProvider) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { return nil, cloudprovider.ErrNotSupported } diff --git a/pkg/multicloud/openstack/provider/provider.go b/pkg/multicloud/openstack/provider/provider.go index b4648164ae..923cafb436 100644 --- a/pkg/multicloud/openstack/provider/provider.go +++ b/pkg/multicloud/openstack/provider/provider.go @@ -170,6 +170,14 @@ func (self *SOpenStackProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (self *SOpenStackProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (self *SOpenStackProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SOpenStackProvider) GetIRegionById(extId string) (cloudprovider.ICloudRegion, error) { return self.client.GetIRegionById(extId) } diff --git a/pkg/multicloud/qcloud/provider/provider.go b/pkg/multicloud/qcloud/provider/provider.go index 4c83cec99c..02d238622b 100644 --- a/pkg/multicloud/qcloud/provider/provider.go +++ b/pkg/multicloud/qcloud/provider/provider.go @@ -161,6 +161,14 @@ func (self *SQcloudProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (client *SQcloudProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (client *SQcloudProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SQcloudProvider) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { return self.client.GetIRegionById(id) } diff --git a/pkg/multicloud/region_base.go b/pkg/multicloud/region_base.go index b9ff24ab3f..7350f56fb5 100644 --- a/pkg/multicloud/region_base.go +++ b/pkg/multicloud/region_base.go @@ -23,6 +23,46 @@ import ( type SRegion struct{} +func (r *SRegion) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) { + return nil, fmt.Errorf("Not Implement GetIDiskById") +} + +func (r *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + return nil, fmt.Errorf("Not Implement GetIHostById") +} + +func (r *SRegion) GetIHosts() ([]cloudprovider.ICloudHost, error) { + return nil, fmt.Errorf("Not Implement GetIHosts") +} + +func (r *SRegion) GetISnapshotById(snapshotId string) (cloudprovider.ICloudSnapshot, error) { + return nil, fmt.Errorf("Not Implement GetISnapshotById") +} + +func (r *SRegion) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + return nil, fmt.Errorf("Not Implement GetISnapshots") +} + +func (r *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + return nil, fmt.Errorf("Not Implement GetIStorageById") +} + +func (r *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) { + return nil, fmt.Errorf("Not Implement GetIStoragecacheById") +} + +func (r *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + return nil, fmt.Errorf("Not Implement GetIStoragecaches") +} + +func (r *SRegion) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + return nil, fmt.Errorf("Not Implement GetIStorages") +} + +func (r *SRegion) GetIVMById(id string) (cloudprovider.ICloudVM, error) { + return nil, fmt.Errorf("Not Implement GetIVMById") +} + func (r *SRegion) CreateSnapshotPolicy(input *cloudprovider.SnapshotPolicyInput) (string, error) { return "", fmt.Errorf("CreateSnapshotPolicy not implement") } diff --git a/pkg/multicloud/ucloud/provider/provider.go b/pkg/multicloud/ucloud/provider/provider.go index 78934ac1e4..06627ed13f 100644 --- a/pkg/multicloud/ucloud/provider/provider.go +++ b/pkg/multicloud/ucloud/provider/provider.go @@ -149,6 +149,14 @@ func (self *SUcloudProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (cli *SUcloudProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (cli *SUcloudProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SUcloudProvider) GetIRegionById(extId string) (cloudprovider.ICloudRegion, error) { return self.client.GetIRegionById(extId) } diff --git a/pkg/multicloud/vpc_base.go b/pkg/multicloud/vpc_base.go index 474a1464f1..e292f90a9c 100644 --- a/pkg/multicloud/vpc_base.go +++ b/pkg/multicloud/vpc_base.go @@ -27,3 +27,7 @@ type SVpc struct { func (self *SVpc) GetINatGateways() ([]cloudprovider.ICloudNatGateway, error) { return nil, fmt.Errorf("Not Implemented GetNatGateways") } + +func (self *SVpc) GetIGlobalNetworkId() string { + return "" +} diff --git a/pkg/multicloud/zstack/provider/provider.go b/pkg/multicloud/zstack/provider/provider.go index f86e3fb85d..751f7278f5 100644 --- a/pkg/multicloud/zstack/provider/provider.go +++ b/pkg/multicloud/zstack/provider/provider.go @@ -124,6 +124,14 @@ func (self *SZStackProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } +func (cli *SZStackProvider) GetIGlobalnetworks() ([]cloudprovider.ICloudGlobalnetwork, error) { + return []cloudprovider.ICloudGlobalnetwork{}, nil +} + +func (cli *SZStackProvider) GetIGlobalnetworkById(id string) (cloudprovider.ICloudGlobalnetwork, error) { + return nil, cloudprovider.ErrNotFound +} + func (self *SZStackProvider) GetIRegionById(extId string) (cloudprovider.ICloudRegion, error) { return self.client.GetIRegionById(extId) } diff --git a/vendor/cloud.google.com/go/AUTHORS b/vendor/cloud.google.com/go/AUTHORS new file mode 100644 index 0000000000..c364af1da0 --- /dev/null +++ b/vendor/cloud.google.com/go/AUTHORS @@ -0,0 +1,15 @@ +# This is the official list of cloud authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. + +Filippo Valsorda +Google Inc. +Ingo Oeser +Palm Stone Games, Inc. +Paweł Knap +Péter Szilágyi +Tyler Treat diff --git a/vendor/cloud.google.com/go/CONTRIBUTORS b/vendor/cloud.google.com/go/CONTRIBUTORS new file mode 100644 index 0000000000..3b3cbed98e --- /dev/null +++ b/vendor/cloud.google.com/go/CONTRIBUTORS @@ -0,0 +1,40 @@ +# People who have agreed to one of the CLAs and can contribute patches. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# https://developers.google.com/open-source/cla/individual +# https://developers.google.com/open-source/cla/corporate +# +# Names should be added to this file as: +# Name + +# Keep the list alphabetically sorted. + +Alexis Hunt +Andreas Litt +Andrew Gerrand +Brad Fitzpatrick +Burcu Dogan +Dave Day +David Sansome +David Symonds +Filippo Valsorda +Glenn Lewis +Ingo Oeser +James Hall +Johan Euphrosine +Jonathan Amsterdam +Kunpei Sakai +Luna Duclos +Magnus Hiie +Mario Castro +Michael McGreevy +Omar Jarjur +Paweł Knap +Péter Szilágyi +Sarah Adams +Thanatat Tamtan +Toby Burress +Tuo Shan +Tyler Treat diff --git a/vendor/cloud.google.com/go/LICENSE b/vendor/cloud.google.com/go/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/vendor/cloud.google.com/go/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/cloud.google.com/go/compute/metadata/metadata.go b/vendor/cloud.google.com/go/compute/metadata/metadata.go new file mode 100644 index 0000000000..125b7033c9 --- /dev/null +++ b/vendor/cloud.google.com/go/compute/metadata/metadata.go @@ -0,0 +1,513 @@ +// Copyright 2014 Google LLC +// +// 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 metadata provides access to Google Compute Engine (GCE) +// metadata and API service accounts. +// +// This package is a wrapper around the GCE metadata service, +// as documented at https://developers.google.com/compute/docs/metadata. +package metadata // import "cloud.google.com/go/compute/metadata" + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "runtime" + "strings" + "sync" + "time" +) + +const ( + // metadataIP is the documented metadata server IP address. + metadataIP = "169.254.169.254" + + // metadataHostEnv is the environment variable specifying the + // GCE metadata hostname. If empty, the default value of + // metadataIP ("169.254.169.254") is used instead. + // This is variable name is not defined by any spec, as far as + // I know; it was made up for the Go package. + metadataHostEnv = "GCE_METADATA_HOST" + + userAgent = "gcloud-golang/0.1" +) + +type cachedValue struct { + k string + trim bool + mu sync.Mutex + v string +} + +var ( + projID = &cachedValue{k: "project/project-id", trim: true} + projNum = &cachedValue{k: "project/numeric-project-id", trim: true} + instID = &cachedValue{k: "instance/id", trim: true} +) + +var ( + defaultClient = &Client{hc: &http.Client{ + Transport: &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 2 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + ResponseHeaderTimeout: 2 * time.Second, + }, + }} + subscribeClient = &Client{hc: &http.Client{ + Transport: &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 2 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + }, + }} +) + +// NotDefinedError is returned when requested metadata is not defined. +// +// The underlying string is the suffix after "/computeMetadata/v1/". +// +// This error is not returned if the value is defined to be the empty +// string. +type NotDefinedError string + +func (suffix NotDefinedError) Error() string { + return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix)) +} + +func (c *cachedValue) get(cl *Client) (v string, err error) { + defer c.mu.Unlock() + c.mu.Lock() + if c.v != "" { + return c.v, nil + } + if c.trim { + v, err = cl.getTrimmed(c.k) + } else { + v, err = cl.Get(c.k) + } + if err == nil { + c.v = v + } + return +} + +var ( + onGCEOnce sync.Once + onGCE bool +) + +// OnGCE reports whether this process is running on Google Compute Engine. +func OnGCE() bool { + onGCEOnce.Do(initOnGCE) + return onGCE +} + +func initOnGCE() { + onGCE = testOnGCE() +} + +func testOnGCE() bool { + // The user explicitly said they're on GCE, so trust them. + if os.Getenv(metadataHostEnv) != "" { + return true + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + resc := make(chan bool, 2) + + // Try two strategies in parallel. + // See https://github.com/googleapis/google-cloud-go/issues/194 + go func() { + req, _ := http.NewRequest("GET", "http://"+metadataIP, nil) + req.Header.Set("User-Agent", userAgent) + res, err := defaultClient.hc.Do(req.WithContext(ctx)) + if err != nil { + resc <- false + return + } + defer res.Body.Close() + resc <- res.Header.Get("Metadata-Flavor") == "Google" + }() + + go func() { + addrs, err := net.LookupHost("metadata.google.internal") + if err != nil || len(addrs) == 0 { + resc <- false + return + } + resc <- strsContains(addrs, metadataIP) + }() + + tryHarder := systemInfoSuggestsGCE() + if tryHarder { + res := <-resc + if res { + // The first strategy succeeded, so let's use it. + return true + } + // Wait for either the DNS or metadata server probe to + // contradict the other one and say we are running on + // GCE. Give it a lot of time to do so, since the system + // info already suggests we're running on a GCE BIOS. + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + select { + case res = <-resc: + return res + case <-timer.C: + // Too slow. Who knows what this system is. + return false + } + } + + // There's no hint from the system info that we're running on + // GCE, so use the first probe's result as truth, whether it's + // true or false. The goal here is to optimize for speed for + // users who are NOT running on GCE. We can't assume that + // either a DNS lookup or an HTTP request to a blackholed IP + // address is fast. Worst case this should return when the + // metaClient's Transport.ResponseHeaderTimeout or + // Transport.Dial.Timeout fires (in two seconds). + return <-resc +} + +// systemInfoSuggestsGCE reports whether the local system (without +// doing network requests) suggests that we're running on GCE. If this +// returns true, testOnGCE tries a bit harder to reach its metadata +// server. +func systemInfoSuggestsGCE() bool { + if runtime.GOOS != "linux" { + // We don't have any non-Linux clues available, at least yet. + return false + } + slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name") + name := strings.TrimSpace(string(slurp)) + return name == "Google" || name == "Google Compute Engine" +} + +// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no +// ResponseHeaderTimeout). +func Subscribe(suffix string, fn func(v string, ok bool) error) error { + return subscribeClient.Subscribe(suffix, fn) +} + +// Get calls Client.Get on the default client. +func Get(suffix string) (string, error) { return defaultClient.Get(suffix) } + +// ProjectID returns the current instance's project ID string. +func ProjectID() (string, error) { return defaultClient.ProjectID() } + +// NumericProjectID returns the current instance's numeric project ID. +func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() } + +// InternalIP returns the instance's primary internal IP address. +func InternalIP() (string, error) { return defaultClient.InternalIP() } + +// ExternalIP returns the instance's primary external (public) IP address. +func ExternalIP() (string, error) { return defaultClient.ExternalIP() } + +// Hostname returns the instance's hostname. This will be of the form +// ".c..internal". +func Hostname() (string, error) { return defaultClient.Hostname() } + +// InstanceTags returns the list of user-defined instance tags, +// assigned when initially creating a GCE instance. +func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() } + +// InstanceID returns the current VM's numeric instance ID. +func InstanceID() (string, error) { return defaultClient.InstanceID() } + +// InstanceName returns the current VM's instance ID string. +func InstanceName() (string, error) { return defaultClient.InstanceName() } + +// Zone returns the current VM's zone, such as "us-central1-b". +func Zone() (string, error) { return defaultClient.Zone() } + +// InstanceAttributes calls Client.InstanceAttributes on the default client. +func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() } + +// ProjectAttributes calls Client.ProjectAttributes on the default client. +func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() } + +// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client. +func InstanceAttributeValue(attr string) (string, error) { + return defaultClient.InstanceAttributeValue(attr) +} + +// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client. +func ProjectAttributeValue(attr string) (string, error) { + return defaultClient.ProjectAttributeValue(attr) +} + +// Scopes calls Client.Scopes on the default client. +func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) } + +func strsContains(ss []string, s string) bool { + for _, v := range ss { + if v == s { + return true + } + } + return false +} + +// A Client provides metadata. +type Client struct { + hc *http.Client +} + +// NewClient returns a Client that can be used to fetch metadata. All HTTP requests +// will use the given http.Client instead of the default client. +func NewClient(c *http.Client) *Client { + return &Client{hc: c} +} + +// getETag returns a value from the metadata service as well as the associated ETag. +// This func is otherwise equivalent to Get. +func (c *Client) getETag(suffix string) (value, etag string, err error) { + // Using a fixed IP makes it very difficult to spoof the metadata service in + // a container, which is an important use-case for local testing of cloud + // deployments. To enable spoofing of the metadata service, the environment + // variable GCE_METADATA_HOST is first inspected to decide where metadata + // requests shall go. + host := os.Getenv(metadataHostEnv) + if host == "" { + // Using 169.254.169.254 instead of "metadata" here because Go + // binaries built with the "netgo" tag and without cgo won't + // know the search suffix for "metadata" is + // ".google.internal", and this IP address is documented as + // being stable anyway. + host = metadataIP + } + u := "http://" + host + "/computeMetadata/v1/" + suffix + req, _ := http.NewRequest("GET", u, nil) + req.Header.Set("Metadata-Flavor", "Google") + req.Header.Set("User-Agent", userAgent) + res, err := c.hc.Do(req) + if err != nil { + return "", "", err + } + defer res.Body.Close() + if res.StatusCode == http.StatusNotFound { + return "", "", NotDefinedError(suffix) + } + all, err := ioutil.ReadAll(res.Body) + if err != nil { + return "", "", err + } + if res.StatusCode != 200 { + return "", "", &Error{Code: res.StatusCode, Message: string(all)} + } + return string(all), res.Header.Get("Etag"), nil +} + +// Get returns a value from the metadata service. +// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". +// +// If the GCE_METADATA_HOST environment variable is not defined, a default of +// 169.254.169.254 will be used instead. +// +// If the requested metadata is not defined, the returned error will +// be of type NotDefinedError. +func (c *Client) Get(suffix string) (string, error) { + val, _, err := c.getETag(suffix) + return val, err +} + +func (c *Client) getTrimmed(suffix string) (s string, err error) { + s, err = c.Get(suffix) + s = strings.TrimSpace(s) + return +} + +func (c *Client) lines(suffix string) ([]string, error) { + j, err := c.Get(suffix) + if err != nil { + return nil, err + } + s := strings.Split(strings.TrimSpace(j), "\n") + for i := range s { + s[i] = strings.TrimSpace(s[i]) + } + return s, nil +} + +// ProjectID returns the current instance's project ID string. +func (c *Client) ProjectID() (string, error) { return projID.get(c) } + +// NumericProjectID returns the current instance's numeric project ID. +func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) } + +// InstanceID returns the current VM's numeric instance ID. +func (c *Client) InstanceID() (string, error) { return instID.get(c) } + +// InternalIP returns the instance's primary internal IP address. +func (c *Client) InternalIP() (string, error) { + return c.getTrimmed("instance/network-interfaces/0/ip") +} + +// ExternalIP returns the instance's primary external (public) IP address. +func (c *Client) ExternalIP() (string, error) { + return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") +} + +// Hostname returns the instance's hostname. This will be of the form +// ".c..internal". +func (c *Client) Hostname() (string, error) { + return c.getTrimmed("instance/hostname") +} + +// InstanceTags returns the list of user-defined instance tags, +// assigned when initially creating a GCE instance. +func (c *Client) InstanceTags() ([]string, error) { + var s []string + j, err := c.Get("instance/tags") + if err != nil { + return nil, err + } + if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { + return nil, err + } + return s, nil +} + +// InstanceName returns the current VM's instance ID string. +func (c *Client) InstanceName() (string, error) { + host, err := c.Hostname() + if err != nil { + return "", err + } + return strings.Split(host, ".")[0], nil +} + +// Zone returns the current VM's zone, such as "us-central1-b". +func (c *Client) Zone() (string, error) { + zone, err := c.getTrimmed("instance/zone") + // zone is of the form "projects//zones/". + if err != nil { + return "", err + } + return zone[strings.LastIndex(zone, "/")+1:], nil +} + +// InstanceAttributes returns the list of user-defined attributes, +// assigned when initially creating a GCE VM instance. The value of an +// attribute can be obtained with InstanceAttributeValue. +func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") } + +// ProjectAttributes returns the list of user-defined attributes +// applying to the project as a whole, not just this VM. The value of +// an attribute can be obtained with ProjectAttributeValue. +func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") } + +// InstanceAttributeValue returns the value of the provided VM +// instance attribute. +// +// If the requested attribute is not defined, the returned error will +// be of type NotDefinedError. +// +// InstanceAttributeValue may return ("", nil) if the attribute was +// defined to be the empty string. +func (c *Client) InstanceAttributeValue(attr string) (string, error) { + return c.Get("instance/attributes/" + attr) +} + +// ProjectAttributeValue returns the value of the provided +// project attribute. +// +// If the requested attribute is not defined, the returned error will +// be of type NotDefinedError. +// +// ProjectAttributeValue may return ("", nil) if the attribute was +// defined to be the empty string. +func (c *Client) ProjectAttributeValue(attr string) (string, error) { + return c.Get("project/attributes/" + attr) +} + +// Scopes returns the service account scopes for the given account. +// The account may be empty or the string "default" to use the instance's +// main account. +func (c *Client) Scopes(serviceAccount string) ([]string, error) { + if serviceAccount == "" { + serviceAccount = "default" + } + return c.lines("instance/service-accounts/" + serviceAccount + "/scopes") +} + +// Subscribe subscribes to a value from the metadata service. +// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". +// The suffix may contain query parameters. +// +// Subscribe calls fn with the latest metadata value indicated by the provided +// suffix. If the metadata value is deleted, fn is called with the empty string +// and ok false. Subscribe blocks until fn returns a non-nil error or the value +// is deleted. Subscribe returns the error value returned from the last call to +// fn, which may be nil when ok == false. +func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error { + const failedSubscribeSleep = time.Second * 5 + + // First check to see if the metadata value exists at all. + val, lastETag, err := c.getETag(suffix) + if err != nil { + return err + } + + if err := fn(val, true); err != nil { + return err + } + + ok := true + if strings.ContainsRune(suffix, '?') { + suffix += "&wait_for_change=true&last_etag=" + } else { + suffix += "?wait_for_change=true&last_etag=" + } + for { + val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag)) + if err != nil { + if _, deleted := err.(NotDefinedError); !deleted { + time.Sleep(failedSubscribeSleep) + continue // Retry on other errors. + } + ok = false + } + lastETag = etag + + if err := fn(val, ok); err != nil || !ok { + return err + } + } +} + +// Error contains an error response from the server. +type Error struct { + // Code is the HTTP response status code. + Code int + // Message is the server response message. + Message string +} + +func (e *Error) Error() string { + return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message) +} diff --git a/vendor/golang.org/x/oauth2/google/appengine.go b/vendor/golang.org/x/oauth2/google/appengine.go new file mode 100644 index 0000000000..feb1157b15 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/appengine.go @@ -0,0 +1,38 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "context" + "time" + + "golang.org/x/oauth2" +) + +// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible. +var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error) + +// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible. +var appengineAppIDFunc func(c context.Context) string + +// AppEngineTokenSource returns a token source that fetches tokens from either +// the current application's service account or from the metadata server, +// depending on the App Engine environment. See below for environment-specific +// details. If you are implementing a 3-legged OAuth 2.0 flow on App Engine that +// involves user accounts, see oauth2.Config instead. +// +// First generation App Engine runtimes (<= Go 1.9): +// AppEngineTokenSource returns a token source that fetches tokens issued to the +// current App Engine application's service account. The provided context must have +// come from appengine.NewContext. +// +// Second generation App Engine runtimes (>= Go 1.11) and App Engine flexible: +// AppEngineTokenSource is DEPRECATED on second generation runtimes and on the +// flexible environment. It delegates to ComputeTokenSource, and the provided +// context and scopes are not used. Please use DefaultTokenSource (or ComputeTokenSource, +// which DefaultTokenSource will use in this case) instead. +func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { + return appEngineTokenSource(ctx, scope...) +} diff --git a/vendor/golang.org/x/oauth2/google/appengine_gen1.go b/vendor/golang.org/x/oauth2/google/appengine_gen1.go new file mode 100644 index 0000000000..83dacac320 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/appengine_gen1.go @@ -0,0 +1,77 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine + +// This file applies to App Engine first generation runtimes (<= Go 1.9). + +package google + +import ( + "context" + "sort" + "strings" + "sync" + + "golang.org/x/oauth2" + "google.golang.org/appengine" +) + +func init() { + appengineTokenFunc = appengine.AccessToken + appengineAppIDFunc = appengine.AppID +} + +// See comment on AppEngineTokenSource in appengine.go. +func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { + scopes := append([]string{}, scope...) + sort.Strings(scopes) + return &gaeTokenSource{ + ctx: ctx, + scopes: scopes, + key: strings.Join(scopes, " "), + } +} + +// aeTokens helps the fetched tokens to be reused until their expiration. +var ( + aeTokensMu sync.Mutex + aeTokens = make(map[string]*tokenLock) // key is space-separated scopes +) + +type tokenLock struct { + mu sync.Mutex // guards t; held while fetching or updating t + t *oauth2.Token +} + +type gaeTokenSource struct { + ctx context.Context + scopes []string + key string // to aeTokens map; space-separated scopes +} + +func (ts *gaeTokenSource) Token() (*oauth2.Token, error) { + aeTokensMu.Lock() + tok, ok := aeTokens[ts.key] + if !ok { + tok = &tokenLock{} + aeTokens[ts.key] = tok + } + aeTokensMu.Unlock() + + tok.mu.Lock() + defer tok.mu.Unlock() + if tok.t.Valid() { + return tok.t, nil + } + access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...) + if err != nil { + return nil, err + } + tok.t = &oauth2.Token{ + AccessToken: access, + Expiry: exp, + } + return tok.t, nil +} diff --git a/vendor/golang.org/x/oauth2/google/appengine_gen2_flex.go b/vendor/golang.org/x/oauth2/google/appengine_gen2_flex.go new file mode 100644 index 0000000000..04c2c2216a --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/appengine_gen2_flex.go @@ -0,0 +1,27 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !appengine + +// This file applies to App Engine second generation runtimes (>= Go 1.11) and App Engine flexible. + +package google + +import ( + "context" + "log" + "sync" + + "golang.org/x/oauth2" +) + +var logOnce sync.Once // only spam about deprecation once + +// See comment on AppEngineTokenSource in appengine.go. +func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { + logOnce.Do(func() { + log.Print("google: AppEngineTokenSource is deprecated on App Engine standard second generation runtimes (>= Go 1.11) and App Engine flexible. Please use DefaultTokenSource or ComputeTokenSource.") + }) + return ComputeTokenSource("") +} diff --git a/vendor/golang.org/x/oauth2/google/default.go b/vendor/golang.org/x/oauth2/google/default.go new file mode 100644 index 0000000000..5087d845fe --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/default.go @@ -0,0 +1,155 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" + + "cloud.google.com/go/compute/metadata" + "golang.org/x/oauth2" +) + +// Credentials holds Google credentials, including "Application Default Credentials". +// For more details, see: +// https://developers.google.com/accounts/docs/application-default-credentials +type Credentials struct { + ProjectID string // may be empty + TokenSource oauth2.TokenSource + + // JSON contains the raw bytes from a JSON credentials file. + // This field may be nil if authentication is provided by the + // environment and not with a credentials file, e.g. when code is + // running on Google Cloud Platform. + JSON []byte +} + +// DefaultCredentials is the old name of Credentials. +// +// Deprecated: use Credentials instead. +type DefaultCredentials = Credentials + +// DefaultClient returns an HTTP Client that uses the +// DefaultTokenSource to obtain authentication credentials. +func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { + ts, err := DefaultTokenSource(ctx, scope...) + if err != nil { + return nil, err + } + return oauth2.NewClient(ctx, ts), nil +} + +// DefaultTokenSource returns the token source for +// "Application Default Credentials". +// It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource. +func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) { + creds, err := FindDefaultCredentials(ctx, scope...) + if err != nil { + return nil, err + } + return creds.TokenSource, nil +} + +// FindDefaultCredentials searches for "Application Default Credentials". +// +// It looks for credentials in the following places, +// preferring the first location found: +// +// 1. A JSON file whose path is specified by the +// GOOGLE_APPLICATION_CREDENTIALS environment variable. +// 2. A JSON file in a location known to the gcloud command-line tool. +// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. +// On other systems, $HOME/.config/gcloud/application_default_credentials.json. +// 3. On Google App Engine standard first generation runtimes (<= Go 1.9) it uses +// the appengine.AccessToken function. +// 4. On Google Compute Engine, Google App Engine standard second generation runtimes +// (>= Go 1.11), and Google App Engine flexible environment, it fetches +// credentials from the metadata server. +// (In this final case any provided scopes are ignored.) +func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) { + // First, try the environment variable. + const envVar = "GOOGLE_APPLICATION_CREDENTIALS" + if filename := os.Getenv(envVar); filename != "" { + creds, err := readCredentialsFile(ctx, filename, scopes) + if err != nil { + return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) + } + return creds, nil + } + + // Second, try a well-known file. + filename := wellKnownFile() + if creds, err := readCredentialsFile(ctx, filename, scopes); err == nil { + return creds, nil + } else if !os.IsNotExist(err) { + return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err) + } + + // Third, if we're on a Google App Engine standard first generation runtime (<= Go 1.9) + // use those credentials. App Engine standard second generation runtimes (>= Go 1.11) + // and App Engine flexible use ComputeTokenSource and the metadata server. + if appengineTokenFunc != nil { + return &DefaultCredentials{ + ProjectID: appengineAppIDFunc(ctx), + TokenSource: AppEngineTokenSource(ctx, scopes...), + }, nil + } + + // Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime, + // or App Engine flexible, use the metadata server. + if metadata.OnGCE() { + id, _ := metadata.ProjectID() + return &DefaultCredentials{ + ProjectID: id, + TokenSource: ComputeTokenSource(""), + }, nil + } + + // None are found; return helpful error. + const url = "https://developers.google.com/accounts/docs/application-default-credentials" + return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) +} + +// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can +// represent either a Google Developers Console client_credentials.json file (as in +// ConfigFromJSON) or a Google Developers service account key file (as in +// JWTConfigFromJSON). +func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) { + var f credentialsFile + if err := json.Unmarshal(jsonData, &f); err != nil { + return nil, err + } + ts, err := f.tokenSource(ctx, append([]string(nil), scopes...)) + if err != nil { + return nil, err + } + return &DefaultCredentials{ + ProjectID: f.ProjectID, + TokenSource: ts, + JSON: jsonData, + }, nil +} + +func wellKnownFile() string { + const f = "application_default_credentials.json" + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("APPDATA"), "gcloud", f) + } + return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) +} + +func readCredentialsFile(ctx context.Context, filename string, scopes []string) (*DefaultCredentials, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + return CredentialsFromJSON(ctx, b, scopes...) +} diff --git a/vendor/golang.org/x/oauth2/google/doc.go b/vendor/golang.org/x/oauth2/google/doc.go new file mode 100644 index 0000000000..73be629033 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/doc.go @@ -0,0 +1,40 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package google provides support for making OAuth2 authorized and authenticated +// HTTP requests to Google APIs. It supports the Web server flow, client-side +// credentials, service accounts, Google Compute Engine service accounts, and Google +// App Engine service accounts. +// +// A brief overview of the package follows. For more information, please read +// https://developers.google.com/accounts/docs/OAuth2 +// and +// https://developers.google.com/accounts/docs/application-default-credentials. +// +// OAuth2 Configs +// +// Two functions in this package return golang.org/x/oauth2.Config values from Google credential +// data. Google supports two JSON formats for OAuth2 credentials: one is handled by ConfigFromJSON, +// the other by JWTConfigFromJSON. The returned Config can be used to obtain a TokenSource or +// create an http.Client. +// +// +// Credentials +// +// The Credentials type represents Google credentials, including Application Default +// Credentials. +// +// Use FindDefaultCredentials to obtain Application Default Credentials. +// FindDefaultCredentials looks in some well-known places for a credentials file, and +// will call AppEngineTokenSource or ComputeTokenSource as needed. +// +// DefaultClient and DefaultTokenSource are convenience methods. They first call FindDefaultCredentials, +// then use the credentials to construct an http.Client or an oauth2.TokenSource. +// +// Use CredentialsFromJSON to obtain credentials from either of the two JSON formats +// described in OAuth2 Configs, above. The TokenSource in the returned value is the +// same as the one obtained from the oauth2.Config returned from ConfigFromJSON or +// JWTConfigFromJSON, but the Credentials may contain additional information +// that is useful is some circumstances. +package google // import "golang.org/x/oauth2/google" diff --git a/vendor/golang.org/x/oauth2/google/google.go b/vendor/golang.org/x/oauth2/google/google.go new file mode 100644 index 0000000000..df8e87d74a --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/google.go @@ -0,0 +1,193 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "cloud.google.com/go/compute/metadata" + "golang.org/x/oauth2" + "golang.org/x/oauth2/jwt" +) + +// Endpoint is Google's OAuth 2.0 endpoint. +var Endpoint = oauth2.Endpoint{ + AuthURL: "https://accounts.google.com/o/oauth2/auth", + TokenURL: "https://oauth2.googleapis.com/token", + AuthStyle: oauth2.AuthStyleInParams, +} + +// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow. +const JWTTokenURL = "https://oauth2.googleapis.com/token" + +// ConfigFromJSON uses a Google Developers Console client_credentials.json +// file to construct a config. +// client_credentials.json can be downloaded from +// https://console.developers.google.com, under "Credentials". Download the Web +// application credentials in the JSON format and provide the contents of the +// file as jsonKey. +func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) { + type cred struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectURIs []string `json:"redirect_uris"` + AuthURI string `json:"auth_uri"` + TokenURI string `json:"token_uri"` + } + var j struct { + Web *cred `json:"web"` + Installed *cred `json:"installed"` + } + if err := json.Unmarshal(jsonKey, &j); err != nil { + return nil, err + } + var c *cred + switch { + case j.Web != nil: + c = j.Web + case j.Installed != nil: + c = j.Installed + default: + return nil, fmt.Errorf("oauth2/google: no credentials found") + } + if len(c.RedirectURIs) < 1 { + return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json") + } + return &oauth2.Config{ + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + RedirectURL: c.RedirectURIs[0], + Scopes: scope, + Endpoint: oauth2.Endpoint{ + AuthURL: c.AuthURI, + TokenURL: c.TokenURI, + }, + }, nil +} + +// JWTConfigFromJSON uses a Google Developers service account JSON key file to read +// the credentials that authorize and authenticate the requests. +// Create a service account on "Credentials" for your project at +// https://console.developers.google.com to download a JSON key file. +func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) { + var f credentialsFile + if err := json.Unmarshal(jsonKey, &f); err != nil { + return nil, err + } + if f.Type != serviceAccountKey { + return nil, fmt.Errorf("google: read JWT from JSON credentials: 'type' field is %q (expected %q)", f.Type, serviceAccountKey) + } + scope = append([]string(nil), scope...) // copy + return f.jwtConfig(scope), nil +} + +// JSON key file types. +const ( + serviceAccountKey = "service_account" + userCredentialsKey = "authorized_user" +) + +// credentialsFile is the unmarshalled representation of a credentials file. +type credentialsFile struct { + Type string `json:"type"` // serviceAccountKey or userCredentialsKey + + // Service Account fields + ClientEmail string `json:"client_email"` + PrivateKeyID string `json:"private_key_id"` + PrivateKey string `json:"private_key"` + TokenURL string `json:"token_uri"` + ProjectID string `json:"project_id"` + + // User Credential fields + // (These typically come from gcloud auth.) + ClientSecret string `json:"client_secret"` + ClientID string `json:"client_id"` + RefreshToken string `json:"refresh_token"` +} + +func (f *credentialsFile) jwtConfig(scopes []string) *jwt.Config { + cfg := &jwt.Config{ + Email: f.ClientEmail, + PrivateKey: []byte(f.PrivateKey), + PrivateKeyID: f.PrivateKeyID, + Scopes: scopes, + TokenURL: f.TokenURL, + } + if cfg.TokenURL == "" { + cfg.TokenURL = JWTTokenURL + } + return cfg +} + +func (f *credentialsFile) tokenSource(ctx context.Context, scopes []string) (oauth2.TokenSource, error) { + switch f.Type { + case serviceAccountKey: + cfg := f.jwtConfig(scopes) + return cfg.TokenSource(ctx), nil + case userCredentialsKey: + cfg := &oauth2.Config{ + ClientID: f.ClientID, + ClientSecret: f.ClientSecret, + Scopes: scopes, + Endpoint: Endpoint, + } + tok := &oauth2.Token{RefreshToken: f.RefreshToken} + return cfg.TokenSource(ctx, tok), nil + case "": + return nil, errors.New("missing 'type' field in credentials") + default: + return nil, fmt.Errorf("unknown credential type: %q", f.Type) + } +} + +// ComputeTokenSource returns a token source that fetches access tokens +// from Google Compute Engine (GCE)'s metadata server. It's only valid to use +// this token source if your program is running on a GCE instance. +// If no account is specified, "default" is used. +// Further information about retrieving access tokens from the GCE metadata +// server can be found at https://cloud.google.com/compute/docs/authentication. +func ComputeTokenSource(account string) oauth2.TokenSource { + return oauth2.ReuseTokenSource(nil, computeSource{account: account}) +} + +type computeSource struct { + account string +} + +func (cs computeSource) Token() (*oauth2.Token, error) { + if !metadata.OnGCE() { + return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE") + } + acct := cs.account + if acct == "" { + acct = "default" + } + tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token") + if err != nil { + return nil, err + } + var res struct { + AccessToken string `json:"access_token"` + ExpiresInSec int `json:"expires_in"` + TokenType string `json:"token_type"` + } + err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res) + if err != nil { + return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err) + } + if res.ExpiresInSec == 0 || res.AccessToken == "" { + return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata") + } + return &oauth2.Token{ + AccessToken: res.AccessToken, + TokenType: res.TokenType, + Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second), + }, nil +} diff --git a/vendor/golang.org/x/oauth2/google/jwt.go b/vendor/golang.org/x/oauth2/google/jwt.go new file mode 100644 index 0000000000..b0fdb3a888 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/jwt.go @@ -0,0 +1,74 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "crypto/rsa" + "fmt" + "time" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/internal" + "golang.org/x/oauth2/jws" +) + +// JWTAccessTokenSourceFromJSON uses a Google Developers service account JSON +// key file to read the credentials that authorize and authenticate the +// requests, and returns a TokenSource that does not use any OAuth2 flow but +// instead creates a JWT and sends that as the access token. +// The audience is typically a URL that specifies the scope of the credentials. +// +// Note that this is not a standard OAuth flow, but rather an +// optimization supported by a few Google services. +// Unless you know otherwise, you should use JWTConfigFromJSON instead. +func JWTAccessTokenSourceFromJSON(jsonKey []byte, audience string) (oauth2.TokenSource, error) { + cfg, err := JWTConfigFromJSON(jsonKey) + if err != nil { + return nil, fmt.Errorf("google: could not parse JSON key: %v", err) + } + pk, err := internal.ParseKey(cfg.PrivateKey) + if err != nil { + return nil, fmt.Errorf("google: could not parse key: %v", err) + } + ts := &jwtAccessTokenSource{ + email: cfg.Email, + audience: audience, + pk: pk, + pkID: cfg.PrivateKeyID, + } + tok, err := ts.Token() + if err != nil { + return nil, err + } + return oauth2.ReuseTokenSource(tok, ts), nil +} + +type jwtAccessTokenSource struct { + email, audience string + pk *rsa.PrivateKey + pkID string +} + +func (ts *jwtAccessTokenSource) Token() (*oauth2.Token, error) { + iat := time.Now() + exp := iat.Add(time.Hour) + cs := &jws.ClaimSet{ + Iss: ts.email, + Sub: ts.email, + Aud: ts.audience, + Iat: iat.Unix(), + Exp: exp.Unix(), + } + hdr := &jws.Header{ + Algorithm: "RS256", + Typ: "JWT", + KeyID: string(ts.pkID), + } + msg, err := jws.Encode(hdr, cs, ts.pk) + if err != nil { + return nil, fmt.Errorf("google: could not encode JWT: %v", err) + } + return &oauth2.Token{AccessToken: msg, TokenType: "Bearer", Expiry: exp}, nil +} diff --git a/vendor/golang.org/x/oauth2/google/sdk.go b/vendor/golang.org/x/oauth2/google/sdk.go new file mode 100644 index 0000000000..456224bc78 --- /dev/null +++ b/vendor/golang.org/x/oauth2/google/sdk.go @@ -0,0 +1,201 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "bufio" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" + "time" + + "golang.org/x/oauth2" +) + +type sdkCredentials struct { + Data []struct { + Credential struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenExpiry *time.Time `json:"token_expiry"` + } `json:"credential"` + Key struct { + Account string `json:"account"` + Scope string `json:"scope"` + } `json:"key"` + } +} + +// An SDKConfig provides access to tokens from an account already +// authorized via the Google Cloud SDK. +type SDKConfig struct { + conf oauth2.Config + initialToken *oauth2.Token +} + +// NewSDKConfig creates an SDKConfig for the given Google Cloud SDK +// account. If account is empty, the account currently active in +// Google Cloud SDK properties is used. +// Google Cloud SDK credentials must be created by running `gcloud auth` +// before using this function. +// The Google Cloud SDK is available at https://cloud.google.com/sdk/. +func NewSDKConfig(account string) (*SDKConfig, error) { + configPath, err := sdkConfigPath() + if err != nil { + return nil, fmt.Errorf("oauth2/google: error getting SDK config path: %v", err) + } + credentialsPath := filepath.Join(configPath, "credentials") + f, err := os.Open(credentialsPath) + if err != nil { + return nil, fmt.Errorf("oauth2/google: failed to load SDK credentials: %v", err) + } + defer f.Close() + + var c sdkCredentials + if err := json.NewDecoder(f).Decode(&c); err != nil { + return nil, fmt.Errorf("oauth2/google: failed to decode SDK credentials from %q: %v", credentialsPath, err) + } + if len(c.Data) == 0 { + return nil, fmt.Errorf("oauth2/google: no credentials found in %q, run `gcloud auth login` to create one", credentialsPath) + } + if account == "" { + propertiesPath := filepath.Join(configPath, "properties") + f, err := os.Open(propertiesPath) + if err != nil { + return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err) + } + defer f.Close() + ini, err := parseINI(f) + if err != nil { + return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err) + } + core, ok := ini["core"] + if !ok { + return nil, fmt.Errorf("oauth2/google: failed to find [core] section in %v", ini) + } + active, ok := core["account"] + if !ok { + return nil, fmt.Errorf("oauth2/google: failed to find %q attribute in %v", "account", core) + } + account = active + } + + for _, d := range c.Data { + if account == "" || d.Key.Account == account { + if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" { + return nil, fmt.Errorf("oauth2/google: no token available for account %q", account) + } + var expiry time.Time + if d.Credential.TokenExpiry != nil { + expiry = *d.Credential.TokenExpiry + } + return &SDKConfig{ + conf: oauth2.Config{ + ClientID: d.Credential.ClientID, + ClientSecret: d.Credential.ClientSecret, + Scopes: strings.Split(d.Key.Scope, " "), + Endpoint: Endpoint, + RedirectURL: "oob", + }, + initialToken: &oauth2.Token{ + AccessToken: d.Credential.AccessToken, + RefreshToken: d.Credential.RefreshToken, + Expiry: expiry, + }, + }, nil + } + } + return nil, fmt.Errorf("oauth2/google: no such credentials for account %q", account) +} + +// Client returns an HTTP client using Google Cloud SDK credentials to +// authorize requests. The token will auto-refresh as necessary. The +// underlying http.RoundTripper will be obtained using the provided +// context. The returned client and its Transport should not be +// modified. +func (c *SDKConfig) Client(ctx context.Context) *http.Client { + return &http.Client{ + Transport: &oauth2.Transport{ + Source: c.TokenSource(ctx), + }, + } +} + +// TokenSource returns an oauth2.TokenSource that retrieve tokens from +// Google Cloud SDK credentials using the provided context. +// It will returns the current access token stored in the credentials, +// and refresh it when it expires, but it won't update the credentials +// with the new access token. +func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource { + return c.conf.TokenSource(ctx, c.initialToken) +} + +// Scopes are the OAuth 2.0 scopes the current account is authorized for. +func (c *SDKConfig) Scopes() []string { + return c.conf.Scopes +} + +func parseINI(ini io.Reader) (map[string]map[string]string, error) { + result := map[string]map[string]string{ + "": {}, // root section + } + scanner := bufio.NewScanner(ini) + currentSection := "" + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, ";") { + // comment. + continue + } + if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { + currentSection = strings.TrimSpace(line[1 : len(line)-1]) + result[currentSection] = map[string]string{} + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 && parts[0] != "" { + result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) + } + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error scanning ini: %v", err) + } + return result, nil +} + +// sdkConfigPath tries to guess where the gcloud config is located. +// It can be overridden during tests. +var sdkConfigPath = func() (string, error) { + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil + } + homeDir := guessUnixHomeDir() + if homeDir == "" { + return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty") + } + return filepath.Join(homeDir, ".config", "gcloud"), nil +} + +func guessUnixHomeDir() string { + // Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470 + if v := os.Getenv("HOME"); v != "" { + return v + } + // Else, fall back to user.Current: + if u, err := user.Current(); err == nil { + return u.HomeDir + } + return "" +} diff --git a/vendor/golang.org/x/oauth2/jws/jws.go b/vendor/golang.org/x/oauth2/jws/jws.go new file mode 100644 index 0000000000..683d2d271a --- /dev/null +++ b/vendor/golang.org/x/oauth2/jws/jws.go @@ -0,0 +1,182 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package jws provides a partial implementation +// of JSON Web Signature encoding and decoding. +// It exists to support the golang.org/x/oauth2 package. +// +// See RFC 7515. +// +// Deprecated: this package is not intended for public use and might be +// removed in the future. It exists for internal use only. +// Please switch to another JWS package or copy this package into your own +// source tree. +package jws // import "golang.org/x/oauth2/jws" + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "strings" + "time" +) + +// ClaimSet contains information about the JWT signature including the +// permissions being requested (scopes), the target of the token, the issuer, +// the time the token was issued, and the lifetime of the token. +type ClaimSet struct { + Iss string `json:"iss"` // email address of the client_id of the application making the access token request + Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests + Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional). + Exp int64 `json:"exp"` // the expiration time of the assertion (seconds since Unix epoch) + Iat int64 `json:"iat"` // the time the assertion was issued (seconds since Unix epoch) + Typ string `json:"typ,omitempty"` // token type (Optional). + + // Email for which the application is requesting delegated access (Optional). + Sub string `json:"sub,omitempty"` + + // The old name of Sub. Client keeps setting Prn to be + // complaint with legacy OAuth 2.0 providers. (Optional) + Prn string `json:"prn,omitempty"` + + // See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3 + // This array is marshalled using custom code (see (c *ClaimSet) encode()). + PrivateClaims map[string]interface{} `json:"-"` +} + +func (c *ClaimSet) encode() (string, error) { + // Reverting time back for machines whose time is not perfectly in sync. + // If client machine's time is in the future according + // to Google servers, an access token will not be issued. + now := time.Now().Add(-10 * time.Second) + if c.Iat == 0 { + c.Iat = now.Unix() + } + if c.Exp == 0 { + c.Exp = now.Add(time.Hour).Unix() + } + if c.Exp < c.Iat { + return "", fmt.Errorf("jws: invalid Exp = %v; must be later than Iat = %v", c.Exp, c.Iat) + } + + b, err := json.Marshal(c) + if err != nil { + return "", err + } + + if len(c.PrivateClaims) == 0 { + return base64.RawURLEncoding.EncodeToString(b), nil + } + + // Marshal private claim set and then append it to b. + prv, err := json.Marshal(c.PrivateClaims) + if err != nil { + return "", fmt.Errorf("jws: invalid map of private claims %v", c.PrivateClaims) + } + + // Concatenate public and private claim JSON objects. + if !bytes.HasSuffix(b, []byte{'}'}) { + return "", fmt.Errorf("jws: invalid JSON %s", b) + } + if !bytes.HasPrefix(prv, []byte{'{'}) { + return "", fmt.Errorf("jws: invalid JSON %s", prv) + } + b[len(b)-1] = ',' // Replace closing curly brace with a comma. + b = append(b, prv[1:]...) // Append private claims. + return base64.RawURLEncoding.EncodeToString(b), nil +} + +// Header represents the header for the signed JWS payloads. +type Header struct { + // The algorithm used for signature. + Algorithm string `json:"alg"` + + // Represents the token type. + Typ string `json:"typ"` + + // The optional hint of which key is being used. + KeyID string `json:"kid,omitempty"` +} + +func (h *Header) encode() (string, error) { + b, err := json.Marshal(h) + if err != nil { + return "", err + } + return base64.RawURLEncoding.EncodeToString(b), nil +} + +// Decode decodes a claim set from a JWS payload. +func Decode(payload string) (*ClaimSet, error) { + // decode returned id token to get expiry + s := strings.Split(payload, ".") + if len(s) < 2 { + // TODO(jbd): Provide more context about the error. + return nil, errors.New("jws: invalid token received") + } + decoded, err := base64.RawURLEncoding.DecodeString(s[1]) + if err != nil { + return nil, err + } + c := &ClaimSet{} + err = json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c) + return c, err +} + +// Signer returns a signature for the given data. +type Signer func(data []byte) (sig []byte, err error) + +// EncodeWithSigner encodes a header and claim set with the provided signer. +func EncodeWithSigner(header *Header, c *ClaimSet, sg Signer) (string, error) { + head, err := header.encode() + if err != nil { + return "", err + } + cs, err := c.encode() + if err != nil { + return "", err + } + ss := fmt.Sprintf("%s.%s", head, cs) + sig, err := sg([]byte(ss)) + if err != nil { + return "", err + } + return fmt.Sprintf("%s.%s", ss, base64.RawURLEncoding.EncodeToString(sig)), nil +} + +// Encode encodes a signed JWS with provided header and claim set. +// This invokes EncodeWithSigner using crypto/rsa.SignPKCS1v15 with the given RSA private key. +func Encode(header *Header, c *ClaimSet, key *rsa.PrivateKey) (string, error) { + sg := func(data []byte) (sig []byte, err error) { + h := sha256.New() + h.Write(data) + return rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h.Sum(nil)) + } + return EncodeWithSigner(header, c, sg) +} + +// Verify tests whether the provided JWT token's signature was produced by the private key +// associated with the supplied public key. +func Verify(token string, key *rsa.PublicKey) error { + parts := strings.Split(token, ".") + if len(parts) != 3 { + return errors.New("jws: invalid token received, token must have 3 parts") + } + + signedContent := parts[0] + "." + parts[1] + signatureString, err := base64.RawURLEncoding.DecodeString(parts[2]) + if err != nil { + return err + } + + h := sha256.New() + h.Write([]byte(signedContent)) + return rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), []byte(signatureString)) +} diff --git a/vendor/golang.org/x/oauth2/jwt/jwt.go b/vendor/golang.org/x/oauth2/jwt/jwt.go new file mode 100644 index 0000000000..99f3e0a32c --- /dev/null +++ b/vendor/golang.org/x/oauth2/jwt/jwt.go @@ -0,0 +1,170 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly +// known as "two-legged OAuth 2.0". +// +// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12 +package jwt + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/internal" + "golang.org/x/oauth2/jws" +) + +var ( + defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer" + defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"} +) + +// Config is the configuration for using JWT to fetch tokens, +// commonly known as "two-legged OAuth 2.0". +type Config struct { + // Email is the OAuth client identifier used when communicating with + // the configured OAuth provider. + Email string + + // PrivateKey contains the contents of an RSA private key or the + // contents of a PEM file that contains a private key. The provided + // private key is used to sign JWT payloads. + // PEM containers with a passphrase are not supported. + // Use the following command to convert a PKCS 12 file into a PEM. + // + // $ openssl pkcs12 -in key.p12 -out key.pem -nodes + // + PrivateKey []byte + + // PrivateKeyID contains an optional hint indicating which key is being + // used. + PrivateKeyID string + + // Subject is the optional user to impersonate. + Subject string + + // Scopes optionally specifies a list of requested permission scopes. + Scopes []string + + // TokenURL is the endpoint required to complete the 2-legged JWT flow. + TokenURL string + + // Expires optionally specifies how long the token is valid for. + Expires time.Duration + + // Audience optionally specifies the intended audience of the + // request. If empty, the value of TokenURL is used as the + // intended audience. + Audience string +} + +// TokenSource returns a JWT TokenSource using the configuration +// in c and the HTTP client from the provided context. +func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { + return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c}) +} + +// Client returns an HTTP client wrapping the context's +// HTTP transport and adding Authorization headers with tokens +// obtained from c. +// +// The returned client and its Transport should not be modified. +func (c *Config) Client(ctx context.Context) *http.Client { + return oauth2.NewClient(ctx, c.TokenSource(ctx)) +} + +// jwtSource is a source that always does a signed JWT request for a token. +// It should typically be wrapped with a reuseTokenSource. +type jwtSource struct { + ctx context.Context + conf *Config +} + +func (js jwtSource) Token() (*oauth2.Token, error) { + pk, err := internal.ParseKey(js.conf.PrivateKey) + if err != nil { + return nil, err + } + hc := oauth2.NewClient(js.ctx, nil) + claimSet := &jws.ClaimSet{ + Iss: js.conf.Email, + Scope: strings.Join(js.conf.Scopes, " "), + Aud: js.conf.TokenURL, + } + if subject := js.conf.Subject; subject != "" { + claimSet.Sub = subject + // prn is the old name of sub. Keep setting it + // to be compatible with legacy OAuth 2.0 providers. + claimSet.Prn = subject + } + if t := js.conf.Expires; t > 0 { + claimSet.Exp = time.Now().Add(t).Unix() + } + if aud := js.conf.Audience; aud != "" { + claimSet.Aud = aud + } + h := *defaultHeader + h.KeyID = js.conf.PrivateKeyID + payload, err := jws.Encode(&h, claimSet, pk) + if err != nil { + return nil, err + } + v := url.Values{} + v.Set("grant_type", defaultGrantType) + v.Set("assertion", payload) + resp, err := hc.PostForm(js.conf.TokenURL, v) + if err != nil { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) + } + if c := resp.StatusCode; c < 200 || c > 299 { + return nil, &oauth2.RetrieveError{ + Response: resp, + Body: body, + } + } + // tokenRes is the JSON response body. + var tokenRes struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + IDToken string `json:"id_token"` + ExpiresIn int64 `json:"expires_in"` // relative seconds from now + } + if err := json.Unmarshal(body, &tokenRes); err != nil { + return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) + } + token := &oauth2.Token{ + AccessToken: tokenRes.AccessToken, + TokenType: tokenRes.TokenType, + } + raw := make(map[string]interface{}) + json.Unmarshal(body, &raw) // no error checks for optional fields + token = token.WithExtra(raw) + + if secs := tokenRes.ExpiresIn; secs > 0 { + token.Expiry = time.Now().Add(time.Duration(secs) * time.Second) + } + if v := tokenRes.IDToken; v != "" { + // decode returned id token to get expiry + claimSet, err := jws.Decode(v) + if err != nil { + return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err) + } + token.Expiry = time.Unix(claimSet.Exp, 0) + } + return token, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 66950edcb8..f00a6c15ac 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,5 @@ +# cloud.google.com/go v0.37.4 +cloud.google.com/go/compute/metadata # github.com/360EntSecGroup-Skylar/excelize v1.4.0 github.com/360EntSecGroup-Skylar/excelize # github.com/Azure/azure-sdk-for-go v36.1.0+incompatible @@ -586,7 +588,10 @@ golang.org/x/net/publicsuffix golang.org/x/net/trace # golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 golang.org/x/oauth2 +golang.org/x/oauth2/google golang.org/x/oauth2/internal +golang.org/x/oauth2/jws +golang.org/x/oauth2/jwt # golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sync/errgroup # golang.org/x/sys v0.0.0-20191008105621-543471e840be