Lemoe 3 年之前
当前提交
c8de8887af
共有 7 个文件被更改,包括 1407 次插入0 次删除
  1. 44 0
      .github/workflows/release.yml
  2. 21 0
      .gitignore
  3. 140 0
      Makefile
  4. 9 0
      go.mod
  5. 18 0
      go.sum
  6. 1136 0
      main.go
  7. 39 0
      oci-help.ini

+ 44 - 0
.github/workflows/release.yml

@@ -0,0 +1,44 @@
+name: Release
+
+# Controls when the workflow will run
+on:
+  release:
+    types: [published]
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Setup Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: ^1.17.1
+
+      - name: Cache go module
+        uses: actions/cache@v2
+        with:
+          path: |
+            ~/.cache/go-build
+            ~/go/pkg/mod
+          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+          restore-keys: |
+            ${{ runner.os }}-go-
+
+      - name: Build
+        if: startsWith(github.ref, 'refs/tags/')
+        env:
+          NAME: oci-help
+          BINDIR: build
+        run: |
+          make release -j$(nproc)
+
+      - name: Upload Release
+        uses: softprops/action-gh-release@v1
+        if: startsWith(github.ref, 'refs/tags/')
+        with:
+          files: build/*.zip

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+.DS_Store
+
+*.pem
+build/
+example/

+ 140 - 0
Makefile

@@ -0,0 +1,140 @@
+NAME := oci-help
+PACKAGE_NAME := github.com/lemoex/oci-help
+VERSION := $(shell git describe --tags || echo "unknown-version")
+COMMIT := $(shell git rev-parse HEAD)
+BUILDTIME := $(shell date -u "+%Y-%m-%d %H:%M:%S %Z")
+BUILD_DIR := build
+VAR_SETTING := -X "$(PACKAGE_NAME)/constant.Version=$(VERSION)" -X "$(PACKAGE_NAME)/constant.Commit=$(COMMIT)" -X "$(PACKAGE_NAME)/constant.BuildTime=$(BUILDTIME)"
+GOBUILD = CGO_ENABLED=0 go build -trimpath -ldflags '-s -w -buildid= $(VAR_SETTING)' \
+		-o $(BUILD_DIR)
+
+PLATFORM_LIST = \
+	darwin-amd64 \
+	darwin-arm64 \
+	linux-amd64 \
+	linux-arm64 \
+	windows-amd64 \
+	windows-arm64
+	
+
+zip_release = $(addsuffix .zip, $(PLATFORM_LIST))
+
+
+.PHONY: build clean release
+normal: clean build
+
+clean:
+	@rm -rf $(BUILD_DIR)
+	@echo "Cleaning up."
+
+$(zip_release): %.zip : %
+	@zip -du $(BUILD_DIR)/$(NAME)-$<-$(VERSION).zip -j -m $(BUILD_DIR)/$</$(NAME)*
+	@zip -du $(BUILD_DIR)/$(NAME)-$<-$(VERSION).zip *.ini
+	@-zip -du $(BUILD_DIR)/$(NAME)-$<-$(VERSION).zip README.md
+	@echo "✅ $(NAME)-$<-$(VERSION).zip"
+
+all: linux-amd64 darwin-amd64 windows-amd64 # Most used
+
+all-arch: $(PLATFORM_LIST)
+
+release: $(zip_release)
+
+build:
+	@-mkdir -p $(BUILD_DIR)
+	$(GOBUILD)/$(NAME)
+
+darwin-amd64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=amd64 GOOS=darwin $(GOBUILD)/$@/$(NAME)
+
+darwin-arm64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm64 GOOS=darwin $(GOBUILD)/$@/$(NAME)
+
+linux-386:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=386 GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-amd64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=amd64 GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-arm:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-armv5:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD)/$@/$(NAME)
+
+linux-armv6:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD)/$@/$(NAME)
+
+linux-armv7:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD)/$@/$(NAME)
+
+linux-arm64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm64 GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-mips-softfloat:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-mips-hardfloat:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-mipsle-softfloat:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-mipsle-hardfloat:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-mips64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=mips64 GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+linux-mips64le:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=mips64le GOOS=linux $(GOBUILD)/$@/$(NAME)
+
+freebsd-386:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=386 GOOS=freebsd $(GOBUILD)/$@/$(NAME)
+
+freebsd-amd64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=amd64 GOOS=freebsd $(GOBUILD)/$@/$(NAME)
+
+freebsd-arm64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm64 GOOS=freebsd $(GOBUILD)/$@/$(NAME)
+
+windows-386:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=386 GOOS=windows $(GOBUILD)/$@/$(NAME).exe
+
+windows-amd64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=amd64 GOOS=windows $(GOBUILD)/$@/$(NAME).exe
+
+windows-arm:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm GOOS=windows $(GOBUILD)/$@/$(NAME).exe
+
+windows-armv6:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm GOOS=windows GOARM=6 $(GOBUILD)/$@/$(NAME).exe
+
+windows-armv7:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD)/$@/$(NAME).exe
+
+windows-arm64:
+	mkdir -p $(BUILD_DIR)/$@
+	GOARCH=arm64 GOOS=windows $(GOBUILD)/$@/$(NAME).exe

+ 9 - 0
go.mod

@@ -0,0 +1,9 @@
+module oci-help
+
+go 1.17
+
+require (
+	github.com/oracle/oci-go-sdk/v49 v49.2.0
+	github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b // indirect
+	gopkg.in/ini.v1 v1.63.2
+)

+ 18 - 0
go.sum

@@ -0,0 +1,18 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/oracle/oci-go-sdk/v49 v49.2.0 h1:l4PUk81EKdTDD4mDg5wrELpdWFqYeE9KYejfNgtsyUI=
+github.com/oracle/oci-go-sdk/v49 v49.2.0/go.mod h1:E8q2DXmXnSozLdXHUFF+o3L2gzcWbiFIPFYOYWdqOfc=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b h1:br+bPNZsJWKicw/5rALEo67QHs5weyD5tf8WST+4sJ0=
+github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
+gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 1136 - 0
main.go

@@ -0,0 +1,1136 @@
+package main
+
+import (
+	"context"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"math/rand"
+	"net/http"
+	"net/url"
+	"os"
+	"strings"
+	"time"
+
+	"github.com/oracle/oci-go-sdk/v49/common"
+	"github.com/oracle/oci-go-sdk/v49/core"
+	"github.com/oracle/oci-go-sdk/v49/example/helpers"
+	"github.com/oracle/oci-go-sdk/v49/identity"
+	"gopkg.in/ini.v1"
+)
+
+const (
+	defConfigFilePath = "./oci-help.ini"
+	IPsFilePrefix     = "IPs"
+)
+
+var (
+	provider       common.ConfigurationProvider
+	config         Config
+	providerName   string
+	proxy          string
+	token          string
+	chat_id        string
+	sendMessageUrl string
+	EACH           bool
+)
+
+type Config struct {
+	AvailabilityDomain     string  `ini:"availabilityDomain"`
+	SSH_Public_Key         string  `ini:"ssh_authorized_key"`
+	CompartmentID          string  `ini:"tenancy"`
+	VcnDisplayName         string  `ini:"vcnDisplayName"`
+	SubnetDisplayName      string  `ini:"subnetDisplayName"`
+	Shape                  string  `ini:"shape"`
+	OperatingSystem        string  `ini:"OperatingSystem"`
+	OperatingSystemVersion string  `ini:"OperatingSystemVersion"`
+	InstanceDisplayName    string  `ini:"instanceDisplayName"`
+	Ocpus                  float32 `ini:"cpus"`
+	MemoryInGBs            float32 `ini:"memoryInGBs"`
+	BootVolumeSizeInGBs    int64   `ini:"bootVolumeSizeInGBs"`
+	Sum                    int32   `ini:"sum"`
+	Each                   int32   `ini:"each"`
+	Retry                  int32   `ini:"retry"`
+	CloudInit              string  `ini:"cloud-init"`
+	MinTime                int32   `ini:"minTime"`
+	MaxTime                int32   `ini:"maxTime"`
+}
+
+func main() {
+	var configFilePath string
+	flag.StringVar(&configFilePath, "config", defConfigFilePath, "配置文件路径")
+	flag.StringVar(&configFilePath, "c", defConfigFilePath, "配置文件路径")
+	flag.Parse()
+
+	cfg, err := ini.Load(configFilePath)
+	helpers.FatalIfError(err)
+	sections := cfg.Sections()
+	defSec := cfg.Section(ini.DefaultSection)
+	proxy = defSec.Key("proxy").Value()
+	token = defSec.Key("token").Value()
+	chat_id = defSec.Key("chat_id").Value()
+	if defSec.HasKey("EACH") {
+		EACH, _ = defSec.Key("EACH").Bool()
+	} else {
+		EACH = true
+	}
+	sendMessageUrl = "https://api.telegram.org/bot" + token + "/sendMessage"
+	rand.Seed(time.Now().UnixNano())
+
+	fmt.Printf("\n\033[1;32;40m%s\033[0m\n\n", "欢迎使用甲骨文实例创建工具")
+	fmt.Printf("\033[1;36;40m%s\033[0m %s\n", "1.", "开始创建实例")
+	fmt.Printf("\033[1;36;40m%s\033[0m %s\n", "2.", "获取实例IP地址")
+	fmt.Println("")
+	fmt.Print("请选择[1-2]: ")
+	var num int
+	fmt.Scanln(&num)
+	switch num {
+	case 1:
+		CreateInstances(sections, configFilePath)
+	case 2:
+		ListAllIPs(sections, configFilePath)
+	default:
+	}
+
+}
+
+func CreateInstances(sections []*ini.Section, configFile string) {
+	for _, section := range sections {
+		if len(section.ChildSections()) > 0 {
+			provider = getProvider(configFile, section.Name(), "")
+
+			printf("\033[1;36;40m[%s]\033[0m\n", section.Name())
+
+			var SUM, NUM int32 = 0, 0
+			sendMessage(section.Name(), "开始创建")
+
+			// 获取可用性域
+			AvailabilityDomains := ListAvailabilityDomains()
+
+			for _, child := range section.ChildSections() {
+				providerName = child.Name()
+				config = Config{}
+				err := child.MapTo(&config)
+				helpers.FatalIfError(err)
+
+				sum, num := LaunchInstances(AvailabilityDomains)
+
+				SUM = SUM + sum
+				NUM = NUM + num
+
+			}
+
+			printf("\033[1;36;40m[%s], 创建总数: %d, 创建成功 %d , 创建失败 %d\033[0m\n", section.Name(), SUM, NUM, SUM-NUM)
+
+			text := fmt.Sprintf("结束创建。创建总数: %d, 创建成功 %d , 创建失败 %d", SUM, NUM, SUM-NUM)
+			sendMessage(section.Name(), text)
+		}
+	}
+}
+
+// 返回值 sum: 创建实例总数; num: 创建成功的个数
+func LaunchInstances(ads []identity.AvailabilityDomain) (sum, num int32) {
+	/* 创建实例的几种情况
+	 * 1. 设置了 availabilityDomain 参数,即在设置的可用性域中创建 sum 个实例。
+	 * 2. 没有设置 availabilityDomain 但是设置了 each 参数。即在获取的每个可用性域中创建 each 个实例,创建的实例总数 sum =  each * adCount。
+	 * 3. 没有设置 availabilityDomain 且没有设置 each 参数,即在获取到的可用性域中创建的实例总数为 sum。
+	 */
+
+	//可用性域数量
+	var adCount int32 = int32(len(ads))
+	adName := common.String(config.AvailabilityDomain)
+	each := config.Each
+	sum = config.Sum
+
+	// 没有设置可用性域并且没有设置each时,才有用。
+	var usableAds = make([]identity.AvailabilityDomain, 0)
+
+	//可用性域不固定,即没有提供 availabilityDomain 参数
+	var AD_NOT_FIXED bool = false
+	var EACH_AD = false
+	if adName == nil || *adName == "" {
+		AD_NOT_FIXED = true
+		if each > 0 {
+			EACH_AD = true
+			sum = each * adCount
+		} else {
+			EACH_AD = false
+			usableAds = ads
+		}
+	}
+
+	printf("\033[1;36;40m[%s] 开始创建...\033[0m\n", providerName)
+	computeClient, err := core.NewComputeClientWithConfigurationProvider(provider)
+	helpers.FatalIfError(err)
+	setProxyOrNot(&computeClient.BaseClient)
+	networkClient, err := core.NewVirtualNetworkClientWithConfigurationProvider(provider)
+	helpers.FatalIfError(err)
+	setProxyOrNot(&networkClient.BaseClient)
+
+	ctx := context.Background()
+	name := config.InstanceDisplayName
+	if name == "" {
+		name = time.Now().Format("instance-20060102-1504")
+	}
+	displayName := common.String(name)
+	if sum > 1 {
+		displayName = common.String(name + "-1")
+	}
+	// create the launch instance request
+	request := core.LaunchInstanceRequest{}
+	request.CompartmentId = common.String(config.CompartmentID)
+	request.DisplayName = displayName
+	// create a subnet or get the one already created
+	subnet := CreateOrGetNetworkInfrastructure(ctx, networkClient)
+	printf("子网: %s\n", *subnet.DisplayName)
+	request.CreateVnicDetails = &core.CreateVnicDetails{SubnetId: subnet.Id}
+	// Get a image.
+	image, err := GetImage(ctx, computeClient)
+	helpers.FatalIfError(err)
+	printf("系统镜像: %s\n", *image.DisplayName)
+	sd := core.InstanceSourceViaImageDetails{}
+	sd.ImageId = image.Id
+	if config.BootVolumeSizeInGBs > 0 {
+		sd.BootVolumeSizeInGBs = common.Int64(config.BootVolumeSizeInGBs)
+	}
+	request.SourceDetails = sd
+	request.IsPvEncryptionInTransitEnabled = common.Bool(true)
+	request.Shape = common.String(config.Shape)
+	if config.Ocpus > 0 && config.MemoryInGBs > 0 {
+		request.ShapeConfig = &core.LaunchInstanceShapeConfigDetails{
+			Ocpus:       common.Float32(config.Ocpus),
+			MemoryInGBs: common.Float32(config.MemoryInGBs),
+		}
+	}
+	metaData := map[string]string{}
+	metaData["ssh_authorized_keys"] = config.SSH_Public_Key
+	if config.CloudInit != "" {
+		metaData["user_data"] = config.CloudInit
+	}
+	request.Metadata = metaData
+
+	minTime := config.MinTime
+	maxTime := config.MaxTime
+
+	SKIP_RETRY_MAP := make(map[int32]bool)
+	var usableAdsTemp = make([]identity.AvailabilityDomain, 0)
+
+	retry := config.Retry   // 重试次数
+	var failTimes int32 = 0 // 失败次数
+
+	var adIndex int32 = 0 // 当前可用性域下标
+	var pos int32 = 0     // for 循环次数
+	var SUCCESS = false   // 创建是否成功
+
+	for pos < sum {
+
+		if AD_NOT_FIXED {
+			if EACH_AD {
+				if pos%each == 0 && failTimes == 0 {
+					adName = ads[adIndex].Name
+					adIndex++
+				}
+			} else {
+				if SUCCESS {
+					adIndex = 0
+				}
+				if adIndex >= adCount {
+					adIndex = 0
+				}
+				//adName = ads[adIndex].Name
+				adName = usableAds[adIndex].Name
+				adIndex++
+			}
+		}
+
+		printf("\033[1;36;40m[%s] 第 %d 个实例正在创建, AD: %s\033[0m\n", providerName, pos+1, *adName)
+
+		request.AvailabilityDomain = adName
+		createResp, err := computeClient.LaunchInstance(ctx, request)
+
+		if err == nil {
+			// 创建实例成功
+			SUCCESS = true
+			num++ //成功个数+1
+
+			printf("\033[1;32;40m[%s] 第 %d 个实例创建成功, 实例名称: %s\033[0m\n", providerName, pos+1, *createResp.Instance.DisplayName)
+			if EACH {
+				sendMessage(providerName, "创建成功,实例名称: "+*createResp.DisplayName)
+			}
+
+			ips := getInstancePublicIps(ctx, computeClient, networkClient, createResp.Instance.Id)
+			strIps := strings.Join(ips, ",")
+			printf("\033[1;32;40m[%s] 实例名称: %s, IP: %s\033[0m\n", providerName, *createResp.Instance.DisplayName, strIps)
+			if EACH {
+				sendMessage(providerName, "实例名称: "+*createResp.DisplayName+", IP: "+strIps)
+			}
+
+			sleepRandomSecond(minTime, maxTime)
+
+			displayName = common.String(fmt.Sprintf("%s-%d", name, pos+1))
+			request.DisplayName = displayName
+
+		} else {
+			// 创建实例失败
+			SUCCESS = false
+			// 错误信息
+			errInfo := err.Error()
+			// 是否跳过重试
+			SKIP_RETRY := false
+
+			//isRetryable := common.IsErrorRetryableByDefault(err)
+			//fmt.Println("IsErrorRetryableByDefault", isRetryable)
+			isNetErr := common.IsNetworkError(err)
+			servErr, isServErr := common.IsServiceError(err)
+			if isNetErr || (isServErr && (servErr.GetHTTPStatusCode() == 409 || servErr.GetHTTPStatusCode() == 429 || (500 <= servErr.GetHTTPStatusCode() && servErr.GetHTTPStatusCode() < 505))) {
+				// 可重试
+				if isServErr {
+					errInfo = servErr.GetMessage()
+				}
+				printf("\033[1;31;40m[%s] 第 %d 个实例创建失败, Error: \033[0m%s\n", providerName, pos+1, errInfo)
+
+				SKIP_RETRY = false
+				if AD_NOT_FIXED && !EACH_AD {
+					SKIP_RETRY_MAP[adIndex-1] = false
+				}
+
+			} else {
+				// 无需重试
+				if isServErr {
+					errInfo = servErr.GetMessage()
+				}
+				printf("\033[1;31;40m[%s] 第 %d 个实例创建失败, Error: \033[0m%s\n", providerName, pos+1, errInfo)
+				if EACH {
+					sendMessage(providerName, "创建失败,Error: "+errInfo)
+				}
+
+				SKIP_RETRY = true
+				if AD_NOT_FIXED && !EACH_AD {
+					SKIP_RETRY_MAP[adIndex-1] = true
+				}
+			}
+
+			sleepRandomSecond(minTime, maxTime)
+
+			if AD_NOT_FIXED {
+				if !EACH_AD {
+
+					if adIndex < adCount {
+						// 没有设置可用性域,且没有设置each。即在获取到的每个可用性域里尝试创建。当前使用的可用性域不是最后一个,继续尝试。
+						continue
+
+					} else {
+						// 当前使用的可用性域是最后一个,判断失败次数是否达到重试次数,未达到重试次数继续尝试。
+						failTimes++
+
+						for index, skip := range SKIP_RETRY_MAP {
+							if !skip {
+								usableAdsTemp = append(usableAdsTemp, usableAds[index])
+							}
+						}
+
+						// 重新设置 usableAds
+						usableAds = usableAdsTemp
+						adCount = int32(len(usableAds))
+
+						// 重置变量
+						usableAdsTemp = nil
+						for k := range SKIP_RETRY_MAP {
+							delete(SKIP_RETRY_MAP, k)
+						}
+
+						// 判断是否需要重试
+						if (retry < 0 || failTimes <= retry) && adCount > 0 {
+							continue
+						}
+					}
+
+				} else {
+					// 没有设置可用性域,且设置了each,即在每个域创建each个实例。判断失败次数继续尝试。
+					failTimes++
+					if (retry < 0 || failTimes <= retry) && !SKIP_RETRY {
+						continue
+					}
+				}
+
+			} else {
+				//设置了可用性域,判断是否需要重试
+				failTimes++
+				if (retry < 0 || failTimes <= retry) && !SKIP_RETRY {
+					continue
+				}
+			}
+
+		}
+
+		// 重置变量
+		usableAds = ads
+		adCount = int32(len(usableAds))
+		usableAdsTemp = nil
+		for k := range SKIP_RETRY_MAP {
+			delete(SKIP_RETRY_MAP, k)
+		}
+
+		// 成功或者失败次数达到重试次数,重置失败次数为0
+		failTimes = 0
+
+		// for 循环次数+1
+		pos++
+	}
+
+	return
+}
+
+func sleepRandomSecond(min, max int32) {
+	var second int32
+	if min <= 0 || max <= 0 {
+		second = 1
+	} else if min >= max {
+		second = max
+	} else {
+		second = rand.Int31n(max-min) + min
+	}
+	printf("Sleep %d Second...\n", second)
+	time.Sleep(time.Duration(second) * time.Second)
+}
+
+func ListAllIPs(sections []*ini.Section, configFile string) {
+	IPsFilePath := IPsFilePrefix + "-" + time.Now().Format("2006-01-02-150405.txt")
+	_, err := os.Stat(IPsFilePath)
+	if err != nil && os.IsNotExist(err) {
+		os.Create(IPsFilePath)
+	}
+
+	printf("获取实例IP地址...\n")
+	for _, section := range sections {
+
+		if len(section.ChildSections()) > 0 {
+			provider = getProvider(configFile, section.Name(), "")
+			ListInstancesIPs(IPsFilePath, section.Name())
+		}
+
+	}
+	printf("获取实例IP地址完成,请查看文件 %s\n", IPsFilePath)
+}
+
+func ListInstancesIPs(filePath string, sectionName string) {
+	ctx := context.Background()
+	computeClient, err := core.NewComputeClientWithConfigurationProvider(provider)
+	helpers.FatalIfError(err)
+	setProxyOrNot(&computeClient.BaseClient)
+	netClient, err := core.NewVirtualNetworkClientWithConfigurationProvider(provider)
+	helpers.FatalIfError(err)
+	setProxyOrNot(&netClient.BaseClient)
+
+	vnicAttachments, err := ListVnicAttachments(ctx, computeClient, nil)
+	if err != nil {
+		printf("ListVnicAttachments Error: %s\n", err.Error())
+	}
+
+	file, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
+	if err != nil {
+		printf("打开文件失败, Error: %s\n", err.Error())
+		return
+	}
+	_, err = io.WriteString(file, "["+sectionName+"]\n")
+	if err != nil {
+		printf("%s\n", err.Error())
+	}
+	for _, vnicAttachment := range vnicAttachments {
+
+		vnic, err := GetVnic(ctx, netClient, vnicAttachment.VnicId)
+		if err != nil {
+			printf("IP地址获取失败, %s\n", err.Error())
+			continue
+		}
+		printf("实例: %s, IP: %s\n", *vnic.DisplayName, *vnic.PublicIp)
+		_, err = io.WriteString(file, "实例: "+*vnic.DisplayName+", IP: "+*vnic.PublicIp+"\n")
+		if err != nil {
+			printf("写入文件失败, Error: %s\n", err.Error())
+		}
+	}
+	_, err = io.WriteString(file, "\n")
+	if err != nil {
+		printf("%s\n", err.Error())
+	}
+}
+
+// ExampleLaunchInstance does create an instance
+// NOTE: launch instance will create a new instance and VCN. please make sure delete the instance
+// after execute this sample code, otherwise, you will be charged for the running instance
+func ExampleLaunchInstance() {
+	c, err := core.NewComputeClientWithConfigurationProvider(provider)
+	helpers.FatalIfError(err)
+	networkClient, err := core.NewVirtualNetworkClientWithConfigurationProvider(provider)
+	helpers.FatalIfError(err)
+	ctx := context.Background()
+
+	// create the launch instance request
+	request := core.LaunchInstanceRequest{}
+	request.CompartmentId = common.String(config.CompartmentID)
+	request.DisplayName = common.String(config.InstanceDisplayName)
+	request.AvailabilityDomain = common.String(config.AvailabilityDomain)
+
+	// create a subnet or get the one already created
+	subnet := CreateOrGetNetworkInfrastructure(ctx, networkClient)
+	fmt.Println("subnet created")
+	request.CreateVnicDetails = &core.CreateVnicDetails{SubnetId: subnet.Id}
+
+	// get a image
+	image := listImages(ctx, c)[0]
+	fmt.Println("list images")
+	request.SourceDetails = core.InstanceSourceViaImageDetails{
+		ImageId:             image.Id,
+		BootVolumeSizeInGBs: common.Int64(config.BootVolumeSizeInGBs),
+	}
+
+	// use [config.Shape] to create instance
+	request.Shape = common.String(config.Shape)
+
+	request.ShapeConfig = &core.LaunchInstanceShapeConfigDetails{
+		Ocpus:       common.Float32(config.Ocpus),
+		MemoryInGBs: common.Float32(config.MemoryInGBs),
+	}
+
+	// add ssh_authorized_keys
+	//metaData := map[string]string{
+	//	"ssh_authorized_keys": config.SSH_Public_Key,
+	//}
+	//request.Metadata = metaData
+	request.Metadata = map[string]string{"ssh_authorized_keys": config.SSH_Public_Key}
+
+	// default retry policy will retry on non-200 response
+	request.RequestMetadata = helpers.GetRequestMetadataWithDefaultRetryPolicy()
+
+	createResp, err := c.LaunchInstance(ctx, request)
+	helpers.FatalIfError(err)
+
+	fmt.Println("launching instance")
+
+	// should retry condition check which returns a bool value indicating whether to do retry or not
+	// it checks the lifecycle status equals to Running or not for this case
+	shouldRetryFunc := func(r common.OCIOperationResponse) bool {
+		if converted, ok := r.Response.(core.GetInstanceResponse); ok {
+			return converted.LifecycleState != core.InstanceLifecycleStateRunning
+		}
+		return true
+	}
+
+	// create get instance request with a retry policy which takes a function
+	// to determine shouldRetry or not
+	pollingGetRequest := core.GetInstanceRequest{
+		InstanceId:      createResp.Instance.Id,
+		RequestMetadata: helpers.GetRequestMetadataWithCustomizedRetryPolicy(shouldRetryFunc),
+	}
+
+	instance, pollError := c.GetInstance(ctx, pollingGetRequest)
+	helpers.FatalIfError(pollError)
+
+	fmt.Println("instance launched")
+
+	// 创建辅助 VNIC 并将其附加到指定的实例
+	attachVnicResponse, err := c.AttachVnic(context.Background(), core.AttachVnicRequest{
+		AttachVnicDetails: core.AttachVnicDetails{
+			CreateVnicDetails: &core.CreateVnicDetails{
+				SubnetId:       subnet.Id,
+				AssignPublicIp: common.Bool(true),
+			},
+			InstanceId: instance.Id,
+		},
+	})
+
+	helpers.FatalIfError(err)
+	fmt.Println("vnic attached")
+
+	vnicState := attachVnicResponse.VnicAttachment.LifecycleState
+	for vnicState != core.VnicAttachmentLifecycleStateAttached {
+		time.Sleep(15 * time.Second)
+		getVnicAttachmentRequest, err := c.GetVnicAttachment(context.Background(), core.GetVnicAttachmentRequest{
+			VnicAttachmentId: attachVnicResponse.Id,
+		})
+		helpers.FatalIfError(err)
+		vnicState = getVnicAttachmentRequest.VnicAttachment.LifecycleState
+	}
+
+	// 分离并删除指定的辅助 VNIC
+	_, err = c.DetachVnic(context.Background(), core.DetachVnicRequest{
+		VnicAttachmentId: attachVnicResponse.Id,
+	})
+
+	helpers.FatalIfError(err)
+	fmt.Println("vnic dettached")
+
+	defer func() {
+		terminateInstance(ctx, c, createResp.Id)
+
+		client, clerr := core.NewVirtualNetworkClientWithConfigurationProvider(common.DefaultConfigProvider())
+		helpers.FatalIfError(clerr)
+
+		vcnID := subnet.VcnId
+		deleteSubnet(ctx, client, subnet.Id)
+		deleteVcn(ctx, client, vcnID)
+	}()
+
+	// Output:
+	// subnet created
+	// list images
+	// list shapes
+	// launching instance
+	// instance launched
+	// vnic attached
+	// vnic dettached
+	// terminating instance
+	// instance terminated
+	// deleteing subnet
+	// subnet deleted
+	// deleteing VCN
+	// VCN deleted
+}
+
+func getProvider(configPath, profile, privateKeyPassword string) common.ConfigurationProvider {
+	//provider := common.DefaultConfigProvider()
+	//provider, err := common.ConfigurationProviderFromFile("./oci-config", "")
+	provider, err := common.ConfigurationProviderFromFileWithProfile(configPath, profile, privateKeyPassword)
+	helpers.FatalIfError(err)
+	return provider
+}
+
+// 创建或获取基础网络设施
+func CreateOrGetNetworkInfrastructure(ctx context.Context, c core.VirtualNetworkClient) core.Subnet {
+	vcn := createOrGetVcn(ctx, c)
+	gateway := createOrGetInternetGateway(c, vcn.Id)
+	createOrGetRouteTable(c, gateway.Id, vcn.Id)
+	subnet := createOrGetSubnetWithDetails(
+		ctx, c, vcn.Id,
+		common.String(config.SubnetDisplayName),
+		common.String("10.0.0.0/24"),
+		common.String("subnetdns"),
+		common.String(config.AvailabilityDomain))
+
+	return subnet
+}
+
+// CreateOrGetSubnetWithDetails either creates a new Virtual Cloud Network (VCN) or get the one already exist
+// with detail info
+func createOrGetSubnetWithDetails(ctx context.Context, c core.VirtualNetworkClient, vcnID *string,
+	displayName *string, cidrBlock *string, dnsLabel *string, availableDomain *string) core.Subnet {
+	subnets := listSubnets(ctx, c, vcnID)
+
+	if displayName == nil {
+		displayName = common.String(config.SubnetDisplayName)
+	}
+
+	if len(subnets) > 0 && *displayName == "" {
+		return subnets[0]
+	}
+
+	// check if the subnet has already been created
+	for _, element := range subnets {
+		if *element.DisplayName == *displayName {
+			// find the subnet, return it
+			return element
+		}
+	}
+
+	// create a new subnet
+	printf("开始创建Subnet(没有可用的Subnet,或指定的Subnet不存在)\n")
+	// 子网名称为空,以当前时间为名称创建子网
+	if *displayName == "" {
+		displayName = common.String(time.Now().Format("subnet-20060102-1504"))
+	}
+	request := core.CreateSubnetRequest{}
+	//request.AvailabilityDomain = availableDomain //省略此属性创建区域性子网(regional subnet),提供此属性创建特定于可用性域的子网。建议创建区域性子网。
+	request.CompartmentId = &config.CompartmentID
+	request.CidrBlock = cidrBlock
+	request.DisplayName = displayName
+	request.DnsLabel = dnsLabel
+	request.RequestMetadata = helpers.GetRequestMetadataWithDefaultRetryPolicy()
+
+	request.VcnId = vcnID
+	r, err := c.CreateSubnet(ctx, request)
+	helpers.FatalIfError(err)
+	// retry condition check, stop unitl return true
+	pollUntilAvailable := func(r common.OCIOperationResponse) bool {
+		if converted, ok := r.Response.(core.GetSubnetResponse); ok {
+			return converted.LifecycleState != core.SubnetLifecycleStateAvailable
+		}
+		return true
+	}
+
+	pollGetRequest := core.GetSubnetRequest{
+		SubnetId:        r.Id,
+		RequestMetadata: helpers.GetRequestMetadataWithCustomizedRetryPolicy(pollUntilAvailable),
+	}
+
+	// wait for lifecyle become running
+	_, pollErr := c.GetSubnet(ctx, pollGetRequest)
+	helpers.FatalIfError(pollErr)
+
+	// update the security rules
+	getReq := core.GetSecurityListRequest{
+		SecurityListId: common.String(r.SecurityListIds[0]),
+	}
+
+	getResp, err := c.GetSecurityList(ctx, getReq)
+	helpers.FatalIfError(err)
+
+	// this security rule allows remote control the instance
+	/*portRange := core.PortRange{
+		Max: common.Int(1521),
+		Min: common.Int(1521),
+	}*/
+
+	newRules := append(getResp.IngressSecurityRules, core.IngressSecurityRule{
+		//Protocol: common.String("6"), // TCP
+		Protocol: common.String("all"), // 允许所有协议
+		Source:   common.String("0.0.0.0/0"),
+		/*TcpOptions: &core.TcpOptions{
+			DestinationPortRange: &portRange, // 省略该参数,允许所有目标端口。
+		},*/
+	})
+
+	updateReq := core.UpdateSecurityListRequest{
+		SecurityListId: common.String(r.SecurityListIds[0]),
+	}
+
+	updateReq.IngressSecurityRules = newRules
+
+	_, err = c.UpdateSecurityList(ctx, updateReq)
+	helpers.FatalIfError(err)
+	printf("Subnet创建成功: %s\n", *r.Subnet.DisplayName)
+	return r.Subnet
+}
+
+// 列出指定虚拟云网络 (VCN) 中的所有子网,如果该 VCN 不存在会创建 VCN
+func listSubnets(ctx context.Context, c core.VirtualNetworkClient, vcnID *string) []core.Subnet {
+	request := core.ListSubnetsRequest{
+		CompartmentId: &config.CompartmentID,
+		VcnId:         vcnID,
+	}
+	r, err := c.ListSubnets(ctx, request)
+	helpers.FatalIfError(err)
+	return r.Items
+}
+
+// 创建一个新的虚拟云网络 (VCN) 或获取已经存在的虚拟云网络
+func createOrGetVcn(ctx context.Context, c core.VirtualNetworkClient) core.Vcn {
+	vcnItems := listVcns(ctx, c)
+
+	displayName := common.String(config.VcnDisplayName)
+
+	if len(vcnItems) > 0 && *displayName == "" {
+		return vcnItems[0]
+	}
+
+	for _, element := range vcnItems {
+		if *element.DisplayName == config.VcnDisplayName {
+			// VCN already created, return it
+			return element
+		}
+	}
+
+	// create a new VCN
+	printf("开始创建VCN(没有可用的VCN,或指定的VCN不存在)\n")
+	if *displayName == "" {
+		displayName = common.String(time.Now().Format("vcn-20060102-1504"))
+	}
+	request := core.CreateVcnRequest{}
+	request.CidrBlock = common.String("10.0.0.0/16")
+	request.CompartmentId = common.String(config.CompartmentID)
+	request.DisplayName = displayName
+	request.DnsLabel = common.String("vcndns")
+	r, err := c.CreateVcn(ctx, request)
+	helpers.FatalIfError(err)
+	printf("VCN创建成功: %s\n", *r.Vcn.DisplayName)
+	return r.Vcn
+}
+
+// 列出所有虚拟云网络 (VCN)
+func listVcns(ctx context.Context, c core.VirtualNetworkClient) []core.Vcn {
+	request := core.ListVcnsRequest{
+		CompartmentId: &config.CompartmentID,
+	}
+	r, err := c.ListVcns(ctx, request)
+	helpers.FatalIfError(err)
+	return r.Items
+}
+
+// 创建或者获取 Internet 网关
+func createOrGetInternetGateway(c core.VirtualNetworkClient, vcnID *string) (gateway core.InternetGateway) {
+	ctx := context.Background()
+	//List Gateways
+	listGWRequest := core.ListInternetGatewaysRequest{
+		CompartmentId: &config.CompartmentID,
+		VcnId:         vcnID,
+	}
+
+	listGWRespone, err := c.ListInternetGateways(ctx, listGWRequest)
+	if err != nil {
+		printf("Internet gateway list error: %s\n", err.Error())
+	}
+
+	if len(listGWRespone.Items) >= 1 {
+		//Gateway with name already exists
+		gateway = listGWRespone.Items[0]
+	} else {
+		//Create new Gateway
+		printf("开始创建Internet网关\n")
+		enabled := true
+		createGWDetails := core.CreateInternetGatewayDetails{
+			CompartmentId: &config.CompartmentID,
+			IsEnabled:     &enabled,
+			VcnId:         vcnID,
+		}
+
+		createGWRequest := core.CreateInternetGatewayRequest{CreateInternetGatewayDetails: createGWDetails}
+
+		createGWResponse, err := c.CreateInternetGateway(ctx, createGWRequest)
+
+		if err != nil {
+			printf("Internet gateway create error: %s\n", err.Error())
+		}
+		gateway = createGWResponse.InternetGateway
+		printf("Internet网关创建成功: %s\n", *gateway.DisplayName)
+	}
+	return
+}
+
+// 创建或者获取路由表
+func createOrGetRouteTable(c core.VirtualNetworkClient, gatewayID, VcnID *string) (routeTable core.RouteTable) {
+	ctx := context.Background()
+	//List Route Table
+	listRTRequest := core.ListRouteTablesRequest{
+		CompartmentId: &config.CompartmentID,
+		VcnId:         VcnID,
+	}
+
+	listRTResponse, err := c.ListRouteTables(ctx, listRTRequest)
+	if err != nil {
+		printf("Route table list error: %s\n", err.Error())
+	}
+
+	cidrRange := "0.0.0.0/0"
+	rr := core.RouteRule{
+		NetworkEntityId: gatewayID,
+		Destination:     &cidrRange,
+		DestinationType: core.RouteRuleDestinationTypeCidrBlock,
+	}
+
+	if len(listRTResponse.Items) >= 1 {
+		//Default Route Table found and has at least 1 route rule
+		if len(listRTResponse.Items[0].RouteRules) >= 1 {
+			routeTable = listRTResponse.Items[0]
+			//Default Route table needs route rule adding
+		} else {
+			printf("路由表未添加规则,开始添加Internet路由规则\n")
+			updateRTDetails := core.UpdateRouteTableDetails{
+				RouteRules: []core.RouteRule{rr},
+			}
+
+			updateRTRequest := core.UpdateRouteTableRequest{
+				RtId:                    listRTResponse.Items[0].Id,
+				UpdateRouteTableDetails: updateRTDetails,
+			}
+
+			updateRTResponse, err := c.UpdateRouteTable(ctx, updateRTRequest)
+			if err != nil {
+				printf("Error updating route table: %s\n", err)
+			}
+			printf("Internet路由规则添加成功\n")
+			routeTable = updateRTResponse.RouteTable
+		}
+
+	} else {
+		//No default route table found
+		printf("Error could not find VCN default route table, VCN OCID: %s Could not find route table.\n", *VcnID)
+	}
+	return
+}
+
+// 获取符合条件系统镜像中的第一个
+func GetImage(ctx context.Context, c core.ComputeClient) (image core.Image, err error) {
+	images := listImages(ctx, c)
+	if len(images) > 0 {
+		image = images[0]
+	} else {
+		err = fmt.Errorf("未找到[%s %s]的镜像, 或该镜像不支持[%s]", config.OperatingSystem, config.OperatingSystemVersion, config.Shape)
+	}
+	return
+}
+
+// 列出所有符合条件的系统镜像
+func listImages(ctx context.Context, c core.ComputeClient) []core.Image {
+	request := core.ListImagesRequest{
+		CompartmentId:          common.String(config.CompartmentID),
+		OperatingSystem:        common.String(config.OperatingSystem),
+		OperatingSystemVersion: common.String(config.OperatingSystemVersion),
+		Shape:                  common.String(config.Shape),
+	}
+	r, err := c.ListImages(ctx, request)
+	helpers.FatalIfError(err)
+	return r.Items
+}
+
+// ListShapes Lists the shapes that can be used to launch an instance within the specified compartment.
+func listShapes(ctx context.Context, c core.ComputeClient, imageID *string) []core.Shape {
+	request := core.ListShapesRequest{
+		CompartmentId: common.String(config.CompartmentID),
+		ImageId:       imageID,
+	}
+
+	r, err := c.ListShapes(ctx, request)
+	helpers.FatalIfError(err)
+
+	if r.Items == nil || len(r.Items) == 0 {
+		log.Fatalln("Invalid response from ListShapes")
+	}
+
+	return r.Items
+}
+
+// 列出符合条件的可用性域
+func ListAvailabilityDomains() []identity.AvailabilityDomain {
+	c, err := identity.NewIdentityClientWithConfigurationProvider(provider)
+	helpers.FatalIfError(err)
+	setProxyOrNot(&c.BaseClient)
+	req := identity.ListAvailabilityDomainsRequest{}
+	compartmentID, err := provider.TenancyOCID()
+	helpers.FatalIfError(err)
+	req.CompartmentId = common.String(compartmentID)
+	resp, err := c.ListAvailabilityDomains(context.Background(), req)
+	helpers.FatalIfError(err)
+	return resp.Items
+}
+
+func ListInstances(ctx context.Context, c core.ComputeClient) []core.Instance {
+	compartmentID, err := provider.TenancyOCID()
+	helpers.FatalIfError(err)
+	req := core.ListInstancesRequest{
+		CompartmentId: &compartmentID,
+	}
+	resp, err := c.ListInstances(ctx, req)
+	helpers.FatalIfError(err)
+	return resp.Items
+}
+
+func ListVnicAttachments(ctx context.Context, c core.ComputeClient, instanceId *string) ([]core.VnicAttachment, error) {
+	compartmentID, err := provider.TenancyOCID()
+	helpers.FatalIfError(err)
+	req := core.ListVnicAttachmentsRequest{CompartmentId: &compartmentID}
+	if instanceId != nil && *instanceId != "" {
+		req.InstanceId = instanceId
+	}
+	resp, err := c.ListVnicAttachments(ctx, req)
+	return resp.Items, err
+}
+
+func GetVnic(ctx context.Context, c core.VirtualNetworkClient, vnicID *string) (core.Vnic, error) {
+	req := core.GetVnicRequest{VnicId: vnicID}
+	resp, err := c.GetVnic(ctx, req)
+	if err != nil && resp.RawResponse != nil {
+		err = errors.New(resp.RawResponse.Status)
+	}
+	return resp.Vnic, err
+}
+
+func listPublicIPs(ctx context.Context, c core.VirtualNetworkClient, ad *string) []core.PublicIp {
+	com, err := provider.TenancyOCID()
+	helpers.FatalIfError(err)
+	req := core.ListPublicIpsRequest{
+		Scope:              core.ListPublicIpsScopeAvailabilityDomain,
+		CompartmentId:      &com,
+		AvailabilityDomain: ad,
+	}
+	resp, err := c.ListPublicIps(ctx, req)
+	helpers.FatalIfError(err)
+	return resp.Items
+}
+
+// 终止实例
+func terminateInstance(ctx context.Context, c core.ComputeClient, id *string) {
+	request := core.TerminateInstanceRequest{
+		InstanceId:      id,
+		RequestMetadata: helpers.GetRequestMetadataWithDefaultRetryPolicy(),
+	}
+
+	_, err := c.TerminateInstance(ctx, request)
+	helpers.FatalIfError(err)
+
+	fmt.Println("terminating instance")
+
+	// should retry condition check which returns a bool value indicating whether to do retry or not
+	// it checks the lifecycle status equals to Terminated or not for this case
+	shouldRetryFunc := func(r common.OCIOperationResponse) bool {
+		if converted, ok := r.Response.(core.GetInstanceResponse); ok {
+			return converted.LifecycleState != core.InstanceLifecycleStateTerminated
+		}
+		return true
+	}
+
+	pollGetRequest := core.GetInstanceRequest{
+		InstanceId:      id,
+		RequestMetadata: helpers.GetRequestMetadataWithCustomizedRetryPolicy(shouldRetryFunc),
+	}
+
+	_, pollErr := c.GetInstance(ctx, pollGetRequest)
+	helpers.FatalIfError(pollErr)
+	fmt.Println("instance terminated")
+}
+
+// 删除虚拟云网络
+func deleteVcn(ctx context.Context, c core.VirtualNetworkClient, id *string) {
+	request := core.DeleteVcnRequest{
+		VcnId:           id,
+		RequestMetadata: helpers.GetRequestMetadataWithDefaultRetryPolicy(),
+	}
+
+	fmt.Println("deleteing VCN")
+	_, err := c.DeleteVcn(ctx, request)
+	helpers.FatalIfError(err)
+
+	// should retry condition check which returns a bool value indicating whether to do retry or not
+	// it checks the lifecycle status equals to Terminated or not for this case
+	shouldRetryFunc := func(r common.OCIOperationResponse) bool {
+		if serviceError, ok := common.IsServiceError(r.Error); ok && serviceError.GetHTTPStatusCode() == 404 {
+			// resource been deleted, stop retry
+			return false
+		}
+
+		if converted, ok := r.Response.(core.GetVcnResponse); ok {
+			return converted.LifecycleState != core.VcnLifecycleStateTerminated
+		}
+		return true
+	}
+
+	pollGetRequest := core.GetVcnRequest{
+		VcnId:           id,
+		RequestMetadata: helpers.GetRequestMetadataWithCustomizedRetryPolicy(shouldRetryFunc),
+	}
+
+	_, pollErr := c.GetVcn(ctx, pollGetRequest)
+	if serviceError, ok := common.IsServiceError(pollErr); !ok ||
+		(ok && serviceError.GetHTTPStatusCode() != 404) {
+		// fail if the error is not service error or
+		// if the error is service error and status code not equals to 404
+		helpers.FatalIfError(pollErr)
+	}
+	fmt.Println("VCN deleted")
+}
+
+// 删除子网
+func deleteSubnet(ctx context.Context, c core.VirtualNetworkClient, id *string) {
+	request := core.DeleteSubnetRequest{
+		SubnetId:        id,
+		RequestMetadata: helpers.GetRequestMetadataWithDefaultRetryPolicy(),
+	}
+
+	_, err := c.DeleteSubnet(context.Background(), request)
+	helpers.FatalIfError(err)
+
+	fmt.Println("deleteing subnet")
+
+	// should retry condition check which returns a bool value indicating whether to do retry or not
+	// it checks the lifecycle status equals to Terminated or not for this case
+	shouldRetryFunc := func(r common.OCIOperationResponse) bool {
+		if serviceError, ok := common.IsServiceError(r.Error); ok && serviceError.GetHTTPStatusCode() == 404 {
+			// resource been deleted
+			return false
+		}
+
+		if converted, ok := r.Response.(core.GetSubnetResponse); ok {
+			return converted.LifecycleState != core.SubnetLifecycleStateTerminated
+		}
+		return true
+	}
+
+	pollGetRequest := core.GetSubnetRequest{
+		SubnetId:        id,
+		RequestMetadata: helpers.GetRequestMetadataWithCustomizedRetryPolicy(shouldRetryFunc),
+	}
+
+	_, pollErr := c.GetSubnet(ctx, pollGetRequest)
+	if serviceError, ok := common.IsServiceError(pollErr); !ok ||
+		(ok && serviceError.GetHTTPStatusCode() != 404) {
+		// fail if the error is not service error or
+		// if the error is service error and status code not equals to 404
+		helpers.FatalIfError(pollErr)
+	}
+
+	fmt.Println("subnet deleted")
+}
+
+func printf(format string, a ...interface{}) {
+	fmt.Printf("%s ", time.Now().Format("2006-01-02 15:04:05"))
+	fmt.Printf(format, a...)
+}
+
+// 根据实例OCID获取公共IP
+func getInstancePublicIps(ctx context.Context, computeClient core.ComputeClient, networkClient core.VirtualNetworkClient, instanceId *string) (ips []string) {
+	var err error
+	var vnicAttachments []core.VnicAttachment
+	var vnic core.Vnic
+	vnicAttachments, err = ListVnicAttachments(ctx, computeClient, instanceId)
+	if err != nil {
+		printf("ListVnicAttachments error: %s\n", err.Error())
+		return
+	}
+	for _, vnicAttachment := range vnicAttachments {
+		vnic, err = GetVnic(ctx, networkClient, vnicAttachment.VnicId)
+		if err != nil {
+			printf("GetVnic error: %s\n", err.Error())
+			continue
+		}
+		ips = append(ips, *vnic.PublicIp)
+	}
+	return
+}
+
+func sendMessage(name, text string) {
+	if token != "" && chat_id != "" {
+		data := url.Values{
+			"parse_mode": {"Markdown"},
+			"chat_id":    {chat_id},
+			"text":       {"*甲骨文通知*\n名称: " + name + "\n" + "内容: " + text},
+		}
+		req, err := http.NewRequest(http.MethodPost, sendMessageUrl, strings.NewReader(data.Encode()))
+		if err != nil {
+			printf("\033[1;31;40mNewRequest Error: \033[0m%s\n", err.Error())
+		}
+		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+		client := common.BaseClient{HTTPClient: &http.Client{}}
+		setProxyOrNot(&client)
+
+		resp, err := client.HTTPClient.Do(req)
+		if err != nil {
+			printf("\033[1;31;40mTelegram 消息提醒发送失败, Error: \033[0m%s\n", err.Error())
+		} else {
+			if resp.StatusCode != 200 {
+				bodyBytes, err := ioutil.ReadAll(resp.Body)
+				var error string
+				if err != nil {
+					error = err.Error()
+				} else {
+					error = string(bodyBytes)
+				}
+				printf("\033[1;31;40mTelegram 消息提醒发送失败, Error: \033[0m%s\n", error)
+			}
+		}
+
+	}
+}
+
+func setProxyOrNot(client *common.BaseClient) {
+	if proxy != "" {
+		proxyURL, err := url.Parse(proxy)
+		helpers.FatalIfError(err)
+		client.HTTPClient = &http.Client{
+			Transport: &http.Transport{
+				Proxy: http.ProxyURL(proxyURL),
+			},
+		}
+	}
+}

+ 39 - 0
oci-help.ini

@@ -0,0 +1,39 @@
+# 使用代理 socks5://127.0.0.1:7890 / http://127.0.0.1:7890
+#proxy=socks5://127.0.0.1:7890
+# Telegram Bot 消息提醒
+token=
+chat_id=
+
+
+[DEFAULT]
+user=
+fingerprint=
+tenancy=
+region=
+key_file=xxxxxx.pem
+# 系统 Canonical Ubuntu / CentOS / Oracle Linux
+OperatingSystem=Canonical Ubuntu
+# 系统版本 Canonical Ubuntu: 20.04|18.04 / CentOS :8|7 / Oracle Linux: 8|7.9
+OperatingSystemVersion=20.04
+# 失败后重试次数
+retry=3
+# 延迟时间(秒)
+minTime=5
+maxTime=30
+# 初始化脚本(将脚本内容base64编码后添加)。该脚本将在您的实例引导或重新启动时运行。
+cloud-init=
+
+
+### ARM 实例配置
+[DEFAULT.ARM]
+shape=VM.Standard.A1.Flex
+cpus=1 # cpu个数
+memoryInGBs=6 # 内存大小(GB)
+# 引导卷大小
+bootVolumeSizeInGBs=50
+sum=1 # 创建实例个数
+retry=-1 # 失败后重试次数设置为-1,失败后一直尝试直到成功。
+# 可用性域
+availabilityDomain=
+# SSH 公钥
+ssh_authorized_key=