Go:bufio.NewScanner 在 MacOS 上工作,但在 Windows 上被跳过。

huangapple go评论74阅读模式
英文:

Go: bufio.NewScanner works on MacOS but is skipped on Windows

问题

我有这段 Go 代码,大部分是从这里复制来的:

fmt.Println("请输入您的角色:")
fmt.Scanf("%s", &roleName)

flag.StringVar(&startURL, "start-url", "", "AWS SSO 启动 URL")
flag.StringVar(&accountID, "account-id", "", "要获取凭证的 AWS 账户 ID")
flag.Parse()
if startURL == "" || accountID == "" || roleName == "" {
	flag.Usage()
	os.Exit(1)
}

cfg := aws.Config{Region: "eu-west-1"}

// 创建 SSO OIDC 客户端以触发登录流程
ssooidcClient := ssooidc.NewFromConfig(cfg)

// 注册触发登录流程的客户端
register, err := ssooidcClient.RegisterClient(context.TODO(), &ssooidc.RegisterClientInput{
	ClientName: aws.String("sample-client-name"),
	ClientType: aws.String("public"),
	Scopes:     []string{"sso-portal:*"},
})
if err != nil {
	fmt.Println(err)
}
// 使用客户端注册响应授权设备
deviceAuth, err := ssooidcClient.StartDeviceAuthorization(context.TODO(), &ssooidc.StartDeviceAuthorizationInput{
	ClientId:     register.ClientId,
	ClientSecret: register.ClientSecret,
	StartUrl:     aws.String(startURL),
})
if err != nil {
	fmt.Println(err)
}
// 触发 OIDC 登录。打开浏览器进行登录。登录完成后关闭标签页。按回车键继续
url := aws.ToString(deviceAuth.VerificationUriComplete)
fmt.Printf("如果浏览器没有自动打开,请手动打开链接:\n%v\n", url)
err = browser.OpenURL(url)
if err != nil {
	fmt.Println(err)
}

fmt.Println("登录完成后请按回车键")
// 这些行在 Windows 上被跳过
// 也尝试过 bufio.NewReader(os.Stdin).ReadBytes('\n')
// 和 fmt.Scanf("%s", &test)
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
if scanner.Err() != nil {
	fmt.Println()
}

token, err := ssooidcClient.CreateToken(context.TODO(), &ssooidc.CreateTokenInput{
	ClientId:     register.ClientId,
	ClientSecret: register.ClientSecret,
	DeviceCode:   deviceAuth.DeviceCode,
	GrantType:    aws.String("urn:ietf:params:oauth:grant-type:device_code"),
})

在 macOS 上,程序等待用户输入,因此 SSO 登录正常工作,但在 Windows 上,它被跳过,用户没有时间接受 AWS 端的登录,因此程序失败。此外,第一个提示要求用户输入角色的部分正常工作,所以我真的不明白为什么第二个部分会被跳过?

我使用以下命令从 macOS 机器构建二进制文件:

GOOS=darwin go build
GOOS=windows go build
英文:

I have this piece of go code, mostly taken from here:

fmt.Println("Please enter your role: ")
fmt.Scanf("%s", &roleName)

flag.StringVar(&startURL, "start-url", "", "AWS SSO Start URL")
flag.StringVar(&accountID, "account-id", "", "AWS Account ID to fetch credentials for")
flag.Parse()
if startURL == "" || accountID == "" || roleName == "" {
	flag.Usage()
	os.Exit(1)
}

cfg := aws.Config{Region: "eu-west-1"}

// create sso oidc client to trigger login flow
ssooidcClient := ssooidc.NewFromConfig(cfg)

// register your client which is triggering the login flow
register, err := ssooidcClient.RegisterClient(context.TODO(), &ssooidc.RegisterClientInput{
	ClientName: aws.String("sample-client-name"),
	ClientType: aws.String("public"),
	Scopes:     []string{"sso-portal:*"},
})
if err != nil {
	fmt.Println(err)
}
// authorize your device using the client registration response
deviceAuth, err := ssooidcClient.StartDeviceAuthorization(context.TODO(), &ssooidc.StartDeviceAuthorizationInput{
	ClientId:     register.ClientId,
	ClientSecret: register.ClientSecret,
	StartUrl:     aws.String(startURL),
})
if err != nil {
	fmt.Println(err)
}
// trigger OIDC login. open browser to login. close tab once login is done. press enter to continue
url := aws.ToString(deviceAuth.VerificationUriComplete)
fmt.Printf("If browser is not opened automatically, please open link:\n%v\n", url)
err = browser.OpenURL(url)
if err != nil {
	fmt.Println(err)
}

fmt.Println("Press ENTER key once login is done")
// These lines get skipped on Windows
// also tried bufio.NewReader(os.Stdin).ReadBytes('\n')
// and fmt.Scanf("%s", &test)
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
if scanner.Err() != nil {
	fmt.Println()
}

token, err := ssooidcClient.CreateToken(context.TODO(), &ssooidc.CreateTokenInput{
	ClientId:     register.ClientId,
	ClientSecret: register.ClientSecret,
	DeviceCode:   deviceAuth.DeviceCode,
	GrantType:    aws.String("urn:ietf:params:oauth:grant-type:device_code"),
})

While on MacOS, the program waits for user input and hence the SSO login works perfectly, on Windows it gets skipped and the user does not have time to accept the login on AWS side, so the program fails. Moreover, the first prompt that asks for the users' role works correctly, so I really don't understand the second one just gets skipped ?
I use these commands to build the binary, from a MacOS machine:

GOOS=darwin go build
GOOS=windows go build

答案1

得分: 3

调用fmt.Scanf("%s", &roleName)在读取令牌后的第一个空格字符后返回。

Windows上的行终止符是\r\n。fmt.Scanf调用在读取\r后返回。\n保留在stdin中。后续对scanner.Scan()的调用读取stdin中剩余的\n并立即返回。

其他系统上的行终止符是\n。调用fmt.Scanf在读取整个行终止符后返回。对scanner.Scan()的调用等待用户键入另一个行终止符。

一种修复方法是对所有输入使用scanner:

scanner := bufio.NewScanner(os.Stdin)
fmt.Println("请输入您的角色:")
if !scanner.Scan() {
// 处理EOF
}
roleName = strings.TrimSpace(scanner.Text())

fmt.Println("登录完成后请按ENTER键")
scanner.Scan()

英文:

The call fmt.Scanf("%s", &roleName) returns after reading the first whitespace character after the token.

The line terminator on Windows is \r\n. The fmt.Scanf call returns after reading the \r. The \n remains in stdin. The later call to scanner.Scan() reads the remaining \n in stdin and returns immediately.

The line terminator on other systems is \n. The call fmt.Scanf returns after reading the entire line terminator. The call to scanner.Scan() waits for the user to type another line terminator.

One fix is to use the scanner for all input:

scanner := bufio.NewScanner(os.Stdin)
fmt.Println("Please enter your role: ")
if !scanner.Scan() {
// handle EOF
}
roleName = strings.TrimSpace(scanner.Text())
…
fmt.Println("Press ENTER key once login is done")
scanner.Scan()
…

huangapple
  • 本文由 发表于 2022年6月16日 03:36:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/72636957.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定