One place for hosting & domains

      Driver

      Como usar Go com o MongoDB usando o driver Go do MongoDB


      O autor selecionou a Free Software Foundation para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Após contar com soluções desenvolvidas pela comunidade por muitos anos, o MongoDB anunciou que estava trabalhando em um driver oficial para Go. Em março 2019, esse novo driver atingiu um status de pronto para a produção com o lançamento da versão v1.0.0 e tem sido atualizado continuamente desde então.

      Assim como os outros drivers oficiais do MongoDB, o driver Go compartilha a linguagem de programação Go e fornece uma maneira fácil de usar o MongoDB como a solução de banco de dados para um programa Go. Ele está totalmente integrado com a API do MongoDB e expõe todas as consultas, indexações e recursos de agregação da API, além de outros recursos avançados. Ao contrário das bibliotecas de terceiros, ele receberá total suporte dos engenheiros do MongoDB para que você possa ter certeza de sua continuidade de desenvolvimento e manutenção.

      Neste tutorial, você começará a usar o driver Go oficial do MongoDB. Você instalará o driver, se conectará a um banco de dados do MongoDB e executará várias operações CRUD. No processo, você criará um programa gerenciador de tarefas para gerenciar tarefas através da linha de comando.

      Pré-requisitos

      Para este tutorial, você precisará do seguinte:

      • O Go instalado em sua máquina e um espaço de trabalho de Go configurado seguindo Como instalar o Go e configurar um ambiente de programação local. Neste tutorial, o projeto se chamará tasker. Você precisará da versão do Go v1.11 ou mais recente instalada em sua máquina com o Go Modules habilitado.
      • O MongoDB instalado para seu sistema operacional seguindo Como instalar o MongoDB. O MongoDB 2.6 ou superior é a versão mínima suportada pelo driver Go do MongoDB.

      Se você estiver usando o Go v1.11 ou 1.12, certifique-se de que o Go Modules esteja habilitado definindo a variável de ambiente GO111MODULE para on, como mostrado a seguir:

      Para obter mais informações sobre a implementação das variáveis de ambiente, leia este tutorial sobre Como ler e configurar variáveis de ambiente e de shell.

      O código e os comandos mostrados neste guia foram testados com o Go v1.14.1 e o MongoDB v3.6.3.

      Passo 1 — Instalando o driver Go do MongoDB

      Neste passo, você instalará o pacote Go Driver para o MongoDB e o importará para seu projeto. Você também se conectará ao seu banco de dados MongoDB e verificará o status da conexão.

      Vá em frente e crie um novo diretório para este tutorial em seu sistema de arquivos:

      Assim que seu diretório do projeto estiver configurado, vá até ele com o seguinte comando:

      Em seguida, inicialize o projeto Go com um arquivo go.mod. Este arquivo define requisitos de projeto e fixa dependências para suas versões corretas:

      Se seu diretório do projeto estiver fora de $GOPATH, você precisa especificar o caminho de importação para o seu módulo como mostrado:

      • go mod init github.com/<your_username>/tasker

      Neste ponto, seu arquivo go.mod se parecerá com este:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      

      Adicione o driver Go do MongoDB ao seu projeto com o seguinte comando:

      • go get go.mongodb.org/mongo-driver

      Você verá uma saída como a seguinte:

      Output

      go: downloading go.mongodb.org/mongo-driver v1.3.2 go: go.mongodb.org/mongo-driver upgrade => v1.3.2

      Neste ponto, seu arquivo go.mod se parecerá com este:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      
      require go.mongodb.org/mongo-driver v1.3.1 // indirect
      

      Em seguida, crie um arquivo main.go na raiz de projeto e abra-o em seu editor de texto:

      Para começar a usar o driver, importe os seguintes pacotes para seu arquivo main.go:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      

      Aqui, você adiciona os pacotes mongo e options, que o driver Go do MongoDB oferece.

      Em seguida, após suas importações, crie um novo cliente MongoDB e se conecte ao seu servidor MongoDB em execução:

      main.go

      . . .
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      O mongo.Connect() aceita um Context e um objeto options.ClientOptions, que é usado para definir a string de conexão e outras configurações do driver. Você pode visitar a documentação de pacotes de opções para ver quais opções de configuração estão disponíveis.

      O Context é como um tempo limite ou prazo que indica quando uma operação deve parar de ser executada e retornar. Ele ajuda a evitar a degradação de desempenho em sistemas de produção quando operações específicas apresentam lentidão. Neste código, você está passando o context.TODO() para indicar que você não tem certeza sobre qual contexto usar agora, mas planeja adicionar um no futuro.

      Em seguida, vamos garantir que seu servidor MongoDB tenha sido encontrado e que você tenha se conectado a ele com sucesso usando o método Ping. Adicione o código a seguir dentro da função init:

      main.go

      . . .
          log.Fatal(err)
        }
      
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      }
      

      Se houver erros ao se conectar ao banco de dados, o programa deve falhar enquanto você tenta corrigir o problema, pois não há motivos para manter o programa em execução sem uma conexão com o banco de dados ativa.

      Adicione o código a seguir para criar um banco de dados:

      main.go

      . . .
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      
        collection = client.Database("tasker").Collection("tasks")
      }
      

      Cria-se um banco de dados tasker e uma coleção task para armazenar as tarefas que você estará criando. Define-se também uma collection (coleção) como uma variável de nível de pacote, para que você possa reutilizar a conexão com o banco de dados por todo o pacote.

      Salve e saia do arquivo.

      O main.go completo neste ponto é o seguinte:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      
          err = client.Ping(ctx, nil)
          if err != nil {
              log.Fatal(err)
          }
      
          collection = client.Database("tasker").Collection("tasks")
      }
      

      Você configurou seu programa para se conectar ao seu servidor MongoDB usando o driver Go. No próximo passo, você prosseguirá com a criação do seu programa gerenciador de tarefas.

      Passo 2 — Criando um programa CLI

      Neste passo, você instalará o famoso pacote cli para ajudar com o desenvolvimento do seu programa gerenciador de tarefas. Ele oferece uma interface que você pode aproveitar para criar rapidamente ferramentas modernas de linha de comando. Por exemplo, esse pacote dá a capacidade de definir subcomandos para o seu programa, resultando em uma experiência de linha de comando semelhante ao git.

      Execute o seguinte comando para adicionar o pacote como uma dependência:

      • go get github.com/urfave/cli/v2

      Em seguida, abra seu arquivo main.go novamente:

      Adicione o código destacado ao seu arquivo main.go:

      main.go

      package main
      
      import (
          "context"
          "log"
          "os"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Importa-se o pacote cli como mencionado. Além disso, importa-se também o pacote os, que você usará para passar os argumentos de linha de comando para o seu programa:

      Adicione o código a seguir após sua função init para criar seu programa CLI e fazer com que seu código seja compilado:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:     "tasker",
              Usage:    "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{},
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Este trecho de código cria um programa CLI chamado tasker e adiciona uma descrição curta de uso que será impressa ao executar o programa. A fatia Commands é onde você adicionará os comandos para o seu programa. O comando Run analisa a fatia de argumentos para o comando apropriado.

      Salve e saia do seu arquivo.

      Aqui está o comando que você precisa para compilar e executar o programa:

      Você verá o seguinte resultado:

      Output

      NAME: tasker - A simple CLI program to manage your tasks USAGE: main [global options] command [command options] [arguments...] COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false)

      O programa funciona e mostra texto de ajuda, que é útil para aprender sobre o que o programa pode fazer e como usá-lo.

      Nos próximos passos, você melhorará o utilitário do seu programa, adicionando subcomandos para ajudar no gerenciamento de suas tarefas no MongoDB.

      Passo 3 — Criando uma tarefa

      Neste passo, você adicionará um subcomando ao seu programa CLI usando o pacote cli. Ao final desta seção, você será capaz de adicionar uma nova tarefa ao seu banco de dados MongoDB , usando um novo comando add em seu programa CLI.

      Comece abrindo seu arquivo main.go:

      Em seguida, importe os pacotes go.mongodb.org/mongo-driver/bson/primitive, time e errors:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Então, crie uma nova estrutura para representar uma única tarefa no banco de dados e insira-a imediatamente antes da função main:

      main.go

      . . .
      type Task struct {
          ID        primitive.ObjectID `bson:"_id"`
          CreatedAt time.Time          `bson:"created_at"`
          UpdatedAt time.Time          `bson:"updated_at"`
          Text      string             `bson:"text"`
          Completed bool               `bson:"completed"`
      }
      . . .
      

      Usa-se o pacote primitive para definir o tipo de ID de cada tarefa, uma vez que o MongoDB usa os ObjectIDs para o campo _id por padrão. Outro comportamento padrão do MongoDB é que o nome em letra minúscula do campo é usado como a chave para cada campo exportado quanto está sendo serializado. No entanto, isso pode ser alterado usando sinalizadores struct bson.

      Em seguida, crie uma função que recebe uma instância de Task e salve-a no banco de dados. Adicione este trecho de código após a função main:

      main.go

      . . .
      func createTask(task *Task) error {
          _, err := collection.InsertOne(ctx, task)
        return err
      }
      . . .
      

      O método collection.InsertOne() insere a tarefa fornecida na coleção do banco de dados e retorna o ID do documento que foi inserido. Como você não precisa desse ID, descarte-o atribuindo-o ao operador underline.

      O próximo passo é adicionar um novo comando ao seu programa gerenciador de tarefas para a criação de novas tarefas. Vamos chamá-lo de add:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Todo novo comando que é adicionado ao seu programa CLI é colocado dentro da fatia Commands. Cada um consiste em um nome, descrição de uso e ação. Esse é o código que será executado após a execução de comando.

      Nesse código, você pega o primeiro argumento em add e usa-o para definir a propriedade Text de uma nova instância de Task, ao mesmo tempo em que atribui os padrões apropriados para as outras propriedades. A nova tarefa é subsequentemente passada ao createTask, que insere a tarefa no banco de dados e retorna nil se tudo correr bem. Isso faz com o o comando seja finalizado.

      Salve e saia do seu arquivo.

      Teste-o adicionando algumas tarefas com o comando add. Se o teste for bem-sucedido, você não verá erros impressos em sua tela:

      • go run main.go add "Learn Go"
      • go run main.go add "Read a book"

      Agora que você pode adicionar tarefas com sucesso, vamos implementar uma maneira de exibir todas as tarefas que você já adicionou ao banco de dados.

      Passo 4 — Listando todas as tarefas

      Listar os documentos em uma coleção pode ser feito usando o método collection.Find(), que espera receber um filtro, bem como um ponteiro para um valor no qual o resultado pode ser decodificado. Seu valor de retorno é um Cursor, que fornece um fluxo de documentos que podem ser iterados e decodificados, um de cada vez. Em seguida, o Cursor é fechado depois de esgotado.

      Abra o seu arquivo main.go:

      Certifique-se de importar o pacote bson:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Em seguida, crie as seguintes funções imediatamente após o createTask:

      main.go

      . . .
      func getAll() ([]*Task, error) {
        // passing bson.D{{}} matches all documents in the collection
          filter := bson.D{{}}
          return filterTasks(filter)
      }
      
      func filterTasks(filter interface{}) ([]*Task, error) {
          // A slice of tasks for storing the decoded documents
          var tasks []*Task
      
          cur, err := collection.Find(ctx, filter)
          if err != nil {
              return tasks, err
          }
      
          for cur.Next(ctx) {
              var t Task
              err := cur.Decode(&t)
              if err != nil {
                  return tasks, err
              }
      
              tasks = append(tasks, &t)
          }
      
          if err := cur.Err(); err != nil {
              return tasks, err
          }
      
        // once exhausted, close the cursor
          cur.Close(ctx)
      
          if len(tasks) == 0 {
              return tasks, mongo.ErrNoDocuments
          }
      
          return tasks, nil
      }
      

      O BSON (JSON codificado em binário) é como os documentos são representados em um banco de dados MongoDB e o pacote bson é o que nos ajuda a trabalhar com os objetos BSON em Go. O tipo bson.D usado na função getAll() representa um documento BSON e é usado onde a ordem das propriedades importa. Ao passar o bson.D{}} como seu filtro para o filterTasks(), você está indicando que deseja combinar todos os documentos na coleção.

      Na função filterTasks(), você itera sobre o Cursor retornado pelo método collection.Find() e decodifica cada documento em uma instância de Task. Em seguida, cada Task é anexada à fatia de tarefas criada no início da função. Assim que o Cursor é esgotado, ele é fechado e a fatia tasks é retornada.

      Antes de você criar um comando para listar todas as tarefas, vamos criar uma função auxiliar que recebe uma fatia de tasks e imprime na saída padrão. Você usará o pacote color para colorir a saída.

      Antes de poder usar esse pacote, instale-o com:

      • go get gopkg.in/gookit/color.v1

      Você verá o seguinte resultado:

      Output

      go: downloading gopkg.in/gookit/color.v1 v1.1.6 go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

      E importe-o para seu arquivo main.go, junto com o pacote fmt:

      main.go

      package main
      
      import (
          "context"
          "errors"
        "fmt"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
          "gopkg.in/gookit/color.v1"
      )
      . . .
      

      Em seguida, crie uma nova função printTasks após sua função main:

      main.go

      . . .
      func printTasks(tasks []*Task) {
          for i, v := range tasks {
              if v.Completed {
                  color.Green.Printf("%d: %sn", i+1, v.Text)
              } else {
                  color.Yellow.Printf("%d: %sn", i+1, v.Text)
              }
          }
      }
      . . .
      

      Essa função printTasks recebe uma fatia de tasks, itera sobre cada uma e imprime-as na saída padrão usando a cor verde para indicar tarefas concluídas, e amarelo para tarefas incompletas.

      Vá em frente e adicione as linhas destacadas a seguir para criar um novo comando all para a fatia Commands. Este comando imprimirá todas as tarefas adicionadas à saída padrão:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      O comando all recupera todas as tarefas presentes no banco de dados e os imprime-as na saída padrão. Se nenhuma tarefa estiver presente, ao invés disso, um aviso para adicionar uma nova tarefa é impresso.

      Salve e saia do seu arquivo.

      Compile e execute seu programa com o comando all:

      Ele listará todas as tarefas que você adicionou até agora:

      Output

      1: Learn Go 2: Read a book

      Agora que você pode visualizar todas as tarefas no banco de dados, vamos adicionar a capacidade de marcar uma tarefa como concluída no próximo passo.

      Passo 5 — Concluindo uma tarefa

      Neste passo, você criará um novo subcomando chamado done, que permitirá que você marque uma tarefa existente no banco de dados como concluída. Para marcar uma tarefa como concluída, você pode usar o método collection.FindOneAndUpdate(). Ele permite que você localize um documento em uma coleção e atualize algumas ou todas as suas propriedades. Esse método exige um filtro para localizar o documento e um documento de atualização para descrever a operação. Ambos são construídos usando tipos bson.D.

      Comece abrindo seu arquivo main.go:

      Insira o trecho de código após sua função filterTasks:

      main.go

      . . .
      func completeTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          update := bson.D{primitive.E{Key: "$set", Value: bson.D{
              primitive.E{Key: "completed", Value: true},
          }}}
      
          t := &Task{}
          return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
      }
      . . .
      

      A função corresponde ao primeiro documento onde a propriedade de texto é igual ao parâmetro text. O documento update (atualizar) especifica que a propriedade completed (concluído) está definida como true (verdadeiro). Se houver um erro na operação FindOneAndUpdate(), ele será retornado pelo completeTask(). Caso contrário, o nil é retornado.

      Em seguida, vamos adicionar ao seu programa CLI um novo comando done, que marca uma tarefa como concluída:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Usa-se o argumento passado ao comando done para encontrar o primeiro documento cuja propriedade text é correspondente. Caso seja encontrado, a propriedade completed no documento é definida como true.

      Salve e saia do seu arquivo.

      Em seguida, execute seu programa com o comando done:

      • go run main.go done "Learn Go"

      Se usar o comando all novamente, você notará que a tarefa que foi marcada como concluída está agora impressa em verde.

      Captura de tela da saída do terminal após completar uma tarefa

      Às vezes, você deseja visualizar apenas tarefas que ainda não foram feitas. Vamos adicionar esse recurso a seguir.

      Passo 6 — Exibindo somente tarefas pendentes

      Neste passo, você incorporará um código para recuperar tarefas pendentes a partir do banco de dados usando o driver MongoDB. As tarefas pendentes são aquelas nas quais a propriedade completed está definida como false.

      Vamos adicionar uma nova função que recupera tarefas que ainda não foram concluídas. Abra o seu arquivo main.go:

      Adicione este trecho de código após a função completeTask:

      main.go

      . . .
      func getPending() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: false},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Cria-se um filtro usando os pacotes bson e primitive do driver do MongoDB, que agruparão documentos cuja propriedade completed está definida como false. Em seguida, a fatia das tarefas pendentes é retornada ao autor da chamada.

      Em vez de criar um novo comando para listar as tarefas pendentes, vamos tornar isso a ação padrão ao executar o programa sem nenhum comando. Faça isso adicionando uma propriedade Action ao programa, como mostrado a seguir:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
      . . .
      

      A propriedade Action executa uma ação padrão quando o programa é executado sem nenhum subcomando. É aqui que a lógica de listar as tarefas pendentes é colocada. A função getPending() é chamada e as tarefas resultantes são impressas na saída padrão usando o printTasks(). Se não houver tarefas pendentes, ao invés disso, um aviso é exibido, encorajando o usuário a adicionar uma nova tarefa usando o comando add.

      Salve e saia do seu arquivo.

      Executar o programa agora sem adicionar nenhum comando listará todas as tarefas pendentes no banco de dados:

      Você verá o seguinte resultado:

      Output

      1: Read a book

      Agora que você pode listar as tarefas incompletas, vamos adicionar outro comando que permite que você veja apenas tarefas concluídas.

      Passo 7 — Exibindo tarefas finalizadas

      Neste passo, você adicionará um novo subcomando finished (finalizado) que busca as tarefas concluídas no banco de dados e as exibe na tela. Isso envolve a filtragem e a devolução de tarefas cuja propriedade completed estiver definida como true.

      Abra o seu arquivo main.go:

      Então, adicione ao código a seguir ao final do seu arquivo:

      main.go

      . . .
      func getFinished() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: true},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      De maneira similar à função getPending(), você adicionou uma função getFinished(), que retorna uma parte das tarefas concluídas. Neste caso, o filtro tem a propriedade completed definida como true, de modo que apenas os documentos que correspondem a essa condição serão retornados.

      Em seguida, crie um comando finished que imprime todas as tarefas concluídas:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      O comando finished recupera as tarefas cuja propriedade completed está definida como true através da função getFinished() aqui criada. Em seguida, ele passa-as para a função printTasks, de modo que elas sejam impressas na saída padrão.

      Salve e saia do seu arquivo.

      Execute o seguinte comando:

      Você verá o seguinte resultado:

      Output

      1: Learn Go

      No passo final, você dará aos usuários a opção de excluir tarefas do banco de dados.

      Passo 8 — Excluindo uma tarefa

      Neste passo, você adicionará um novo subcomando delete (excluir) para permitir que os usuários excluam uma tarefa do banco de dados. Para excluir uma única tarefa, você usará o método collection.DeleteOne() do driver do MongoDB. Ele também depende de um filtro que corresponda ao documento a ser excluído.

      Abra seu arquivo main.go mais uma vez:

      Adicione essa função deleteTask para excluir tarefas do banco de dados, logo após sua função getFinished:

      main.go

      . . .
      func deleteTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          res, err := collection.DeleteOne(ctx, filter)
          if err != nil {
              return err
          }
      
          if res.DeletedCount == 0 {
              return errors.New("No tasks were deleted")
          }
      
          return nil
      }
      . . .
      

      Esse método deleteTask recebe um argumento em string que representa o item da tarefa a ser excluído. Um filtro é construído para que corresponda ao item de tarefa cuja propriedade text está definida como o argumento em string. Passa-se o filtro ao método DeleteOne(), que encontra o item na coleção e o exclui.

      Você pode verificar a propriedade DeletedCount no resultado do método DeleteOne para confirmar se algum documento foi excluído. Se o filtro não corresponder a um documento que será excluído, o DeletedCount será zero. Neste caso, retorne um erro.

      Agora, adicione um novo comando rm, como destacado:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:  "rm",
                      Usage: "deletes a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          err := deleteTask(text)
                          if err != nil {
                              return err
                          }
      
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Assim como em todos os outros subcomandos adicionados anteriormente, o comando rm usa seu primeiro argumento para encontrar uma tarefa no banco de dados e a exclui.

      Salve e saia do seu arquivo.

      Você pode listar as tarefas pendentes executando seu programa sem passar nenhum subcomando:

      Output

      1: Read a book

      Executar o subcomando rm na tarefa "Read a book" a excluirá do banco de dados:

      • go run main.go rm "Read a book"

      Se listar todas as tarefas pendentes novamente, você notará que a tarefa "Read a book" não aparece mais e, ao invés disso, um prompt para adicionar uma nova tarefa é mostrado:

      Output

      Nothing to see here Run `add 'task'` to add a task

      Neste passo, você adicionou uma função para excluir tarefas do banco de dados.

      Conclusão

      Você criou um programa de linha de comando gerenciador de tarefas e aprendeu os fundamentos para usar o driver Go do MongoDB no processo.

      Certifique-se de verificar a documentação completa para o driver Go do MongoDB no GoDoc para aprender mais sobre os recursos oferecidos pelo driver. A documentação que descreve o uso de agregações ou transações pode despertar seu interesse.

      O código final para este tutorial pode ser visualizado neste repositório do GitHub.



      Source link

      How To Use Go with MongoDB Using the MongoDB Go Driver


      The author selected the Free Software Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      After relying on community developed solutions for many years, MongoDB announced that they were working on an official driver for Go. In March 2019, this new driver reached a production-ready status with the release of v1.0.0 and has been updated continually since then.

      Like the other official MongoDB drivers, the Go driver is idiomatic to the Go programming language and provides an easy way to use MongoDB as the database solution for a Go program. It is fully integrated with the MongoDB API, and exposes all of the query, indexing, and aggregation features of the API, along with other advanced features. Unlike third-party libraries, it will be fully supported by MongoDB engineers so you can be assured of its continued development and maintenance.

      In this tutorial you’ll get started with using the official MongoDB Go Driver. You’ll install the driver, connect to a MongoDB database, and perform several CRUD operations. In the process, you’ll create a task manager program for managing tasks through the command line.

      Prerequisites

      For this tutorial, you’ll need the following:

      • Go installed on your machine and a Go workspace configured following How To Install Go and Set Up a Local Programming Environment. In this tutorial, the project will be named tasker. You’ll need Go v1.11 or higher installed on your machine with Go Modules enabled.
      • MongoDB installed for your operating system following How To Install MongoDB. MongoDB 2.6 or higher is the minimum version supported by the MongoDB Go driver.

      If you’re using Go v1.11 or 1.12, ensure Go Modules is enabled by setting the GO111MODULE environment variable to on as shown following:

      For more information on implementing environment variables, read this tutorial on How To Read and Set Environmental and Shell Variables.

      The commands and code shown in this guide were tested with Go v1.14.1 and MongoDB v3.6.3.

      Step 1 — Installing the MongoDB Go Driver

      In this step, you’ll install the Go Driver package for MongoDB and import it into your project. You’ll also connect to your MongoDB database and check the status of the connection.

      Go ahead and create a new directory for this tutorial in your filesystem:

      Once your project directory is set up, change into it with the following command:

      Next, initialize the Go project with a go.mod file. This file defines project requirements and locks dependencies to their correct versions:

      If your project directory is outside the $GOPATH, you need to specify the import path for your module as follows:

      • go mod init github.com/<your_username>/tasker

      At this point, your go.mod file will look like this:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      

      Add the MongoDB Go Driver as a dependency for your project using following command:

      • go get go.mongodb.org/mongo-driver

      You’ll see output like the following:

      Output

      go: downloading go.mongodb.org/mongo-driver v1.3.2 go: go.mongodb.org/mongo-driver upgrade => v1.3.2

      At this point, your go.mod file will look like this:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      
      require go.mongodb.org/mongo-driver v1.3.1 // indirect
      

      Next, create a main.go file in your project root and open it in your text editor:

      To get started with the driver, import the following packages into your main.go file:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      

      Here you add the mongo and options packages, which the MongoDB Go driver provides.

      Next, following your imports, create a new MongoDB client and connect to your running MongoDB server:

      main.go

      . . .
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      mongo.Connect() accepts a Context and a options.ClientOptions object, which is used to set the connection string and other driver settings. You can visit the options package documentation to see what configuration options are available.

      Context is like a timeout or deadline that indicates when an operation should stop running and return. It helps to prevent performance degradation on production systems when specific operations are running slow. In this code, you’re passing context.TODO() to indicate that you’re not sure what context to use right now, but you plan to add one in the future.

      Next, let’s ensure that your MongoDB server was found and connected to successfully using the Ping method. Add the following code inside the init function:

      main.go

      . . .
          log.Fatal(err)
        }
      
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      }
      

      If there are any errors while connecting to the database, the program should crash while you try to fix the problem as there’s no point keeping the program running without an active database connection.

      Add the following code to create a database:

      main.go

      . . .
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      
        collection = client.Database("tasker").Collection("tasks")
      }
      

      You create a tasker database and a task collection to store the tasks you’ll be creating. You also set up collection as a package-level variable so you can reuse the database connection throughout the package.

      Save and exit the file.

      The full main.go at this point is as follows:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      
          err = client.Ping(ctx, nil)
          if err != nil {
              log.Fatal(err)
          }
      
          collection = client.Database("tasker").Collection("tasks")
      }
      

      You’ve set up your program to connect to your MongoDB server using the Go driver. In the next step, you’ll proceed with the creation of your task manager program.

      Step 2 — Creating a CLI Program

      In this step, you’ll install the well-known cli package to aid with the development of your task manager program. It provides an interface that you can take advantage of to rapidly create modern command line tools. For example, this package gives the ability to define subcommands for your program for a more git-like command line experience.

      Run the following command to add the package as a dependency:

      • go get github.com/urfave/cli/v2

      Next, open up your main.go file again:

      Add the following highlighted code to your main.go file:

      main.go

      package main
      
      import (
          "context"
          "log"
          "os"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      You import the cli package as mentioned. You also import the os package, which you’ll use to pass command line arguments to your program:

      Add the following code after your init function to create your CLI program and cause your code to compile:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:     "tasker",
              Usage:    "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{},
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      This snippet creates a CLI program called tasker and adds a short usage description that will be printed out when you run the program. The Commands slice is where you’ll add commands for your program. The Run command parses the arguments slice to the appropriate command.

      Save and exit your file.

      Here’s the command you need to build and run the program:

      You’ll see the following output:

      Output

      NAME: tasker - A simple CLI program to manage your tasks USAGE: main [global options] command [command options] [arguments...] COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false)

      The program runs and shows help text, which is handy for learning about what the program can do, and how to use it.

      In the next steps, you’ll improve the utility of your program by adding subcommands to help manage your tasks in MongoDB.

      Step 3 — Creating a Task

      In this step, you’ll add a subcommand to your CLI program using the cli package. At the end of this section, you’ll be able to add a new task to your MongoDB database by using a new add command in your CLI program.

      Begin by opening up your main.go file:

      Next, import the go.mongodb.org/mongo-driver/bson/primitive, time, and errors packages:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Then create a new struct to represent a single task in the database and insert it immediately preceding the main function:

      main.go

      . . .
      type Task struct {
          ID        primitive.ObjectID `bson:"_id"`
          CreatedAt time.Time          `bson:"created_at"`
          UpdatedAt time.Time          `bson:"updated_at"`
          Text      string             `bson:"text"`
          Completed bool               `bson:"completed"`
      }
      . . .
      

      You use the primitive package to set the type of the ID of each task since MongoDB uses ObjectIDs for the _id field by default. Another default behavior of MongoDB is that the lowercase field name is used as the key for each exported field when it is being serialized, but this can be changed using bson struct tags.

      Next, create a function that receives an instance of Task and saves it in the database. Add this snippet following the main function:

      main.go

      . . .
      func createTask(task *Task) error {
          _, err := collection.InsertOne(ctx, task)
        return err
      }
      . . .
      

      The collection.InsertOne() method inserts the provided task in the database collection and returns the ID of the document that was inserted. Since you don’t need this ID, you discard it by assigning to the underscore operator.

      The next step is to add a new command to your task manager program for creating new tasks. Let’s call it add:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Every new command that is added to your CLI program is placed inside the Commands slice. Each one consists of a name, usage description, and action. This is the code that will run upon command execution.

      In this code, you collect the first argument to add and use it to set the Text property of a new Task instance while assigning the appropriate defaults for the other properties. The new task is subsequently passed on to createTask, which inserts the task into the database and returns nil if all goes well causing the command to exit.

      Save and exit your file.

      Test it out by adding a few tasks using the add command. If successful, you’ll see no errors printed to your screen:

      • go run main.go add "Learn Go"
      • go run main.go add "Read a book"

      Now that you can add tasks successfully, let’s implement a way to display all the tasks that you’ve added to the database.

      Step 4 — Listing all Tasks

      Listing the documents in a collection can be done using the collection.Find() method, which expects a filter as well as a pointer to a value into which the result can be decoded. Its return value is a Cursor, which provides a stream of documents that can be iterated over and decoded one at a time. The Cursor is then closed once it has been exhausted.

      Open your main.go file:

      Make sure to import the bson package:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Then create the following functions immediately after createTask:

      main.go

      . . .
      func getAll() ([]*Task, error) {
        // passing bson.D{{}} matches all documents in the collection
          filter := bson.D{{}}
          return filterTasks(filter)
      }
      
      func filterTasks(filter interface{}) ([]*Task, error) {
          // A slice of tasks for storing the decoded documents
          var tasks []*Task
      
          cur, err := collection.Find(ctx, filter)
          if err != nil {
              return tasks, err
          }
      
          for cur.Next(ctx) {
              var t Task
              err := cur.Decode(&t)
              if err != nil {
                  return tasks, err
              }
      
              tasks = append(tasks, &t)
          }
      
          if err := cur.Err(); err != nil {
              return tasks, err
          }
      
        // once exhausted, close the cursor
          cur.Close(ctx)
      
          if len(tasks) == 0 {
              return tasks, mongo.ErrNoDocuments
          }
      
          return tasks, nil
      }
      

      BSON (Binary-encoded JSON) is how documents are represented in a MongoDB database and the bson package is what helps us work with BSON objects in Go. The bson.D type used in the getAll() function represents a BSON document and it’s used where the order of the properties matter. By passing bson.D{{}} as your filter to filterTasks(), you’re indicating that you want to match all the documents in the collection.

      In the filterTasks() function, you iterate over the Cursor returned by the collection.Find() method and decode each document into an instance of Task. Each Task is then appended to the slice of tasks created at the start of the function. Once the Cursor is exhausted, it is closed and the tasks slice is returned.

      Before you create a command for listing all tasks, let’s create a helper function that takes a slice of tasks and prints to the standard output. You’ll be using the color package to colorize the output.

      Before you can use the this package, install it with:

      • go get gopkg.in/gookit/color.v1

      You’ll see the following output:

      Output

      go: downloading gopkg.in/gookit/color.v1 v1.1.6 go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

      And import it into your main.go file along with the fmt package:

      main.go

      package main
      
      import (
          "context"
          "errors"
        "fmt"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
          "gopkg.in/gookit/color.v1"
      )
      . . .
      

      Next, create a new printTasks function following your main function:

      main.go

      . . .
      func printTasks(tasks []*Task) {
          for i, v := range tasks {
              if v.Completed {
                  color.Green.Printf("%d: %sn", i+1, v.Text)
              } else {
                  color.Yellow.Printf("%d: %sn", i+1, v.Text)
              }
          }
      }
      . . .
      

      This printTasks function takes a slice of tasks, iterates over each one, and prints it out to the standard output using the green color to indicate completed tasks, and yellow for incomplete tasks.

      Go ahead and add the following highlighted lines to create a new all command to the Commands slice. This command will print all added tasks to the standard output:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      The all command retrieves all the tasks present in the database and prints them to the standard output. If no tasks are present, a prompt to add a new task is printed instead.

      Save and exit your file.

      Build and run your program with the all command:

      It will list all the tasks that you’ve added so far:

      Output

      1: Learn Go 2: Read a book

      Now that you can view all the tasks in the database, let’s add the ability to mark a task as completed in the next step.

      Step 5 — Completing a Task

      In this step, you’ll create a new subcommand called done that will allow you to mark an existing task in the database as completed. To mark a task as completed, you can use the collection.FindOneAndUpdate() method. It allows you to locate a document in a collection and update some or all of its properties. This method requires a filter to locate the document and an update document to describe the operation. Both of these are built using bson.D types.

      Start by opening up your main.go file:

      Insert the following snippet following your filterTasks function:

      main.go

      . . .
      func completeTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          update := bson.D{primitive.E{Key: "$set", Value: bson.D{
              primitive.E{Key: "completed", Value: true},
          }}}
      
          t := &Task{}
          return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
      }
      . . .
      

      The function matches the first document where the text property is equal to the text parameter. The update document specifies that the completed property be set to true. If there’s an error in the FindOneAndUpdate() operation, it will be returned by completeTask(). Otherwise nil is returned.

      Next, let’s add a new done command to your CLI program that marks a task as completed:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      You use the argument passed to the done command to find the first document whose text property matches. If found, the completed property on the document is set to true.

      Save and exit your file.

      Then run your program with the done command:

      • go run main.go done "Learn Go"

      If you use the all command again, you will notice that the task that was marked as completed is now printed with green.

      Screenshot of terminal output after completing a task

      Sometimes, you only want to view tasks that have not yet been done. We’ll add that feature next.

      Step 6 — Displaying Pending Tasks Only

      In this step, you’ll incorporate code to retrieve pending tasks from the database using the MongoDB driver. Pending tasks are those whose completed property is set to false.

      Let’s add a new function that retrieves tasks that have not been completed yet. Open your main.go file:

      Then add this snippet following the completeTask function:

      main.go

      . . .
      func getPending() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: false},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      You create a filter using the bson and primitive packages from the MongoDB driver, which will match documents whose completed property is set to false. The slice of pending tasks is then returned to the caller.

      Instead of creating a new command to list pending tasks, let’s make it the default action when running the program without any commands. You can do this by adding an Action property to the program as follows:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
      . . .
      

      The Action property performs a default action when the program is executed without any subcommands. This is where logic for listing pending tasks is placed. The getPending() function is called and the resulting tasks are printed to the standard output using printTasks(). If there are no pending tasks, a prompt is displayed instead, encouraging the user to add a new task using the add command.

      Save and exit your file.

      Running the program now without adding any commands will list all pending tasks in the database:

      You’ll see the following output:

      Output

      1: Read a book

      Now that you can list incomplete tasks, let’s add another command that allows you to view completed tasks only.

      Step 7 — Displaying Finished Tasks

      In this step, you’ll add a new finished subcommand that fetches completed tasks from the database and displays them on the screen. This involves filtering and returning tasks whose completed property is set to true.

      Open your main.go file:

      Then add in the following code at the end of your file:

      main.go

      . . .
      func getFinished() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: true},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Similar to the getPending() function, you’ve added a getFinished() function that returns a slice of completed tasks. In this case, the filter has the completed property set to true so only the documents that match this condition will be returned.

      Next, create a finished command that prints all completed tasks:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      The finished command retrieves tasks whose completed property is set to true via the getFinished() function created here. It then passes it to the printTasks function so that they are printed to the standard output.

      Save and exit your file.

      Run the following command:

      You’ll see the following output:

      Output

      1: Learn Go

      In the final step, you’ll give users the option to delete tasks from the database.

      Step 8 — Deleting a Task

      In this step, you’ll add a new delete subcommand to allow users to delete a task from the database. To delete a single task, you’ll use the collection.DeleteOne() method from the MongoDB driver. It also relies on a filter to match the document to delete.

      Open your main.go file once more:

      Add this deleteTask function to delete tasks from the database straight after your getFinished function:

      main.go

      . . .
      func deleteTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          res, err := collection.DeleteOne(ctx, filter)
          if err != nil {
              return err
          }
      
          if res.DeletedCount == 0 {
              return errors.New("No tasks were deleted")
          }
      
          return nil
      }
      . . .
      

      This deleteTask method takes a string argument that represents the task item to be deleted. A filter is constructed to match the task item whose text property is set to the string argument. You pass the filter to the DeleteOne() method that matches the item in the collection and deletes it.

      You can check the DeletedCount property on the result from the DeleteOne method to confirm if any documents were deleted. If the filter is unable to match a document to be deleted, the DeletedCount will be zero and you can return an error in that case.

      Now add a new rm command as highlighted:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:  "rm",
                      Usage: "deletes a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          err := deleteTask(text)
                          if err != nil {
                              return err
                          }
      
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Just as with all the other subcommands added previously, the rm command uses its first argument to match a task in the database and deletes it.

      Save and exit your file.

      You can list pending tasks by running your program without passing any subcommands:

      Output

      1: Read a book

      Running the rm subcommand on the "Read a book" task will delete it from the database:

      • go run main.go rm "Read a book"

      If you list all pending tasks again, you’ll notice that the "Read a book" task does not appear anymore and a prompt to add a new task is shown instead:

      Output

      Nothing to see here Run `add 'task'` to add a task

      In this step you added a function to delete tasks from the database.

      Conclusion

      You’ve successfully created a task manager command line program and learned the fundamentals of using the MongoDB Go driver in the process.

      Be sure to check out the full documentation for the MongoDB Go Driver at GoDoc to learn more about the features that using the driver provides. The documentation that describes using aggregations or transactions may be of particular interest to you.

      The final code for this tutorial can be viewed in this GitHub repo.



      Source link

      Deploy Persistent Volume Claims with the Linode Block Storage CSI Driver


      Updated by Linode Written by Linode Community

      What is the Linode Block Storage CSI Driver?

      The Container Storage Interface (CSI) defines a standard that storage providers can use to expose block and file storage systems to container orchestration systems. Linode’s Block Storage CSI driver follows this specification to allow container orchestration systems, like Kubernetes, to use Block Storage Volumes to persist data despite a Pod’s lifecycle. A Block Storage Volume can be attached to any Linode to provide additional storage.

      Before You Begin

      • This guide assumes you have a working Kubernetes cluster running on Linode. You can deploy a Kubernetes cluster on Linode in the following ways:

        1. Use Linode’s k8s-alpha CLI to deploy a Kubernetes cluster via the command line.

        2. Deploy a cluster using Terraform and the Linode Kubernetes Terraform installer.

        3. Use kubeadm to manually deploy a Kubernetes cluster on Linode. You can follow the Getting Started with Kubernetes: Use kubeadm to Deploy a Cluster on Linode guide to do this.

        Note

        • If using the k8s-alpha CLI or the Linode Kubernetes Terraform installer methods to deploy a cluster, you can skip the Installing the CSI Driver section of this guide, since it will be automatically installed when you deploy a cluster.

          Move on to the Attach a Pod to the Persistent Volume Claim section to learn how to consume a Block Storage volume as part of your deployment.

      • The Block Storage CSI supports Kubernetes version 1.13 or higher. To check the version of Kubernetes you are running, you can issue the following command:

        kubectl version
        

      Installing the CSI Driver

      Create a Kubernetes Secret

      A secret in Kubernetes is any token, password, or credential that you want Kubernetes to store for you. In the case of the Block Storage CSI, you’ll want to store an API token, and for convenience, the region you would like your Block Storage Volume to be placed in.

      Note

      Your Block Storage Volume must be in the same data center as your Kubernetes cluster.

      To create an API token:

      1. Log into the Linode Cloud Manager.

      2. Navigate to your account profile by clicking on your username at the top of the page and selecting My Profile. On mobile screen resolutions, this link is in the sidebar navigation.

      3. Click on the API Tokens tab.

      4. Click on Add a Personal Access Token. The Add Personal Access Token menu appears.

      5. Provide a label for the token. This is how you will reference your token within the Cloud Manager.

      6. Set an expiration date for the token with the Expiry dropdown.

      7. Set your permissions for the token. You will need Read/Write access for Volumes, and Read/Write access for Linodes.

      8. Click Submit.

      Your access token will appear on the screen. Copy this down somewhere safe, as once you click OK you will not be able to retrieve the token again, and will need to create a new one.

      Once you have your API token, it’s time to create your secret.

      1. Run the following command to enter your token into memory:

        read -s -p "Linode API Access Token: " LINODE_TOKEN
        

        Press enter, and then paste in your API token.

      2. Run the following command to enter your region into memory:

        read -p "Linode Region of Cluster: " LINODE_REGION
        

        You can retrieve a full list of regions by using the Linode CLI:

        linode-cli regions list
        

        For example, if you want to use the Newark, NJ, USA data center, you would use us-east as your region.

      3. Create the secret by piping in the following secret manifest to the kubectl create command. Issue the following here document:

        cat <<EOF | kubectl create -f -
        
      4. Now, paste in the following manifest and press enter:

        apiVersion: v1
        kind: Secret
        metadata:
          name: linode
          namespace: kube-system
        stringData:
          token: "$LINODE_TOKEN"
          region: "$LINODE_REGION"
        EOF
        

      You can check to see if the command was successful by running the get secrets command in the kube-system namespaces and looking for linode in the NAME column of the output:

      kubectl -n kube-system get secrets
      

      You should see output similar to the following:

      NAME                                             TYPE                                  DATA   AGE
      ...
      job-controller-token-6zzkw                       kubernetes.io/service-account-token   3      43h
      kube-proxy-token-td7k8                           kubernetes.io/service-account-token   3      43h
      linode                                           Opaque                                2      42h
      ...
      

      You are now ready to install the Block Storage CSI driver.

      Apply CSI Driver to your Cluster

      To install the Block Storage CSI driver, use the apply command and specify the following URL:

      kubectl apply -f https://raw.githubusercontent.com/linode/linode-blockstorage-csi-driver/master/pkg/linode-bs/deploy/releases/linode-blockstorage-csi-driver-v0.0.3.yaml
      

      The above file concatenates a few files needed to run the Block Storage CSI driver, including the volume attachment, driver registration, and provisioning sidecars. To see these files individually, visit the project’s GitHub repository.

      Once you have the Block Storage CSI driver installed, you are ready to provision a Persistent Volume Claim.

      Create a Persistent Volume Claim

      Caution

      The instructions in this section will create a Block Storage volume billable resource on your Linode account. A single volume can range from 10 GiB to 10,000 GiB in size and costs $0.10/GiB per month or $0.00015/GiB per hour. If you do not want to keep using the Block Storage volume that you create, be sure to delete it when you have finished the guide.

      If you remove the resources afterward, you will only be billed for the hour(s) that the resources were present on your account. Consult the Billing and Payments guide for detailed information about how hourly billing works and for a table of plan pricing.

      A Persistent Volume Claim (PVC) consumes a Block Storage Volume. To create a PVC, create a manifest file with the following YAML:

      pvc.yaml
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: pvc-example
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi
        storageClassName: linode-block-storage

      This PVC represents a Block Storage Volume. Because Block Storage Volumes have a minimum size of 10 gigabytes, the storage has been set to 10Gi. If you choose a size smaller than 10 gigabytes, the PVC will default to 10 gigabytes.

      Currently the only mode supported by the Linode Block Storage CSI driver is ReadWriteOnce, meaning that it can only be connected to one Kubernetes node at a time.

      To create the PVC in Kubernetes, issue the create command and pass in the pvc.yaml file:

      kubectl create -f pvc.yaml
      

      After a few moments your Block Storage Volume will be provisioned and your Persistent Volume Claim will be ready to use.

      You can check the status of your PVC by issuing the following command:

      kubectl get pvc
      

      You should see output like the following:

      NAME          STATUS   VOLUME                 CAPACITY   ACCESS MODES   STORAGECLASS           AGE
      pvc-example   Bound    pvc-0e95b811652111e9   10Gi       RWO            linode-block-storage   2m
      

      Now that you have a PVC, you can attach it to a Pod.

      Attach a Pod to the Persistent Volume Claim

      Now you need to instruct a Pod to use the Persistent Volume Claim. For this example, you will create a Pod that is running an ownCloud container, which will use the PVC.

      To create a pod that will use the PVC:

      1. Create a manifest file for the Pod and give it the following YAML:

        owncloud-pod.yaml
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        
        apiVersion: v1
        kind: Pod
        metadata:
          name: owncloud
          labels:
            app: owncloud
        spec:
          containers:
            - name: owncloud
              image: owncloud/server
              ports:
                - containerPort: 8080
              volumeMounts:
              - mountPath: "/mnt/data/files"
                name: pvc-example
          volumes:
            - name: pvc-example
              persistentVolumeClaim:
                claimName: pvc-example

        This Pod will run the owncloud/server Docker container image. Because ownCloud stores its files in the /mnt/data/files directory, this owncloud-pod.yaml manifest instructs the ownCloud container to create a mount point at that file path for your PVC.

        In the volumes section of the owncloud-pod.yaml, it is important to set the claimName to the exact name you’ve given your PersistentVolumeClaim in its manifest’s metadata. In this case, the name is pvc-example.

      2. Use the create command to create the ownCloud Pod:

        kubectl create -f owncloud-pod.yaml
        
      3. After a few moments your Pod should be up and running. To see the status of your Pod, issue the get pods command:

        kubectl get pods
        

        You should see output like the following:

        NAME       READY   STATUS    RESTARTS   AGE
        owncloud   1/1     Running   0          2m
        
      4. To list the contents of the /mnt/data/files directory within the container, which is the mount point for your PVC, issue the following command on your container:

        kubectl exec -it owncloud -- /bin/sh -c "ls /mnt/data/files"
        

        You should see output similar to the following:

        admin  avatars  files_external  index.html  owncloud.db  owncloud.log
        

        These files are created by ownCloud, and those files now live on your Block Storage Volume. The admin directory is the directory for the default user, and any files you upload to the admin account will appear in this folder.

      To complete the example, you should be able to access the ownCloud Pod via your browser. To accomplish this task, you will need to create a Service.

      1. Create a Service manifest file and copy in the following YAML:

        owncloud-service.yaml
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        
        kind: Service
        apiVersion: v1
        metadata:
          name: owncloud
        spec:
          selector:
            app: owncloud
          ports:
          - protocol: TCP
            port: 80
            targetPort: 8080
          type: NodePort

        Note

        The service manifest file will use the NodePort method to get external traffic to the ownCloud service. NodePort opens a specific port on all cluster Nodes and any traffic that is sent to this port is forwarded to the service. Kubernetes will choose the port to open on the nodes if you do not provide one in your service manifest file. It is recommended to let Kubernetes handle the assignment. Kubernetes will choose a port in the default range, 30000-32767.

        Alternatively, you could use the LoadBalancer service type, instead of NodePort, which will create Linode NodeBalancers that will direct traffic to the ownCloud Pods. Linode’s Cloud Controller Manager (CCM) is responsible for provisioning the Linode NodeBalancers. For more details, see the Kubernetes Cloud Controller Manager for Linode repository.

      2. Create the service in Kubernetes by using the create command and passing in the owncloud-service.yaml file you created in the previous step:

        kubectl create -f owncloud-service.yaml
        
      3. To retrieve the port that the ownCloud Pod is listening on, use the describe command on the newly created Service:

        kubectl describe service owncloud
        

        You should see output like the following:

        Name:                     owncloud
        Namespace:                default
        Labels:                   <none>
        Annotations:              <none>
        Selector:                 app=owncloud
        Type:                     NodePort
        IP:                       10.106.101.155
        Port:                     <unset>  80/TCP
        TargetPort:               8080/TCP
        NodePort:                 <unset>  30068/TCP
        Endpoints:                10.244.1.17:8080
        Session Affinity:         None
        External Traffic Policy:  Cluster
        Events:                   <none>
        

        Find the NodePort. In this example the port is 30068.

      4. Now you need to find out which Node your Pod is running on. Use the describe command on the Pod to find the IP address of the Node:

        kubectl describe pod owncloud
        

        You should see output like the following:

        Name:               owncloud
        Namespace:          default
        Priority:           0
        PriorityClassName:  <none>
        Node:               kube-node/192.0.2.155
        Start Time:         Mon, 22 Apr 2019 17:07:20 +0000
        Labels:             app=owncloud
        Annotations:        <none>
        Status:             Running
        IP:                 10.244.1.17
        

        The IP address of the Node in this example is 192.0.2.155. Your ownCloud Pod in this example would be accessible from http://192.9.2.155:30068.

      5. Navigate to the URL of the Node, including the NodePort you looked up in a previous step. You will be presented with the ownCloud log in page. You can log in with the username admin and the password admin.

      6. Upload a file. You will use this file to test the Persistent Volume Claim.

      7. The Persistent Storage Claim has been created and is using your Block Storage Volume. To prove this point, you can delete the ownCloud Pod and recreate it, and the Persistent Storage Claim will continue to house your data:

        kubectl delete pod owncloud
        
        kubectl create -f owncloud-pod.yaml
        

        Once the Pod has finished provisioning you can log back in to ownCloud and view the file you previously uploaded.

      You have successfully You have successfully created a Block Storage Volume tied to a Persistent Volume Claim and have mounted it with a container in a Pod.

      Delete a Persistent Volume Claim

      To delete the Block Storage volume created in this guide:

      1. First, delete the ownCloud Pod:

        kubectl delete pods owncloud
        
      2. Then, delete the persistent volume claim:

        kubectl delete pvc pvc-example
        

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link