Showing posts with label Go. Show all posts
Showing posts with label Go. Show all posts

Saturday, 14 March 2020

Blogger Go Cli

Blogger Go Cli

Blogger Go Cli

2020-03-14T21:16:00Z



Introduction

We will be creating a commandLine utility in Go to upload a post in Google blogger.

Account Preparation

  • You need to have a google account (gmail) (Paid account NOT required).
  • Once you have a google account, Login to https://console.developers.google.com/apis/credentials and create a new Project here.
  • Click on Dashboard on left hand column, and click on `+ ENABLE APIS AND SERVICES`.
    • Type in blogger in search bar, and select Blogger API v3 and then click ENABLE.`
  • Click on OAuth consent screen on left hand column, and select User Type to External and click CREATE.
    • On next screen type in Application Name as Blogger CLI (It can be any string).
    • Click on Add Scope and select both the Blogger API v3 options and click ADD and click Save.
  • Click on Credentials on the left hand side and you will be presented with following two options of creating credentials for Blogger API.
    • API Keys
    • OAuth 2.0 Client IDs
    • Service Accounts
    • Click on `+ CREATE CREDENTAILSon the top and selectOAuth Client ID` from drop down menu.
    • Select Application Type to other.
    • Type in Name to CLI Utility (it can be any string) and click create.
    • You will see an entry with name CLI Utility under OAuth 2.0 Client IDs.
    • Download the credential files by clicking on a down arrow button.
    • This will be a json file and we need it to authenticate it with google OAuth2.0 services.
  • Login to blogger site https://www.blogger.com
    • Sign-in to blogger.
    • Enter a display name you like.
    • Create a NEW BLOG and give it a name you like in Ttile
    • Type in address. It has to be a unique e.g somethingrandom23455.blogspot.com and click create blog
    • On next screen, look at the address bar of browser, it will be like https://www.blogger.com/blogger.g?blogID=3342324243435#allposts
    • Note down blog ID (a string after blogID=, excluding #allposts) in above URL. We need this ID to put in Go script.

Go development environment creation

I am using following Go version.

$ go version
go version go1.13.8 darwin/amd64

Setup following Go development directory structure.

mkdir -p BloggerCli/go-cli
touch BloggerCli/go-cli/createAndActivateGoWorkSpace.sh

Populate BloggerCli/go-cli/createAndActivateGoWorkSpace.sh with following contents

#! /bin/bash
# current directory is workspace
WORKSPACE=$(pwd)
cd "$WORKSPACE"
mkdir -p src bin pkg
export GOPATH=$WORKSPACE
export GOBIN=$WORKSPACE/bin

Create directory structure

cd BloggerCli/go-cli
source ./createAndActivateGoWorkSpace.sh
mkdir src/blogger

Install required libraries.

go get -v google.golang.org/api/blogger/v3
go get -v golang.org/x/oauth2/google

Copy credential file.

Copy above downloaded credential json file in BloggerCli/go-cli/src/blogger folder and rename it to OAuth2.0_secret.json.

Note: You can rename it any name

Create Go file BloggerCli/go-cli/src/blogger/bloggerCli.go file with following contents.

package main

/*
https://pkg.go.dev/google.golang.org/api/blogger/v3?tab=doc
https://godoc.org/google.golang.org/api/option
https://pkg.go.dev/golang.org/x/oauth2/google?tab=doc
https://pkg.go.dev/golang.org/x/oauth2?tab=doc#Config.AuthCodeURL
*/

import (
    "context"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "strings"

    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"

    "google.golang.org/api/blogger/v3"
    "google.golang.org/api/option"
)

func checkErrorAndExit(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func printNoOfPosts(postsService *blogger.PostsService, BlogID string) {
    postListCall := postsService.List(BlogID).MaxResults(500)
    postList, err := postListCall.Do()
    checkErrorAndExit(err)
    fmt.Printf("Total posts in this blog: %d\n", len(postList.Items))
}

func getBloggerClient() *blogger.Service {
    ctx := context.Background()

    contents, err := ioutil.ReadFile("OAuth2.0_secret.json")
    checkErrorAndExit(err)

    conf, err := google.ConfigFromJSON(contents, blogger.BloggerScope)
    checkErrorAndExit(err)

    url := conf.AuthCodeURL("state")
    fmt.Println("Visit the following URL for the auth dialog:")
    fmt.Printf("\n%v\n", url)

    var code string
    fmt.Printf("\nEnter the code obtained from above here: ")
    fmt.Scanln(&code)

    token, err := conf.Exchange(oauth2.NoContext, code)
    checkErrorAndExit(err)

    bloggerService, err := blogger.NewService(ctx, option.WithTokenSource(conf.TokenSource(ctx, token)))
    checkErrorAndExit(err)

    return bloggerService
}

func checkMandatoryArguments(args []*string) {

    allArgsExists := true
    for _, val := range args {
        if len(*val) == 0 {
            allArgsExists = allArgsExists && false
        } else {
            allArgsExists = allArgsExists && true
        }
    }

    if !allArgsExists {
        flag.PrintDefaults()
        os.Exit(2)
    }
}

func createNewPost(title *string, labels *string, fileToUpload *string) *blogger.Post {

    labelsSlice := strings.Split(*labels, ",")
    contents, err := ioutil.ReadFile(*fileToUpload)
    checkErrorAndExit(err)

    newPost := blogger.Post{}

    if len(*labels) == 0 {
        newPost = blogger.Post{
            Content: string(contents),
            Title:   *title,
        }
    } else {
        newPost = blogger.Post{
            Content: string(contents),
            Labels:  labelsSlice,
            Title:   *title,
        }
    }
    return &newPost
}

func insertAPost(title *string, labels *string, fileToUpload *string, BlogID string, postsService *blogger.PostsService) {
    fmt.Println("============= Inserting a new Post ===============")

    newPost := createNewPost(title, labels, fileToUpload)

    postInsertCall := postsService.Insert(BlogID, newPost)
    post, err := postInsertCall.Do()
    checkErrorAndExit(err)
    fmt.Println("A new post added to Blog with following details")
    fmt.Printf("Title: %s\nPostID: %s\n", post.Title, post.Id)
    fmt.Println("=================================================")
    printNoOfPosts(postsService, BlogID)
}

func updateAPost(title *string, labels *string, fileToUpload *string, BlogID string, postsService *blogger.PostsService, postID string) {
    fmt.Println("============= updating a Post ===============")

    // Search post from Title if postID not specified.
    if len(postID) == 0 {
        postsSearchCall := postsService.Search(BlogID, *title)
        postsList, err := postsSearchCall.Do()
        checkErrorAndExit(err)
        if len(postsList.Items) > 1 {
            fmt.Printf("Following multiple posts found for title = %s\n", *title)
            for _, post := range postsList.Items {
                fmt.Println(post.Title)
            }
            log.Fatal("\n\nERROR: Not updating post. Title must identify a single post only. Try specifying PostID")
        } else {
            postID = postsList.Items[0].Id
        }
    }

    newPost := createNewPost(title, labels, fileToUpload)
    postsPatchCall := postsService.Patch(BlogID, postID, newPost)
    post, err := postsPatchCall.Do()
    checkErrorAndExit(err)
    fmt.Println("Following post has been updated in Blog with following details")
    fmt.Printf("Title: %s\nPostID: %s\n", post.Title, post.Id)
    fmt.Println("=================================================")
    printNoOfPosts(postsService, BlogID)
}

func main() {

    fileToUpload := flag.String("f", "", "html file to upload (mandatory)")
    title := flag.String("t", "", "Post title (mandatory)")
    blogid := flag.String("b", "", "Blod ID (mandatory)")
    labels := flag.String("l", "", "comma separated list of labels (optional)")
    modify := flag.Bool("m", false, "Modify a post contents (optional: modifies post in mentioned in -t )")
    postID := flag.String("p", "", "post ID (optional: To be specified when updating a post)")
    flag.Parse()

    if flag.NFlag() == 0 {
        flag.PrintDefaults()
        os.Exit(1)
    }

    mandatoryArgs := []*string{fileToUpload, title, blogid}
    checkMandatoryArguments(mandatoryArgs)

    BlogID := *blogid
    PostID := *postID

    bloggerService := getBloggerClient()
    postsService := bloggerService.Posts

    printNoOfPosts(postsService, BlogID)

    if !*modify {
        insertAPost(title, labels, fileToUpload, BlogID, postsService)
    } else {
        updateAPost(title, labels, fileToUpload, BlogID, postsService, PostID)
    }
}

Create a test file to be uploaded.

echo 'This is a first post' > sometext.txt

Note: You can create an HTML file as well.

Run the script

Create a New Post

$ cd BloggerCli/go-cli/src/blogger

$ go run bloggerCli.go -b 232343232322 -f sometext.txt -t "A new Post" -l "label1,label2"
Visit the URL for the auth dialog: https://accounts.google.com/o/oauth2/auth?client_id=99999999-ccc55566666iiioo999ddd.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fblogger&state=state
Enter the code obtained from above here: 9/rf7hM-sds98JnHgsdK90OiYhsss790NHdsaDs
Total posts in this blog: 51
============= Inserting a new Post ===============
A new post added to Blog with following details
Title: A new Post
PostID: 2344555555555999
=================================================
Total posts in this blog: 52

Update a Post

$ cd BloggerCli/go-cli/src/blogger

$ go run bloggerCli.go -b 232343232322 -f sometext.txt -t "Multiple Vms Vagrantfile" -m
Visit the URL for the auth dialog: https://accounts.google.com/o/oauth2/auth?client_id=99999999-ccc55566666iiioo999ddd.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fblogger&state=state
Enter the code obtained from above here: 9/rf7hM-sds98JnHgsdK90OiYhsss790NHdsaDs
Total posts in this blog: 51
============= updating a Post ===============
Following post has been updated in Blog with following details
Title: Multiple Vms Vagrantfile
PostID: 334556669992323
=================================================
Total posts in this blog: 51

Note: You need to open the URL mentioned in the above output in a browser and authorize the request. This will return a code that need to enter above.

References used

  • https://pkg.go.dev/google.golang.org/api/blogger/v3?tab=doc
  • https://godoc.org/google.golang.org/api/option
  • https://pkg.go.dev/golang.org/x/oauth2/google?tab=doc
  • https://pkg.go.dev/golang.org/x/oauth2?tab=doc#Config.AuthCodeURL

Tuesday, 20 September 2016

Remote OpenStack Instances using Gophercloud (using neutron networking)

Remote OpenStack Instances using Gophercloud (using neutron networking)

Remote OpenStack Instances using Gophercloud (using neutron networking)

2020-03-16T22:24:21Z



Introduction:

Following program uses gophercloud library to spin instances in OpenStack environment. This program uses Neutron Networking. This program will also assign FloatingIP to very first instance.

Requirments:

An already running OpenStack environment. You can get a free playground for OpenStack to play with at http://trystack.org/. At minimum you need following information.

Auth URL : It may look like this http://10.0.2.15:5000/v2.0
UserName : This is a user in OpenStack. default: admin
Password : This is an API password. API password can be different from user password.
Tenant   : This is more like a project/Account or Organization. A tenant can have multiple users associated with it.. default: admin
Region   : Default region is set to RegionOne.

In trystack.org this information can be found at https://x86.trystack.org/dashboard/project/access_and_security/ and click on Download OpenStack RC file. Contents of RC file will have all the required information.

Following is the source code.

package main

import (
        "crypto/rand"
        "flag"
        "fmt"
        "github.com/rackspace/gophercloud"
        "github.com/rackspace/gophercloud/openstack"
        "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
        "github.com/rackspace/gophercloud/openstack/compute/v2/images"
        "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
        "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
        "github.com/rackspace/gophercloud/openstack/networking/v2/networks"
        "github.com/rackspace/gophercloud/openstack/networking/v2/ports"
        "os"
        "strconv"
)

func checkErrorAndExit(err error) {
        if err != nil {
                fmt.Printf("Error: %v\n", err.Error())
                os.Exit(3)
        }
}

func checkErrorAndContinue(err error) {
        if err != nil {
                fmt.Printf("Error: %v\n", err.Error())
        }
}

func generateRandomString() string {
        b := make([]byte, 8)
        _, err := rand.Read(b)
        checkErrorAndExit(err)
        name := fmt.Sprintf("%X", b[0:4])
        return name
}

func main() {

        user := flag.String("user", "admin", "OS Username")
        pass := flag.String("password", "acf0d596fbb44b1d", "Password")
        authUrl := flag.String("authurl", "http://10.0.2.15:5000/v2.0", "AUTH URL")
        tenant := flag.String("tenant", "admin", "Tenant Name")
        region := flag.String("region", "RegionOne", "Region for VM")
        defaultRun := flag.Bool("runwithdefaultvalues", false, "Use this flag to use default values")
        image := flag.String("image", "cirros", "Image to be used")
        flavor := flag.String("flavor", "m1.tiny", "Size of VM")
        private_network := flag.String("private_network", "private", "private network for VM")
        public_network := flag.String("public_network", "public", "public network for VM")
        securityGroup := flag.String("securitygroup", "default", "Security Group for VM")

        hostNamePrefix := flag.String("hostnameprefix", generateRandomString(), "Hostname Prefix. default: randomly generated.")
        total := flag.String("total", "5", "Total number of instances")

        flag.Parse()

        os.Setenv("OS_USERNAME", *user)
        os.Setenv("OS_PASSWORD", *pass)
        os.Setenv("OS_AUTH_URL", *authUrl)
        os.Setenv("OS_TENANT_NAME", *tenant)
        os.Setenv("OS_REGION_NAME", *region)

        if flag.NFlag() == 0 {
                flag.PrintDefaults()
                os.Exit(1)
        } else if *defaultRun == true {
                fmt.Println("Creating VMs: ")
        } else {
                fmt.Println("Creating VMs: ")
        }

        authOpts, err := openstack.AuthOptionsFromEnv()
        checkErrorAndExit(err)

        provider, err := openstack.AuthenticatedClient(authOpts)
        checkErrorAndExit(err)

        otherOpts := gophercloud.EndpointOpts{Region: *region}
        client, err := openstack.NewComputeV2(provider, otherOpts)

        imageID, err := images.IDFromName(client, *image)
        checkErrorAndExit(err)

        flavorID, err := flavors.IDFromName(client, *flavor)
        checkErrorAndExit(err)

        netClient, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{Name: "neutron", Region: *region})
        checkErrorAndExit(err)

        privateNetworkID, err := networks.IDFromName(netClient, *private_network)
        checkErrorAndExit(err)

        publicNetworkID, err := networks.IDFromName(netClient, *public_network)
        checkErrorAndExit(err)

        portOpts := ports.CreateOpts{NetworkID: privateNetworkID,
                AdminStateUp: ports.Up}

        netport, err := ports.Create(netClient, portOpts).Extract()
        checkErrorAndExit(err)

        network1 := servers.Network{UUID: privateNetworkID}
        network2 := servers.Network{Port: netport.ID}

        allNetworks := []servers.Network{network1}
        allPortNetworks := []servers.Network{network2}

        fmt.Printf("Obtaining free floating IP: ")

        floatingIPOpts := floatingips.CreateOpts{FloatingNetworkID: publicNetworkID,
                PortID: netport.ID}

        floatingIP, err := floatingips.Create(netClient, floatingIPOpts).Extract()
        checkErrorAndExit(err)

        fmt.Printf("%s found.\n", floatingIP.FloatingIP)

        noOfInstances, err := strconv.Atoi(*total)
        checkErrorAndExit(err)

        for i := 1; i <= noOfInstances; i++ {
                networks := allNetworks
                if i == 1 {
                        networks = allPortNetworks
                } else {
                        networks = allNetworks
                }
                vmName := *hostNamePrefix + strconv.Itoa(i)
                serverCreateOpts := servers.CreateOpts{Name: vmName,
                        FlavorRef:      flavorID,
                        ImageRef:       imageID,
                        Networks:       networks,
                        UserData:       []byte{},
                        SecurityGroups: []string{*securityGroup}}

                _, err := servers.Create(client, serverCreateOpts).Extract()
                checkErrorAndContinue(err)
                fmt.Printf("%s VM creation request sent successfully.\n Details: { flavor=%s, image=%s, network=%s, sec_group=%s }\n", vmName, *flavor, *image, *private_network, *securityGroup)

                fmt.Println("===================================")
        }

}

Create a file launchOpenStackInstances.go and populate it with above contents. Use following steps on Linux and MacOS to compile/build it.

  • Make sure GO Lang compiler is installed.
  • Place go source code in a directory. (most likely home directory)
mkdir MYGO
export GOPATH=$HOME/MYGO
go get github.com/rackspace/gophercloud
go build launchOpenStackInstances.go
  • An executable with name launchOpenStackInstances will be there .

Below is the sample run:

# ./launchOpenStackInstances.go -authurl=http://10.1.0.10:5000/v2.0 -flavor=m1.tiny -image=cirros -private_network=private_network -public_network=public_network -user=admin -password=106d84bf40fb4413 -total=2
Creating VMs:
Obtaining free floating IP: 10.1.0.159 found.
5F52A0801 VM creation request sent successfully.
 Details: { flavor=m1.tiny, image=cirros, network=private_network, sec_group=default }
===================================
5F52A0802 VM creation request sent successfully.
 Details: { flavor=m1.tiny, image=cirros, network=private_network, sec_group=default }
===================================

Observation 1: This program uses Neutron Networking.

Observation 2: Compare this program with the program using Nova Networking. There is no infinite loop required to assign floatingIP in this program.