# GitOps e Qualidade: Infraestrutura como Código Testável

**Meta Description:** Aprenda GitOps: infraestrutura como código, testes de infraestrutura, Terraform, Ansible e como garantir qualidade em configs.

---

## O Que é GitOps?

GitOps usa Git como fonte única de verdade para infraestrutura e configurações. Changes são feitos via Git commits, não manualmente.

### Princípios

1. **Tudo no Git** - Código, configs, infraestrutura
2. **Automação** - CI/CD reconcile com Git
3. **Idempotência** - mesmo resultado sempre
4. **Auditoria** - histórico completo de mudanças

---

## Terraform - Infraestrutura como Código

### Setup

```hcl
# main.tf
terraform {
  required_version = ">= 1.0"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "prod/terraform.tfstate"
    region = "us-east-1"
  }
}

provider "aws" {
  region = var.aws_region
}

# Variables
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "environment" {
  description = "Environment name"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}
```

### Recursos

```hcl
# modules/api-service/main.tf
variable "environment" {}
variable "app_name" {}
variable "desired_count" { default = 2 }
variable "instance_type" { default = "t3.micro" }

resource "aws_ecs_cluster" "main" {
  name = "${var.app_name}-${var.environment}"
  
  setting {
    name  = "containerInsights"
    value = "enabled"
  }
}

resource "aws_ecs_task_definition" "api" {
  family                   = "${var.app_name}-api"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  
  container_definitions = jsonencode([
    {
      name      = "api"
      image     = "${var.app_name}:latest"
      essential = true
      
      portMappings = [{
        containerPort = 8080
        protocol      = "tcp"
      }]
      
      environment = [
        { name = "ENV", value = var.environment }
      ]
      
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = "/ecs/${var.app_name}"
          "awslogs-region"        = "us-east-1"
          "awslogs-stream-prefix" = "ecs"
        }
      }
    }
  ])
}

resource "aws_ecs_service" "api" {
  name            = "${var.app_name}-api-${var.environment}"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.api.arn
  desired_count   = var.desired_count
  
  deployment_controller {
    type = "ECS"
  }
  
  deployment_maximum_percent         = 200
  deployment_minimum_healthy_percent = 100
  
  load_balancer {
    target_group_arn = aws_lb_target_group.api.arn
    container_name   = "api"
    container_port   = 8080
  }
  
  depends_on = [aws_lb.api]
}
```

---

## Testando Infraestrutura

### Terratest (Go)

```go
// api_service_test.go
package test

import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
    "github.com/gruntwork-io/terratest/modules/aws"
    "time"
)

func TestAPIService(t *testing.T) {
    // Setup
    terraformOptions := &terraform.Options{
        TerraformDir: "../modules/api-service",
        Vars: map[string]interface{}{
            "environment": "test",
            "app_name":    "myapp",
            "desired_count": 2,
        },
        BackendConfig: map[string]interface{}{
            "bucket": "my-test-tfstate",
            "key":    "test/terraform.tfstate",
        },
    }
    
    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)
    
    // Get outputs
    clusterName := terraform.Output(t, terraformOptions, "cluster_name")
    serviceName := terraform.Output(t, terraformOptions, "service_name")
    
    // Assertions
    assert.NotEmpty(t, clusterName)
    assert.NotEmpty(t, serviceName)
    
    // Wait for service to be stable
    aws.WaitForServiceToStabilize(t, "us-east-1", clusterName, serviceName, 10*time.Minute)
    
    // Get service details
    service := aws.GetEcsServiceDetails(t, "us-east-1", clusterName, serviceName)
    assert.Equal(t, int32(2), *service.DesiredCount)
    
    // Verify load balancer
    lbArn := terraform.Output(t, terraformOptions, "lb_arn")
    assert.NotEmpty(t, lbArn)
}
```

### Checkov (Security Scanning)

```yaml
# .github/workflows/security-scan.yml
name: Infrastructure Security Scan

on:
  pull_request:
    paths:
      - '**.tf'
      - '**/terrform/**'

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run Checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: .
          framework: terraform
          output_format: sarif
          output_file_path: results.sarif
      
      - name: Upload to GitHub Security
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: results.sarif
```

```bash
# CLI
checkov -d ./infrastructure --framework terraform

# Com fail
checkov -d ./infrastructure --check CK_VPC_SG_1

# Skip específico
checkov -d ./infrastructure --skip-check CK_VPC_SG_1
```

### Sentinel (Policy as Code)

```python
# policies/api-service.sentinel
import "tfplan/v2" as tfplan

# Validate environment
validate_environment = func() {
    violations = []
    
    for tfplan.resource_changes as rc {
        if rc.type is "aws_ecs_service" {
            environment = rc.change.after.environment
            if environment is "production" {
                desired_count = rc.change.after.desired_count
                if desired_count < 2 {
                    append(violations, rc.name)
                }
            }
        }
    }
    
    return violations
}

main = rule {
    validate_environment() is []
}
```

---

## Ansible - Configuração

### Playbook

```yaml
# playbooks/deploy-api.yml
---
- name: Deploy API Service
  hosts: api_servers
  become: yes
  vars:
    app_version: "1.2.3"
    deploy_path: "/opt/myapp"
    
  tasks:
    - name: Ensure deploy directory exists
      file:
        path: "{{ deploy_path }}"
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'
    
    - name: Deploy application
      template:
        src: "app.conf.j2"
        dest: "{{ deploy_path }}/config.conf"
        owner: www-data
        group: www-data
        mode: '0644'
      notify: Restart API
    
    - name: Pull latest image
      docker_image:
        name: myapp/api
        tag: "{{ app_version }}"
        source: pull
        force_source: yes
    
    - name: Ensure API is running
      docker_container:
        name: api
        image: "myapp/api:{{ app_version }}"
        state: started
        restart_policy: always
        ports:
          - "8080:8080"
        env:
          ENV: "{{ app_environment }}"
          DATABASE_URL: "{{ db_connection_string }}"
        volumes:
          - "{{ deploy_path }}/config.conf:/app/config.conf:ro"
    
    - name: Wait for API to be healthy
      uri:
        url: "http://localhost:8080/health"
        status_code: 200
      register: result
      until: result.status == 200
      retries: 30
      delay: 5
    
  handlers:
    - name: Restart API
      docker_container:
        name: api
        state: restarted
```

### Molecule (Testing Ansible)

```yaml
# molecule/default/molecule.yml
---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: instance
    image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}"
    command: ""
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    privileged: true
provisioner:
  name: ansible
  lint: |
    ansible-lint
verifier:
  name: testinfra
  lint:
    - flake8
```

```python
# molecule/default/tests/test_default.py
import os
import pytest

def test_api_installed(host):
    api = host.file("/opt/myapp")
    assert api.exists
    assert api.is_directory

def test_api_config(host):
    config = host.file("/opt/myapp/config.conf")
    assert config.exists
    assert config.user == "www-data"

def test_api_service(host):
    service = host.service("api")
    assert service.is_running
    assert service.is_enabled

def test_api_health(host):
    response = host.run("curl -f http://localhost:8080/health")
    assert response.rc == 0
    assert "healthy" in response.stdout
```

---

## ArgoCD - GitOps

### Application

```yaml
# argocd/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-api
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/manifests.git
    targetRevision: HEAD
    path: apps/api-service/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - PruneLast=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas
```

---

## Conclusão

GitOps garante infraestrutura versionada e testável. As chaves são:

1. **Tudo no Git** - Audit trail completo
2. **Testar infraestrutura** - Terratest, Checkov, Molecule
3. **Automação** - ArgoCD reconcile
4. **Policy as Code** - Validate with Sentinel
5. **Drift detection** - Detect unauthorized changes
