Unverified Commit 2a694d90 authored by kakaZhou719's avatar kakaZhou719 Committed by GitHub
Browse files

fix ci lint inspection (#53)


* fix ci lint

* update ci_lint

* golangci-lint two

* fix ci lint

* fix inspect lint

* fix ci lint error after add new plugins

* fix ci lint error after add new plugins

* use version v1.39.0 gfor golangci-lint
Co-authored-by: default avatarwb-hjh933779 <wb-hjh933779@alibaba-inc.com>
Co-authored-by: default avatartingxuan233 <stx01023092@alibaba-inc.com>
Showing with 144 additions and 141 deletions
+144 -141
......@@ -26,7 +26,7 @@ jobs:
ref: ${{ github.ref }}
path: src/github.com/alibaba/sealer
- name: Install go ci lint
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.35.2
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.39.0
- name: Run Linter
run: golangci-lint run -v
......
......@@ -45,4 +45,4 @@ issues:
# https://github.com/golangci/golangci/wiki/Configuration
service:
# use the fixed version to not introduce new linters unexpectedly
golangci-lint-version: 1.35.2
golangci-lint-version: 1.39.0
......@@ -6,6 +6,8 @@ import (
"path/filepath"
"sync"
"github.com/pkg/errors"
"github.com/alibaba/sealer/logger"
"github.com/alibaba/sealer/common"
......@@ -88,7 +90,9 @@ func (f *FileSystem) UnMount(cluster *v1.Cluster) error {
func mountRootfs(ipList []string, target string, cluster *v1.Cluster) error {
SSH := ssh.NewSSHByCluster(cluster)
ssh.WaitSSHReady(SSH, ipList...)
if err := ssh.WaitSSHReady(SSH, ipList...); err != nil {
return errors.Wrap(err, "check for node ssh service time out")
}
var wg sync.WaitGroup
var flag bool
var mutex sync.Mutex
......
......@@ -75,10 +75,7 @@ func GetClusterFileFromBaseImage(imageName string) string {
mountTarget, _ := utils.MkTmpdir()
mountUpper, _ := utils.MkTmpdir()
defer func() {
err := utils.CleanDirs(mountTarget, mountUpper)
if err != nil {
logger.Warn(err)
}
utils.CleanDirs(mountTarget, mountUpper)
}()
if err := NewImageService().PullIfNotExist(imageName); err != nil {
......
package utils
import "strings"
const (
Latest = "latest"
)
// image name would not contain scheme like http https
func imageHostName(imageName string) string {
/*func imageHostName(imageName string) string {
//TODO strengthen the image host verification
ind := strings.IndexRune(imageName, '/')
if ind >= 0 && strings.ContainsAny(imageName[0:ind], ".:") {
return imageName[0:ind]
}
return ""
}
}*/
// input: urlImageName could be like "***.com/k8s:v1.1" or "k8s:v1.1"
// output: like "k8s:v1.1"
func repoAndTag(imageName string) (string, string) {
/*func repoAndTag(imageName string) (string, string) {
newImageName := strings.TrimPrefix(
strings.TrimPrefix(imageName, imageHostName(imageName)),
"/")
......@@ -29,15 +27,15 @@ func repoAndTag(imageName string) (string, string) {
tag = splits[1]
}
return repo, tag
}
}*/
// input: urlImageName could be like "***.com/k8s:v1.1" or "k8s:v1.1"
// output: like "***.com/k8s"
func rawRepo(imageName string) string {
/*func rawRepo(imageName string) string {
repo := imageName
splits := strings.Split(imageName, ":")
if len(splits) == 2 {
repo = splits[0]
}
return repo
}
}*/
......@@ -24,8 +24,8 @@ type Instance struct {
}
type EcsManager struct {
config Config
client *ecs.Client
/* config Config
client *ecs.Client*/
}
func (a *AliProvider) RetryEcsRequest(request requests.AcsRequest, response responses.AcsResponse) error {
......
package logger
import (
"encoding/json"
"runtime"
"github.com/alibaba/sealer/common"
)
func Cfg(level int, logFIle string) {
/*func Cfg(level int, logFIle string) {
config := logConfig{
TimeFormat: "15:04:05",
Console: &consoleLogger{
......@@ -28,7 +21,7 @@ func Cfg(level int, logFIle string) {
cfg, _ := json.Marshal(config)
SetLogger(string(cfg))
SetLogPath(true)
}
}*/
// "File": { // 文件日志配置
// "filename": "app.log", // 初始日志文件名
......
......@@ -99,7 +99,11 @@ func (c *connLogger) connect() error {
}
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
err = tcpConn.SetKeepAlive(true)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to set tcp keep alive :%v\n", err)
continue
}
}
c.innerWriter = conn
return nil
......
......@@ -43,9 +43,6 @@ func (c *consoleLogger) Init(jsonConfig string) error {
if len(jsonConfig) == 0 {
return nil
}
if jsonConfig != "{}" {
//fmt.Fprintf(os.Stdout, "consoleLogger Init:%s\n", jsonConfig)
}
err := json.Unmarshal([]byte(jsonConfig), c)
if runtime.GOOS == common.WINDOWS {
......
......@@ -124,7 +124,10 @@ func (f *fileLogger) createLogFile() (*os.File, error) {
fd, err := os.OpenFile(f.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
if err == nil {
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
os.Chmod(f.Filename, os.FileMode(perm))
err = os.Chmod(f.Filename, os.FileMode(perm))
if err != nil {
return nil, err
}
}
return fd, err
}
......@@ -247,7 +250,7 @@ RestartLogger:
func (f *fileLogger) deleteOldLog() {
dir := filepath.Dir(f.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
......@@ -266,6 +269,9 @@ func (f *fileLogger) deleteOldLog() {
}
return
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to delete old log error: %v\n", err)
}
}
func (f *fileLogger) Destroy() {
......
......@@ -122,11 +122,16 @@ func TestFileByTime(t *testing.T) {
LogLevel: LevelTrace,
PermitMask: "0660",
}
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
err := fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
if err != nil {
fmt.Println("failed to init file logger")
}
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
fw.dailyOpenDate = fw.dailyOpenTime.Day()
fw.LogWrite(time.Now(), "this is a msg for test", LevelTrace)
err = fw.LogWrite(time.Now(), "this is a msg for test", LevelTrace)
if err != nil {
fmt.Println("failed to write msg to file logger")
}
for _, file := range []string{fn1, fn2} {
_, err := os.Stat(file)
if err != nil {
......
......@@ -130,24 +130,24 @@ func init() {
defaultLogger = NewLogger(3)
}
func (locallog *LocalLogger) SetLogger(adapterName string, configs ...string) error {
locallog.lock.Lock()
defer locallog.lock.Unlock()
func (localLog *LocalLogger) SetLogger(adapterName string, configs ...string) {
localLog.lock.Lock()
defer localLog.lock.Unlock()
if !locallog.init {
locallog.outputs = []*nameLogger{}
locallog.init = true
if !localLog.init {
localLog.outputs = []*nameLogger{}
localLog.init = true
}
config := append(configs, "{}")[0]
var num = -1
var i int
var l *nameLogger
for i, l = range locallog.outputs {
for i, l = range localLog.outputs {
if l.name == adapterName {
if l.config == config {
//配置没有变动,不重新设置
return fmt.Errorf("you have set same config for locallog adaptername %s", adapterName)
fmt.Printf("you have set same config for locallog adaptername %s", adapterName)
}
l.Logger.Destroy()
num = i
......@@ -156,47 +156,44 @@ func (locallog *LocalLogger) SetLogger(adapterName string, configs ...string) er
}
logger, ok := adapters[adapterName]
if !ok {
return fmt.Errorf("unknown adaptername %s (forgotten Register?)", adapterName)
fmt.Printf("unknown adaptername %s (forgotten Register?)", adapterName)
}
err := logger.Init(config)
if err != nil {
fmt.Fprintf(os.Stderr, "logger Init <%s> err:%v, %s output ignore!\n",
adapterName, err, adapterName)
return err
}
if num >= 0 {
locallog.outputs[i] = &nameLogger{name: adapterName, Logger: logger, config: config}
return nil
localLog.outputs[i] = &nameLogger{name: adapterName, Logger: logger, config: config}
}
locallog.outputs = append(locallog.outputs, &nameLogger{name: adapterName, Logger: logger, config: config})
return nil
localLog.outputs = append(localLog.outputs, &nameLogger{name: adapterName, Logger: logger, config: config})
}
func (locallog *LocalLogger) DelLogger(adapterName string) error {
locallog.lock.Lock()
defer locallog.lock.Unlock()
func (localLog *LocalLogger) DelLogger(adapterName string) error {
localLog.lock.Lock()
defer localLog.lock.Unlock()
var outputs []*nameLogger
for _, lg := range locallog.outputs {
for _, lg := range localLog.outputs {
if lg.name == adapterName {
lg.Destroy()
} else {
outputs = append(outputs, lg)
}
}
if len(outputs) == len(locallog.outputs) {
if len(outputs) == len(localLog.outputs) {
return fmt.Errorf("logs: unknown adaptername %s (forgotten Register?)", adapterName)
}
locallog.outputs = outputs
localLog.outputs = outputs
return nil
}
// 设置日志起始路径
func (locallog *LocalLogger) SetLogPath(bPath bool) {
locallog.usePath = bPath
func (localLog *LocalLogger) SetLogPath(bPath bool) {
localLog.usePath = bPath
}
func (locallog *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level int) {
for _, l := range locallog.outputs {
func (localLog *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level int) {
for _, l := range localLog.outputs {
if l.name == AdapterConn {
//网络日志,使用json格式发送,此处使用结构体,用于类似ElasticSearch功能检索
err := l.LogWrite(when, msg, level)
......@@ -208,11 +205,11 @@ func (locallog *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level
strLevel := " [" + msg.Level + "] "
strPath := "[" + msg.Path + "] "
if !locallog.usePath {
if !localLog.usePath {
strPath = ""
}
msgStr := when.Format(locallog.timeFormat) + strLevel + strPath + msg.Content
msgStr := when.Format(localLog.timeFormat) + strLevel + strPath + msg.Content
err := l.LogWrite(when, msgStr, level)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
......@@ -220,9 +217,9 @@ func (locallog *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level
}
}
func (locallog *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
if !locallog.init {
locallog.SetLogger(AdapterConsole)
func (localLog *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}) {
if !localLog.init {
localLog.SetLogger(AdapterConsole)
}
msgSt := new(loginfo)
src := ""
......@@ -231,8 +228,8 @@ func (locallog *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}
}
when := time.Now()
//
if locallog.usePath {
_, file, lineno, ok := runtime.Caller(locallog.callDepth)
if localLog.usePath {
_, file, lineno, ok := runtime.Caller(localLog.callDepth)
var strim = "/"
if ok {
codeArr := strings.Split(file, strim)
......@@ -245,79 +242,77 @@ func (locallog *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}
msgSt.Level = levelPrefix[logLevel]
msgSt.Path = src
msgSt.Content = msg
msgSt.Name = locallog.appName
msgSt.Time = when.Format(locallog.timeFormat)
locallog.writeToLoggers(when, msgSt, logLevel)
return nil
msgSt.Name = localLog.appName
msgSt.Time = when.Format(localLog.timeFormat)
localLog.writeToLoggers(when, msgSt, logLevel)
}
func (locallog *LocalLogger) Fatal(format string, args ...interface{}) {
locallog.Emer("###Exec Panic:"+format, args...)
func (localLog *LocalLogger) Fatal(format string, args ...interface{}) {
localLog.Emer("###Exec Panic:"+format, args...)
os.Exit(1)
}
func (locallog *LocalLogger) Panic(format string, args ...interface{}) {
locallog.Emer("###Exec Panic:"+format, args...)
func (localLog *LocalLogger) Panic(format string, args ...interface{}) {
localLog.Emer("###Exec Panic:"+format, args...)
panic(fmt.Sprintf(format, args...))
}
// Emer Log EMERGENCY level message.
func (locallog *LocalLogger) Emer(format string, v ...interface{}) {
locallog.writeMsg(LevelEmergency, format, v...)
func (localLog *LocalLogger) Emer(format string, v ...interface{}) {
localLog.writeMsg(LevelEmergency, format, v...)
}
// Alert Log ALERT level message.
func (locallog *LocalLogger) Alert(format string, v ...interface{}) {
locallog.writeMsg(LevelAlert, format, v...)
func (localLog *LocalLogger) Alert(format string, v ...interface{}) {
localLog.writeMsg(LevelAlert, format, v...)
}
// Crit Log CRITICAL level message.
func (locallog *LocalLogger) Crit(format string, v ...interface{}) {
locallog.writeMsg(LevelCritical, format, v...)
func (localLog *LocalLogger) Crit(format string, v ...interface{}) {
localLog.writeMsg(LevelCritical, format, v...)
}
// Error Log ERROR level message.
func (locallog *LocalLogger) Error(format string, v ...interface{}) {
locallog.writeMsg(LevelError, format, v...)
func (localLog *LocalLogger) Error(format string, v ...interface{}) {
localLog.writeMsg(LevelError, format, v...)
}
// Warn Log WARNING level message.
func (locallog *LocalLogger) Warn(format string, v ...interface{}) {
locallog.writeMsg(LevelWarning, format, v...)
func (localLog *LocalLogger) Warn(format string, v ...interface{}) {
localLog.writeMsg(LevelWarning, format, v...)
}
// Info Log INFO level message.
func (locallog *LocalLogger) Info(format string, v ...interface{}) {
locallog.writeMsg(LevelInformational, format, v...)
func (localLog *LocalLogger) Info(format string, v ...interface{}) {
localLog.writeMsg(LevelInformational, format, v...)
}
// Debug Log DEBUG level message.
func (locallog *LocalLogger) Debug(format string, v ...interface{}) {
locallog.writeMsg(LevelDebug, format, v...)
func (localLog *LocalLogger) Debug(format string, v ...interface{}) {
localLog.writeMsg(LevelDebug, format, v...)
}
// Trace Log TRAC level message.
func (locallog *LocalLogger) Trace(format string, v ...interface{}) {
locallog.writeMsg(LevelTrace, format, v...)
func (localLog *LocalLogger) Trace(format string, v ...interface{}) {
localLog.writeMsg(LevelTrace, format, v...)
}
func (locallog *LocalLogger) Close() {
for _, l := range locallog.outputs {
func (localLog *LocalLogger) Close() {
for _, l := range localLog.outputs {
l.Destroy()
}
locallog.outputs = nil
localLog.outputs = nil
}
func (locallog *LocalLogger) Reset() {
for _, l := range locallog.outputs {
func (localLog *LocalLogger) Reset() {
for _, l := range localLog.outputs {
l.Destroy()
}
locallog.outputs = nil
localLog.outputs = nil
}
func (locallog *LocalLogger) SetCallDepth(depth int) {
locallog.callDepth = depth
func (localLog *LocalLogger) SetCallDepth(depth int) {
localLog.callDepth = depth
}
// GetlocalLogger returns the defaultLogger
......@@ -335,11 +330,10 @@ func SetLogPath(show bool) {
}
// param 可以是log配置文件名,也可以是log配置内容,默认DEBUG输出到控制台
func SetLogger(param ...string) error {
func SetLogger(param ...string) {
if len(param) == 0 {
//默认只输出到控制台
defaultLogger.SetLogger(AdapterConsole)
return nil
}
c := param[0]
......@@ -351,20 +345,17 @@ func SetLogger(param ...string) error {
if err != nil {
fmt.Fprintf(os.Stderr, "Could not open %s for configure: %s\n", c, err)
os.Exit(1)
return err
}
contents, err := ioutil.ReadAll(fd)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not read %s: %s\n", c, err)
os.Exit(1)
return err
}
err = json.Unmarshal(contents, conf)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not Unmarshal %s: %s\n", contents, err)
os.Exit(1)
return err
}
}
if conf.TimeFormat != "" {
......@@ -382,7 +373,6 @@ func SetLogger(param ...string) error {
conn, _ := json.Marshal(conf.Conn)
defaultLogger.SetLogger(AdapterConn, string(conn))
}
return nil
}
// Painc logs a message at emergency level and panic.
......@@ -459,10 +449,10 @@ func formatLog(f interface{}, v ...interface{}) string {
return fmt.Sprintf(msg, v...)
}
func stringTrim(s string, cut string) string {
/*func stringTrim(s string, cut string) string {
ss := strings.SplitN(s, cut, 2)
if len(ss) == 1 {
return ss[0]
}
return ss[1]
}
}*/
......@@ -3,7 +3,6 @@ package runtime
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
......@@ -232,7 +231,7 @@ func (d *Default) InitCNI() error {
return d.SSH.CmdAsync(d.Masters[0], fmt.Sprintf(RemoteApplyYaml, netYaml))
}
func (d *Default) mountEtcdDisk(targetHosts []string, etcdDisk string) error {
/*func (d *Default) mountEtcdDisk(targetHosts []string, etcdDisk string) error {
if etcdDisk == "" {
logger.Warn("Etcd Disk is not set, etcd now uses root disk which is not recommended due to stability requirement.")
return nil
......@@ -255,7 +254,7 @@ func (d *Default) mountEtcdDisk(targetHosts []string, etcdDisk string) error {
wg.Wait()
return nil
}
}*/
func (d *Default) LinkStaticFiles(nodes []string) error {
var flag bool
......
......@@ -141,7 +141,11 @@ func (d *Default) ReplaceKubeConfigV1991V1992(masters []string) bool {
for _, v := range masters {
ip := utils.GetHostIP(v)
cmd := fmt.Sprintf(RemoteReplaceKubeConfig, KUBESCHEDULERCONFIGFILE, ip, KUBECONTROLLERCONFIGFILE, ip, KUBESCHEDULERCONFIGFILE)
d.SSH.CmdAsync(v, cmd)
err := d.SSH.CmdAsync(v, cmd)
if err != nil {
logger.Info("failed to replace kube config on %s ", v)
return false
}
}
return true
}
......@@ -312,7 +316,7 @@ func (d *Default) joinMasters(masters []string) error {
return nil
}
func (d *Default) joinMastersAsync(masters []string) error {
/*func (d *Default) joinMastersAsync(masters []string) error {
d.SendJoinMasterKubeConfigs(masters)
d.sendNewCertAndKey(masters)
d.sendJoinCPConfig(masters)
......@@ -339,7 +343,7 @@ func (d *Default) joinMastersAsync(masters []string) error {
}
wg.Wait()
return nil
}
}*/
func (d *Default) deleteMasters(masters []string) error {
if len(masters) == 0 {
......
......@@ -30,7 +30,7 @@ func (d *Default) joinNodes(nodes []string) error {
return nil
}
if err := ssh.WaitSSHReady(d.SSH, nodes...); err != nil {
errors.Wrap(err, "join nodes wait for ssh ready time out")
return errors.Wrap(err, "join nodes wait for ssh ready time out")
}
if err := d.GetJoinTokenHashAndKey(); err != nil {
return err
......
......@@ -56,7 +56,10 @@ type Default struct {
func NewDefaultRuntime(cluster *v1.Cluster) Interface {
d := &Default{}
d.initRunner(cluster)
err := d.initRunner(cluster)
if err != nil {
return nil
}
return d
}
......
......@@ -16,9 +16,12 @@ limitations under the License.
package cmd
import (
"os"
"github.com/spf13/cobra"
"github.com/alibaba/sealer/cert"
"github.com/alibaba/sealer/logger"
)
type Flag struct {
......@@ -39,7 +42,11 @@ var certsCmd = &cobra.Command{
Short: "generate kubernetes certes",
Long: `seautil cert --node-ip 192.168.0.2 --node-name master1 --dns-domain aliyun.com --alt-names aliyun.local`,
Run: func(cmd *cobra.Command, args []string) {
cert.GenerateCert(config.CertPath, config.CertEtcdPath, config.AltNames, config.NodeIP, config.NodeName, config.ServiceCIDR, config.DNSDomain)
err := cert.GenerateCert(config.CertPath, config.CertEtcdPath, config.AltNames, config.NodeIP, config.NodeName, config.ServiceCIDR, config.DNSDomain)
if err != nil {
logger.Error(err)
os.Exit(-1)
}
},
}
......
......@@ -59,9 +59,9 @@ func Compress(src, newFolder string, existingFile *os.File) (file *os.File, err
srcPrefix := filepath.ToSlash(src + "/")
err = filepath.Walk(src, func(file string, fi os.FileInfo, err error) error {
// generate tar header
header, err := tar.FileInfoHeader(fi, file)
if err != nil {
return err
header, walkErr := tar.FileInfoHeader(fi, file)
if walkErr != nil {
return walkErr
}
if file != src {
absPath := filepath.ToSlash(file)
......@@ -76,17 +76,17 @@ func Compress(src, newFolder string, existingFile *os.File) (file *os.File, err
}
// write header
if err = tw.WriteHeader(header); err != nil {
return err
if walkErr = tw.WriteHeader(header); walkErr != nil {
return walkErr
}
// if not a dir, write file content
if !fi.IsDir() {
data, err := os.Open(file)
if err != nil {
return err
data, walkErr := os.Open(file)
if walkErr != nil {
return walkErr
}
if _, err = io.Copy(tw, data); err != nil {
return err
if _, walkErr = io.Copy(tw, data); walkErr != nil {
return walkErr
}
}
return nil
......
......@@ -161,25 +161,23 @@ func CleanFile(file *os.File) {
}
}
func CleanDir(dir string) (err error) {
func CleanDir(dir string) {
if dir == "" {
return errors.New("dir name is empty")
logger.Error("clean dir path is empty")
}
err := os.RemoveAll(dir)
if err != nil {
logger.Error("failed to remove dir %s ", dir)
}
err = os.RemoveAll(dir)
return
}
func CleanDirs(dirs ...string) (err error) {
func CleanDirs(dirs ...string) {
if len(dirs) == 0 {
return nil
return
}
for _, dir := range dirs {
err = CleanDir(dir)
if err != nil {
return err
}
CleanDir(dir)
}
return
}
func CleanFiles(file ...string) error {
for _, f := range file {
......
......@@ -5,13 +5,11 @@ import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
)
//DirMD5 count files md5
func DirMD5(dirName string) string {
/*func DirMD5(dirName string) string {
var md5Value []byte
filepath.Walk(dirName, func(path string, info os.FileInfo, err error) error {
if err != nil {
......@@ -30,7 +28,7 @@ func DirMD5(dirName string) string {
})
md5Values := md5.Sum(md5Value)
return hex.EncodeToString(md5Values[:])
}
}*/
func MD5(body []byte) string {
bytes := md5.Sum(body)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment