diff --git a/cmd/ptah-agent/ptah-agent.go b/cmd/ptah-agent/ptah-agent.go index fe63f0c..4f8fb2d 100644 --- a/cmd/ptah-agent/ptah-agent.go +++ b/cmd/ptah-agent/ptah-agent.go @@ -15,9 +15,9 @@ var version string = "dev" func main() { baseUrl := os.Getenv("PTAH_BASE_URL") if baseUrl == "" { - log.Println("PTAH_BASE_URL is not set, using https://app.ptah.sh") + log.Println("PTAH_BASE_URL is not set, using https://ctl.ptah.sh") - baseUrl = "https://app.ptah.sh" + baseUrl = "https://ctl.ptah.sh" } baseUrl = strings.Trim(baseUrl, "/") diff --git a/go.mod b/go.mod index b84a13e..82531fd 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/ptah-sh/ptah-agent go 1.22.4 require ( + github.com/aws/aws-sdk-go-v2/config v1.27.26 + github.com/aws/aws-sdk-go-v2/credentials v1.17.26 + github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 github.com/docker/docker v27.0.0+incompatible github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.9.0 @@ -10,6 +13,21 @@ require ( require ( github.com/Microsoft/go-winio v0.4.14 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect + github.com/aws/smithy-go v1.20.3 // indirect github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect diff --git a/go.sum b/go.sum index 1792c49..e53bcbd 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,42 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= +github.com/aws/aws-sdk-go-v2/config v1.27.26 h1:T1kAefbKuNum/AbShMsZEro6eRkeOT8YILfE9wyjAYQ= +github.com/aws/aws-sdk-go-v2/config v1.27.26/go.mod h1:ivWHkAWFrw/nxty5Fku7soTIVdqZaZ7dw+tc5iGW3GA= +github.com/aws/aws-sdk-go-v2/credentials v1.17.26 h1:tsm8g/nJxi8+/7XyJJcP2dLrnK/5rkFp6+i2nhmz5fk= +github.com/aws/aws-sdk-go-v2/credentials v1.17.26/go.mod h1:3vAM49zkIa3q8WT6o9Ve5Z0vdByDMwmdScO0zvThTgI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 h1:Fv1vD2L65Jnp5QRsdiM64JvUM4Xe+E0JyVsRQKv6IeA= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.3/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= diff --git a/install.sh b/install.sh index f00e843..30deb9c 100644 --- a/install.sh +++ b/install.sh @@ -83,7 +83,7 @@ if [ -z "$PTAH_TOKEN" ]; then fi PTAH_TOKEN="${PTAH_TOKEN:-$PTAH_TOKEN}" -PTAH_BASE_URL="${PTAH_BASE_URL:-"https://app.ptah.sh"}" +PTAH_BASE_URL="${PTAH_BASE_URL:-"https://ctl.ptah.sh"}" echo "User: $USER:$GROUP" @@ -207,4 +207,4 @@ systemctl daemon-reload systemctl enable ptah-agent systemctl start ptah-agent -echo "Installation completed. Please check status on https://app.ptah.sh." +echo "Installation completed. Please check status on https://ctl.ptah.sh." diff --git a/internal/app/ptah-agent/parse_task.go b/internal/app/ptah-agent/parse_task.go index e0860c2..0ca3ac6 100644 --- a/internal/app/ptah-agent/parse_task.go +++ b/internal/app/ptah-agent/parse_task.go @@ -39,6 +39,10 @@ func parseTask(taskType int, payload string) (interface{}, error) { return unmarshalTask(payload, &ptahClient.CheckRegistryAuthReq{}) case 14: return unmarshalTask(payload, &ptahClient.PullImageReq{}) + case 15: + return unmarshalTask(payload, &ptahClient.CreateS3StorageReq{}) + case 16: + return unmarshalTask(payload, &ptahClient.CheckS3StorageReq{}) default: return nil, fmt.Errorf("parse task: unknown task type %d", taskType) } diff --git a/internal/app/ptah-agent/s3.go b/internal/app/ptah-agent/s3.go new file mode 100644 index 0000000..e84371a --- /dev/null +++ b/internal/app/ptah-agent/s3.go @@ -0,0 +1,116 @@ +package ptah_agent + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + awsConfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" + smithyendpoints "github.com/aws/smithy-go/endpoints" + t "github.com/ptah-sh/ptah-agent/internal/pkg/ptah-client" + "io" + "net/url" + path2 "path" + "strings" +) + +func (e *taskExecutor) createS3Storage(ctx context.Context, req *t.CreateS3StorageReq) (*t.CreateS3StorageRes, error) { + var res t.CreateS3StorageRes + + if req.S3StorageSpec.AccessKey == "" || req.S3StorageSpec.SecretKey == "" { + if req.PrevConfigName == "" { + return nil, fmt.Errorf("create s3 storage: prev config name is empty - empty credentials") + } + + prev, err := e.getConfigByName(ctx, req.PrevConfigName) + if err != nil { + return nil, err + } + + var prevSpec t.S3StorageSpec + err = json.Unmarshal(prev.Spec.Data, &prevSpec) + if err != nil { + return nil, fmt.Errorf("create s3 storage: unmarshal prev config: %w", err) + } + + req.S3StorageSpec.AccessKey = prevSpec.AccessKey + req.S3StorageSpec.SecretKey = prevSpec.SecretKey + } + + data, err := json.Marshal(req.S3StorageSpec) + if err != nil { + return nil, err + } + + req.SwarmConfigSpec.Data = data + + config, err := e.docker.ConfigCreate(ctx, req.SwarmConfigSpec) + if err != nil { + return nil, err + } + + res.Docker.ID = config.ID + + return &res, nil +} + +func (e *taskExecutor) checkS3Storage(ctx context.Context, req *t.CheckS3StorageReq) (*t.CheckS3StorageRes, error) { + err := e.uploadToS3(ctx, ".check-access", bytes.NewReader([]byte("https://ptah.sh")), req.S3StorageConfigName) + if err != nil { + return nil, err + } + + return &t.CheckS3StorageRes{}, nil +} + +type staticResolver struct { + URL string +} + +func (r *staticResolver) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) (smithyendpoints.Endpoint, error) { + uri, err := url.Parse(r.URL) + if err != nil { + return smithyendpoints.Endpoint{}, err + } + + return smithyendpoints.Endpoint{ + URI: *uri, + }, nil +} + +func (e *taskExecutor) uploadToS3(ctx context.Context, path string, body io.Reader, s3ConfigName string) error { + credentialsConfig, err := e.getConfigByName(ctx, s3ConfigName) + if err != nil { + return err + } + + var s3StorageSpec t.S3StorageSpec + err = json.Unmarshal(credentialsConfig.Spec.Data, &s3StorageSpec) + if err != nil { + return err + } + + credentialsProvider := credentials.NewStaticCredentialsProvider(s3StorageSpec.AccessKey, s3StorageSpec.SecretKey, "") + config, err := awsConfig.LoadDefaultConfig(ctx, awsConfig.WithRegion(s3StorageSpec.Region), awsConfig.WithCredentialsProvider(credentialsProvider)) + if err != nil { + return err + } + + client := s3.NewFromConfig(config, s3.WithEndpointResolverV2(&staticResolver{ + URL: s3StorageSpec.Endpoint, + })) + + //expires := time.Now().Add(time.Duration(30) * time.Second).UTC() + filePath := strings.Trim(path2.Join(s3StorageSpec.PathPrefix, path), "/") + + _, err = client.PutObject(ctx, &s3.PutObjectInput{ + Bucket: &s3StorageSpec.Bucket, + Key: &filePath, + Body: body, + //Expires: &expires, + }) + + return err +} diff --git a/internal/app/ptah-agent/task_executor.go b/internal/app/ptah-agent/task_executor.go index 40e7f7e..bb72ed2 100644 --- a/internal/app/ptah-agent/task_executor.go +++ b/internal/app/ptah-agent/task_executor.go @@ -50,6 +50,10 @@ func (e *taskExecutor) executeTask(ctx context.Context, task interface{}) (inter return e.checkRegistryAuth(ctx, task.(*t.CheckRegistryAuthReq)) case *t.PullImageReq: return e.pullImage(ctx, task.(*t.PullImageReq)) + case *t.CreateS3StorageReq: + return e.createS3Storage(ctx, task.(*t.CreateS3StorageReq)) + case *t.CheckS3StorageReq: + return e.checkS3Storage(ctx, task.(*t.CheckS3StorageReq)) default: return nil, fmt.Errorf("execute task: unknown task type %T", task) } diff --git a/internal/pkg/ptah-client/task_types.go b/internal/pkg/ptah-client/task_types.go index f308ce2..9552cd9 100644 --- a/internal/pkg/ptah-client/task_types.go +++ b/internal/pkg/ptah-client/task_types.go @@ -164,3 +164,29 @@ type PullImageReq struct { type PullImageRes struct { Output []string `json:"output"` } + +type S3StorageSpec struct { + Endpoint string + AccessKey string + SecretKey string + Region string + Bucket string + PathPrefix string +} + +type CreateS3StorageReq struct { + PrevConfigName string + S3StorageSpec S3StorageSpec + SwarmConfigSpec swarm.ConfigSpec +} + +type CreateS3StorageRes struct { + dockerIdRes +} + +type CheckS3StorageReq struct { + S3StorageConfigName string +} + +type CheckS3StorageRes struct { +}