Skip to content

Commit 77e4599

Browse files
authored
Merge pull request #3 from sakura-internet/feat/register_sim
"SIM一括登録"コマンドを追加
2 parents 7863e2e + 59646af commit 77e4599

21 files changed

+1058
-7
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77

88
# コマンド一覧
99

10-
- [モバイルゲートウェイ内のSIMに割り当て可能なIPアドレスを出力(get_unused_ip)](./get_unused_ip)
10+
- [モバイルゲートウェイ内のSIMに割り当て可能なIPアドレスを出力(get_unused_ip)](./get_unused_ip)
11+
- [CSVファイルからSIM一括登録(register_sim)](./register_sim)

common/common.go

Lines changed: 289 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package common
22

33
import (
4+
"bytes"
45
"encoding/base64"
56
"encoding/json"
67
"fmt"
@@ -22,17 +23,24 @@ type SimAPIResponse struct {
2223
Count int `json:"Count"`
2324
}
2425

25-
// GetUsedIPAddressesInMGW
26-
// MGW 内での利用されている IP アドレス一覧を取得する
27-
// 利用されている IPアドレス一覧を取得
28-
func GetUsedIPAddressesInMGW(accessToken string, accessTokenSecret string, zone string, mgwID string) (map[string]struct{}, error) {
29-
// ベーシック認証
30-
auth := fmt.Sprintf("%s:%s", accessToken, accessTokenSecret)
26+
// BASIC認証のAuthorizationヘッダが設定されたhttp.Header{}インスタンスを作成
27+
func createHeadersWithBasicAuth(user string, password string) http.Header {
28+
auth := fmt.Sprintf("%s:%s", user, password)
3129
encodedAuth := base64.StdEncoding.EncodeToString([]byte(auth))
3230
headers := http.Header{
3331
"Authorization": []string{"Basic " + encodedAuth},
3432
}
3533

34+
return headers
35+
}
36+
37+
// GetUsedIPAddressesInMGW
38+
// MGW 内での利用されている IP アドレス一覧を取得する
39+
// 利用されている IPアドレス一覧を取得
40+
func GetUsedIPAddressesInMGW(accessToken string, accessTokenSecret string, zone string, mgwID string) (map[string]struct{}, error) {
41+
// BASIC認証
42+
headers := createHeadersWithBasicAuth(accessToken, accessTokenSecret)
43+
3644
// モバイルゲートウェイ配下のSIMを全件取得するための URL の組み立て
3745
baseURL := fmt.Sprintf("https://secure.sakura.ad.jp/cloud/zone/%s/api/cloud/1.1/appliance/%s/mobilegateway/sims", zone, mgwID)
3846
queryParams := url.Values{}
@@ -152,3 +160,278 @@ func incrementIP(ip net.IP) {
152160
}
153161
}
154162
}
163+
164+
type SimRegisterInfo struct {
165+
ICCID string
166+
PassCode string
167+
}
168+
169+
// SIM作成APIのレスポンス
170+
type SimCreateAPIResponse struct {
171+
CommonServiceItem struct {
172+
ID string `json:"ID"`
173+
}
174+
Success bool `json:"Success"`
175+
IsOK bool `json:"is_ok"`
176+
}
177+
178+
// APIのis_okレスポンス
179+
type SimApiIsOkResponse struct {
180+
IsOK bool `json:"is_ok"`
181+
}
182+
183+
// APIのis_fatalレスポンス
184+
type SimApiIsFatalResponse struct {
185+
IsFatal bool `json:"is_fatal"`
186+
Serial string `json:"serial"`
187+
Status string `json:"status"`
188+
ErrorCode string `json:"error_code"`
189+
ErrorMsg string `json:"error_msg"`
190+
}
191+
192+
// SIMの作成
193+
func createSim(accessToken string, accessTokenSecret string, simID string, simPasscode string) (string, error) {
194+
// BASIC認証
195+
headers := createHeadersWithBasicAuth(accessToken, accessTokenSecret)
196+
197+
// SIM作成リクエストの組み立て
198+
baseURL := "https://secure.sakura.ad.jp/cloud/zone/is1a/api/cloud/1.1/commonserviceitem"
199+
body := fmt.Sprintf(`
200+
{
201+
"CommonServiceItem": {
202+
"Name": "%s",
203+
"Status": {
204+
"ICCID": "%s"
205+
},
206+
"Remark": {
207+
"PassCode": "%s"
208+
},
209+
"Provider": {
210+
"Class": "sim"
211+
}
212+
}
213+
}`, simID, simID, simPasscode)
214+
bytesBody := []byte(body)
215+
bufBody := bytes.NewBuffer(bytesBody)
216+
217+
// リクエスト送信
218+
req, err := http.NewRequest("POST", baseURL, bufBody)
219+
if err != nil {
220+
return "", fmt.Errorf("HTTPクライアントの初期化に失敗しました...%s", err.Error())
221+
}
222+
req.Header = headers
223+
224+
client := &http.Client{}
225+
resp, err := client.Do(req)
226+
if err != nil {
227+
return "", fmt.Errorf("HTTPクライアントの実行に失敗しました...%s", err.Error())
228+
}
229+
defer resp.Body.Close()
230+
231+
// レスポンスの確認
232+
respBody, err := io.ReadAll(resp.Body)
233+
if err != nil {
234+
return "", fmt.Errorf("SIM作成のレスポンスの読み込みに失敗しました...%s", err.Error())
235+
}
236+
if resp.StatusCode != http.StatusCreated {
237+
// 認証情報の間違い
238+
if resp.StatusCode == http.StatusUnauthorized {
239+
return "", fmt.Errorf("アクセストークン、アクセストークンシークレットを確認してください。SIM作成に失敗しました")
240+
}
241+
242+
// 作成済み
243+
if resp.StatusCode == http.StatusConflict {
244+
// エラーにしないけどリソースIDは空
245+
return "", nil
246+
}
247+
248+
// 認証エラーじゃない場合
249+
var apiFatalRes SimApiIsFatalResponse
250+
err = json.Unmarshal(respBody, &apiFatalRes)
251+
if err != nil {
252+
return "", fmt.Errorf("SIM作成のレスポンスのパースに失敗しました...%s", err.Error())
253+
}
254+
// レスポンスに含まれているエラーメッセージを返す
255+
return "", fmt.Errorf("%s: (%s)%s", apiFatalRes.Serial, apiFatalRes.Status, apiFatalRes.ErrorMsg)
256+
}
257+
258+
var apiResponse SimCreateAPIResponse
259+
err = json.Unmarshal(respBody, &apiResponse)
260+
if err != nil {
261+
return "", fmt.Errorf("SIM作成のレスポンスのパースに失敗しました...%s", err.Error())
262+
}
263+
264+
// SIMのリソースIDを返す
265+
return apiResponse.CommonServiceItem.ID, nil
266+
}
267+
268+
// SIMのIPアドレス設定
269+
func assignIPAddressToSim(accessToken string, accessTokenSecret string, simID string, ipAddress string) error {
270+
// BASIC認証
271+
headers := createHeadersWithBasicAuth(accessToken, accessTokenSecret)
272+
273+
// 『SIMのIPアドレス指定』のリクエストの組み立て
274+
baseURL := fmt.Sprintf("https://secure.sakura.ad.jp/cloud/zone/is1a/api/cloud/1.1/commonserviceitem/%s/sim/ip", simID)
275+
body := fmt.Sprintf(`{
276+
"sim": {
277+
"ip": "%s"
278+
}}`, ipAddress)
279+
bytesBody := []byte(body)
280+
bufBody := bytes.NewBuffer(bytesBody)
281+
282+
// リクエスト送信
283+
req, err := http.NewRequest("PUT", baseURL, bufBody)
284+
if err != nil {
285+
return fmt.Errorf("HTTPクライアントの初期化に失敗しました...%s", err.Error())
286+
}
287+
req.Header = headers
288+
289+
client := &http.Client{}
290+
resp, err := client.Do(req)
291+
if err != nil {
292+
return fmt.Errorf("HTTPクライアントの実行に失敗しました...%s", err.Error())
293+
}
294+
defer resp.Body.Close()
295+
296+
// レスポンスの確認
297+
respBody, err := io.ReadAll(resp.Body)
298+
if err != nil {
299+
return fmt.Errorf("SIMのIPアドレス設定のレスポンスの読み込みに失敗しました...%s", err.Error())
300+
}
301+
if resp.StatusCode != http.StatusOK {
302+
// 認証情報の間違い
303+
if resp.StatusCode == http.StatusUnauthorized {
304+
return fmt.Errorf("アクセストークン、アクセストークンシークレットを確認してください。SIMのIPアドレス設定に失敗しました")
305+
}
306+
// 認証エラーじゃない場合
307+
var apiFatalRes SimApiIsFatalResponse
308+
err = json.Unmarshal(respBody, &apiFatalRes)
309+
if err != nil {
310+
return fmt.Errorf("SIMのIPアドレス設定のレスポンスのパースに失敗しました...%s", err.Error())
311+
}
312+
// レスポンスに含まれているエラーメッセージを返す
313+
return fmt.Errorf("%s: (%s)%s", apiFatalRes.Serial, apiFatalRes.Status, apiFatalRes.ErrorMsg)
314+
}
315+
316+
// 設定の成否を返す
317+
var apiOkRes SimApiIsOkResponse
318+
err = json.Unmarshal(respBody, &apiOkRes)
319+
if err != nil {
320+
return fmt.Errorf("SIMのIPアドレス設定のレスポンスのパースに失敗しました...%s", err.Error())
321+
}
322+
323+
if !apiOkRes.IsOK {
324+
return fmt.Errorf("SIMのIPアドレス設定が失敗しました")
325+
}
326+
327+
return nil
328+
}
329+
330+
// モバイルゲートウェイにSIMを登録
331+
func assignSimToMgw(accessToken string, accessTokenSecret string, zone string, mgwID string, simID string) error {
332+
// BASIC認証
333+
headers := createHeadersWithBasicAuth(accessToken, accessTokenSecret)
334+
335+
// 『モバイルゲートウェイにSIMを登録』のリクエストの組み立て
336+
baseURL := fmt.Sprintf("https://secure.sakura.ad.jp/cloud/zone/%s/api/cloud/1.1/appliance/%s/mobilegateway/sims", zone, mgwID)
337+
body := fmt.Sprintf(`{
338+
"sim": {
339+
"resource_id": "%s"
340+
}}`, simID)
341+
bytesBody := []byte(body)
342+
bufBody := bytes.NewBuffer(bytesBody)
343+
344+
// リクエスト送信
345+
req, err := http.NewRequest("POST", baseURL, bufBody)
346+
if err != nil {
347+
return fmt.Errorf("HTTPクライアントの初期化に失敗しました...%s", err.Error())
348+
}
349+
req.Header = headers
350+
351+
client := &http.Client{}
352+
resp, err := client.Do(req)
353+
if err != nil {
354+
return fmt.Errorf("HTTPクライアントの実行に失敗しました...%s", err.Error())
355+
}
356+
defer resp.Body.Close()
357+
358+
// レスポンスの確認
359+
respBody, err := io.ReadAll(resp.Body)
360+
if err != nil {
361+
return fmt.Errorf("モバイルゲートウェイにSIMを登録のレスポンスの読み込みに失敗しました...%s", err.Error())
362+
}
363+
364+
if resp.StatusCode != http.StatusOK {
365+
// 認証情報の間違い
366+
if resp.StatusCode == http.StatusUnauthorized {
367+
return fmt.Errorf("認証に失敗しました...%s", err.Error())
368+
}
369+
// 認証エラーじゃない場合
370+
var apiFatalRes SimApiIsFatalResponse
371+
err = json.Unmarshal(respBody, &apiFatalRes)
372+
if err != nil {
373+
return fmt.Errorf("モバイルゲートウェイにSIMを登録のレスポンスのパースに失敗しました...%s", err.Error())
374+
}
375+
// レスポンスに含まれているエラーメッセージを返す
376+
return fmt.Errorf("%s: (%s)%s", apiFatalRes.Serial, apiFatalRes.Status, apiFatalRes.ErrorMsg)
377+
}
378+
379+
// 設定の成否を返す
380+
var apiOkRes SimApiIsOkResponse
381+
err = json.Unmarshal(respBody, &apiOkRes)
382+
if err != nil {
383+
return fmt.Errorf("モバイルゲートウェイにSIMを登録のレスポンスのパースに失敗しました...%s", err.Error())
384+
}
385+
386+
if !apiOkRes.IsOK {
387+
return fmt.Errorf("SIMのIPアドレス設定が失敗しました")
388+
}
389+
390+
return nil
391+
}
392+
393+
//  リスト内のSIMを登録する
394+
func RegisterSimFromList(accessToken string, accessTokenSecret string, zone string, mgwID string, simList []SimRegisterInfo, ipList []string) error {
395+
396+
if len(simList) > len(ipList) {
397+
return fmt.Errorf("登録対象のSIM %d 枚に対して割り当て可能なIPアドレスが %d 個しかありません", len(simList), len(ipList))
398+
}
399+
400+
ipListIndex := 0
401+
for _, sim := range simList {
402+
// SIMを作成
403+
fmt.Printf("SIM登録(ICCID: %s)", sim.ICCID)
404+
simResourceId, err := createSim(accessToken, accessTokenSecret, sim.ICCID, sim.PassCode)
405+
if err != nil {
406+
fmt.Printf("[FAILED]\n")
407+
return err
408+
}
409+
if simResourceId == "" {
410+
//登録済みだからスキップ
411+
fmt.Printf("[SKIP]\n")
412+
continue
413+
}
414+
fmt.Printf("[OK]")
415+
416+
// MGWにSIMを登録
417+
fmt.Printf(", モバイルゲートウェイに追加")
418+
err = assignSimToMgw(accessToken, accessTokenSecret, zone, mgwID, simResourceId)
419+
if err != nil {
420+
fmt.Printf("[FAILED]\n")
421+
return err
422+
}
423+
fmt.Printf("[OK]")
424+
425+
// SIMにIPアドレスを設定
426+
fmt.Printf(", IPアドレスを設定(%s)", ipList[ipListIndex])
427+
err = assignIPAddressToSim(accessToken, accessTokenSecret, simResourceId, ipList[ipListIndex])
428+
if err != nil {
429+
fmt.Printf("[FAILED]\n")
430+
return err
431+
}
432+
fmt.Printf("[OK]\n")
433+
434+
ipListIndex++
435+
}
436+
return nil
437+
}

register_sim/Makefile

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
APP_NAME := register_sim
2+
3+
VERSION ?= latest
4+
5+
BINARIES := \
6+
bin/$(APP_NAME)-$(VERSION)-linux-amd64 \
7+
bin/$(APP_NAME)-$(VERSION)-linux-arm64 \
8+
bin/$(APP_NAME)-$(VERSION)-darwin-amd64 \
9+
bin/$(APP_NAME)-$(VERSION)-darwin-arm64 \
10+
bin/$(APP_NAME)-$(VERSION)-windows-amd64.exe \
11+
bin/$(APP_NAME)-$(VERSION)-windows-arm64.exe
12+
13+
all: $(BINARIES)
14+
15+
bin/$(APP_NAME)-$(VERSION)-linux-amd64:
16+
GOOS=linux GOARCH=amd64 go build -o $@
17+
18+
bin/$(APP_NAME)-$(VERSION)-linux-arm64:
19+
GOOS=linux GOARCH=arm64 go build -o $@
20+
21+
bin/$(APP_NAME)-$(VERSION)-darwin-amd64:
22+
GOOS=darwin GOARCH=amd64 go build -o $@
23+
24+
bin/$(APP_NAME)-$(VERSION)-darwin-arm64:
25+
GOOS=darwin GOARCH=arm64 go build -o $@
26+
27+
bin/$(APP_NAME)-$(VERSION)-windows-amd64.exe:
28+
GOOS=windows GOARCH=amd64 go build -o $@
29+
30+
bin/$(APP_NAME)-$(VERSION)-windows-arm64.exe:
31+
GOOS=windows GOARCH=arm64 go build -o $@
32+
33+
zip: all
34+
zip -j bin/$(APP_NAME)-$(VERSION)-all.zip $(BINARIES)
35+
36+
clean:
37+
rm -r bin
38+
39+
.PHONY: all clean

0 commit comments

Comments
 (0)