Situation: I have 2 data pipelines that run on-demand. Pipeline B cannot run until Pipeline A has completed. I'm trying to automate running both pipelines in a single script/program but I'm unsure how to do all of this in Go.
I have some Go code that activates a data pipeline:
func awsActivatePipeline(pipelineID, region string) (*datapipeline.ActivatePipelineOutput, error) {
svc := datapipeline.New(session.New(&aws.Config{Region: aws.String(region)}))
input := &datapipeline.ActivatePipelineInput{
PipelineId: aws.String(pipelineID),
}
result, err := svc.ActivatePipeline(input)
if err != nil {
fmt.Println("error activating pipeline: ", err)
}
fmt.Println(result)
return result, nil
}
After activating, I want to be able to monitor that pipeline and determine when it's finished so that I can run a second pipeline. Similar to the list-runs CLI command but I'm not sure what the corresponding Go function would be.
$ aws datapipeline list-runs --region us-west-2 --pipeline-id df-EXAMPLE
Name Scheduled Start Status
ID Started Ended
---------------------------------------------------------------------------------------------------
1. EC2ResourceObj 2017-09-12T17:49:55 FINISHED
#EC2ResourceObj_2017-09-12T17:49:55 2017-09-12T17:49:58 2017-09-12T17:56:52
2. Installation 2017-09-12T17:49:55 FINISHED
#Installation_#ShellCommandActivityObj_2017-09-12T 2017-09-12T17:49:57 2017-09-12T17:54:09
3. S3OutputLocation 2017-09-12T17:49:55 FINISHED
#S3OutputLocation_2017-09-12T17:49:55 2017-09-12T17:49:58 2017-09-12T17:54:50
4. ShellCommandActivityObj 2017-09-12T17:49:55 FINISHED
#ShellCommandActivityObj_2017-09-12T17:49:55 2017-09-12T17:49:57 2017-09-12T17:54:49
So once all actions are marked 'FINISHED', I want to activate my second pipeline. What's the best way to accomplish this?
FYI in case anyone else comes across this, this is how I resolved this:
Golang AWS API call to describe objects/actions of a data pipeline, returns true if all objects are finished
func awsDescribeObjects(pipelineID, region string, objects []string) bool {
var r Object
var s []string
var f bool
svc := datapipeline.New(session.New(&aws.Config{Region: aws.String(region)}))
input := &datapipeline.DescribeObjectsInput{
PipelineId: aws.String(pipelineID),
ObjectIds: aws.StringSlice(objects),
}
result, err := svc.DescribeObjects(input)
if err != nil {
fmt.Println("error describing pipeline objects: ", err)
f = false
return f
}
//fmt.Println("original result: ", result)
result2 := re.ReplaceAllString(result.String(), `"$1"$2`) //add "" around keys
result3 := re1.ReplaceAllString(result2, `$3$2`) //remove key and string/ref value from fields struct
result4 := strings.Replace(result3, "#", "", -1) //remove # from keys and values
result5 := re2.ReplaceAllString(result4, `$1$3$5$7$9`) //remove "" from timestamps
result6 := re3.ReplaceAllString(result5, `$1,`) // remove {} from fields struct
json.Unmarshal([]byte(result6), &r)
// fmt.Printf("R: %+v\n", r)
p := r.PipelineObjects
// fmt.Printf("P: %+v\n", p)
for i := range p {
for m := range p[i].Fields {
fmt.Printf("%v STATUS: %v\n", p[i].Name, p[i].Fields[m].Status)
s = append(s, p[i].Fields[m].Status)
if p[i].Fields[m].Status != "FINISHED" {
f = false
} else {
f = true
}
}
// fmt.Println("bool: ", f)
}
return f
}
my main go function
func main() {
if *action == "describe" {
obj := strings.Split(*object, ",")
for i := 0; i <= 20; i++ {
f := awsDescribeObjects(*pipeline, *region, obj)
fmt.Printf("%v - Status Check %v - Finished?: %v\n", time.Now(), i, f)
if f == true {
fmt.Println("FINISHED describing pipeline complete")
break
}
time.Sleep(5 * time.Minute)
if i == 20 {
fmt.Println("TIME OUT - describe pipeline timed out, max time reached")
os.Exit(1)
}
}
}
}
Shell script with go executable:
#PIPELINE 1
echo "Starting Pipeline 1..."
echo ./runpipeline.linux -region $REGION1 -pipeline-id $PIPELINEID1 -action activate
echo sleep 1m
echo ./runpipeline.linux -region $REGION1 -pipeline-id $PIPELINEID1 -action describe -object ShellCommandActivityObj
echo "Pipeline 1 complete"
#PIPELINE 2
echo "Starting Pipeline 2..."
echo ./runpipeline.linux -region $REGION2 -pipeline-id $PIPELINEID2 -action activate
echo sleep 1m
echo ./runpipeline.linux -region $REGION2 -pipeline-id $PIPELINEID2 -action describe -object ShellCommandActivityObj,CliActivity
echo "Pipeline 2 complete"
echo "FINISHED"
Related
I need to update a secret with specific value,(the secret contain additional data) my question is how I can update just the value and not all the secret data (I don't want to override the existing data). I mean if the secret have additional values I don’t want to override them just the entry foo
updSec := v1.Secret{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "d-values",
Namespace: "terv”,
},
Immutable: nil,
Data: nil,
StringData: nil,
Type: "Opaque",
}
updSec.Data[“foo”] = newVal
if err := r.Client.Update(ctx, &updSec); err != nil {
return ctrl.Result{}, err
}
The issue is that the secret is already exist and here im creating new object and not sure how to do it right ...I need for secret that called d-values just update the newVal for key foo
update
when trying the code in the answer after I run the
patch, err := yaml.Marshal(updSec)
the data looks like following
and the patch are failed with error, any idea if its related ?
if I try with the c.Client.Update it works but not with Patch but the Patch is the right way as if I've ties before is should keep them..
I don't think you can update a single key using the Update method, but you can certainly do that using Patch instead. Here's an example that uses a StrategicMergePatch; it will replace the key val2 in a secret with the value newval:
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"path/filepath"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
func main() {
var kubeconfig *string
var namespace *string
var secretname *string
namespace = flag.String("namespace", "", "namespace of secret")
secretname = flag.String("name", "", "name of secret")
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
if *namespace == "" {
panic(fmt.Errorf("you must specify a namespace"))
}
if *secretname == "" {
panic(fmt.Errorf("you must specify a secret name"))
}
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
secretClient := clientset.CoreV1().Secrets(*namespace)
ctx := context.TODO()
updSec := v1.Secret{
Data: map[string][]byte{
"val2": []byte("newval"),
},
}
payloadBytes, err := json.Marshal(updSec)
if err != nil {
panic(err)
}
if _, err = secretClient.Patch(ctx, *secretname,
types.StrategicMergePatchType, payloadBytes, metav1.PatchOptions{}); err != nil {
panic(err)
}
// Fetch updated secret
sec, err := secretClient.Get(ctx, *secretname, metav1.GetOptions{})
if err != nil {
panic(err)
}
secJson, err := json.MarshalIndent(sec, "", " ")
if err != nil {
panic(err)
}
fmt.Print(string(secJson))
}
For example, if I create a secret like this:
kubectl create secret generic \
--from-literal val1=key1 \
--from-literal val2=key2 example
And then run the above code like this:
go run main.go -namespace default -name example
The code will output the update secret. Looking at the data section, we see:
"data": {
"val1": "a2V5MQ==",
"val2": "bmV3dmFs"
},
And if we decode val2 we see:
$ kubectl get secret example -o json | jq '.data.val2|#base64d'
"newval"
Using the Operator SDK
If you're working with the Operator SDK, you can use Update if you're first reading the existing value, like this:
// Read the existing secret
secret := &corev1.Secret{}
if err := r.Get(ctx, req.NamespacedName, secret); err != nil {
panic(err)
}
// Check if it needs to be modified
val, ok := secret.Data["val2"]
// If yes, update the secret with a new value and then write
// the entire object back with Update
if !ok || !bytes.Equal(val, []byte("val2")) {
ctxlog.Info("needs update", "secret", secret)
secret.Data["val2"] = []byte("newval")
if err := r.Update(ctx, secret); err != nil {
panic(err)
}
}
You can use the Patch method if you only want to submit a partial update:
if !ok || !bytes.Equal(val, []byte("val2")) {
ctxlog.Info("needs update", "secret", secret)
newVal := corev1.Secret{
Data: map[string][]byte{
"val2": []byte("newval"),
},
}
patch, err := json.Marshal(newVal)
if err != nil {
panic(err)
}
if err := r.Client.Patch(ctx, secret, client.RawPatch(types.StrategicMergePatchType, patch)); err != nil {
panic(err)
}
}
This is pretty much identical to the earlier example. There are examples of using the client.Patch method in the docs, but I'll be honest, I don't find the example very clear.
Thanks in advance :) . I'm using the following code to get metadata from an s3 object after listing all the object in a bucket . But I don't know why it gives the error undefined: s3.HeadObject when running go run listObjects.go -bucket xxxx -prefix xxxx
I tried two solutions: giving the client as the one created from the config and from the context as in this link appears [1]. BUt both gave the same error. Can you give me any clue?
package main
import (
"context"
"flag"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
var (
bucketName string
objectPrefix string
objectDelimiter string
maxKeys int
)
func init() {
flag.StringVar(&bucketName, "bucket", "", "The `name` of the S3 bucket to list objects from.")
flag.StringVar(&objectPrefix, "prefix", "", "The optional `object prefix` of the S3 Object keys to list.")
flag.StringVar(&objectDelimiter, "delimiter", "",
"The optional `object key delimiter` used by S3 List objects to group object keys.")
flag.IntVar(&maxKeys, "max-keys", 0,
"The maximum number of `keys per page` to retrieve at once.")
}
// Lists all objects in a bucket using pagination
func main() {
flag.Parse()
if len(bucketName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, bucket name required")
}
// Load the SDK's configuration from environment and shared config, and
// create the client with this.
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("failed to load SDK configuration, %v", err)
}
client := s3.NewFromConfig(cfg)
// Set the parameters based on the CLI flag inputs.
params := &s3.ListObjectsV2Input{
Bucket: &bucketName,
}
if len(objectPrefix) != 0 {
params.Prefix = &objectPrefix
}
if len(objectDelimiter) != 0 {
params.Delimiter = &objectDelimiter
}
// Create the Paginator for the ListObjectsV2 operation.
p := s3.NewListObjectsV2Paginator(client, params, func(o *s3.ListObjectsV2PaginatorOptions) {
if v := int32(maxKeys); v != 0 {
o.Limit = v
}
})
// Iterate through the S3 object pages, printing each object returned.
var i int
log.Println("Objects:")
for p.HasMorePages() {
i++
// Next Page takes a new context for each page retrieval. This is where
// you could add timeouts or deadlines.
page, err := p.NextPage(context.TODO())
if err != nil {
log.Fatalf("failed to get page %v, %v", i, err)
}
// Log the objects found
// Headobject function is called
for _, obj := range page.Contents {
input := &s3.HeadObjectInput{
Bucket: &bucketName,
Key: obj.Key,
}
result, err := &s3.HeadObject(client, input)
if err != nil {
panic(err)
}
fmt.Println("Object:", *obj.Key)
}
}
}
./listObjects.go:86:20: undefined: s3.HeadObject
1
Doing the headObject as an auxiliary method works
package main
import (
"context"
"flag"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
var (
bucketName string
objectPrefix string
objectDelimiter string
maxKeys int
)
func init() {
flag.StringVar(&bucketName, "bucket", "", "The `name` of the S3 bucket to list objects from.")
flag.StringVar(&objectPrefix, "prefix", "", "The optional `object prefix` of the S3 Object keys to list.")
flag.StringVar(&objectDelimiter, "delimiter", "",
"The optional `object key delimiter` used by S3 List objects to group object keys.")
flag.IntVar(&maxKeys, "max-keys", 0,
"The maximum number of `keys per page` to retrieve at once.")
}
// Lists all objects in a bucket using pagination
func main() {
flag.Parse()
if len(bucketName) == 0 {
flag.PrintDefaults()
log.Fatalf("invalid parameters, bucket name required")
}
// Load the SDK's configuration from environment and shared config, and
// create the client with this.
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("failed to load SDK configuration, %v", err)
}
client := s3.NewFromConfig(cfg)
// Set the parameters based on the CLI flag inputs.
params := &s3.ListObjectsV2Input{
Bucket: &bucketName,
}
if len(objectPrefix) != 0 {
params.Prefix = &objectPrefix
}
if len(objectDelimiter) != 0 {
params.Delimiter = &objectDelimiter
}
// Create the Paginator for the ListObjectsV2 operation.
p := s3.NewListObjectsV2Paginator(client, params, func(o *s3.ListObjectsV2PaginatorOptions) {
if v := int32(maxKeys); v != 0 {
o.Limit = v
}
})
// Iterate through the S3 object pages, printing each object returned.
var i int
log.Println("Objects:")
for p.HasMorePages() {
i++
// Next Page takes a new context for each page retrieval. This is where
// you could add timeouts or deadlines.
page, err := p.NextPage(context.TODO())
if err != nil {
log.Fatalf("failed to get page %v, %v", i, err)
}
// Log the objects found
// Headobject function is called
for _, obj := range page.Contents {
fmt.Println("Object:", *obj.Key)
OpHeadObject(client, bucketName, *obj.Key)
}
}
}
func OpHeadObject(sess *s3.Client, bucketName, objectName string) {
input := &s3.HeadObjectInput{
Bucket: &bucketName,
Key: &objectName,
}
resp, err := sess.HeadObject(context.TODO(), input)
if err != nil {
panic(err)
}
fmt.Println(resp.StorageClass) // that you want.
}
I'm using the following API to retrieve a list of amazon regions.
However, it basically returns the regions as "us-west1, us-west2" etc. Is there a way to get the region name from the API with output such as "US West (N. California)", "US West (Oregon)" ?
// Get a list of regions from our default region
svc := ec2.NewFromConfig(cfg)
result, err := svc.DescribeRegions(context.TODO(), &ec2.DescribeRegionsInput{})
if err != nil {
return nil, err
}
var regions []portaineree.Pair
for _, region := range result.Regions {
fmt.Println("region.Name=", *region.RegionName)
// do something with region...
}
You can use the SSM Agent to both get the list of regions, and pull out the long name for each region:
package main
import (
"log"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ssm"
)
func main() {
// Build a AWS SSM Agent
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
}))
// We're requesting global data, the region doesn't matter
svc := ssm.New(sess, &aws.Config{Region: aws.String("us-east-1")})
var nextToken *string
for {
// Request all regions, paginating the results if needed
var input = &ssm.GetParametersByPathInput{
Path: aws.String("/aws/service/global-infrastructure/regions"),
NextToken: nextToken,
}
var output, err = svc.GetParametersByPath(input)
if err != nil {
log.Fatal(err)
}
// For each region, get the "longName" for the region
for _, element := range output.Parameters {
region := (*element.Name)[strings.LastIndex(*element.Name, "/")+1:]
var regionInfo, err = svc.GetParameter(&ssm.GetParameterInput{
Name: aws.String("/aws/service/global-infrastructure/regions/" + region + "/longName"),
})
if err != nil {
log.Fatal(err)
}
regionDesc := *regionInfo.Parameter.Value
// Just output the region and region description
log.Println(region, " = ", regionDesc)
}
// Pull in the next page of regions if needed
nextToken = output.NextToken
if nextToken == nil {
break
}
}
}
This can be done pretty easily through CLI commands if that's an option
region=us-east-1
aws ssm get-parameter --name /aws/service/global-infrastructure/regions/$region/longName --query "Parameter.Value" --output text```
I'm trying to download a particular data chunk from S3. Following is the code snippet.
func DownloadFromS3() ([]byte, error) {
retries := 5
awsSession = session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: aws.Config{
MaxRetries: &retries,
LogLevel: aws.LogLevel(aws.LogDebugWithHTTPBody),
},
}))
// Create S3 service client
serviceS3 = s3.New(awsSession)
d := s3manager.NewDownloaderWithClient(serviceS3,func(d *s3manager.Downloader) {
d.Concurrency = 10 // should be ignored
d.PartSize = 1 // should be ignored
})
w := &aws.WriteAtBuffer{}
n, err := d.Download(w, &s3.GetObjectInput{
Bucket: aws.String("mybucket"),
Key: aws.String("key1"),
Range: aws.String("bytes=0-9"),
})
if err != nil {
return nil, err
}
return w.Bytes(), err
}
But this keeps downloading part by part continuously till the entire object is retrieved; without downloading only the specified part. Am I missing any configurations here?
Looks like an issue with the Go SDK; try the s3.GetObject instead of downloader.
This was an issue with previous AWS-Go-SDK. It's now fixed https://github.com/aws/aws-sdk-go/pull/1311
Here's a minification of using the stackdriver trace go client package.
It seems like this trivial example should work but it produces an error.
package main
import (
"context"
"flag"
"log"
"net/http"
"cloud.google.com/go/trace"
"github.com/davecgh/go-spew/spew"
"google.golang.org/api/option"
)
var (
projectID = flag.String("projectID", "", "projcect id")
serviceAccountKey = flag.String("serviceAccountKey", "", "path to serviceacount json")
)
func main() {
flag.Parse()
mux := http.NewServeMux()
log.Fatalln(http.ListenAndServe(":9000", Trace(*projectID, *serviceAccountKey, mux)))
}
func Trace(projectID string, keyPath string, h http.Handler) http.HandlerFunc {
client, err := trace.NewClient(context.Background(), projectID, option.WithServiceAccountFile(keyPath))
if err != nil {
panic(err)
}
return func(w http.ResponseWriter, r *http.Request) {
s := client.SpanFromRequest(r)
defer func() { err := s.FinishWait(); spew.Dump(err) }()
h.ServeHTTP(w, r)
}
}
I'm running this as:
$ teststackdrivertrace -projectID ${GCLOUD_PROJECT_ID} -serviceAccountKey ${PATH_TO_SERVICEACCOUNT_JSON}
and curling it produces:
$ curl -s --header "X-Cloud-Trace-Context: 205445aa7843bc8bf206b120001000/0;o=1" localhost:9000
$ (*googleapi.Error)(0xc4203a2c80)(googleapi: Error 400: Request contains an invalid argument., badRequest)
What am I missing?
My traceId was 30 bytes long not 32. I took the example curl from https://cloud.google.com/trace/docs/faq which is also only 30 bytes.